nordisk-pairing 0.5.2

The boxes of an estate find each other: a seed each box is installed with, a one-time join against the front, a tunnel (WireGuard by default) they meet inside, roles decided once, a pairing loop each box runs itself, and the signal it reports. Shared by gunnar and holger; the laptop's xtask only lays machines, writes seeds and reads the signal. Design: .nornir/pairing-design.md (2026-09-24).
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
//! **The installer's mutual TLS — valid during bootstrap only.**
//!
//! The owner, 2026-09-24: the installer has a temporary mTLS certificate the
//! installing machines know, so they can contact the installer, which feeds
//! them what they need; and it is valid ONLY during bootstrap.
//!
//! One installer CA per MEDIUM (it was per lay until 2026-09-24 evening, and
//! that made every lay a new medium): minted by the laptop when the medium is
//! composed, its key kept beside the medium's receipt on the laptop (0600)
//! and pushed to each new front's door — never onto the medium, never onto a
//! box. It issues exactly three kinds of certificate:
//!
//! * **the door's** — the front's server certificate, for its utility address;
//!   every box PINS the installer CA, so a box talks to this door or to nothing
//!   (the join used to be plain HTTP, and a box believed whatever answered);
//! * **the bootstrap client certificate** — ON THE MEDIUM, with its key, the
//!   same for every box. A key on a medium is only as secret as the medium
//!   (it is uploaded to the cloud's storage), so this certificate is weak on
//!   purpose: it may only JOIN, the door accepts it only for a slot that has
//!   not joined yet, and it is refused outright once every slot has joined;
//! * **a box's own certificate** — signed at the first join from a request for
//!   a key the box minted itself (the private half never leaves the box). From
//!   then on that box speaks with it, and the bootstrap certificate is refused
//!   for its slot.
//!
//! **"Valid during bootstrap only" is the DOOR's rule, not an expiry.** When
//! every slot has joined the door refuses the bootstrap certificate outright
//! (`join-withdrawn`), and a slot that has joined refuses it for good
//! (`join-spent`) — per lay, because each lay's door has its own ledger. The
//! CA's own lifetime is the caller's choice, tied to the medium: long enough
//! that one medium serves many lays, and the medium is recomposed (with a new
//! CA) before it ends ([`cert_left`]). Pairing inside the tunnel then runs on
//! gunnar's own issuer, as it always has.
//!
//! Pure Rust (`rcgen` + `rustls` on `ring`), blocking sockets: an OS-less
//! PID 1 and a laptop both drive it without an async runtime.

use rcgen::{BasicConstraints, CertificateParams, CertificateSigningRequestParams, DistinguishedName, DnType, ExtendedKeyUsagePurpose, IsCa, KeyPair, KeyUsagePurpose, SanType};
use rustls::pki_types::pem::PemObject;
use rustls::pki_types::{CertificateDer, PrivateKeyDer, ServerName};
use std::net::{IpAddr, Ipv4Addr};
use std::sync::Arc;
use std::time::Duration;

/// The CN every bootstrap client certificate carries. A box's own certificate
/// carries its NAME; the door tells them apart by this.
pub const BOOTSTRAP_CN: &str = "bootstrap";

/// The CN of the installer's own client certificate — the ONLY client a VM's
/// bootstrap listener serves (0.3.0). A box name can never be this.
pub const INSTALLER_CN: &str = "installer";

/// **The one name every VM's bootstrap listener answers to** — a DNS SAN on
/// the bootstrap certificate and on every box certificate. The installer
/// checks the chain against the installer CA and this name, never the
/// address: the cloud hands the address out after the medium is composed.
pub const VM_SERVER_NAME: &str = "bootstrap.installer";

fn vm_sans() -> Vec<SanType> {
    match VM_SERVER_NAME.to_string().try_into() {
        Ok(n) => vec![SanType::DnsName(n)],
        Err(_) => Vec::new(),
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MtlsError(pub String);

impl std::fmt::Display for MtlsError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "REFUSED [pairing-mtls] {}", self.0)
    }
}

