use crate::framing::Framing;
use crate::handshake;
use crate::protocol::{ArkToHost, HostToArk};
use crate::session::Session;
use crate::side_ark::Attestation;
use crate::{
CRYPTO_DOMAIN_WIRE, CRYPTO_DOMAIN_WIRE_ARK_TO_HOST, CRYPTO_DOMAIN_WIRE_HOST_TO_ARK, Error,
};
use darkbio_cobs as cobs;
use darkbio_crypto::{cbor, cose, xdsa, xhpke};
use darkbio_trust as trust;
use std::io::{Read, Write};
use std::time::{SystemTime, UNIX_EPOCH};
use tracing::{trace, warn};
const MAX_STALE_FRAMES: usize = 32;
pub trait Verifier {
type Info;
fn verify(&self, attestation: &Attestation) -> Result<(xdsa::PublicKey, Self::Info), String>;
}
impl Verifier for xdsa::PublicKey {
type Info = Attestation;
fn verify(&self, attestation: &Attestation) -> Result<(xdsa::PublicKey, Self::Info), String> {
Ok((self.clone(), attestation.clone()))
}
}
pub struct Roots<'a> {
pub hardware: &'a [xdsa::PublicKey], pub emulator: &'a [xdsa::PublicKey], }
impl Verifier for Roots<'_> {
type Info = trust::device::Device;
fn verify(&self, attestation: &Attestation) -> Result<(xdsa::PublicKey, Self::Info), String> {
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map_err(|err| err.to_string())?
.as_secs();
let device = trust::device::verify(
attestation.as_bytes(),
self.hardware,
self.emulator,
Some(now),
)
.map_err(|err| err.to_string())?;
Ok((device.signer.clone(), device))
}
}
pub struct HostSide<R: Read, W: Write> {
framing: Framing<R, W>, session: Option<Session>, }
impl<R: Read, W: Write> HostSide<R, W> {
pub fn new(reader: R, writer: W) -> Self {
Self {
framing: Framing::new(reader, writer),
session: None,
}
}
pub fn handshake<V: Verifier>(&mut self, verifier: &V) -> Result<V::Info, Error> {
self.session = None;
self.framing.send_reset()?;
let host_xdsa_sk = xdsa::SecretKey::generate();
let host_xdsa_pk = host_xdsa_sk.public_key();
let host_xhpke_sk = xhpke::SecretKey::generate();
let host_xhpke_pk = host_xhpke_sk.public_key();
let hello = cbor::encode(&handshake::HostHello {
host_signer: host_xdsa_pk.clone(),
host_crypto: host_xhpke_pk.clone(),
})
.map_err(|err| Error::HandshakeFailed(format!("failed to encode host hello: {}", err)))?;
self.framing.send_packet(&hello)?;
let host_xhpke_fp = host_xhpke_pk.fingerprint();
let mut stale = 0;
let size = loop {
let size = self.framing.next_packet()?.unwrap_or_default();
let recipient = cose::recipient(&self.framing.decobs_buffer[..size]);
if recipient.is_ok_and(|fp| fp == host_xhpke_fp) {
break size;
}
stale += 1;
if stale > MAX_STALE_FRAMES {
return Err(Error::HandshakeFailed(
"too many stale frames before ark hello".into(),
));
}
warn!("skipping stale frame during handshake");
};
let auth = handshake::ArkHelloAuth {
host_signer: host_xdsa_pk.clone(),
host_crypto: host_xhpke_pk.clone(),
};
let sign1 = cose::decrypt(
&self.framing.decobs_buffer[..size],
&auth,
&host_xhpke_sk,
CRYPTO_DOMAIN_WIRE,
)
.map_err(|err| Error::HandshakeFailed(format!("failed to decrypt ark hello: {}", err)))?;
let unverified: handshake::ArkHello = cose::peek(&sign1)
.map_err(|err| Error::HandshakeFailed(format!("invalid ark hello payload: {}", err)))?;
let attestation = Attestation::new(unverified.ark_attest)?;
let (ark_identity, info) = verifier
.verify(&attestation)
.map_err(Error::HandshakeFailed)?;
let ark_hello: handshake::ArkHello =
cose::verify(&sign1, &auth, &ark_identity, CRYPTO_DOMAIN_WIRE, None).map_err(
|err| Error::HandshakeFailed(format!("ark hello signature invalid: {}", err)),
)?;
let enc_a2h: [u8; xhpke::ENCAP_KEY_SIZE] = ark_hello
.a2h_encap
.try_into()
.map_err(|_| Error::HandshakeFailed("invalid a2h_encap size".into()))?;
let receiver = host_xhpke_sk
.new_receiver(&enc_a2h, CRYPTO_DOMAIN_WIRE_ARK_TO_HOST)
.map_err(|err| {
Error::HandshakeFailed(format!("host receiver setup failed: {}", err))
})?;
let ark_xhpke_pk = ark_hello.ark_crypto;
let (sender, enc_h2a) = ark_xhpke_pk
.new_sender(CRYPTO_DOMAIN_WIRE_HOST_TO_ARK)
.map_err(|err| Error::HandshakeFailed(format!("host sender setup failed: {}", err)))?;
let ack = cose::seal(
&handshake::HostAck {
h2a_encap: enc_h2a.to_vec(),
},
&handshake::HostAckAuth {
ark_signer: ark_identity,
ark_crypto: ark_xhpke_pk.clone(),
},
&host_xdsa_sk,
&ark_xhpke_pk,
CRYPTO_DOMAIN_WIRE,
)
.map_err(|err| Error::HandshakeFailed(format!("failed to seal host ack: {}", err)))?;
self.framing.send_packet(&ack)?;
self.session = Some(Session { sender, receiver });
Ok(info)
}
pub fn next_message(&mut self) -> Result<ArkToHost, Error> {
let size = match self.framing.next_packet() {
Err(err) => {
self.session = None;
return Err(err);
}
Ok(None) => return Err(Error::FrameDecodingFailed(cobs::DecodeError::EmptyInput)),
Ok(Some(size)) => size,
};
let session = self
.session
.as_mut()
.ok_or_else(|| Error::EncryptionFailed("no active session".into()))?;
let res = match session.open(&self.framing.decobs_buffer[..size]) {
Err(err @ Error::EncryptionFailed(_)) => {
self.session = None;
return Err(err);
}
Err(err) => return Err(err),
Ok(res) => res,
};
trace!("read ark-to-host message ({} bytes encrypted)", size);
Ok(res)
}
pub fn send_message(&mut self, req: HostToArk) -> Result<(), Error> {
let session = self
.session
.as_mut()
.ok_or_else(|| Error::EncryptionFailed("no active session".into()))?;
let blob = match session.seal(&req, &mut self.framing.encode_buffer) {
Err(err @ Error::EncryptionFailed(_)) => {
self.session = None;
return Err(err);
}
Err(err) => return Err(err),
Ok(blob) => blob,
};
if let Err(err) = self.framing.send_packet(&blob) {
self.session = None;
return Err(err);
}
trace!("sent host-to-ark message ({} bytes)", blob.len());
Ok(())
}
#[doc(hidden)]
#[inline]
#[cfg(any(test, feature = "bench", feature = "fuzz"))]
pub fn next_packet_blob(&mut self) -> Result<Option<&[u8]>, Error> {
self.framing.next_packet_blob()
}
#[doc(hidden)]
#[inline]
#[cfg(any(test, feature = "bench", feature = "fuzz"))]
pub fn send_packet_blob(&mut self, packet: &[u8]) -> Result<(), Error> {
self.framing.send_packet(packet)
}
#[doc(hidden)]
#[inline]
#[cfg(any(test, feature = "bench", feature = "fuzz"))]
pub fn next_frame_blob(&mut self) -> Result<&[u8], Error> {
self.framing.next_frame_blob()
}
#[doc(hidden)]
#[inline]
#[cfg(any(test, feature = "bench", feature = "fuzz"))]
pub fn send_frame_blob(&mut self, frame: &[u8]) -> Result<(), Error> {
self.framing.send_frame_blob(frame)
}
}