Skip to main content

darkbio_wire/transport/
server.rs

1// wire-rs: encrypted protocol between Ark and host
2// Copyright 2025 Dark Bio AG. All rights reserved.
3//
4// Use of this source code is governed by a BSD-style
5// license that can be found in the LICENSE file.
6
7use crate::LogId;
8use crate::transport::DEFAULT_HANDSHAKE_TIMEOUT;
9use crate::transport::framing::FrameReader;
10use crate::transport::handshake;
11use crate::transport::io::check_deadline;
12use crate::transport::outbound::{Outbound, Side};
13use crate::transport::sealing;
14use crate::transport::sender::Sender;
15use crate::transport::{
16    CRYPTO_DOMAIN_WIRE, CRYPTO_DOMAIN_WIRE_ARK_TO_HOST, CRYPTO_DOMAIN_WIRE_HOST_TO_ARK, Closer,
17    Error, Read, Stream, Write,
18};
19use darkbio_crypto::{cbor, cose, cwt, xdsa, xhpke};
20use darkbio_trust as trust;
21use std::fmt;
22use std::sync::{Arc, Mutex};
23use std::time::{Duration, Instant};
24use tracing::{debug, info, trace, warn};
25
26/// Device attestation presented during the handshake. The CWT must contain
27/// hardware or emulator claims as defined by darkbio-trust. Construction checks
28/// that shape; the client's verifier decides whether to trust the attestation.
29#[derive(Clone)]
30pub struct Attestation(Vec<u8>);
31
32impl Attestation {
33    /// Wraps a CWT after checking that it decodes as a device attestation.
34    pub fn new(cwt: Vec<u8>) -> Result<Self, Error> {
35        if cwt::peek::<trust::device::HardwareClaims>(&cwt).is_err()
36            && cwt::peek::<trust::device::EmulatorClaims>(&cwt).is_err()
37        {
38            return Err(Error::InvalidAttestation);
39        }
40        Ok(Self(cwt))
41    }
42
43    /// CWT bytes of the attestation.
44    pub fn as_bytes(&self) -> &[u8] {
45        &self.0
46    }
47
48    /// Unwraps the attestation into its CWT bytes.
49    pub fn into_bytes(self) -> Vec<u8> {
50        self.0
51    }
52}
53
54impl fmt::Debug for Attestation {
55    /// Shows the size of the CWT, never its bytes.
56    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57        f.debug_struct("Attestation")
58            .field("len", &self.0.len())
59            .finish()
60    }
61}
62
63/// Supplies the server's device attestation on every handshake. This lets the
64/// server pick up a new attestation after onboarding without recreating transport.
65pub trait Attester {
66    /// Returns the device attestation to present to the client (e.g. a root-signed
67    /// CWT read from disk, or a self-signed fallback for pre-onboarding devices).
68    /// The identity key it embeds must be the one signing the wire's handshake.
69    fn attest(&mut self) -> Attestation;
70}
71
72/// A fixed attestation, presented as is on every handshake.
73impl Attester for Attestation {
74    fn attest(&mut self) -> Attestation {
75        self.clone()
76    }
77}
78
79/// A decrypted message or encrypted session transition returned by [`Server::recv`].
80/// Events arrive in receive order and refer to sessions over the existing byte
81/// stream. Permanent stream closure is observed through I/O results.
82pub enum Event<W: Write> {
83    /// A handshake completed and established an encrypted session. The sender
84    /// belongs to that session and cannot send into a later replacement.
85    /// Concurrent send failure or stream closure may make it unusable before
86    /// the caller handles the event.
87    Connected(Sender<W>),
88
89    /// The previously opened session ended through a peer reset, invalid
90    /// incoming data or an observed send failure. After a peer reset, the next
91    /// receive call runs the handshake. A local [`Server::disconnect`] does not emit this
92    /// event. Permanent stream closure is reported through I/O results instead.
93    Disconnected,
94
95    /// A decrypted message from the client.
96    Message(Vec<u8>),
97}
98
99impl<W: Write> fmt::Debug for Event<W> {
100    /// Names the event, showing the sender or the message length.
101    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
102        match self {
103            Self::Connected(sender) => f.debug_tuple("Connected").field(sender).finish(),
104            Self::Disconnected => f.write_str("Disconnected"),
105            Self::Message(message) => f.debug_tuple("Message").field(&message.len()).finish(),
106        }
107    }
108}
109
110/// Server side of the wire, accepting encrypted sessions over a supplied byte
111/// stream. [`Server::recv`] handles client resets and handshakes. Each successful
112/// handshake returns a sender through [`Event::Connected`]. Later reads deliver
113/// decrypted messages or report that the session ended.
114/// [`Server::disconnect`] ends a session while leaving the stream available for
115/// another; [`Server::close`] permanently closes the stream.
116///
117/// On a local disconnect, a session failure, a failed handshake or data received
118/// outside a session, the server attempts an empty frame notification. A client
119/// receiving it drops its old session. Notifications are best effort and bounded
120/// by an output deadline. A failed write's own notification uses only its
121/// remaining budget and is skipped after timeout. Later incoming traffic can
122/// prompt a standalone notification.
123///
124/// An [`Attester`] supplies the device attestation. Transport forwards it to the
125/// client, whose verifier decides whether to trust it.
126pub struct Server<R: Read, W: Write, A: Attester> {
127    reader: FrameReader<R>,     // COBS framed transport for ingress data
128    outbound: Arc<Outbound<W>>, // Outgoing transport, shared with the senders
129
130    signer: xdsa::SecretKey, // Server's identity key, signing the ArkHello
131    attester: A,             // Source of the device attestation for handshakes
132
133    receiver: Option<xhpke::Receiver>, // Receive context used exclusively by this server
134    sealer: Option<Arc<Mutex<xhpke::Sender>>>, // Send context shared with active sends
135
136    handshake_timeout: Duration, // Budget for each new handshake attempt
137    handshake_deadline: Option<Instant>, // Deadline of the handshake requested by a reset
138    log_id: LogId, // Label of the current session in log lines, unset before the first
139
140    #[cfg(any(test, feature = "bench", feature = "fuzz"))]
141    timestamp: Option<i64>, // Test signing time for ArkHello; otherwise use the clock
142}
143
144impl<R: Read, W: Write, A: Attester> Server<R, W, A> {
145    /// Creates a server owning the byte stream and its shutdown operation.
146    /// The signer is the server's identity key. It must match the key embedded
147    /// in the device attestation. Output uses the stream's configured write
148    /// timeout. The adapter must enforce deadlines and shutdown cancellation.
149    pub fn new(stream: Stream<R, W>, signer: xdsa::SecretKey, attester: A) -> Self {
150        let (reader, writer, close, timeout) = stream.into_parts();
151        let outbound = Arc::new(Outbound::new(writer, Side::Server, close.clone(), timeout));
152        Self {
153            reader: FrameReader::new(reader, close),
154            outbound,
155            signer,
156            attester,
157            receiver: None,
158            sealer: None,
159            handshake_timeout: DEFAULT_HANDSHAKE_TIMEOUT,
160            handshake_deadline: None,
161            log_id: LogId::default(),
162            #[cfg(any(test, feature = "bench", feature = "fuzz"))]
163            timestamp: None,
164        }
165    }
166
167    /// Sets the budget for each subsequent handshake, starting when a reset is
168    /// received. Defaults to [`DEFAULT_HANDSHAKE_TIMEOUT`]. Output and peer
169    /// replies share one deadline; progress and repeated resets within the attempt
170    /// do not refresh it. An already pending handshake keeps its deadline. Each
171    /// outgoing frame is also limited by the stream's write timeout. Waiting for
172    /// locks and attester callbacks can extend the call beyond the deadline.
173    /// Time between recv calls also consumes the budget.
174    ///
175    /// Zero expires attempts immediately. A duration too large to add to an
176    /// [`Instant`] panics when the next handshake's deadline is constructed.
177    pub fn set_handshake_timeout(mut self, timeout: Duration) -> Self {
178        self.handshake_timeout = timeout;
179        self
180    }
181
182    /// A handle that permanently closes the stream from another thread.
183    pub fn closer(&self) -> Closer {
184        self.outbound.closer()
185    }
186
187    /// Permanently closes the stream and waits for adapter shutdown. Senders
188    /// observe closure through write failure; buffered messages remain readable.
189    /// See [`Closer::close`].
190    pub fn close(&self) {
191        self.outbound.close();
192    }
193
194    /// Creates a test server with a fixed ArkHello signing time for vector replay.
195    /// Not part of the normal transport API.
196    #[doc(hidden)]
197    #[inline]
198    #[cfg(any(test, feature = "bench", feature = "fuzz"))]
199    #[cfg_attr(coverage_nightly, coverage(off))]
200    pub fn new_at(
201        stream: Stream<R, W>,
202        signer: xdsa::SecretKey,
203        attester: A,
204        timestamp: i64,
205    ) -> Self {
206        let mut server = Self::new(stream, signer, attester);
207        server.timestamp = Some(timestamp);
208        server
209    }
210
211    /// Receives a decrypted message or a session transition. A client reset
212    /// starts a handshake, whose completion returns [`Event::Connected`] with
213    /// a sender before any messages from that session are delivered.
214    ///
215    /// A client reset or invalid incoming data ends the current session and
216    /// returns [`Event::Disconnected`]. Oversized frames count as invalid data.
217    /// After a reset, the next call runs the handshake under one configured
218    /// deadline starting at that reset. Repeated resets within that attempt do
219    /// not refresh it. Expiry returns `RecvFailed(TimedOut)` and a fresh reset
220    /// can start another attempt. A send failure also ends
221    /// the session, but does not wake a blocked read. It is reported once
222    /// receiving progresses. Sessions ended by a local disconnect are not
223    /// reported again.
224    ///
225    /// After decryption, message acceptance is ordered with session ending
226    /// without waiting for the writer. A concurrent send failure can cause a
227    /// decrypted message to be discarded before acceptance. An accepted message
228    /// may reach the caller after another thread ends the session. Reporting
229    /// a session's end waits for outgoing writes to finish.
230    ///
231    /// Junk outside a session and handshake protocol or authentication failures
232    /// are logged, answered with a best-effort empty frame and skipped. Handshake
233    /// write failures surface as errors; calling again waits for a new reset on
234    /// the same stream. Adapter read failures and EOF also surface as errors,
235    /// without removing the binding. The caller can retry a transient read error,
236    /// disconnect the session or close the stream. Outside a handshake, reads
237    /// wait for data or adapter shutdown without a session timeout.
238    ///
239    /// Outgoing frames and standalone empty notifications use the stream's
240    /// configured write timeout. A notification sent while handling a failed
241    /// write shares that frame's remaining budget and is skipped after timeout.
242    /// These output failures do not themselves close the byte stream.
243    pub fn recv(&mut self) -> Result<Event<W>, Error> {
244        // Continue until a message, session transition or I/O error is ready.
245        // Empty frames request a handshake on the next pass.
246        loop {
247            // If a reset just arrived, run the handshake
248            if let Some(deadline) = self.handshake_deadline.take() {
249                match self.handshake(deadline) {
250                    // Transport errors propagate immediately
251                    Err(Error::Terminated) => return Err(Error::Terminated),
252                    Err(Error::RecvFailed(err)) => return Err(Error::RecvFailed(err)),
253                    // Outbound already attempted notification within the failed
254                    // frame's budget; a fresh attempt here could block again.
255                    Err(Error::SendFailed(err)) => return Err(Error::SendFailed(err)),
256
257                    // Tell the client that the handshake did not establish a session
258                    Err(err) => {
259                        warn!("dropping wire handshake: {}", err);
260                        if let Err(err) = self.outbound.send_dropped(Some(deadline)) {
261                            warn!("failed to signal dropped handshake: {}", err);
262                        }
263                        // Do not swallow an attempt deadline exhausted during
264                        // authentication or its failure notification.
265                        check_deadline(&self.outbound.clock, deadline)
266                            .map_err(Error::RecvFailed)?;
267                    }
268                    // Report the completed handshake before reading messages.
269                    // The caller can now send without waiting for a client request.
270                    Ok((sender, receiver)) => {
271                        let sender = self.new_session(sender, receiver);
272                        info!("wire session {} established", self.log_id);
273                        return Ok(Event::Connected(sender));
274                    }
275                }
276                continue;
277            }
278            // Retrieve the next COBS encoded packet
279            let packet = match self.reader.next_packet(None) {
280                // Transport errors propagate immediately
281                Err(Error::Terminated) => return Err(Error::Terminated),
282                Err(Error::RecvFailed(err)) => return Err(Error::RecvFailed(err)),
283
284                // A reset can terminate a partial frame and cause a framing error.
285                // The frame may also have carried a sealed message. End any active
286                // session because its encryption sequence can no longer be trusted.
287                Err(err) => {
288                    let ended = self.end_session();
289                    if ended {
290                        warn!("ending session {}: {}", self.log_id, err);
291                    } else {
292                        debug!("discarding invalid frame outside session: {}", err);
293                    }
294                    self.send_dropped();
295                    if ended {
296                        return Ok(Event::Disconnected);
297                    }
298                    continue;
299                }
300                // A reset ends any active session. Run the handshake on the next
301                // receive call if we return an event, or on the next loop pass.
302                Ok(None) => {
303                    self.handshake_deadline =
304                        Some(self.outbound.clock.now() + self.handshake_timeout);
305                    if self.end_session() {
306                        info!("wire session {} reset by host", self.log_id);
307                        return Ok(Event::Disconnected);
308                    }
309                    debug!("wire reset received, awaiting handshake");
310                    continue;
311                }
312                // Valid COBS packet
313                Ok(Some(packet)) => packet,
314            };
315            let receiver = match self.receiver.as_mut() {
316                None => {
317                    debug!("discarding data outside session");
318                    self.send_dropped();
319                    continue;
320                }
321                Some(receiver) => receiver,
322            };
323            // Finish after decrypting, ordering message acceptance with a send
324            // failure without ever waiting for the writer on a successful receive.
325            let sealer = self
326                .sealer
327                .as_ref()
328                .expect("receiver has a sending context");
329            let opened = sealing::open(receiver, packet);
330            let undecryptable = opened.is_err();
331            let message = match self.outbound.finish_receive(sealer, opened) {
332                Err(err) => {
333                    if undecryptable {
334                        warn!("ending session {}: {}", self.log_id, err);
335                    } else {
336                        debug!(
337                            "discarding message read after session {} ended",
338                            self.log_id
339                        );
340                    }
341                    self.end_session();
342                    self.send_dropped();
343                    return Ok(Event::Disconnected);
344                }
345                Ok(message) => message,
346            };
347            trace!("received host-to-ark message ({} bytes)", packet.len());
348            return Ok(Event::Message(message));
349        }
350    }
351
352    /// Stores the negotiated contexts and returns a sender for the new session.
353    /// The sending context's allocation identifies the session. The server owns
354    /// both contexts and shares the sending context with active sends. Idle
355    /// senders hold weak references and keep neither context nor stream alive.
356    ///
357    /// Takes the writer lock, then the binding lock. An old write that already
358    /// holds the writer lock may finish first. Once the binding is replaced,
359    /// old sends cannot write and old received messages cannot be accepted.
360    /// This method performs no handshake, crypto or stream I/O.
361    fn new_session(&mut self, sender: xhpke::Sender, receiver: xhpke::Receiver) -> Sender<W> {
362        let sealer = Arc::new(Mutex::new(sender));
363        let sender = self.outbound.bind(&sealer);
364        self.log_id = sender.log_id();
365        self.receiver = Some(receiver);
366        self.sealer = Some(sealer);
367        sender
368    }
369
370    /// Ends the current binding before releasing the server's crypto contexts.
371    /// Waits for the writer. After this returns, no write or flush for that
372    /// session is running or can start. A send that gets the writer first may
373    /// finish. A send still sealing after removal cannot write its packet.
374    /// This takes no encryption lock and does not wait for crypto work.
375    ///
376    /// This does not close the stream or send a notification. An active write
377    /// may delay ending until its frame deadline. Another thread can use the
378    /// Closer to cancel I/O without taking the writer lock.
379    ///
380    /// Returns true if it removed a receive context, even if a send failure
381    /// already ended the binding. The receive loop uses this removal to emit
382    /// Disconnected once. Local disconnect ignores the result because its caller
383    /// already knows the session ended.
384    fn end_session(&mut self) -> bool {
385        if let Some(sealer) = self.sealer.as_ref() {
386            self.outbound.end(sealer);
387        }
388        self.sealer = None;
389        self.receiver.take().is_some()
390    }
391
392    /// Sends an empty frame to tell the client it has no session. Logs failures.
393    fn send_dropped(&self) {
394        if let Err(err) = self.outbound.send_dropped(None) {
395            warn!("failed to signal dropped session: {}", err);
396        }
397    }
398
399    /// Ends the encrypted session and tells the client with an empty frame.
400    /// The stream remains available for the client to connect again. Notification
401    /// failures are logged. This does not produce a Disconnected event because
402    /// the caller already knows the session ended.
403    ///
404    /// Waits for the current writer and its flush, then retires the binding.
405    /// The notification gets its own frame budget. Another thread can use the
406    /// Closer to cancel output earlier.
407    pub fn disconnect(&mut self) {
408        if self.end_session() {
409            debug!("wire session {} dropped locally", self.log_id);
410        }
411        self.send_dropped();
412    }
413
414    /// Responds to the handshake after a session reset, establishing the
415    /// HPKE contexts of both directions:
416    ///
417    ///   1. Client -> Server: HostHello { host_signer, host_crypto }           (plain CBOR)
418    ///   2. Server -> Client: ArkHello  { ark_attest, ark_crypto, a2h_encap }  (cose::seal)
419    ///   3. Client -> Server: HostAck   { h2a_encap }                          (cose::seal)
420    fn handshake(&mut self, deadline: Instant) -> Result<(xhpke::Sender, xhpke::Receiver), Error> {
421        self.outbound.unbind();
422        loop {
423            // Message 1: Read the HostHello (skip any trailing empty reset frames)
424            let packet = loop {
425                if let Some(packet) = self.reader.next_packet(Some(deadline))? {
426                    break packet;
427                }
428            };
429            let host_hello: handshake::HostHello = cbor::decode(packet)
430                .map_err(|err| Error::HandshakeFailed(format!("invalid client hello: {}", err)))?;
431
432            // Generate ephemeral keys and set up server-to-client encryption
433            let ark_crypto_key = xhpke::SecretKey::generate();
434            let ark_crypto_pub = ark_crypto_key.public_key();
435
436            let (sender, a2h_encap) = host_hello
437                .host_crypto
438                .new_sender(CRYPTO_DOMAIN_WIRE_ARK_TO_HOST)
439                .map_err(|err| {
440                    Error::HandshakeFailed(format!("server sender setup failed: {}", err))
441                })?;
442
443            // Message 2: Seal and send the ArkHello
444            let ark_hello = handshake::ArkHello {
445                ark_attest: self.attester.attest().into_bytes(),
446                ark_crypto: ark_crypto_pub.clone(),
447                a2h_encap: a2h_encap.to_vec(),
448            };
449            let auth = handshake::ArkHelloAuth {
450                host_signer: host_hello.host_signer.clone(),
451                host_crypto: host_hello.host_crypto.clone(),
452            };
453            #[cfg(not(any(test, feature = "bench", feature = "fuzz")))]
454            let timestamp = handshake::timestamp(&self.outbound.clock);
455            #[cfg(any(test, feature = "bench", feature = "fuzz"))]
456            let timestamp = self
457                .timestamp
458                .unwrap_or_else(|| handshake::timestamp(&self.outbound.clock));
459            let sealed = cose::seal_at(
460                &ark_hello,
461                &auth,
462                &self.signer,
463                &host_hello.host_crypto,
464                CRYPTO_DOMAIN_WIRE,
465                timestamp,
466            );
467            let ark_hello = sealed.map_err(|err| {
468                Error::HandshakeFailed(format!("failed to seal server hello: {}", err))
469            })?;
470
471            self.outbound.send_packet(&ark_hello, Some(deadline))?;
472
473            // Message 3: Read and open HostAck. An empty frame is another reset;
474            // discard this attempt and wait for the next HostHello.
475            let Some(packet) = self.reader.next_packet(Some(deadline))? else {
476                debug!("wire reset received during handshake");
477                continue;
478            };
479            let host_ack: handshake::HostAck = cose::open_at(
480                packet,
481                &handshake::HostAckAuth {
482                    ark_signer: self.signer.public_key(),
483                    ark_crypto: ark_crypto_pub.clone(),
484                },
485                &ark_crypto_key,
486                &host_hello.host_signer,
487                CRYPTO_DOMAIN_WIRE,
488                None, // clock possibly unset, ephemeral keys guarantee freshness
489                0,    // unused without a drift check, so the clock is not read
490            )
491            .map_err(|err| Error::HandshakeFailed(format!("invalid client ack: {}", err)))?;
492
493            // Set up client-to-server decryption
494            let enc_h2a: [u8; xhpke::ENCAP_KEY_SIZE] = host_ack
495                .h2a_encap
496                .try_into()
497                .map_err(|_| Error::HandshakeFailed("invalid h2a_encap size".into()))?;
498
499            let receiver = ark_crypto_key
500                .new_receiver(&enc_h2a, CRYPTO_DOMAIN_WIRE_HOST_TO_ARK)
501                .map_err(|err| {
502                    Error::HandshakeFailed(format!("server receiver setup failed: {}", err))
503                })?;
504
505            // Session established
506            check_deadline(&self.outbound.clock, deadline).map_err(Error::RecvFailed)?;
507            return Ok((sender, receiver));
508        }
509    }
510}
511
512impl<R: Read, W: Write, A: Attester> Drop for Server<R, W, A> {
513    /// Closes the stream to cancel blocked I/O, then ends the binding before
514    /// releasing the contexts. Shutdown must precede waiting for the writer.
515    /// Idle senders hold weak references and cannot extend the stream's lifetime.
516    fn drop(&mut self) {
517        self.outbound.close();
518        self.end_session();
519    }
520}
521
522impl<R: Read, W: Write, A: Attester> fmt::Debug for Server<R, W, A> {
523    /// Shows the session label, whether a session is established and the
524    /// handshake budget, never the adapters, the keys or the encryption contexts.
525    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
526        f.debug_struct("Server")
527            .field("session", &self.log_id)
528            .field("connected", &self.sealer.is_some())
529            .field("handshake_timeout", &self.handshake_timeout)
530            .finish_non_exhaustive()
531    }
532}
533
534#[cfg(test)]
535#[cfg_attr(coverage_nightly, coverage(off))]
536mod tests {
537    use super::*;
538    use crate::transport::mock::payload;
539    use crate::transport::testing::{Memory, test_clock};
540    use crate::transport::{Client, MAX_FRAME_SIZE, Verifier};
541    use crate::{memory, testing};
542    use darkbio_clock::Clock;
543    use darkbio_cobs as cobs;
544
545    /// Self-signed attestation for a device that has not been onboarded.
546    fn self_attestation(signer: &xdsa::SecretKey) -> Attestation {
547        use darkbio_crypto::cwt::claims::{self, eat};
548
549        let claims = darkbio_trust::device::HardwareClaims {
550            sub: claims::Subject { sub: "".into() },
551            cnf: claims::Confirm::new(signer.public_key()),
552            nbf: claims::NotBefore { nbf: 0 },
553            iat: claims::IssuedAt { iat: 0 },
554            oem: eat::Oemid::new_pen(0),
555            hwm: eat::HwModel { hw_model: vec![] },
556            hwv: eat::HwVersion::new("".into()),
557        };
558        let cwt = cwt::issue_at(
559            &claims,
560            signer,
561            darkbio_trust::CRYPTO_DOMAIN_DEVICE_ATTESTATION,
562            crate::transport::mock::TIMESTAMP,
563        )
564        .unwrap();
565        Attestation::new(cwt).unwrap()
566    }
567
568    /// COBS-encodes data and appends the frame delimiter.
569    fn cobs_frame(data: &[u8]) -> Vec<u8> {
570        let mut buf = vec![0u8; cobs::encode_buffer(data.len())];
571        let n = cobs::encode(data, &mut buf).unwrap();
572        buf.truncate(n);
573        buf.push(0x00);
574        buf
575    }
576
577    // Tests that an oversized server hello produces one failure notification.
578    // It never reaches adapter I/O, so the handshake owns that notification;
579    // the writer must not emit another one with its own budget. Dummy attestation
580    // bytes isolate the framing limit: the server forwards them without parsing.
581    #[test]
582    fn test_oversized_hello_notifies_once() {
583        // Prepare an oversized server hello on a paused clock
584        testing::init_tracing();
585        let tester = test_clock();
586        let clock = tester.clock();
587        let hello = cbor::encode(&handshake::HostHello {
588            host_signer: xdsa::SecretKey::generate().public_key(),
589            host_crypto: xhpke::SecretKey::generate().public_key(),
590        })
591        .unwrap();
592        let mut input = vec![0, 0];
593        input.extend_from_slice(&cobs_frame(&hello));
594        let mut output = Vec::new();
595        let mut server = Server::new(
596            Stream::new(
597                Memory::new(&input[..], &clock),
598                Memory::new(&mut output, &clock),
599                || {},
600            ),
601            xdsa::SecretKey::generate(),
602            Attestation(vec![0; MAX_FRAME_SIZE]),
603        );
604
605        // Require exactly one failure notification before EOF
606        assert!(matches!(server.recv(), Err(Error::Terminated)));
607        drop(server);
608        assert_eq!(output, [0]);
609    }
610
611    // Tests the two real sides against each other. The handshake hands the
612    // attestation to the client's verifier unchanged and a request gets its
613    // response. The server's signal for a dropped session then surfaces on the
614    // client as a reset, which a fresh handshake recovers from.
615    #[test]
616    fn test_message_round_trip() {
617        // Connect both peers on one paused in-memory stream
618        testing::init_tracing();
619        let tester = test_clock();
620        let signer_key = xdsa::SecretKey::generate();
621        let signer_pub = signer_key.public_key();
622        let attestation = self_attestation(&signer_key);
623        let presented = attestation.clone();
624
625        let (host, ark) = memory::duplex(64 * 1024, &tester.clock());
626
627        // Server side: receive two messages (across two sessions), echo each back.
628        let ark_thread = std::thread::spawn(move || {
629            let mut server = Server::new(ark, signer_key, attestation);
630            let mut sender = None;
631            let mut requests = Vec::new();
632            for _ in 0..2 {
633                let req = testing::served(&mut server, &mut sender).unwrap();
634                sender.as_ref().unwrap().send(&req).unwrap();
635                requests.push(req);
636            }
637            requests
638        });
639
640        // Session 1: handshake, checking the attestation, exchange one message.
641        let mut client = Client::new(host);
642        let (sender, attest) = client.connect(&signer_pub).unwrap();
643        assert_eq!(attest.as_bytes(), presented.as_bytes());
644        sender.send(&payload(1)).unwrap();
645        assert_eq!(client.recv().unwrap(), payload(1));
646
647        // Inject a frame the server cannot decrypt. It drops the session and
648        // signals it. The client's next read reports a reset, and its old sender
649        // cannot send again.
650        client.send_packet_blob(b"interrupted transfer").unwrap();
651        let result = client.recv();
652        assert!(matches!(result, Err(Error::SessionReset)), "{result:?}");
653        let result = sender.send(&payload(2));
654        assert!(
655            matches!(result, Err(Error::EncryptionFailed(_))),
656            "{result:?}"
657        );
658
659        // Session 2: new handshake on the same wire, exchange one message.
660        let (sender, _) = client.connect(&signer_pub).unwrap();
661        sender.send(&payload(2)).unwrap();
662        assert_eq!(client.recv().unwrap(), payload(2));
663
664        let requests = ark_thread.join().unwrap();
665        assert_eq!(requests, vec![payload(1), payload(2)]);
666    }
667
668    // Tests that an untrusting verifier rejects the session on the client side.
669    #[test]
670    fn test_verifier_rejects() {
671        testing::init_tracing();
672
673        /// Verifier refusing every attestation.
674        struct Untrusting;
675
676        impl Verifier for Untrusting {
677            type Info = ();
678
679            fn verify(
680                &self,
681                _: &Attestation,
682                _: std::time::SystemTime,
683            ) -> Result<(xdsa::PublicKey, Self::Info), String> {
684                Err("attestation rejected".into())
685            }
686        }
687
688        let signer_key = xdsa::SecretKey::generate();
689
690        let tester = test_clock();
691        let (host, ark) = memory::duplex(64 * 1024, &tester.clock());
692
693        // Server side: serve handshakes until the transport drops. The client aborts
694        // mid-handshake, so the server never delivers a message.
695        let ark_thread = std::thread::spawn(move || {
696            let attestation = self_attestation(&signer_key);
697            let mut server = Server::new(ark, signer_key, attestation);
698            let mut sender = None;
699            testing::served(&mut server, &mut sender)
700        });
701
702        // Client side: refuse the attestation in the verifier.
703        let mut client = Client::new(host);
704        let result = client.connect(&Untrusting);
705        assert!(result.is_err());
706
707        // Dropping the client tears down the transport, unblocking the server.
708        drop(client);
709        assert!(ark_thread.join().unwrap().is_err());
710    }
711
712    // Tests that the roots verifier accepts hardware and emulator attestations
713    // under the configured roots and returns the verified identity. Unknown
714    // roots and self-signed attestations are refused.
715    #[test]
716    fn test_roots_verifier() {
717        testing::init_tracing();
718
719        use crate::transport::Roots;
720        use darkbio_crypto::cwt;
721        use darkbio_crypto::cwt::claims::{self, eat};
722        use darkbio_trust::device::{EmulatorClaims, HardwareClaims};
723        use darkbio_trust::{CRYPTO_DOMAIN_DEVICE_ATTESTATION, Realm};
724        use std::time::UNIX_EPOCH;
725
726        let tester = test_clock();
727        let clock = tester.clock();
728        let now = clock
729            .system_time()
730            .duration_since(UNIX_EPOCH)
731            .unwrap()
732            .as_secs();
733
734        /// Runs a handshake with the given attestation and trusted roots.
735        /// Returns the client's verification result.
736        fn handshake(
737            clock: &Clock,
738            signer_key: xdsa::SecretKey,
739            attestation: Attestation,
740            hardware: &[xdsa::PublicKey],
741            emulator: &[xdsa::PublicKey],
742        ) -> Result<darkbio_trust::device::Device, Error> {
743            let (host, ark) = memory::duplex(64 * 1024, clock);
744
745            let ark_thread = std::thread::spawn(move || {
746                let mut server = Server::new(ark, signer_key, attestation);
747                let mut sender = None;
748                testing::served(&mut server, &mut sender)
749            });
750            let mut client = Client::new(host);
751            let result = client
752                .connect(&Roots { hardware, emulator })
753                .map(|(_, info)| info);
754
755            // Dropping the client tears down the transport, unblocking the server
756            drop(client);
757            let _ = ark_thread.join().unwrap();
758            result
759        }
760
761        let hardware_root = xdsa::SecretKey::generate();
762        let emulator_root = xdsa::SecretKey::generate();
763        let hardware_roots = [hardware_root.public_key()];
764        let emulator_roots = [emulator_root.public_key()];
765
766        // A hardware server attested by a hardware root is accepted with its identity
767        let signer_key = xdsa::SecretKey::generate();
768        let attestation = cwt::issue_at(
769            &HardwareClaims {
770                sub: claims::Subject {
771                    sub: "ark-1234".into(),
772                },
773                cnf: claims::Confirm::new(signer_key.public_key()),
774                nbf: claims::NotBefore { nbf: now - 10 },
775                iat: claims::IssuedAt { iat: now - 10 },
776                oem: eat::Oemid::new_pen(65145),
777                hwm: eat::HwModel {
778                    hw_model: b"Ark I".to_vec(),
779                },
780                hwv: eat::HwVersion::new("Ark I - 1.0.0".into()),
781            },
782            &hardware_root,
783            CRYPTO_DOMAIN_DEVICE_ATTESTATION,
784            now as i64,
785        )
786        .map(|cwt| Attestation::new(cwt).unwrap())
787        .unwrap();
788        let device = handshake(
789            &clock,
790            signer_key,
791            attestation.clone(),
792            &hardware_roots,
793            &[],
794        )
795        .unwrap();
796        assert_eq!(device.realm, Realm::Hardware);
797        assert_eq!(device.serial, "ark-1234");
798
799        // A hardware attestation is refused when only emulator roots are trusted
800        let signer_key = xdsa::SecretKey::generate();
801        assert!(handshake(&clock, signer_key, attestation, &[], &emulator_roots).is_err());
802
803        // An emulated server attested by an emulator root is accepted with its expiry
804        let signer_key = xdsa::SecretKey::generate();
805        let attestation = cwt::issue_at(
806            &EmulatorClaims {
807                sub: claims::Subject {
808                    sub: "emu-1234".into(),
809                },
810                cnf: claims::Confirm::new(signer_key.public_key()),
811                nbf: claims::NotBefore { nbf: now - 10 },
812                exp: claims::Expiration { exp: now + 1000 },
813                iat: claims::IssuedAt { iat: now - 10 },
814                oem: eat::Oemid::new_pen(65145),
815                hwm: eat::HwModel {
816                    hw_model: b"Ark I".to_vec(),
817                },
818                hwv: eat::HwVersion::new("Ark I - 1.0.0".into()),
819            },
820            &emulator_root,
821            CRYPTO_DOMAIN_DEVICE_ATTESTATION,
822            now as i64,
823        )
824        .map(|cwt| Attestation::new(cwt).unwrap())
825        .unwrap();
826        let device = handshake(
827            &clock,
828            signer_key,
829            attestation,
830            &hardware_roots,
831            &emulator_roots,
832        )
833        .unwrap();
834        assert_eq!(device.realm, Realm::Emulator);
835        assert_eq!(device.expiry, Some(now + 1000));
836
837        // A never onboarded server presenting a self-signed attestation is refused
838        let signer_key = xdsa::SecretKey::generate();
839        let attestation = cwt::issue_at(
840            &HardwareClaims {
841                sub: claims::Subject { sub: "".into() },
842                cnf: claims::Confirm::new(signer_key.public_key()),
843                nbf: claims::NotBefore { nbf: 0 },
844                iat: claims::IssuedAt { iat: 0 },
845                oem: eat::Oemid::new_pen(0),
846                hwm: eat::HwModel { hw_model: vec![] },
847                hwv: eat::HwVersion::new("".into()),
848            },
849            &signer_key,
850            CRYPTO_DOMAIN_DEVICE_ATTESTATION,
851            now as i64,
852        )
853        .map(|cwt| Attestation::new(cwt).unwrap())
854        .unwrap();
855        assert!(
856            handshake(
857                &clock,
858                signer_key,
859                attestation,
860                &hardware_roots,
861                &emulator_roots
862            )
863            .is_err()
864        );
865    }
866
867    // Tests that attestation construction accepts hardware and emulator claims
868    // and rejects junk or CWTs containing other claim types.
869    #[test]
870    fn test_attestation_shapes() {
871        use darkbio_crypto::cwt::claims;
872        use darkbio_trust::CRYPTO_DOMAIN_DEVICE_ATTESTATION;
873
874        let signer = xdsa::SecretKey::generate();
875        let _ = self_attestation(&signer);
876
877        let emulator = darkbio_trust::device::EmulatorClaims {
878            sub: claims::Subject { sub: "".into() },
879            cnf: claims::Confirm::new(signer.public_key()),
880            nbf: claims::NotBefore { nbf: 0 },
881            exp: claims::Expiration { exp: u64::MAX },
882            iat: claims::IssuedAt { iat: 0 },
883            oem: claims::eat::Oemid::new_pen(0),
884            hwm: claims::eat::HwModel { hw_model: vec![] },
885            hwv: claims::eat::HwVersion::new("".into()),
886        };
887        let cwt = cwt::issue_at(
888            &emulator,
889            &signer,
890            CRYPTO_DOMAIN_DEVICE_ATTESTATION,
891            crate::transport::mock::TIMESTAMP,
892        )
893        .unwrap();
894        Attestation::new(cwt).expect("emulator attestation refused");
895
896        let cloud = darkbio_trust::cloud::SignerClaims {
897            iss: claims::Issuer { iss: "".into() },
898            sub: claims::Subject { sub: "".into() },
899            nbf: claims::NotBefore { nbf: 0 },
900            exp: claims::Expiration { exp: 1 },
901            cnf: claims::Confirm::new(signer.public_key()),
902        };
903        let cwt = cwt::issue_at(
904            &cloud,
905            &signer,
906            CRYPTO_DOMAIN_DEVICE_ATTESTATION,
907            crate::transport::mock::TIMESTAMP,
908        )
909        .unwrap();
910        let result = Attestation::new(cwt).map(|_| ());
911        assert!(
912            matches!(result, Err(Error::InvalidAttestation)),
913            "{result:?}"
914        );
915        let result = Attestation::new(b"junk".to_vec()).map(|_| ());
916        assert!(
917            matches!(result, Err(Error::InvalidAttestation)),
918            "{result:?}"
919        );
920    }
921
922    // Tests sending from other threads while the server blocks in a read.
923    // The client must receive every message in encryption order to decrypt it.
924    #[test]
925    fn test_senders() {
926        testing::init_tracing();
927
928        let signer_key = xdsa::SecretKey::generate();
929        let signer_pub = signer_key.public_key();
930        let attestation = self_attestation(&signer_key);
931
932        let tester = test_clock();
933        let (host, ark) = memory::duplex(64 * 1024, &tester.clock());
934
935        // Server side: on the first request, push messages from a few threads
936        // while waiting for the second request.
937        let ark_thread = std::thread::spawn(move || {
938            let mut server = Server::new(ark, signer_key, attestation);
939            let mut sender = None;
940            testing::served(&mut server, &mut sender).unwrap();
941
942            let pushers: Vec<_> = (0..4)
943                .map(|thread| {
944                    let sender = sender.as_ref().unwrap().clone();
945                    std::thread::spawn(move || {
946                        for i in 0..25 {
947                            sender.send(&payload(thread * 100 + i)).unwrap();
948                        }
949                    })
950                })
951                .collect();
952            let stop = testing::served(&mut server, &mut sender).unwrap();
953            for pusher in pushers {
954                pusher.join().unwrap();
955            }
956            stop
957        });
958
959        // Client side: request the push, receive it all, then request the stop.
960        let mut client = Client::new(host);
961        let (sender, _) = client.connect(&signer_pub).unwrap();
962        sender.send(&payload(1)).unwrap();
963
964        let mut pushed: Vec<Vec<u8>> = (0..100).map(|_| client.recv().unwrap()).collect();
965        pushed.sort_unstable();
966        let mut expected: Vec<Vec<u8>> = (0..4)
967            .flat_map(|thread| (0..25).map(move |i| payload(thread * 100 + i)))
968            .collect();
969        expected.sort_unstable();
970        assert_eq!(pushed, expected);
971
972        sender.send(&payload(2)).unwrap();
973        assert_eq!(ark_thread.join().unwrap(), payload(2));
974    }
975}