impl std::error::Error for MtlsError {}

fn e(what: &str) -> impl Fn(rcgen::Error) -> MtlsError + '_ {
    move |err| MtlsError(format!("{what}: {err}"))
}

/// A certificate and its private key, both PEM.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Issued {
    pub cert_pem: String,
    pub key_pem: String,
}

/// **The installer CA.** `key_pem` stays on the front.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct InstallerCa {
    pub cert_pem: String,
    pub key_pem: String,
}

fn window(valid_for: Duration) -> (time::OffsetDateTime, time::OffsetDateTime) {
    // Five minutes of back-dating: a fresh box's clock is set from the front
    // only after it has joined, and a certificate "not yet valid" by a few
    // seconds of skew would refuse the very join that fixes the clock.
    window_back(valid_for, time::Duration::minutes(5))
}

fn window_back(valid_for: Duration, back: time::Duration) -> (time::OffsetDateTime, time::OffsetDateTime) {
    let now = time::OffsetDateTime::now_utc();
    (now - back, now + time::Duration::seconds(valid_for.as_secs() as i64))
}

/// **How far the DOOR's certificate is back-dated: a day.** The door issues
/// it when it starts, from the front's clock, and the BOX checks it with its
/// own clock — which has not been set yet (it is set from the front AFTER the
/// join, `gunnar-clock` fed by the door). A box whose clock runs behind the
/// front's by more than the five minutes the other certificates allow would
/// refuse the only door that can fix it. Nothing about the door certificate
/// is weaker for it: the CA is pinned, and the address is in the SAN.
pub const DOOR_BACKDATE: Duration = Duration::from_secs(24 * 3600);

/// **How long a certificate (PEM) has left**, from its `notAfter`; zero once
/// it has ended. `now_unix` is injected so a caller's test does not wait. The
/// laptop asks it of a medium's installer CA (re-mint and recompose before it
/// ends), the door of its CA (its own certificate lives as long as the CA).
pub fn cert_left(pem: &str, now_unix: i64) -> Result<Duration, MtlsError> {
    let (_, p) = x509_parser::pem::parse_x509_pem(pem.trim().as_bytes()).map_err(|err| MtlsError(format!("not a PEM certificate: {err}")))?;
    let cert = p.parse_x509().map_err(|err| MtlsError(format!("not an X.509 certificate: {err}")))?;
    let end = cert.validity().not_after.timestamp();
    Ok(Duration::from_secs(end.saturating_sub(now_unix).max(0) as u64))
}

/// Seconds since the epoch, now (for [`cert_left`]).
pub fn now_unix() -> i64 {
    time::OffsetDateTime::now_utc().unix_timestamp()
}

impl InstallerCa {
    /// Mint the CA for one lay of `estate`, valid for `valid_for` (hours).
    pub fn mint(estate: &str, valid_for: Duration) -> Result<InstallerCa, MtlsError> {
        let key = KeyPair::generate().map_err(e("the CA key"))?;
        let mut p = CertificateParams::default();
        let mut dn = DistinguishedName::new();
        dn.push(DnType::CommonName, format!("nordisk-pairing installer CA — {estate}"));
        p.distinguished_name = dn;
        p.is_ca = IsCa::Ca(BasicConstraints::Constrained(0));
        p.key_usages = vec![KeyUsagePurpose::KeyCertSign, KeyUsagePurpose::DigitalSignature];
        (p.not_before, p.not_after) = window(valid_for);
        let cert = p.self_signed(&key).map_err(e("the CA certificate"))?;
        Ok(InstallerCa { cert_pem: cert.pem(), key_pem: key.serialize_pem() })
    }

