use std::{
fmt,
time::{Duration, Instant},
};
use crate::errors::SdkError;
const CAPABILITY_LEN: usize = 32;
const SPKI_DIGEST_LEN: usize = 32;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum BootstrapPurpose {
Attach,
Recover,
}
#[derive(Clone, Copy, Eq, Hash, PartialEq)]
pub struct ProcessOwner([u8; 16]);
impl fmt::Debug for ProcessOwner {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("ProcessOwner(<opaque>)")
}
}
impl ProcessOwner {
pub fn generate() -> Result<Self, SdkError> {
let mut bytes = [0_u8; 16];
getrandom::fill(&mut bytes).map_err(|_| SdkError::Authentication)?;
Ok(Self(bytes))
}
pub(crate) const fn from_test_bytes(bytes: [u8; 16]) -> Self {
Self(bytes)
}
}
#[derive(Clone, Copy, Eq, PartialEq)]
pub struct VerifiedServer([u8; SPKI_DIGEST_LEN]);
impl fmt::Debug for VerifiedServer {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("VerifiedServer(<spki-redacted>)")
}
}
impl VerifiedServer {
pub(crate) const fn from_test_digest(digest: [u8; SPKI_DIGEST_LEN]) -> Self {
Self(digest)
}
}
#[derive(Clone, Copy, Eq, PartialEq)]
pub struct BootstrapCapability([u8; CAPABILITY_LEN]);
impl fmt::Debug for BootstrapCapability {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("BootstrapCapability(<redacted>)")
}
}
impl BootstrapCapability {
pub(crate) const fn from_test_bytes(bytes: [u8; CAPABILITY_LEN]) -> Self {
Self(bytes)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct BootstrapGrant {
owner: ProcessOwner,
epoch: u64,
purpose: BootstrapPurpose,
server: VerifiedServer,
capability: BootstrapCapability,
expires_at: Instant,
}
impl BootstrapGrant {
pub(crate) const fn from_verified(
owner: ProcessOwner,
epoch: u64,
purpose: BootstrapPurpose,
server: VerifiedServer,
capability: BootstrapCapability,
expires_at: Instant,
) -> Self {
Self {
owner,
epoch,
purpose,
server,
capability,
expires_at,
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct BootstrapGate {
owner: ProcessOwner,
epoch: u64,
purpose: BootstrapPurpose,
expected_server: VerifiedServer,
consumed: bool,
zero_rtt: bool,
}
impl BootstrapGate {
pub(crate) const fn new(
owner: ProcessOwner,
epoch: u64,
purpose: BootstrapPurpose,
expected_server: VerifiedServer,
) -> Self {
Self {
owner,
epoch,
purpose,
expected_server,
consumed: false,
zero_rtt: false,
}
}
pub const fn zero_rtt_enabled(self) -> bool {
self.zero_rtt
}
pub fn consume(&mut self, grant: BootstrapGrant, now: Instant) -> Result<(), SdkError> {
if self.consumed {
return Err(SdkError::CapabilityReplayed);
}
if grant.expires_at <= now {
return Err(SdkError::CapabilityExpired);
}
if grant.owner != self.owner || grant.epoch != self.epoch || grant.purpose != self.purpose {
return Err(SdkError::CapabilityBindingMismatch);
}
if grant.server != self.expected_server {
return Err(SdkError::HostKeyRejected);
}
let _ = grant.capability;
self.consumed = true;
Ok(())
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct BootstrapRequest {
pub purpose: BootstrapPurpose,
pub epoch: u64,
pub timeout: Duration,
}
impl BootstrapRequest {
pub const fn new(purpose: BootstrapPurpose, epoch: u64, timeout: Duration) -> Self {
Self {
purpose,
epoch,
timeout,
}
}
}
#[doc(hidden)]
pub mod test_support {
use super::*;
pub fn owner(value: u8) -> ProcessOwner {
ProcessOwner::from_test_bytes([value; 16])
}
pub fn server(value: u8) -> VerifiedServer {
VerifiedServer::from_test_digest([value; SPKI_DIGEST_LEN])
}
pub fn grant(
owner: ProcessOwner,
epoch: u64,
purpose: BootstrapPurpose,
server: VerifiedServer,
value: u8,
expires_at: Instant,
) -> BootstrapGrant {
BootstrapGrant::from_verified(
owner,
epoch,
purpose,
server,
BootstrapCapability::from_test_bytes([value; CAPABILITY_LEN]),
expires_at,
)
}
pub fn gate(
owner: ProcessOwner,
epoch: u64,
purpose: BootstrapPurpose,
server: VerifiedServer,
) -> BootstrapGate {
BootstrapGate::new(owner, epoch, purpose, server)
}
}