use super::framing::{FrameReader, FrameWriter};
use super::{
Attestation, CRYPTO_DOMAIN_WIRE, CRYPTO_DOMAIN_WIRE_ARK_TO_HOST,
CRYPTO_DOMAIN_WIRE_HOST_TO_ARK, Client, DEFAULT_HANDSHAKE_TIMEOUT, Error, Event, Read, Roots,
Server, Stream, Verifier, Write, handshake,
};
use crate::{memory, protocol};
use darkbio_clock::TestClock;
use darkbio_crypto::cwt::claims::{self, eat};
use darkbio_crypto::{cbor, cose, cwt, xdsa, xhpke};
use std::io::{self, Read as _, Write as _};
use std::sync::mpsc;
use std::thread;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
fn attestation(signer: &xdsa::SecretKey, wall: SystemTime) -> Attestation {
let now = wall.duration_since(UNIX_EPOCH).unwrap().as_secs();
let claims = darkbio_trust::device::EmulatorClaims {
sub: claims::Subject {
sub: "clock-test".into(),
},
cnf: claims::Confirm::new(signer.public_key()),
nbf: claims::NotBefore { nbf: now - 1 },
exp: claims::Expiration { exp: now + 1 },
iat: claims::IssuedAt { iat: now },
oem: eat::Oemid::new_pen(0),
hwm: eat::HwModel { hw_model: vec![] },
hwv: eat::HwVersion::new("".into()),
};
Attestation::new(
cwt::issue_at(
&claims,
signer,
darkbio_trust::CRYPTO_DOMAIN_DEVICE_ATTESTATION,
now as i64,
)
.unwrap(),
)
.unwrap()
}
#[test]
#[should_panic(expected = "stream halves must use the same clock")]
fn test_stream_rejects_different_clocks() {
let first = TestClock::new();
let second = TestClock::new();
let (first, _first_peer) = memory::duplex(4, &first.clock());
let (second, _second_peer) = memory::duplex(4, &second.clock());
let (reader, _) = first.into_halves();
let (_, writer) = second.into_halves();
let _ = Stream::new(reader, writer, || {});
}
#[test]
fn test_memory_and_adapter_wrappers_keep_the_clock() {
let mut tester = TestClock::new();
tester.advance(Duration::from_secs(86400));
let clock = tester.clock();
let (host, ark) = memory::duplex(64, &clock);
assert_eq!(host.clock(), clock);
assert_eq!(ark.clock(), clock);
let (host_read, host_write) = host.into_halves();
let (mut ark_read, mut ark_write) = ark.into_halves();
assert_eq!(host_read.clock(), clock);
assert_eq!(host_write.clock(), clock);
assert_eq!(ark_read.clock(), clock);
assert_eq!(ark_write.clock(), clock);
let mut reader: Box<dyn Read> = Box::new(host_read);
let mut writer: Box<dyn Write> = Box::new(host_write);
let stream = Stream::new(&mut reader, &mut writer, || {});
assert_eq!(stream.clock(), clock);
let (reader, writer, closer, _) = stream.into_parts();
let mut reader = FrameReader::new(reader, closer.clone());
let mut writer = FrameWriter::new(writer, closer);
let deadline = clock.now() + Duration::from_secs(2);
writer.send_packet(b"out", deadline).unwrap();
let mut frame = [0; 5];
ark_read.read_exact(&mut frame).unwrap();
assert_eq!(&frame, b"\x04out\0");
ark_write.write_all(b"\x03in\0\x03on\0").unwrap();
assert_eq!(
reader.next_packet(Some(deadline)).unwrap(),
Some(&b"in"[..])
);
tester.advance(Duration::from_secs(2));
assert!(matches!(
reader.next_packet(Some(deadline)),
Err(Error::RecvFailed(err)) if err.kind() == io::ErrorKind::TimedOut
));
assert_eq!(reader.next_packet(None).unwrap(), Some(&b"on"[..]));
assert!(matches!(
writer.send_packet(b"late", deadline),
Err(Error::SendFailed(err)) if err.kind() == io::ErrorKind::TimedOut
));
ark_write.set_write_deadline(deadline).unwrap();
assert_eq!(
ark_write.write(b"late").unwrap_err().kind(),
io::ErrorKind::TimedOut
);
assert_eq!(
ark_write.flush().unwrap_err().kind(),
io::ErrorKind::TimedOut
);
ark_read.forbid_waits();
ark_read.set_read_deadline(Some(deadline)).unwrap();
assert_eq!(
ark_read.read(&mut frame).unwrap_err().kind(),
io::ErrorKind::TimedOut
);
}
#[test]
fn test_blocked_framed_read_expires_on_stream_clock() {
let mut tester = crate::transport::testing::test_clock();
let clock = tester.clock();
let (host, _peer) = memory::duplex(64, &clock);
let (reader, _writer, closer, _) = host.into_parts();
let mut reader = FrameReader::new(reader, closer);
let deadline = clock.now() + Duration::from_secs(5);
let reading = thread::spawn(move || reader.next_packet(Some(deadline)).map(|_| ()));
tester.wait_registered(1);
assert_eq!(tester.next_deadline(), Some(deadline));
tester.advance_to(deadline + Duration::from_nanos(1));
assert!(matches!(
reading.join().unwrap(),
Err(Error::RecvFailed(error)) if error.kind() == io::ErrorKind::TimedOut
));
}
#[test]
fn test_client_handshake_uses_clock_wall_time() {
struct VerifierAt {
wall: SystemTime,
root: xdsa::PublicKey,
}
impl Verifier for VerifierAt {
type Info = darkbio_trust::device::Device;
fn verify(
&self,
attestation: &Attestation,
now: SystemTime,
) -> Result<(xdsa::PublicKey, Self::Info), String> {
assert_eq!(now, self.wall);
Roots {
hardware: &[],
emulator: std::slice::from_ref(&self.root),
}
.verify(attestation, now)
}
}
let mut tester = TestClock::new();
tester.advance(Duration::from_secs(86400));
let wall = UNIX_EPOCH + Duration::new(1_234_567_890, 123_456_789);
tester.set_system_time(wall);
let clock = tester.clock();
let signer = xdsa::SecretKey::generate();
let verifier = VerifierAt {
wall,
root: signer.public_key(),
};
let attest = attestation(&signer, wall);
let (host, ark) = memory::duplex(64 * 1024, &clock);
let peer = thread::spawn(move || {
let (reader, writer, closer, _) = ark.into_parts();
let mut reader = FrameReader::new(reader, closer.clone());
let mut writer = FrameWriter::new(writer, closer);
assert!(reader.next_packet(None).unwrap().is_none());
assert!(reader.next_packet(None).unwrap().is_none());
let hello: handshake::HostHello =
cbor::decode(reader.next_packet(None).unwrap().unwrap()).unwrap();
let crypto = xhpke::SecretKey::generate();
let (_, encap) = hello
.host_crypto
.new_sender(CRYPTO_DOMAIN_WIRE_ARK_TO_HOST)
.unwrap();
let sealed = cose::seal_at(
handshake::ArkHello {
ark_attest: attest.into_bytes(),
ark_crypto: crypto.public_key(),
a2h_encap: encap.to_vec(),
},
handshake::ArkHelloAuth {
host_signer: hello.host_signer.clone(),
host_crypto: hello.host_crypto.clone(),
},
&signer,
&hello.host_crypto,
CRYPTO_DOMAIN_WIRE,
&cose::Padding::None,
1_234_567_890,
)
.unwrap();
writer
.send_packet(&sealed, clock.now() + Duration::from_secs(5))
.unwrap();
let ack = reader.next_packet(None).unwrap().unwrap();
let _: handshake::HostAck = cose::open_at(
ack,
&handshake::HostAckAuth {
ark_signer: signer.public_key(),
ark_crypto: crypto.public_key(),
},
&crypto,
&hello.host_signer,
CRYPTO_DOMAIN_WIRE,
Some(0),
1_234_567_890,
)
.unwrap();
});
let mut client = Client::new(host);
client.connect(&verifier).unwrap();
peer.join().unwrap();
}
#[test]
fn test_server_handshake_uses_clock_wall_time() {
let mut tester = TestClock::new();
tester.advance(Duration::from_secs(86400));
let wall = UNIX_EPOCH + Duration::from_secs(1_345_678_901);
tester.set_system_time(wall);
let clock = tester.clock();
let signer = xdsa::SecretKey::generate();
let identity = signer.public_key();
let attest = attestation(&signer, wall);
let (host, ark) = memory::duplex(64 * 1024, &clock);
let peer = thread::spawn(move || {
let mut server = Server::new(ark, signer, attest);
assert!(matches!(server.recv().unwrap(), Event::Connected(_)));
});
let (reader, writer, closer, _) = host.into_parts();
let mut reader = FrameReader::new(reader, closer.clone());
let mut writer = FrameWriter::new(writer, closer);
let host_signer = xdsa::SecretKey::generate();
let host_crypto = xhpke::SecretKey::generate();
let deadline = clock.now() + Duration::from_secs(5);
writer.send_reset(deadline).unwrap();
writer
.send_packet(
&cbor::encode(handshake::HostHello {
host_signer: host_signer.public_key(),
host_crypto: host_crypto.public_key(),
})
.unwrap(),
deadline,
)
.unwrap();
let hello: handshake::ArkHello = cose::open_at(
reader.next_packet(None).unwrap().unwrap(),
&handshake::ArkHelloAuth {
host_signer: host_signer.public_key(),
host_crypto: host_crypto.public_key(),
},
&host_crypto,
&identity,
CRYPTO_DOMAIN_WIRE,
Some(0),
1_345_678_901,
)
.unwrap();
let (_, encap) = hello
.ark_crypto
.new_sender(CRYPTO_DOMAIN_WIRE_HOST_TO_ARK)
.unwrap();
let ack = cose::seal_at(
handshake::HostAck {
h2a_encap: encap.to_vec(),
},
handshake::HostAckAuth {
ark_signer: identity,
ark_crypto: hello.ark_crypto.clone(),
},
&host_signer,
&hello.ark_crypto,
CRYPTO_DOMAIN_WIRE,
&cose::Padding::None,
1_345_678_901,
)
.unwrap();
tester.set_system_time(UNIX_EPOCH - Duration::from_secs(1));
writer.send_packet(&ack, deadline).unwrap();
peer.join().unwrap();
}
#[test]
fn test_client_keeps_session_after_timely_ack() {
let mut tester = crate::transport::testing::test_clock();
let clock = tester.clock();
let signer = xdsa::SecretKey::generate();
let identity = signer.public_key();
let attest = attestation(&signer, clock.system_time());
let (host, ark) = memory::duplex(64 * 1024, &clock);
let (connected, established) = mpsc::channel();
let serving = thread::spawn(move || {
let mut server = Server::new(ark, signer, attest);
assert!(matches!(server.recv().unwrap(), Event::Connected(_)));
connected.send(()).unwrap();
server.recv()
});
let mut client = Client::new(host);
client.ack_hook = Some(Box::new(move || {
established.recv().unwrap();
tester.advance(DEFAULT_HANDSHAKE_TIMEOUT * 2);
}));
let (sender, _) = client.connect(&identity).unwrap();
sender.send(b"hello").unwrap();
assert!(matches!(
serving.join().unwrap(),
Ok(Event::Message(message)) if message == b"hello"
));
}
#[test]
fn test_server_keeps_session_after_timely_ack() {
let mut tester = crate::transport::testing::test_clock();
let clock = tester.clock();
let signer = xdsa::SecretKey::generate();
let identity = signer.public_key();
let attest = attestation(&signer, clock.system_time());
let (host, ark) = memory::duplex(64 * 1024, &clock);
let (connected, established) = mpsc::channel();
let connecting = thread::spawn(move || {
let mut client = Client::new(host);
client.connect(&identity).unwrap();
connected.send(()).unwrap();
client.recv()
});
let mut server = Server::new(ark, signer, attest);
server.ack_hook = Some(Box::new(move || {
established.recv().unwrap();
tester.advance(DEFAULT_HANDSHAKE_TIMEOUT * 2);
}));
let Event::Connected(sender) = server.recv().unwrap() else {
panic!("expected a connection");
};
sender.send(b"hello").unwrap();
assert_eq!(connecting.join().unwrap().unwrap(), b"hello");
}
#[test]
fn test_unrepresentable_handshake_budget_expires_at_once() {
let tester = crate::transport::testing::test_clock();
let clock = tester.clock();
let signer = xdsa::SecretKey::generate();
let identity = signer.public_key();
let attest = attestation(&signer, clock.system_time());
let (host, ark) = memory::duplex(64 * 1024, &clock);
let serving = thread::spawn(move || {
let mut server = Server::new(ark, signer, attest);
matches!(server.recv(), Ok(Event::Connected(_)))
});
let mut client = Client::new(host).set_handshake_timeout(Duration::MAX);
assert!(matches!(
client.connect(&identity),
Err(Error::SendFailed(err)) if err.kind() == io::ErrorKind::TimedOut
));
let mut client = client.set_handshake_timeout(DEFAULT_HANDSHAKE_TIMEOUT);
client.connect(&identity).unwrap();
assert!(serving.join().unwrap());
let signer = xdsa::SecretKey::generate();
let identity = signer.public_key();
let attest = attestation(&signer, clock.system_time());
let (host, ark) = memory::duplex(64 * 1024, &clock);
let connecting = thread::spawn(move || Client::new(host).connect(&identity).map(|_| ()));
let mut server = Server::new(ark, signer, attest).set_handshake_timeout(Duration::MAX);
assert!(matches!(
server.recv(),
Err(Error::RecvFailed(err)) if err.kind() == io::ErrorKind::TimedOut
));
let mut server = server.set_handshake_timeout(DEFAULT_HANDSHAKE_TIMEOUT);
assert!(matches!(server.recv(), Ok(Event::Connected(_))));
connecting.join().unwrap().unwrap();
}
#[test]
fn test_client_expires_before_ack() {
struct Slow {
tester: std::sync::Mutex<TestClock>,
identity: xdsa::PublicKey,
}
impl Verifier for Slow {
type Info = Attestation;
fn verify(
&self,
attestation: &Attestation,
now: SystemTime,
) -> Result<(xdsa::PublicKey, Self::Info), String> {
let mut tester = self.tester.lock().unwrap();
tester.wait_registered(1);
tester.advance(DEFAULT_HANDSHAKE_TIMEOUT * 2);
Verifier::verify(&self.identity, attestation, now)
}
}
let tester = crate::transport::testing::test_clock();
let clock = tester.clock();
let signer = xdsa::SecretKey::generate();
let identity = signer.public_key();
let attest = attestation(&signer, clock.system_time());
let (host, ark) = memory::duplex(64 * 1024, &clock);
let serving = thread::spawn(move || Server::new(ark, signer, attest).recv().map(|_| ()));
let verifier = Slow {
tester: std::sync::Mutex::new(tester),
identity,
};
let mut client = Client::new(host);
assert!(matches!(
client.connect(&verifier),
Err(Error::SendFailed(err)) if err.kind() == io::ErrorKind::TimedOut
));
assert!(matches!(
serving.join().unwrap(),
Err(Error::RecvFailed(err)) if err.kind() == io::ErrorKind::TimedOut
));
}
#[test]
fn test_protocol_handles_keep_the_stream_clock() {
let mut tester = TestClock::new();
tester.advance(Duration::from_secs(86400));
let clock = tester.clock();
let signer = xdsa::SecretKey::generate();
let identity = signer.public_key();
let attest = attestation(&signer, clock.system_time());
let (host, ark) = memory::duplex(64 * 1024, &clock);
let mut server = protocol::Server::new(ark, signer, attest);
let (client, _) = protocol::connect(host, &identity).unwrap();
let mut session = server.accept().unwrap();
assert_eq!(client.clock(), clock);
assert_eq!(session.clock(), clock);
let requester = client.requester();
assert_eq!(requester.clock(), clock);
assert_eq!(session.requester().clock(), clock);
for requester in [&requester, &session.requester()] {
let mut expired = requester.request(vec![0], clock.now()).unwrap();
let (notified, notification) = mpsc::channel();
expired.notify(move || {
let _ = notified.send(());
});
assert_eq!(notification.try_recv(), Ok(()));
assert!(matches!(
expired.wait::<Vec<u8>>(),
Err(protocol::Error::Timeout)
));
}
let deadline = requester.clock().now() + Duration::from_secs(30);
let answer = requester.request(vec![1, 2, 3], deadline).unwrap();
let (message, responder) = session.recv().unwrap();
assert_eq!(responder.clock(), clock);
let written = responder.reply(message, deadline).unwrap();
assert_eq!(answer.wait::<Vec<u8>>().unwrap(), [1, 2, 3]);
written.wait().unwrap();
let _pending = requester.request(vec![4], deadline).unwrap();
let (_, responder) = session.recv().unwrap();
drop(client);
drop(session);
drop(server);
assert_eq!(requester.clock(), clock);
assert_eq!(responder.clock(), clock);
}
#[test]
fn test_server_accept_waits_on_stream_clock() {
let tester = TestClock::new();
let clock = tester.clock();
let signer = xdsa::SecretKey::generate();
let identity = signer.public_key();
let attest = attestation(&signer, clock.system_time());
let (host, ark) = memory::duplex(64 * 1024, &clock);
let mut server = protocol::Server::new(ark, signer, attest);
let accepting = thread::spawn(move || {
let session = server.accept().unwrap();
(server, session)
});
tester.wait_registered(2);
let (client, _) = protocol::connect(host, &identity).unwrap();
let (server, session) = accepting.join().unwrap();
drop(client);
drop(session);
drop(server);
}
#[test]
fn test_protocol_deadline_worker_notifies_without_promise_waiter() {
let mut tester = TestClock::new();
tester.advance(Duration::from_secs(86400));
let clock = tester.clock();
let signer = xdsa::SecretKey::generate();
let identity = signer.public_key();
let attest = attestation(&signer, clock.system_time());
let (host, ark) = memory::duplex(64 * 1024, &clock);
let mut server = protocol::Server::new(ark, signer, attest);
let (client, _) = protocol::connect(host, &identity).unwrap();
let mut session = server.accept().unwrap();
let later = clock.now() + Duration::from_secs(30);
let first = client.requester().request(vec![1], later).unwrap();
let (_, first_responder) = session.recv().unwrap();
tester.wait_parked(6);
let deadline = clock.now() + Duration::from_secs(5);
let mut promise = client.requester().request(vec![2], deadline).unwrap();
let (events, observed) = mpsc::channel();
promise.notify(move || {
let _ = events.send(());
});
let (_, responder) = session.recv().unwrap();
tester.wait_parked(6);
assert!(observed.try_recv().is_err());
tester.advance_to(deadline);
observed.recv().unwrap();
assert!(matches!(
promise.wait::<Vec<u8>>(),
Err(protocol::Error::Timeout)
));
let mut first = first;
let (events, observed) = mpsc::channel();
first.notify(move || {
let _ = events.send(());
});
assert!(observed.try_recv().is_err());
tester.advance_to(later);
observed.recv().unwrap();
assert!(matches!(
first.wait::<Vec<u8>>(),
Err(protocol::Error::Timeout)
));
client.close();
server.close();
drop((first_responder, responder));
}