    /// The CA as an `rcgen` issuer, rebuilt from its PEM and key.
    fn issuer(&self) -> Result<(rcgen::Certificate, KeyPair), MtlsError> {
        let key = KeyPair::from_pem(&self.key_pem).map_err(e("the CA key"))?;
        let params = CertificateParams::from_ca_cert_pem(&self.cert_pem).map_err(e("the CA certificate"))?;
        let cert = params.self_signed(&key).map_err(e("the CA as issuer"))?;
        Ok((cert, key))
    }

    fn leaf(&self, cn: &str, sans: Vec<SanType>, purpose: Vec<ExtendedKeyUsagePurpose>, valid_for: Duration) -> Result<Issued, MtlsError> {
        self.leaf_back(cn, sans, purpose, window(valid_for))
    }

    fn leaf_back(&self, cn: &str, sans: Vec<SanType>, purpose: Vec<ExtendedKeyUsagePurpose>, validity: (time::OffsetDateTime, time::OffsetDateTime)) -> Result<Issued, MtlsError> {
        let (ca, ca_key) = self.issuer()?;
        let key = KeyPair::generate().map_err(e("a leaf key"))?;
        let mut p = CertificateParams::default();
        let mut dn = DistinguishedName::new();
        dn.push(DnType::CommonName, cn);
        p.distinguished_name = dn;
        p.subject_alt_names = sans;
        p.extended_key_usages = purpose;
        (p.not_before, p.not_after) = validity;
        let cert = p.signed_by(&key, &ca, &ca_key).map_err(e("a leaf certificate"))?;
        Ok(Issued { cert_pem: cert.pem(), key_pem: key.serialize_pem() })
    }

    /// The door's server certificate, for the front's utility address —
    /// back-dated by [`DOOR_BACKDATE`], because a box checks it with a clock
    /// nobody has set yet.
    pub fn issue_door(&self, utility: Ipv4Addr, valid_for: Duration) -> Result<Issued, MtlsError> {
        let back = time::Duration::seconds(DOOR_BACKDATE.as_secs() as i64);
        self.leaf_back("nordisk-pairing door", vec![SanType::IpAddress(IpAddr::V4(utility))], vec![ExtendedKeyUsagePurpose::ServerAuth], window_back(valid_for, back))
    }

    /// **The bootstrap certificate the MEDIUM carries** — the same for every
    /// VM of the medium. Since 0.3.0 it is first of all what each VM's
    /// bootstrap listener SERVES the installer with (server auth, the fixed
    /// name [`VM_SERVER_NAME`]). Client auth stays for the 0.2 join against a
    /// front-hosted join service, which holger may still run.
    pub fn issue_bootstrap(&self, valid_for: Duration) -> Result<Issued, MtlsError> {
        self.leaf(BOOTSTRAP_CN, vm_sans(), vec![ExtendedKeyUsagePurpose::ServerAuth, ExtendedKeyUsagePurpose::ClientAuth], valid_for)
    }

    /// **The installer's own certificate** (0.3.0, owner 2026-09-24): the
    /// client certificate the installer on the operator's machine shows every
    /// VM it connects OUT to. CN [`INSTALLER_CN`] — the one role a VM's
    /// bootstrap listener accepts. Minted per medium with the CA and kept
    /// beside it on the operator's machine (0600); it never leaves that
    /// machine. Back-dated a day: a VM checks it with a clock nobody has set.
    pub fn issue_installer(&self, valid_for: Duration) -> Result<Issued, MtlsError> {
        let back = time::Duration::seconds(DOOR_BACKDATE.as_secs() as i64);
        self.leaf_back(INSTALLER_CN, Vec::new(), vec![ExtendedKeyUsagePurpose::ClientAuth], window_back(valid_for, back))
    }

