use crate::{
CRYPTO_DOMAIN_DEVICE_ATTESTATION, EMULATOR_ATTESTATION_MAX_VALIDITY, Error, Realm,
check_validity,
};
use darkbio_crypto::cbor::Cbor;
use darkbio_crypto::cwt::claims::{self, eat};
use darkbio_crypto::{cwt, xdsa};
#[derive(Cbor)]
pub struct HardwareClaims {
#[cbor(embed)]
pub sub: claims::Subject,
#[cbor(embed)]
pub cnf: claims::Confirm<xdsa::PublicKey>,
#[cbor(embed)]
pub nbf: claims::NotBefore,
#[cbor(embed)]
pub iat: claims::IssuedAt,
#[cbor(embed)]
pub oem: eat::Oemid,
#[cbor(embed)]
pub hwm: eat::HwModel,
#[cbor(embed)]
pub hwv: eat::HwVersion,
}
#[derive(Cbor)]
pub struct EmulatorClaims {
#[cbor(embed)]
pub sub: claims::Subject,
#[cbor(embed)]
pub cnf: claims::Confirm<xdsa::PublicKey>,
#[cbor(embed)]
pub nbf: claims::NotBefore,
#[cbor(embed)]
pub exp: claims::Expiration,
#[cbor(embed)]
pub iat: claims::IssuedAt,
#[cbor(embed)]
pub oem: eat::Oemid,
#[cbor(embed)]
pub hwm: eat::HwModel,
#[cbor(embed)]
pub hwv: eat::HwVersion,
}
#[derive(Clone, Debug)]
pub struct Device {
pub realm: Realm,
pub identity: xdsa::PublicKey,
pub serial: String,
pub oem: eat::Oemid,
pub model: Vec<u8>,
pub version: String,
pub issued: u64,
pub expiry: Option<u64>,
}
pub fn verify(
attestation: &[u8],
hardware: &[xdsa::PublicKey],
emulator: &[xdsa::PublicKey],
now: Option<u64>,
) -> Result<Device, Error> {
let signer = cwt::signer(attestation)?;
if let Some(root) = hardware.iter().find(|root| root.fingerprint() == signer) {
let claims: HardwareClaims =
cwt::verify(attestation, root, CRYPTO_DOMAIN_DEVICE_ATTESTATION, now)?;
return Ok(Device {
realm: Realm::Hardware,
identity: claims.cnf.key().clone(),
serial: claims.sub.sub,
oem: claims.oem,
model: claims.hwm.hw_model,
version: claims.hwv.version().to_string(),
issued: claims.iat.iat,
expiry: None,
});
}
if let Some(root) = emulator.iter().find(|root| root.fingerprint() == signer) {
let claims: EmulatorClaims =
cwt::verify(attestation, root, CRYPTO_DOMAIN_DEVICE_ATTESTATION, now)?;
check_validity(
claims.nbf.nbf,
claims.exp.exp,
EMULATOR_ATTESTATION_MAX_VALIDITY,
)?;
return Ok(Device {
realm: Realm::Emulator,
identity: claims.cnf.key().clone(),
serial: claims.sub.sub,
oem: claims.oem,
model: claims.hwm.hw_model,
version: claims.hwv.version().to_string(),
issued: claims.iat.iat,
expiry: Some(claims.exp.exp),
});
}
Err(Error::untrusted_signer(signer))
}
pub fn verify_self_signed(attestation: &[u8]) -> Result<xdsa::PublicKey, Error> {
let (identity, hardware) = match cwt::peek::<HardwareClaims>(attestation) {
Ok(claims) => (claims.cnf.key().clone(), true),
Err(_) => {
let claims: EmulatorClaims = cwt::peek(attestation)?;
(claims.cnf.key().clone(), false)
}
};
let signer = cwt::signer(attestation)?;
if identity.fingerprint() != signer {
return Err(Error::NotSelfSigned);
}
match hardware {
true => {
cwt::verify::<HardwareClaims>(
attestation,
&identity,
CRYPTO_DOMAIN_DEVICE_ATTESTATION,
None,
)?;
}
false => {
cwt::verify::<EmulatorClaims>(
attestation,
&identity,
CRYPTO_DOMAIN_DEVICE_ATTESTATION,
None,
)?;
}
}
Ok(identity)
}
#[cfg(test)]
mod tests {
use super::*;
fn hardware_claims(identity: xdsa::PublicKey) -> HardwareClaims {
HardwareClaims {
sub: claims::Subject {
sub: "ark-1234".into(),
},
cnf: claims::Confirm::new(identity),
nbf: claims::NotBefore { nbf: 1000 },
iat: claims::IssuedAt { iat: 1000 },
oem: eat::Oemid::new_pen(65145),
hwm: eat::HwModel {
hw_model: b"Ark I".to_vec(),
},
hwv: eat::HwVersion::new("Ark I - 1.0.0".into()),
}
}
fn emulator_claims(identity: xdsa::PublicKey, nbf: u64, exp: u64) -> EmulatorClaims {
EmulatorClaims {
sub: claims::Subject {
sub: "emu-1234".into(),
},
cnf: claims::Confirm::new(identity),
nbf: claims::NotBefore { nbf },
exp: claims::Expiration { exp },
iat: claims::IssuedAt { iat: 1000 },
oem: eat::Oemid::new_pen(65145),
hwm: eat::HwModel {
hw_model: b"Ark I".to_vec(),
},
hwv: eat::HwVersion::new("Ark I - 1.0.0".into()),
}
}
#[test]
fn test_hardware_verification() {
let root = xdsa::SecretKey::generate();
let identity = xdsa::SecretKey::generate().public_key();
let attestation = cwt::issue(
&hardware_claims(identity.clone()),
&root,
CRYPTO_DOMAIN_DEVICE_ATTESTATION,
)
.unwrap();
let device = verify(&attestation, &[root.public_key()], &[], Some(1500)).unwrap();
assert_eq!(device.realm, Realm::Hardware, "realm mismatch");
assert_eq!(
device.identity.fingerprint(),
identity.fingerprint(),
"identity mismatch"
);
assert_eq!(device.serial, "ark-1234", "serial mismatch");
assert_eq!(device.model, b"Ark I", "model mismatch");
assert_eq!(device.version, "Ark I - 1.0.0", "version mismatch");
assert_eq!(device.issued, 1000, "issuance mismatch");
assert_eq!(device.expiry, None, "hardware attestation must not expire");
}
#[test]
fn test_emulator_verification() {
let root = xdsa::SecretKey::generate();
let identity = xdsa::SecretKey::generate().public_key();
let attestation = cwt::issue(
&emulator_claims(identity.clone(), 1000, 2000),
&root,
CRYPTO_DOMAIN_DEVICE_ATTESTATION,
)
.unwrap();
let device = verify(&attestation, &[], &[root.public_key()], Some(1500)).unwrap();
assert_eq!(device.realm, Realm::Emulator, "realm mismatch");
assert_eq!(
device.identity.fingerprint(),
identity.fingerprint(),
"identity mismatch"
);
assert_eq!(device.serial, "emu-1234", "serial mismatch");
assert_eq!(device.expiry, Some(2000), "expiry mismatch");
}
#[test]
fn test_shape_mismatch() {
let root = xdsa::SecretKey::generate();
let identity = xdsa::SecretKey::generate().public_key();
let expiring = cwt::issue(
&emulator_claims(identity.clone(), 1000, 2000),
&root,
CRYPTO_DOMAIN_DEVICE_ATTESTATION,
)
.unwrap();
assert!(
matches!(
verify(&expiring, &[root.public_key()], &[], None),
Err(Error::Cwt(cwt::Error::Cbor(_)))
),
"hardware root accepted an expiring attestation"
);
let permanent = cwt::issue(
&hardware_claims(identity),
&root,
CRYPTO_DOMAIN_DEVICE_ATTESTATION,
)
.unwrap();
assert!(
matches!(
verify(&permanent, &[], &[root.public_key()], None),
Err(Error::Cwt(cwt::Error::Cbor(_)))
),
"emulator root accepted a permanent attestation"
);
}
#[test]
fn test_root_selection() {
let hardware = xdsa::SecretKey::generate().public_key();
let emulator = xdsa::SecretKey::generate();
let identity = xdsa::SecretKey::generate().public_key();
let attestation = cwt::issue(
&emulator_claims(identity, 1000, 2000),
&emulator,
CRYPTO_DOMAIN_DEVICE_ATTESTATION,
)
.unwrap();
let device = verify(
&attestation,
std::slice::from_ref(&hardware),
&[emulator.public_key()],
None,
)
.unwrap();
assert_eq!(device.realm, Realm::Emulator, "realm mismatch");
match verify(&attestation, &[hardware], &[], None) {
Err(Error::UntrustedSigner {
fingerprint: signer,
root: None,
}) => {
assert_eq!(
signer,
emulator.public_key().fingerprint(),
"signer mismatch"
);
}
other => panic!("signer not rejected as unexpected, got {other:?}"),
}
}
#[test]
fn test_validity_period() {
let root = xdsa::SecretKey::generate();
let identity = xdsa::SecretKey::generate().public_key();
let attestation = cwt::issue(
&hardware_claims(identity.clone()),
&root,
CRYPTO_DOMAIN_DEVICE_ATTESTATION,
)
.unwrap();
assert!(
matches!(
verify(&attestation, &[root.public_key()], &[], Some(999)),
Err(Error::Cwt(cwt::Error::NotYetValid { .. }))
),
"attestation accepted before validity"
);
verify(&attestation, &[root.public_key()], &[], Some(u64::MAX))
.expect("hardware attestation expired");
let attestation = cwt::issue(
&emulator_claims(identity, 1000, 2000),
&root,
CRYPTO_DOMAIN_DEVICE_ATTESTATION,
)
.unwrap();
assert!(
matches!(
verify(&attestation, &[], &[root.public_key()], Some(2000)),
Err(Error::Cwt(cwt::Error::AlreadyExpired { .. }))
),
"attestation accepted after expiry"
);
verify(&attestation, &[], &[root.public_key()], None)
.expect("timeless verification failed");
}
#[test]
fn test_self_signed() {
let secret = xdsa::SecretKey::generate();
let identity = secret.public_key();
let root = xdsa::SecretKey::generate().public_key();
for attestation in [
cwt::issue(
&hardware_claims(identity.clone()),
&secret,
CRYPTO_DOMAIN_DEVICE_ATTESTATION,
)
.unwrap(),
cwt::issue(
&emulator_claims(identity.clone(), 1000, 2000),
&secret,
CRYPTO_DOMAIN_DEVICE_ATTESTATION,
)
.unwrap(),
] {
assert_eq!(
verify_self_signed(&attestation).unwrap().fingerprint(),
identity.fingerprint(),
"identity mismatch"
);
assert!(
matches!(
verify(&attestation, std::slice::from_ref(&root), &[], None),
Err(Error::UntrustedSigner { .. })
),
"self-signed attestation accepted as attested"
);
}
}
#[test]
fn test_self_signed_rejects_foreign_signer() {
let identity = xdsa::SecretKey::generate().public_key();
let attestation = cwt::issue(
&hardware_claims(identity),
&xdsa::SecretKey::generate(),
CRYPTO_DOMAIN_DEVICE_ATTESTATION,
)
.unwrap();
assert!(
matches!(verify_self_signed(&attestation), Err(Error::NotSelfSigned)),
"foreign signed attestation accepted as self-signed"
);
assert!(
matches!(verify_self_signed(b"junk"), Err(Error::Cwt(_))),
"junk accepted as self-signed"
);
}
#[test]
fn test_emulator_validity_cap() {
let root = xdsa::SecretKey::generate();
let identity = xdsa::SecretKey::generate().public_key();
let max = EMULATOR_ATTESTATION_MAX_VALIDITY.as_secs();
for (nbf, exp, accept) in [
(1000, 1000 + max, true),
(1000, 1000 + max + 1, false),
(1000, 1000, false),
(2000, 1000, false),
] {
let attestation = cwt::issue(
&emulator_claims(identity.clone(), nbf, exp),
&root,
CRYPTO_DOMAIN_DEVICE_ATTESTATION,
)
.unwrap();
for now in [None, Some(1500)] {
let timely = now.is_none_or(|now| nbf <= now && now < exp);
let result = verify(&attestation, &[], &[root.public_key()], now);
match (accept, timely) {
(true, _) => {
result.expect("valid attestation rejected");
}
(false, true) => assert!(
matches!(result, Err(Error::InvalidValidity { max }) if max == EMULATOR_ATTESTATION_MAX_VALIDITY),
"attestation accepted with validity {nbf} to {exp} at {now:?}"
),
(false, false) => assert!(
matches!(result, Err(Error::Cwt(_))),
"untimely attestation not rejected by the time check at {now:?}"
),
}
}
}
}
#[test]
fn test_hardware_fields() {
let root = xdsa::SecretKey::generate();
let roots = [root.public_key()];
let identity = xdsa::SecretKey::generate().public_key();
let mut claims = hardware_claims(identity.clone());
claims.nbf.nbf = 1400;
let mut token = cwt::issue(&claims, &root, CRYPTO_DOMAIN_DEVICE_ATTESTATION).unwrap();
let device = verify(&token, &roots, &[], None).unwrap();
assert_eq!(device.identity.fingerprint(), identity.fingerprint());
assert_eq!(device.issued, 1000);
assert_eq!(device.oem.pen(), Some(65145));
assert!(matches!(
verify(&token, &roots, &[], Some(1399)),
Err(Error::Cwt(cwt::Error::NotYetValid {
nbf: 1400,
now: 1399
}))
));
verify(&token, &roots, &[], Some(1400)).unwrap();
*token.last_mut().unwrap() ^= 1;
assert!(matches!(
verify(&token, &roots, &[], None),
Err(Error::Cwt(_))
));
}
#[test]
fn test_emulator_fields() {
let root = xdsa::SecretKey::generate();
let roots = [root.public_key()];
let identity = xdsa::SecretKey::generate().public_key();
let max = EMULATOR_ATTESTATION_MAX_VALIDITY.as_secs();
let token = cwt::issue(
&emulator_claims(identity, 1000, 1000 + max),
&root,
CRYPTO_DOMAIN_DEVICE_ATTESTATION,
)
.unwrap();
let device = verify(&token, &[], &roots, None).unwrap();
assert_eq!(device.realm, Realm::Emulator);
assert_eq!(device.expiry, Some(1000 + max));
verify(&token, &[], &roots, Some(1000)).unwrap();
verify(&token, &[], &roots, Some(1000 + max - 1)).unwrap();
assert!(matches!(
verify(&token, &[], &roots, Some(1000 + max)),
Err(Error::Cwt(cwt::Error::AlreadyExpired { .. }))
));
}
#[test]
fn test_unverified_signer_hint() {
let attacker = xdsa::SecretKey::generate();
let mut token = cwt::issue(
&hardware_claims(attacker.public_key()),
&attacker,
CRYPTO_DOMAIN_DEVICE_ATTESTATION,
)
.unwrap();
let mut bytes = [0; 32];
hex::decode_to_slice(
"fe56b1de20bde6010569c90e611197356ad521c5e3d27f7afbbb65b5600bbb12",
&mut bytes,
)
.unwrap();
let claimed = xdsa::Fingerprint::from_bytes(&bytes);
let claimed_root = crate::roots::identify(&claimed).unwrap();
let fingerprint = attacker.fingerprint().to_bytes();
let offsets: Vec<_> = token
.windows(32)
.enumerate()
.filter(|(_, bytes)| *bytes == fingerprint)
.map(|(i, _)| i)
.collect();
assert_eq!(offsets.len(), 1);
token[offsets[0]..offsets[0] + 32].copy_from_slice(&claimed.to_bytes());
let err = verify(&token, &[], &[], None).unwrap_err();
assert!(
matches!(&err, Error::UntrustedSigner { fingerprint, root: Some(info) } if *fingerprint == claimed && *info == claimed_root)
);
assert!(err.to_string().starts_with("attestation signed by the "));
let roots = crate::roots::hardware(claimed_root.env);
if !roots.is_empty() {
assert!(matches!(
verify(&token, roots, &[], None),
Err(Error::Cwt(_))
));
}
}
}