use crate::crypto::ASYM_PUBLIC_KEY_LEN;
pub const ACTIVATION_TOKEN_MAX_LEN: usize = 256;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum TrustServiceError {
InvalidChallenge,
ServiceUnavailable,
TransportNotConfigured,
Unauthorized,
InvalidDecision,
TokenExpired,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ActivationChallenge<'a> {
pub model_sha256: [u8; 32],
pub model_signing_pubkey: [u8; ASYM_PUBLIC_KEY_LEN],
pub distribution_policy: u8,
pub machine_fingerprint: &'a [u8],
pub device_id: &'a [u8],
pub issued_at_unix: u64,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ActivationDecision {
pub allowed: bool,
pub expires_unix: u64,
pub activation_token: [u8; ACTIVATION_TOKEN_MAX_LEN],
pub activation_token_len: usize,
}
pub trait PublisherTrustService {
fn request_activation(
&self,
challenge: &ActivationChallenge<'_>,
) -> Result<ActivationDecision, TrustServiceError>;
}
pub fn encode_activation_challenge_binary(
challenge: &ActivationChallenge<'_>,
out: &mut [u8],
) -> Result<usize, TrustServiceError> {
if challenge.machine_fingerprint.len() > u16::MAX as usize
|| challenge.device_id.len() > u16::MAX as usize
{
return Err(TrustServiceError::InvalidChallenge);
}
let needed = 4usize
.checked_add(1)
.and_then(|v| v.checked_add(32))
.and_then(|v| v.checked_add(ASYM_PUBLIC_KEY_LEN))
.and_then(|v| v.checked_add(1))
.and_then(|v| v.checked_add(2))
.and_then(|v| v.checked_add(challenge.machine_fingerprint.len()))
.and_then(|v| v.checked_add(2))
.and_then(|v| v.checked_add(challenge.device_id.len()))
.and_then(|v| v.checked_add(8))
.ok_or(TrustServiceError::InvalidChallenge)?;
if out.len() < needed {
return Err(TrustServiceError::InvalidChallenge);
}
let mut cursor = 0usize;
out[cursor..cursor + 4].copy_from_slice(b"ATS0");
cursor += 4;
out[cursor] = 1;
cursor += 1;
out[cursor..cursor + 32].copy_from_slice(&challenge.model_sha256);
cursor += 32;
out[cursor..cursor + ASYM_PUBLIC_KEY_LEN].copy_from_slice(&challenge.model_signing_pubkey);
cursor += ASYM_PUBLIC_KEY_LEN;
out[cursor] = challenge.distribution_policy;
cursor += 1;
let mlen = challenge.machine_fingerprint.len() as u16;
out[cursor..cursor + 2].copy_from_slice(&mlen.to_le_bytes());
cursor += 2;
out[cursor..cursor + challenge.machine_fingerprint.len()]
.copy_from_slice(challenge.machine_fingerprint);
cursor += challenge.machine_fingerprint.len();
let dlen = challenge.device_id.len() as u16;
out[cursor..cursor + 2].copy_from_slice(&dlen.to_le_bytes());
cursor += 2;
out[cursor..cursor + challenge.device_id.len()].copy_from_slice(challenge.device_id);
cursor += challenge.device_id.len();
out[cursor..cursor + 8].copy_from_slice(&challenge.issued_at_unix.to_le_bytes());
cursor += 8;
Ok(cursor)
}
pub fn validate_activation_decision_placeholder(
decision: &ActivationDecision,
now_unix: u64,
) -> Result<(), TrustServiceError> {
if !decision.allowed {
return Err(TrustServiceError::Unauthorized);
}
if decision.activation_token_len == 0
|| decision.activation_token_len > ACTIVATION_TOKEN_MAX_LEN
{
return Err(TrustServiceError::InvalidDecision);
}
if decision.expires_unix < now_unix {
return Err(TrustServiceError::TokenExpired);
}
Ok(())
}