    /// **A box's own certificate**, from its signing request: the CN is the
    /// NAME the door gave the box (whatever the request asked for), the key is
    /// the box's.
    pub fn sign_box(&self, csr_pem: &str, name: &str, valid_for: Duration) -> Result<String, MtlsError> {
        if name == BOOTSTRAP_CN || name == INSTALLER_CN {
            return Err(MtlsError(format!("`{name}` is not a box name")));
        }
        let (ca, ca_key) = self.issuer()?;
        let mut csr = CertificateSigningRequestParams::from_pem(csr_pem).map_err(e("the box's signing request"))?;
        let mut dn = DistinguishedName::new();
        dn.push(DnType::CommonName, name);
        csr.params.distinguished_name = dn;
        // The VM name too: a VM that holds its own certificate serves its
        // bootstrap listener with it after a restart (0.3.0).
        csr.params.subject_alt_names = vm_sans();
        csr.params.is_ca = IsCa::NoCa;
        csr.params.extended_key_usages = vec![ExtendedKeyUsagePurpose::ServerAuth, ExtendedKeyUsagePurpose::ClientAuth];
        (csr.params.not_before, csr.params.not_after) = window(valid_for);
        Ok(csr.signed_by(&ca, &ca_key).map_err(e("the box's certificate"))?.pem())
    }
}

/// **A box's own key and its signing request.** The key stays on the box.
pub fn box_request() -> Result<(String, String), MtlsError> {
    let key = KeyPair::generate().map_err(e("the box key"))?;
    request_for(key)
}

/// A signing request for a key the box ALREADY holds (PEM) — a box that
/// minted its key on an earlier boot asks for the same key, not a new one.
pub fn box_request_for(key_pem: &str) -> Result<(String, String), MtlsError> {
    request_for(KeyPair::from_pem(key_pem).map_err(e("the box key"))?)
}

fn request_for(key: KeyPair) -> Result<(String, String), MtlsError> {
    let mut p = CertificateParams::default();
    let mut dn = DistinguishedName::new();
    dn.push(DnType::CommonName, "box");
    p.distinguished_name = dn;
    let csr = p.serialize_request(&key).map_err(e("the signing request"))?;
    Ok((csr.pem().map_err(e("the signing request as PEM"))?, key.serialize_pem()))
}

fn certs(pem: &str) -> Result<Vec<CertificateDer<'static>>, MtlsError> {
    let v: Vec<_> = CertificateDer::pem_slice_iter(pem.as_bytes()).collect::<Result<_, _>>().map_err(|err| MtlsError(format!("a certificate PEM: {err:?}")))?;
    if v.is_empty() {
        return Err(MtlsError("a certificate PEM with no certificate in it".into()));
    }
    Ok(v)
}

fn key(pem: &str) -> Result<PrivateKeyDer<'static>, MtlsError> {
    PrivateKeyDer::from_pem_slice(pem.as_bytes()).map_err(|err| MtlsError(format!("a private key PEM: {err:?}")))
}

fn provider() -> Arc<rustls::crypto::CryptoProvider> {
    Arc::new(rustls::crypto::ring::default_provider())
}

fn roots(ca_pem: &str) -> Result<rustls::RootCertStore, MtlsError> {
    let mut r = rustls::RootCertStore::empty();
    for c in certs(ca_pem)? {
        r.add(c).map_err(|err| MtlsError(format!("the installer CA as a root: {err}")))?;
    }
    Ok(r)
}

/// **The door's TLS**: its own certificate, and a client certificate from the
/// installer CA REQUIRED on every connection.
pub fn server_config(ca_pem: &str, door: &Issued) -> Result<Arc<rustls::ServerConfig>, MtlsError> {
    let verifier = rustls::server::WebPkiClientVerifier::builder_with_provider(Arc::new(roots(ca_pem)?), provider())
        .build()
        .map_err(|err| MtlsError(format!("the client verifier: {err}")))?;
    let cfg = rustls::ServerConfig::builder_with_provider(provider())
        .with_protocol_versions(&[&rustls::version::TLS13])
        .map_err(|err| MtlsError(format!("TLS 1.3: {err}")))?
        .with_client_cert_verifier(verifier)
        .with_single_cert(certs(&door.cert_pem)?, key(&door.key_pem)?)
        .map_err(|err| MtlsError(format!("the door's certificate: {err}")))?;
    Ok(Arc::new(cfg))
}

