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