Skip to main content

darkbio_wire/
server.rs

1// wire-rs: encrypted protocol between Ark and host
2// Copyright 2025 Dark Bio AG. All rights reserved.
3
4use crate::framing::Framing;
5use crate::handshake;
6use crate::protocol::{ArkToHost, HostToArk};
7use crate::session::Session;
8use crate::{
9    CRYPTO_DOMAIN_WIRE, CRYPTO_DOMAIN_WIRE_ARK_TO_HOST, CRYPTO_DOMAIN_WIRE_HOST_TO_ARK, Error,
10};
11use darkbio_crypto::{cbor, cose, cwt, xdsa, xhpke};
12use darkbio_trust as trust;
13use std::io::{Read, Write};
14use tracing::{info, trace, warn};
15
16/// Device attestation a server presents in the handshake, a CWT in one of the
17/// shapes darkbio-trust defines (hardware or emulator claims). Only the shape
18/// is checked, so an obviously wrong blob is refused up front; whether it is
19/// accepted is the client's decision.
20#[derive(Clone)]
21pub struct Attestation(Vec<u8>);
22
23impl Attestation {
24    /// Wraps a CWT after checking that it decodes as a device attestation.
25    pub fn new(cwt: Vec<u8>) -> Result<Self, Error> {
26        if cwt::peek::<trust::device::HardwareClaims>(&cwt).is_err()
27            && cwt::peek::<trust::device::EmulatorClaims>(&cwt).is_err()
28        {
29            return Err(Error::InvalidAttestation);
30        }
31        Ok(Self(cwt))
32    }
33
34    /// CWT bytes of the attestation.
35    pub fn as_bytes(&self) -> &[u8] {
36        &self.0
37    }
38
39    /// Unwraps the attestation into its CWT bytes.
40    pub fn into_bytes(self) -> Vec<u8> {
41        self.0
42    }
43}
44
45/// Source of the device attestation the server presents in the handshake; queried
46/// on every handshake, so a freshly onboarded attestation can be picked up
47/// without recreating the wire.
48pub trait Attester {
49    /// Returns the device attestation to present to the client (e.g. a root-signed
50    /// CWT read from disk, or a self-signed fallback for pre-onboarding devices).
51    /// The identity key it embeds must be the one signing the wire's handshake.
52    fn attest(&mut self) -> Attestation;
53}
54
55/// A fixed attestation, presented as is on every handshake.
56impl Attester for Attestation {
57    fn attest(&mut self) -> Attestation {
58        self.clone()
59    }
60}
61
62/// Server side of the wire, an encrypted transport for serving protobuf requests
63/// from a connected client. It waits for session resets (empty frames), responds
64/// to handshake and afterward decrypts inbound and encrypts outbound messages.
65///
66/// Whenever the server drops a session, fails a handshake or receives data while
67/// it has no session, it answers with an empty frame of its own. A client still
68/// holding a session thus learns it is gone instead of having to time out.
69///
70/// The device attestation is not interpreted by the wire, it is provided by an
71/// `Attester` and forwarded to the client verbatim.
72pub struct Server<R: Read, W: Write, A: Attester> {
73    framing: Framing<R, W>, // COBS framed transport for ingress and egress data
74
75    signer: xdsa::SecretKey,  // Server's identity key, signing the ArkHello
76    attester: A,              // Source of the device attestation for handshakes
77    session: Option<Session>, // Active encrypted session (if handshake completed)
78
79    #[cfg(any(test, feature = "bench", feature = "fuzz"))]
80    timestamp: Option<i64>, // Signing time of the ArkHello pinned by a test, the clock otherwise
81}
82
83impl<R: Read, W: Write, A: Attester> Server<R, W, A> {
84    /// Creates a new server side around a low level reader and writer. The signer is
85    /// the server's identity key, which signs the handshake. The client verifies that
86    /// signature against the key it extracts from the attestation, so the two
87    /// must match. Reads block per the transport's semantics, so any timeout
88    /// must be configured on the reader passed in.
89    pub fn new(reader: R, writer: W, signer: xdsa::SecretKey, attester: A) -> Self {
90        Self {
91            framing: Framing::new(reader, writer),
92            signer,
93            attester,
94            session: None,
95            #[cfg(any(test, feature = "bench", feature = "fuzz"))]
96            timestamp: None,
97        }
98    }
99
100    /// Test helper creating a server side signing its ArkHellos at the given
101    /// time instead of the clock, so a run of it is the same every time. Not
102    /// part of the API.
103    #[doc(hidden)]
104    #[inline]
105    #[cfg(any(test, feature = "bench", feature = "fuzz"))]
106    #[cfg_attr(coverage_nightly, coverage(off))]
107    pub fn new_at(
108        reader: R,
109        writer: W,
110        signer: xdsa::SecretKey,
111        attester: A,
112        timestamp: i64,
113    ) -> Self {
114        let mut server = Self::new(reader, writer, signer, attester);
115        server.timestamp = Some(timestamp);
116        server
117    }
118
119    /// Serves the next host-to-ark message, decrypting and protobuf decoding it.
120    /// Empty frames are session resets and run the handshake inline. Junk
121    /// outside a session, undecryptable packets and failed handshakes are
122    /// logged, answered with an empty frame and skipped. Only transport
123    /// failures and malformed messages surface as errors.
124    pub fn next_message(&mut self) -> Result<HostToArk, Error> {
125        // Loop until we can deliver a valid decrypted message. Empty frames
126        // are consumed and trigger a new session handshake.
127        loop {
128            // Retrieve the next COBS encoded packet
129            let size = match self.framing.next_packet() {
130                // Transport errors propagate immediately
131                Err(Error::Terminated) => return Err(Error::Terminated),
132                Err(Error::RecvFailed(err)) => return Err(Error::RecvFailed(err)),
133
134                // Decode errors may be due to session resets, log and ignore.
135                // Within a session the skipped frame may have carried a sealed
136                // message though, leaving the HPKE sequence behind the client's,
137                // so the session cannot continue either way.
138                Err(err) => {
139                    if self.session.take().is_some() {
140                        warn!("failed to decode cobs packet, resetting session: {}", err);
141                    } else {
142                        warn!("failed to decode cobs packet: {}", err);
143                    }
144                    self.send_dropped();
145                    continue;
146                }
147                // Empty frame signals a session reset from the client
148                Ok(None) => {
149                    self.session = None;
150
151                    match self.handshake() {
152                        // Transport errors propagate immediately
153                        Err(Error::Terminated) => return Err(Error::Terminated),
154                        Err(Error::RecvFailed(err)) => return Err(Error::RecvFailed(err)),
155
156                        // Decode or protocol errors are logged and ignored, the
157                        // client learning that no session came out of it
158                        Err(err) => {
159                            warn!("wire handshake failed: {}", err);
160                            self.send_dropped();
161                            continue;
162                        }
163                        // Handshake successful
164                        Ok(session) => {
165                            info!("new wire session established");
166                            self.session = Some(session);
167                            continue;
168                        }
169                    }
170                }
171                // Valid COBS packet
172                Ok(Some(size)) => size,
173            };
174            // Non-empty packet without a session is considered junk, the client
175            // may still think it has a session though, tell it otherwise
176            let session = match self.session.as_mut() {
177                None => {
178                    warn!("dropping data outside session");
179                    self.send_dropped();
180                    continue;
181                }
182                Some(s) => s,
183            };
184            // Decrypt the message and parse it with protobuf
185            let req = match session.open(&self.framing.decobs_buffer[..size]) {
186                // If decryption fails, the HPKE context is most probably
187                // broken, no point continuing with it.
188                Err(Error::EncryptionFailed(err)) => {
189                    warn!("decryption failed, resetting session: {}", err);
190                    self.session = None;
191                    self.send_dropped();
192                    continue;
193                }
194                Err(err) => return Err(err),
195                Ok(req) => req,
196            };
197            trace!("read host-to-ark message ({} bytes encrypted)", size);
198            return Ok(req);
199        }
200    }
201
202    /// Tells the client that the server has no session with it by sending an empty
203    /// frame.
204    fn send_dropped(&mut self) {
205        if let Err(err) = self.framing.send_dropped() {
206            warn!("failed to signal dropped session: {}", err);
207        }
208    }
209
210    /// Protobuf encodes an ark-to-host message, seals it with the session and
211    /// sends it. Fails without an active session. A failure after sealing
212    /// drops the session and signals the client, as the client's HPKE sequence can
213    /// no longer be caught up with.
214    pub fn send_message(&mut self, res: ArkToHost) -> Result<(), Error> {
215        // Encode and seal the message, oversized messages are rejected before
216        // the HPKE sequence advances, only a failed seal breaks the session
217        let session = self
218            .session
219            .as_mut()
220            .ok_or_else(|| Error::EncryptionFailed("no active session".into()))?;
221
222        let blob = match session.seal(&res, &mut self.framing.encode_buffer) {
223            Err(err @ Error::EncryptionFailed(_)) => {
224                self.session = None;
225                self.send_dropped();
226                return Err(err);
227            }
228            Err(err) => return Err(err),
229            Ok(blob) => blob,
230        };
231        // Send the sealed message, tearing down the session if the transport
232        // fails to deliver it
233        if let Err(err) = self.framing.send_packet(&blob) {
234            self.session = None;
235            self.send_dropped();
236            return Err(err);
237        }
238        trace!("sent ark-to-host message ({} bytes)", blob.len());
239        Ok(())
240    }
241
242    /// Responds to the handshake after a session reset, establishing the
243    /// HPKE contexts of both directions:
244    ///
245    ///   1. Client -> Server: HostHello { host_signer, host_crypto }           (plain CBOR)
246    ///   2. Server -> Client: ArkHello  { ark_attest, ark_crypto, a2h_encap }  (cose::seal)
247    ///   3. Client -> Server: HostAck   { h2a_encap }                          (cose::seal)
248    fn handshake(&mut self) -> Result<Session, Error> {
249        loop {
250            // Message 1: Read the HostHello (skip any trailing empty reset frames)
251            let size = loop {
252                if let Some(n) = self.framing.next_packet()? {
253                    break n;
254                }
255            };
256            let host_hello: handshake::HostHello =
257                cbor::decode(&self.framing.decobs_buffer[..size]).map_err(|err| {
258                    Error::HandshakeFailed(format!("invalid client hello: {}", err))
259                })?;
260
261            // Generate an ephemeral server xHPKE keypair and set up the server->Client sender
262            let ark_crypto_key = xhpke::SecretKey::generate();
263            let ark_crypto_pub = ark_crypto_key.public_key();
264
265            let (sender, a2h_encap) = host_hello
266                .host_crypto
267                .new_sender(CRYPTO_DOMAIN_WIRE_ARK_TO_HOST)
268                .map_err(|err| {
269                    Error::HandshakeFailed(format!("server sender setup failed: {}", err))
270                })?;
271
272            // Message 2: Seal and send the ArkHello
273            let ark_hello = handshake::ArkHello {
274                ark_attest: self.attester.attest().into_bytes(),
275                ark_crypto: ark_crypto_pub.clone(),
276                a2h_encap: a2h_encap.to_vec(),
277            };
278            let auth = handshake::ArkHelloAuth {
279                host_signer: host_hello.host_signer.clone(),
280                host_crypto: host_hello.host_crypto.clone(),
281            };
282            #[cfg(not(any(test, feature = "bench", feature = "fuzz")))]
283            let sealed = cose::seal(
284                &ark_hello,
285                &auth,
286                &self.signer,
287                &host_hello.host_crypto,
288                CRYPTO_DOMAIN_WIRE,
289            );
290            #[cfg(any(test, feature = "bench", feature = "fuzz"))]
291            let sealed = match self.timestamp {
292                Some(timestamp) => cose::seal_at(
293                    &ark_hello,
294                    &auth,
295                    &self.signer,
296                    &host_hello.host_crypto,
297                    CRYPTO_DOMAIN_WIRE,
298                    timestamp,
299                ),
300                None => cose::seal(
301                    &ark_hello,
302                    &auth,
303                    &self.signer,
304                    &host_hello.host_crypto,
305                    CRYPTO_DOMAIN_WIRE,
306                ),
307            };
308            let ark_hello = sealed.map_err(|err| {
309                Error::HandshakeFailed(format!("failed to seal server hello: {}", err))
310            })?;
311
312            self.framing.send_packet(&ark_hello)?;
313
314            // Message 3: Read and open the HostAck. An empty frame probably
315            // means the client is restarting the session, start over.
316            let Some(size) = self.framing.next_packet()? else {
317                warn!("session reset during handshake");
318                continue;
319            };
320            let host_ack: handshake::HostAck = cose::open(
321                &self.framing.decobs_buffer[..size],
322                &handshake::HostAckAuth {
323                    ark_signer: self.signer.public_key(),
324                    ark_crypto: ark_crypto_pub.clone(),
325                },
326                &ark_crypto_key,
327                &host_hello.host_signer,
328                CRYPTO_DOMAIN_WIRE,
329                None, // clock possibly unset, ephemeral keys guarantee freshness
330            )
331            .map_err(|err| Error::HandshakeFailed(format!("invalid client ack: {}", err)))?;
332
333            // Set up the Client->server receiver
334            let enc_h2a: [u8; xhpke::ENCAP_KEY_SIZE] = host_ack
335                .h2a_encap
336                .try_into()
337                .map_err(|_| Error::HandshakeFailed("invalid h2a_encap size".into()))?;
338
339            let receiver = ark_crypto_key
340                .new_receiver(&enc_h2a, CRYPTO_DOMAIN_WIRE_HOST_TO_ARK)
341                .map_err(|err| {
342                    Error::HandshakeFailed(format!("server receiver setup failed: {}", err))
343                })?;
344
345            // Session established
346            return Ok(Session { sender, receiver });
347        }
348    }
349}
350
351#[cfg(all(test, unix))]
352#[cfg_attr(coverage_nightly, coverage(off))]
353mod tests {
354    use super::*;
355    use crate::testing;
356    use crate::{Client, Verifier};
357    use darkbio_cobs as cobs;
358    use std::io::Write;
359    use std::os::unix::net::UnixStream;
360
361    /// Self-signed attestation of a never onboarded device, the placeholder an
362    /// Server presents before it is attested by a root.
363    fn self_attestation(signer: &xdsa::SecretKey) -> Attestation {
364        use darkbio_crypto::cwt::claims::{self, eat};
365
366        let claims = darkbio_trust::device::HardwareClaims {
367            sub: claims::Subject { sub: "".into() },
368            cnf: claims::Confirm::new(signer.public_key()),
369            nbf: claims::NotBefore { nbf: 0 },
370            iat: claims::IssuedAt { iat: 0 },
371            oem: eat::Oemid::new_pen(0),
372            hwm: eat::HwModel { hw_model: vec![] },
373            hwv: eat::HwVersion::new("".into()),
374        };
375        let cwt = cwt::issue(
376            &claims,
377            signer,
378            darkbio_trust::CRYPTO_DOMAIN_DEVICE_ATTESTATION,
379        )
380        .unwrap();
381        Attestation::new(cwt).unwrap()
382    }
383
384    /// COBS-encodes data and appends the frame delimiter.
385    fn cobs_frame(data: &[u8]) -> Vec<u8> {
386        let mut buf = vec![0u8; cobs::encode_buffer(data.len())];
387        let n = cobs::encode(data, &mut buf).unwrap();
388        buf.truncate(n);
389        buf.push(0x00);
390        buf
391    }
392
393    // Tests the two real sides against each other. The handshake hands the
394    // attestation to the client's verifier unchanged and a request gets its
395    // response. The server's signal for a dropped session then surfaces on the
396    // client as a reset, which a fresh handshake recovers from.
397    #[test]
398    fn test_message_round_trip() {
399        testing::init_tracing();
400
401        let signer_key = xdsa::SecretKey::generate();
402        let signer_pub = signer_key.public_key();
403        let attestation = self_attestation(&signer_key);
404        let presented = attestation.clone();
405
406        let (host_sock, ark_sock) = UnixStream::pair().unwrap();
407        let ark_reader = ark_sock.try_clone().unwrap();
408        let ark_writer = ark_sock;
409
410        // Server side: receive two messages (across two sessions), echo each back.
411        let ark_thread = std::thread::spawn(move || {
412            let mut server = Server::new(ark_reader, ark_writer, signer_key, attestation);
413            let mut ids = Vec::new();
414            for _ in 0..2 {
415                let req = server.next_message().unwrap();
416                ids.push(req.id);
417                server
418                    .send_message(ArkToHost {
419                        id: req.id,
420                        err: None,
421                        content: None,
422                    })
423                    .unwrap();
424            }
425            ids
426        });
427
428        // Raw handle to inject bytes past the client side.
429        let mut raw_sock = host_sock.try_clone().unwrap();
430
431        // Session 1: handshake, checking the attestation, exchange one message.
432        let mut client = Client::new(host_sock.try_clone().unwrap(), host_sock);
433        let attest = client.handshake(&signer_pub).unwrap();
434        assert_eq!(attest.as_bytes(), presented.as_bytes());
435        client
436            .send_message(HostToArk {
437                id: Some(1),
438                content: None,
439            })
440            .unwrap();
441        let res = client.next_message().unwrap();
442        assert_eq!(res.id, Some(1));
443
444        // Inject a frame the server cannot decrypt. It drops the session and
445        // signals it, the client surfacing the signal as a reset on its next
446        // read and refusing to send into the dead session afterwards.
447        raw_sock
448            .write_all(&cobs_frame(b"interrupted transfer"))
449            .unwrap();
450        let result = client.next_message();
451        assert!(matches!(result, Err(Error::SessionReset)), "{result:?}");
452        let result = client.send_message(HostToArk {
453            id: Some(2),
454            content: None,
455        });
456        assert!(
457            matches!(result, Err(Error::EncryptionFailed(_))),
458            "{result:?}"
459        );
460
461        // Session 2: new handshake on the same wire, exchange one message.
462        client.handshake(&signer_pub).unwrap();
463        client
464            .send_message(HostToArk {
465                id: Some(2),
466                content: None,
467            })
468            .unwrap();
469        let res = client.next_message().unwrap();
470        assert_eq!(res.id, Some(2));
471
472        let ids = ark_thread.join().unwrap();
473        assert_eq!(ids, vec![Some(1), Some(2)]);
474    }
475
476    // Tests that an untrusting verifier rejects the session on the client side.
477    #[test]
478    fn test_verifier_rejects() {
479        testing::init_tracing();
480
481        /// Verifier refusing every attestation.
482        struct Untrusting;
483
484        impl Verifier for Untrusting {
485            type Info = ();
486
487            fn verify(&self, _: &Attestation) -> Result<(xdsa::PublicKey, Self::Info), String> {
488                Err("attestation rejected".into())
489            }
490        }
491
492        let signer_key = xdsa::SecretKey::generate();
493
494        let (host_sock, ark_sock) = UnixStream::pair().unwrap();
495        let ark_reader = ark_sock.try_clone().unwrap();
496        let ark_writer = ark_sock;
497
498        // Server side: serve handshakes until the transport drops. The client aborts
499        // mid-handshake, so the server never delivers a message.
500        let ark_thread = std::thread::spawn(move || {
501            let attestation = self_attestation(&signer_key);
502            let mut server = Server::new(ark_reader, ark_writer, signer_key, attestation);
503            server.next_message()
504        });
505
506        // Client side: refuse the attestation in the verifier.
507        let mut client = Client::new(host_sock.try_clone().unwrap(), host_sock);
508        let result = client.handshake(&Untrusting);
509        assert!(result.is_err());
510
511        // Dropping the client tears down the transport, unblocking the server.
512        drop(client);
513        assert!(ark_thread.join().unwrap().is_err());
514    }
515
516    // Tests that the roots verifier opens sessions with root attested Arks of
517    // either realm, handing back their verified identity. Arks attested under
518    // unknown roots or self-signed ones are refused.
519    #[test]
520    fn test_roots_verifier() {
521        testing::init_tracing();
522
523        use crate::Roots;
524        use darkbio_crypto::cwt;
525        use darkbio_crypto::cwt::claims::{self, eat};
526        use darkbio_trust::device::{EmulatorClaims, HardwareClaims};
527        use darkbio_trust::{CRYPTO_DOMAIN_DEVICE_ATTESTATION, Realm};
528        use std::time::{SystemTime, UNIX_EPOCH};
529
530        let now = SystemTime::now()
531            .duration_since(UNIX_EPOCH)
532            .unwrap()
533            .as_secs();
534
535        /// Drives a handshake with a server presenting the attestation and the client
536        /// trusting the roots, returning the client's verdict.
537        fn handshake(
538            signer_key: xdsa::SecretKey,
539            attestation: Attestation,
540            hardware: &[xdsa::PublicKey],
541            emulator: &[xdsa::PublicKey],
542        ) -> Result<darkbio_trust::device::Device, Error> {
543            let (host_sock, ark_sock) = UnixStream::pair().unwrap();
544            let ark_reader = ark_sock.try_clone().unwrap();
545            let ark_writer = ark_sock;
546
547            let ark_thread = std::thread::spawn(move || {
548                let mut server = Server::new(ark_reader, ark_writer, signer_key, attestation);
549                server.next_message()
550            });
551            let mut client = Client::new(host_sock.try_clone().unwrap(), host_sock);
552            let result = client.handshake(&Roots { hardware, emulator });
553
554            // Dropping the client tears down the transport, unblocking the server
555            drop(client);
556            let _ = ark_thread.join().unwrap();
557            result
558        }
559
560        let hardware_root = xdsa::SecretKey::generate();
561        let emulator_root = xdsa::SecretKey::generate();
562        let hardware_roots = [hardware_root.public_key()];
563        let emulator_roots = [emulator_root.public_key()];
564
565        // A hardware server attested by a hardware root is accepted with its identity
566        let signer_key = xdsa::SecretKey::generate();
567        let attestation = cwt::issue(
568            &HardwareClaims {
569                sub: claims::Subject {
570                    sub: "ark-1234".into(),
571                },
572                cnf: claims::Confirm::new(signer_key.public_key()),
573                nbf: claims::NotBefore { nbf: now - 10 },
574                iat: claims::IssuedAt { iat: now - 10 },
575                oem: eat::Oemid::new_pen(65145),
576                hwm: eat::HwModel {
577                    hw_model: b"Ark I".to_vec(),
578                },
579                hwv: eat::HwVersion::new("Ark I - 1.0.0".into()),
580            },
581            &hardware_root,
582            CRYPTO_DOMAIN_DEVICE_ATTESTATION,
583        )
584        .map(|cwt| Attestation::new(cwt).unwrap())
585        .unwrap();
586        let device = handshake(signer_key, attestation.clone(), &hardware_roots, &[]).unwrap();
587        assert_eq!(device.realm, Realm::Hardware);
588        assert_eq!(device.serial, "ark-1234");
589
590        // The same server is refused by a client trusting only emulator roots
591        let signer_key = xdsa::SecretKey::generate();
592        assert!(handshake(signer_key, attestation, &[], &emulator_roots).is_err());
593
594        // An emulated server attested by an emulator root is accepted with its expiry
595        let signer_key = xdsa::SecretKey::generate();
596        let attestation = cwt::issue(
597            &EmulatorClaims {
598                sub: claims::Subject {
599                    sub: "emu-1234".into(),
600                },
601                cnf: claims::Confirm::new(signer_key.public_key()),
602                nbf: claims::NotBefore { nbf: now - 10 },
603                exp: claims::Expiration { exp: now + 1000 },
604                iat: claims::IssuedAt { iat: now - 10 },
605                oem: eat::Oemid::new_pen(65145),
606                hwm: eat::HwModel {
607                    hw_model: b"Ark I".to_vec(),
608                },
609                hwv: eat::HwVersion::new("Ark I - 1.0.0".into()),
610            },
611            &emulator_root,
612            CRYPTO_DOMAIN_DEVICE_ATTESTATION,
613        )
614        .map(|cwt| Attestation::new(cwt).unwrap())
615        .unwrap();
616        let device = handshake(signer_key, attestation, &hardware_roots, &emulator_roots).unwrap();
617        assert_eq!(device.realm, Realm::Emulator);
618        assert_eq!(device.expiry, Some(now + 1000));
619
620        // A never onboarded server presenting a self-signed attestation is refused
621        let signer_key = xdsa::SecretKey::generate();
622        let attestation = cwt::issue(
623            &HardwareClaims {
624                sub: claims::Subject { sub: "".into() },
625                cnf: claims::Confirm::new(signer_key.public_key()),
626                nbf: claims::NotBefore { nbf: 0 },
627                iat: claims::IssuedAt { iat: 0 },
628                oem: eat::Oemid::new_pen(0),
629                hwm: eat::HwModel { hw_model: vec![] },
630                hwv: eat::HwVersion::new("".into()),
631            },
632            &signer_key,
633            CRYPTO_DOMAIN_DEVICE_ATTESTATION,
634        )
635        .map(|cwt| Attestation::new(cwt).unwrap())
636        .unwrap();
637        assert!(handshake(signer_key, attestation, &hardware_roots, &emulator_roots).is_err());
638    }
639
640    // Tests that only CWTs in a device attestation shape are accepted as
641    // attestations, junk and other token shapes being refused up front.
642    #[test]
643    fn test_attestation_shapes() {
644        use darkbio_crypto::cwt::claims;
645        use darkbio_trust::CRYPTO_DOMAIN_DEVICE_ATTESTATION;
646
647        let signer = xdsa::SecretKey::generate();
648        let _ = self_attestation(&signer);
649
650        let emulator = darkbio_trust::device::EmulatorClaims {
651            sub: claims::Subject { sub: "".into() },
652            cnf: claims::Confirm::new(signer.public_key()),
653            nbf: claims::NotBefore { nbf: 0 },
654            exp: claims::Expiration { exp: u64::MAX },
655            iat: claims::IssuedAt { iat: 0 },
656            oem: claims::eat::Oemid::new_pen(0),
657            hwm: claims::eat::HwModel { hw_model: vec![] },
658            hwv: claims::eat::HwVersion::new("".into()),
659        };
660        let cwt = cwt::issue(&emulator, &signer, CRYPTO_DOMAIN_DEVICE_ATTESTATION).unwrap();
661        Attestation::new(cwt).expect("emulator attestation refused");
662
663        let cloud = darkbio_trust::cloud::SignerClaims {
664            iss: claims::Issuer { iss: "".into() },
665            sub: claims::Subject { sub: "".into() },
666            nbf: claims::NotBefore { nbf: 0 },
667            exp: claims::Expiration { exp: 1 },
668            cnf: claims::Confirm::new(signer.public_key()),
669        };
670        let cwt = cwt::issue(&cloud, &signer, CRYPTO_DOMAIN_DEVICE_ATTESTATION).unwrap();
671        let result = Attestation::new(cwt).map(|_| ());
672        assert!(
673            matches!(result, Err(Error::InvalidAttestation)),
674            "{result:?}"
675        );
676        let result = Attestation::new(b"junk".to_vec()).map(|_| ());
677        assert!(
678            matches!(result, Err(Error::InvalidAttestation)),
679            "{result:?}"
680        );
681    }
682}