/// **A box's TLS**: the installer CA pinned as the ONLY root, and the client
/// certificate it presents (the bootstrap one, or its own after the first join).
pub fn client_config(ca_pem: &str, client: &Issued) -> Result<Arc<rustls::ClientConfig>, MtlsError> {
    let cfg = rustls::ClientConfig::builder_with_provider(provider())
        .with_protocol_versions(&[&rustls::version::TLS13])
        .map_err(|err| MtlsError(format!("TLS 1.3: {err}")))?
        .with_root_certificates(roots(ca_pem)?)
        .with_client_auth_cert(certs(&client.cert_pem)?, key(&client.key_pem)?)
        .map_err(|err| MtlsError(format!("the client certificate: {err}")))?;
    Ok(Arc::new(cfg))
}

/// The server name a box checks the door's certificate against: its address.
pub fn door_name(utility: Ipv4Addr) -> ServerName<'static> {
    ServerName::IpAddress(IpAddr::V4(utility).into())
}

/// The CN of the certificate a client presented — `bootstrap` or a box name.
/// The chain was already verified against the installer CA by the handshake.
pub fn peer_cn(conn: &rustls::ServerConnection) -> Option<String> {
    cn_of(conn.peer_certificates()?.first()?)
}

/// **The name the installer checks a VM's certificate against**
/// ([`VM_SERVER_NAME`]) — never the address it dialled (0.3.0).
pub fn vm_name() -> ServerName<'static> {
    ServerName::try_from(VM_SERVER_NAME).expect("VM_SERVER_NAME is a valid DNS name")
}

/// The CN of the certificate a VM SERVED the installer — `bootstrap`, or the
/// box's own name once it holds one. Verified against the installer CA and
/// [`VM_SERVER_NAME`] by the handshake.
pub fn server_cn(conn: &rustls::ClientConnection) -> Option<String> {
    cn_of(conn.peer_certificates()?.first()?)
}

