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