fn cn_of(der: &CertificateDer<'_>) -> Option<String> {
    let (_, cert) = x509_parser::parse_x509_certificate(der.as_ref()).ok()?;
    let cn = cert.subject().iter_common_name().next()?.as_str().ok()?.to_string();
    Some(cn)
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::io::{Read, Write};

    const H: Duration = Duration::from_secs(3600);

    /// A real TLS 1.3 handshake over loopback: the door sees `bootstrap`, then
    /// the box's own name; a certificate from ANOTHER CA is refused, and a box
    /// that pins the installer CA refuses a door it did not issue.
    #[test]
    fn a_box_joins_with_the_bootstrap_cert_then_speaks_as_itself_and_strangers_are_refused_both_ways() {
        let ca = InstallerCa::mint("fleet.test", H).unwrap();
        let door = ca.issue_door(Ipv4Addr::LOCALHOST, H).unwrap();
        let boot = ca.issue_bootstrap(H).unwrap();
        let (csr, box_key) = box_request().unwrap();
        let mine = Issued { cert_pem: ca.sign_box(&csr, "main", H).unwrap(), key_pem: box_key };
        let other = InstallerCa::mint("another", H).unwrap();
        let stranger = other.issue_bootstrap(H).unwrap();

        let serve = |client: Issued, pin: String| -> Result<String, String> {
            let l = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
            let addr = l.local_addr().unwrap();
            let scfg = server_config(&ca.cert_pem, &door).unwrap();
            let t = std::thread::spawn(move || {
                let (s, _) = l.accept().unwrap();
                let conn = rustls::ServerConnection::new(scfg).unwrap();
                let mut tls = rustls::StreamOwned::new(conn, s);
                let mut b = [0u8; 5];
                match tls.read_exact(&mut b) {
                    Ok(()) => {
                        let cn = peer_cn(&tls.conn).unwrap_or_default();
                        let _ = tls.write_all(cn.as_bytes());
                        let _ = tls.conn.send_close_notify();
                        let _ = tls.flush();
                        Ok(cn)
                    }
                    Err(err) => Err(err.to_string()),
                }
            });
            let ccfg = client_config(&pin, &client).map_err(|e| e.to_string())?;
            let s = std::net::TcpStream::connect(addr).unwrap();
            let conn = rustls::ClientConnection::new(ccfg, door_name(Ipv4Addr::LOCALHOST)).unwrap();
            let mut tls = rustls::StreamOwned::new(conn, s);
            let sent = tls.write_all(b"hello").and_then(|_| tls.flush());
            let mut back = String::new();
            let read = tls.read_to_string(&mut back);
            let server = t.join().unwrap();
            match (sent, read, server) {
                (Ok(()), Ok(_), Ok(cn)) if back == cn => Ok(cn),
                (s, r, sv) => Err(format!("sent {s:?} read {r:?} server {sv:?}")),
            }
        };
        assert_eq!(serve(boot.clone(), ca.cert_pem.clone()), Ok(BOOTSTRAP_CN.to_string()));
        assert_eq!(serve(mine, ca.cert_pem.clone()), Ok("main".to_string()), "the box speaks as the name the door gave it");
        assert!(serve(stranger, ca.cert_pem.clone()).is_err(), "a certificate from another CA is refused by the door");
        assert!(serve(boot, other.cert_pem.clone()).is_err(), "a box that pins the installer CA refuses a door it did not issue");
    }

    /// The door's certificate is valid from a day back (a box checks it with
    /// a clock nobody has set yet); the other leaves keep five minutes. And
    /// what a certificate has left is read off it.
    #[test]
    fn the_door_certificate_is_back_dated_a_day_and_what_a_certificate_has_left_is_read_off_it() {
        let ca = InstallerCa::mint("fleet.test", 90 * 24 * H).unwrap();
        let now = now_unix();
        let nb = |pem: &str| {
            let der = certs(pem).unwrap().remove(0);
            let (_, c) = x509_parser::parse_x509_certificate(der.as_ref()).unwrap();
            c.validity().not_before.timestamp()
        };
        let door = ca.issue_door(Ipv4Addr::LOCALHOST, H).unwrap();
        let back = now - nb(&door.cert_pem);
        assert!((24 * 3600 - 60..=24 * 3600 + 60).contains(&back), "the door is back-dated a day, got {back} s");
        let boot = ca.issue_bootstrap(H).unwrap();
        assert!(now - nb(&boot.cert_pem) <= 5 * 60 + 60, "the bootstrap certificate keeps five minutes");
        let left = cert_left(&ca.cert_pem, now).unwrap();
        assert!(left > 89 * 24 * H && left <= 90 * 24 * H, "{left:?}");
        assert_eq!(cert_left(&ca.cert_pem, now + 91 * 24 * 3600).unwrap(), Duration::ZERO, "ended");
        assert!(cert_left("not a pem", now).is_err());
    }

    #[test]
    fn the_box_request_is_signed_with_the_door_given_name_and_bootstrap_is_not_a_name() {
        let ca = InstallerCa::mint("fleet.test", H).unwrap();
        let (csr, _) = box_request().unwrap();
        let pem = ca.sign_box(&csr, "twin", H).unwrap();
        let der = certs(&pem).unwrap().remove(0);
        let (_, cert) = x509_parser::parse_x509_certificate(der.as_ref()).unwrap();
        assert_eq!(cert.subject().iter_common_name().next().unwrap().as_str().unwrap(), "twin");
        assert!(ca.sign_box(&csr, BOOTSTRAP_CN, H).is_err());
        assert!(ca.sign_box("not a csr", "twin", H).is_err());
    }
}