use core::marker::PhantomData;
use std::time::{Duration, SystemTime};
use ciborium::Value;
use ed25519_dalek::{Signature as Ed25519Signature, Verifier, VerifyingKey};
use thiserror::Error;
use crate::authority::capability::{CapabilityKind, CapabilitySet};
use crate::ingress::{ServiceTrustDeclaration, TrustDeclarationId};
use crate::identity::{KeyId, PublicKey, ServiceIdentity, SignatureAlgorithm};
use crate::proto::Did;
use crate::wire::ResourceScope;
pub const MAX_TRUST_DECLARATION_VALIDITY: Duration =
Duration::from_secs(30 * 86400);
pub(crate) const TRUST_DECLARATION_DOMAIN_TAG: &[u8] =
b"kryphocron/v1/service-trust-declaration/";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub struct TrustRootIdentity {
pub root_key_id: KeyId,
pub root_key: PublicKey,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub struct TrustRootSignature {
pub algorithm: SignatureAlgorithm,
pub bytes: [u8; 64],
}
#[derive(Debug, Clone, PartialEq, Eq, Error)]
#[non_exhaustive]
pub enum TrustDeclarationError {
#[error("trust declaration malformed: {0}")]
Malformed(String),
#[error("trust declaration cites unknown trust root")]
UnknownTrustRoot,
#[error("trust declaration signature invalid")]
SignatureInvalid,
#[error("trust declaration expired (exp={exp:?}, now={now:?})")]
Expired {
exp: SystemTime,
now: SystemTime,
},
#[error("trust declaration not yet valid")]
NotYetValid {
iat: SystemTime,
now: SystemTime,
skew: Duration,
},
#[error("trust declaration validity window {window:?} exceeds max {max:?}")]
ValidityWindowTooLong {
window: Duration,
max: Duration,
},
}
pub fn verify_trust_declaration(
raw_bytes: &[u8],
configured_trust_roots: &[TrustRootIdentity],
max_clock_skew: Duration,
now: SystemTime,
) -> Result<ServiceTrustDeclaration, TrustDeclarationError> {
let value = crate::wire::canonical_cbor_decode(raw_bytes)
.map_err(|()| TrustDeclarationError::Malformed("CBOR decode".into()))?;
let re_encoded = crate::wire::canonical_cbor_encode(value.clone());
if re_encoded != raw_bytes {
return Err(TrustDeclarationError::Malformed(
"non-canonical CBOR encoding".into(),
));
}
let parts = decode_declaration(&value)?;
let DeclarationParts {
declaration_id,
from_service,
to_service,
capabilities,
resource_scope,
issued_at,
expires_at,
trust_root,
signature,
} = parts;
let configured = configured_trust_roots
.iter()
.find(|tr| tr.root_key_id == trust_root.root_key_id)
.ok_or(TrustDeclarationError::UnknownTrustRoot)?;
if configured.root_key.bytes != trust_root.root_key.bytes
|| configured.root_key.algorithm != trust_root.root_key.algorithm
{
return Err(TrustDeclarationError::UnknownTrustRoot);
}
let canonical_payload = encode_canonical_payload(
&declaration_id,
&from_service,
&to_service,
&capabilities,
&resource_scope,
issued_at,
expires_at,
&trust_root,
);
let mut signing_input = Vec::with_capacity(
TRUST_DECLARATION_DOMAIN_TAG.len() + canonical_payload.len(),
);
signing_input.extend_from_slice(TRUST_DECLARATION_DOMAIN_TAG);
signing_input.extend_from_slice(&canonical_payload);
verify_signature(&signing_input, &signature, &configured.root_key)?;
let window = expires_at
.duration_since(issued_at)
.unwrap_or(Duration::ZERO);
if window > MAX_TRUST_DECLARATION_VALIDITY {
return Err(TrustDeclarationError::ValidityWindowTooLong {
window,
max: MAX_TRUST_DECLARATION_VALIDITY,
});
}
if now > expires_at + max_clock_skew {
return Err(TrustDeclarationError::Expired {
exp: expires_at,
now,
});
}
if now + max_clock_skew < issued_at {
return Err(TrustDeclarationError::NotYetValid {
iat: issued_at,
now,
skew: max_clock_skew,
});
}
Ok(ServiceTrustDeclaration {
declaration_id,
from_service,
to_service,
capabilities,
resource_scope,
issued_at,
expires_at,
trust_root,
signature,
_private: PhantomData,
})
}
struct DeclarationParts {
declaration_id: TrustDeclarationId,
from_service: ServiceIdentity,
to_service: ServiceIdentity,
capabilities: CapabilitySet,
resource_scope: ResourceScope,
issued_at: SystemTime,
expires_at: SystemTime,
trust_root: TrustRootIdentity,
signature: TrustRootSignature,
}
fn decode_declaration(value: &Value) -> Result<DeclarationParts, TrustDeclarationError> {
let map = match value {
Value::Map(entries) => entries,
_ => return Err(TrustDeclarationError::Malformed("not a map".into())),
};
let get = |key: &str| -> Result<&Value, TrustDeclarationError> {
map.iter()
.find_map(|(k, v)| match k {
Value::Text(s) if s == key => Some(v),
_ => None,
})
.ok_or_else(|| TrustDeclarationError::Malformed(format!("missing field: {key}")))
};
let declaration_id = decode_declaration_id(get("declaration_id")?)?;
let from_service = decode_service_identity(get("from_service")?)?;
let to_service = decode_service_identity(get("to_service")?)?;
let capabilities = decode_capabilities(get("capabilities")?)?;
let resource_scope = decode_resource_scope(get("resource_scope")?)?;
let issued_at = decode_system_time(get("issued_at")?)?;
let expires_at = decode_system_time(get("expires_at")?)?;
let trust_root = decode_trust_root(get("trust_root")?)?;
let signature = decode_trust_root_signature(get("signature")?)?;
Ok(DeclarationParts {
declaration_id,
from_service,
to_service,
capabilities,
resource_scope,
issued_at,
expires_at,
trust_root,
signature,
})
}
fn decode_declaration_id(v: &Value) -> Result<TrustDeclarationId, TrustDeclarationError> {
let bytes = match v {
Value::Bytes(b) => b,
_ => return Err(TrustDeclarationError::Malformed("declaration_id not bytes".into())),
};
let arr: [u8; 16] = bytes
.as_slice()
.try_into()
.map_err(|_| TrustDeclarationError::Malformed("declaration_id wrong length".into()))?;
Ok(TrustDeclarationId::from_bytes(arr))
}
fn decode_service_identity(v: &Value) -> Result<ServiceIdentity, TrustDeclarationError> {
let map = match v {
Value::Map(e) => e,
_ => return Err(TrustDeclarationError::Malformed("service identity not a map".into())),
};
let get = |key: &str| -> Result<&Value, TrustDeclarationError> {
map.iter()
.find_map(|(k, val)| match k {
Value::Text(s) if s == key => Some(val),
_ => None,
})
.ok_or_else(|| {
TrustDeclarationError::Malformed(format!("missing service identity field: {key}"))
})
};
let did_str = match get("did")? {
Value::Text(s) => s.as_str(),
_ => return Err(TrustDeclarationError::Malformed("did not text".into())),
};
let did = Did::new(did_str)
.map_err(|_| TrustDeclarationError::Malformed("did invalid".into()))?;
let key_id_bytes = match get("key_id")? {
Value::Bytes(b) => b,
_ => return Err(TrustDeclarationError::Malformed("key_id not bytes".into())),
};
let key_id_arr: [u8; 32] = key_id_bytes
.as_slice()
.try_into()
.map_err(|_| TrustDeclarationError::Malformed("key_id wrong length".into()))?;
let key_alg_str = match get("key_alg")? {
Value::Text(s) => s.as_str(),
_ => return Err(TrustDeclarationError::Malformed("key_alg not text".into())),
};
let algorithm = match key_alg_str {
"Ed25519" => SignatureAlgorithm::Ed25519,
"Es256" => SignatureAlgorithm::Es256,
"Es256K" => SignatureAlgorithm::Es256K,
_ => {
return Err(TrustDeclarationError::Malformed(format!(
"unknown signature algorithm: {key_alg_str}"
)))
}
};
let key_material_bytes = match get("key_material")? {
Value::Bytes(b) => b,
_ => {
return Err(TrustDeclarationError::Malformed(
"key_material not bytes".into(),
))
}
};
let key_material_arr: [u8; 32] = key_material_bytes
.as_slice()
.try_into()
.map_err(|_| TrustDeclarationError::Malformed("key_material wrong length".into()))?;
Ok(ServiceIdentity::new_internal(
did,
KeyId::from_bytes(key_id_arr),
PublicKey {
algorithm,
bytes: key_material_arr,
},
None,
))
}
fn decode_capabilities(v: &Value) -> Result<CapabilitySet, TrustDeclarationError> {
let arr = match v {
Value::Array(items) => items,
_ => return Err(TrustDeclarationError::Malformed("capabilities not array".into())),
};
let mut kinds = Vec::with_capacity(arr.len());
for item in arr {
let name = match item {
Value::Text(s) => s.as_str(),
_ => {
return Err(TrustDeclarationError::Malformed(
"capability not text".into(),
))
}
};
let cap = CapabilityKind::from_wire_name(name).ok_or_else(|| {
TrustDeclarationError::Malformed(format!("unknown capability: {name}"))
})?;
kinds.push(cap);
}
Ok(CapabilitySet::from_kinds(kinds))
}
fn decode_resource_scope(v: &Value) -> Result<ResourceScope, TrustDeclarationError> {
let map = match v {
Value::Map(e) => e,
_ => return Err(TrustDeclarationError::Malformed("resource_scope not a map".into())),
};
let kind = map.iter().find_map(|(k, val)| match k {
Value::Text(s) if s == "kind" => Some(val),
_ => None,
});
let kind_str = match kind {
Some(Value::Text(s)) => s.as_str(),
_ => {
return Err(TrustDeclarationError::Malformed(
"resource_scope.kind missing or not text".into(),
))
}
};
match kind_str {
"class_wide_administrative" => Ok(ResourceScope::ClassWideAdministrative),
"all_resources_owned_by" => {
let value = map
.iter()
.find_map(|(k, val)| match k {
Value::Text(s) if s == "value" => Some(val),
_ => None,
})
.ok_or_else(|| {
TrustDeclarationError::Malformed("missing scope.value".into())
})?;
let did_str = match value {
Value::Text(s) => s.as_str(),
_ => {
return Err(TrustDeclarationError::Malformed(
"scope.value not text".into(),
))
}
};
let did = Did::new(did_str)
.map_err(|_| TrustDeclarationError::Malformed("scope.value invalid did".into()))?;
Ok(ResourceScope::AllResourcesOwnedBy(did))
}
"resource" => Err(TrustDeclarationError::Malformed(
"Resource scope not supported in trust declarations in 4c".into(),
)),
other => Err(TrustDeclarationError::Malformed(format!(
"unknown resource_scope kind: {other}"
))),
}
}
fn decode_system_time(v: &Value) -> Result<SystemTime, TrustDeclarationError> {
match v {
Value::Integer(i) => {
let secs: u64 = (*i)
.try_into()
.map_err(|_| TrustDeclarationError::Malformed("time integer out of range".into()))?;
Ok(SystemTime::UNIX_EPOCH + Duration::from_secs(secs))
}
_ => Err(TrustDeclarationError::Malformed("time field not integer".into())),
}
}
fn decode_trust_root(v: &Value) -> Result<TrustRootIdentity, TrustDeclarationError> {
let map = match v {
Value::Map(e) => e,
_ => return Err(TrustDeclarationError::Malformed("trust_root not a map".into())),
};
let get = |key: &str| -> Result<&Value, TrustDeclarationError> {
map.iter()
.find_map(|(k, val)| match k {
Value::Text(s) if s == key => Some(val),
_ => None,
})
.ok_or_else(|| {
TrustDeclarationError::Malformed(format!(
"missing trust_root field: {key}"
))
})
};
let key_id_bytes = match get("root_key_id")? {
Value::Bytes(b) => b,
_ => return Err(TrustDeclarationError::Malformed("root_key_id not bytes".into())),
};
let key_id_arr: [u8; 32] = key_id_bytes
.as_slice()
.try_into()
.map_err(|_| TrustDeclarationError::Malformed("root_key_id wrong length".into()))?;
let key_alg_str = match get("root_key_alg")? {
Value::Text(s) => s.as_str(),
_ => return Err(TrustDeclarationError::Malformed("root_key_alg not text".into())),
};
let algorithm = match key_alg_str {
"Ed25519" => SignatureAlgorithm::Ed25519,
"Es256" => SignatureAlgorithm::Es256,
"Es256K" => SignatureAlgorithm::Es256K,
_ => {
return Err(TrustDeclarationError::Malformed(format!(
"unknown root_key_alg: {key_alg_str}"
)))
}
};
let key_material_bytes = match get("root_key_material")? {
Value::Bytes(b) => b,
_ => {
return Err(TrustDeclarationError::Malformed(
"root_key_material not bytes".into(),
))
}
};
let key_material_arr: [u8; 32] = key_material_bytes
.as_slice()
.try_into()
.map_err(|_| TrustDeclarationError::Malformed("root_key_material wrong length".into()))?;
Ok(TrustRootIdentity {
root_key_id: KeyId::from_bytes(key_id_arr),
root_key: PublicKey {
algorithm,
bytes: key_material_arr,
},
})
}
fn decode_trust_root_signature(
v: &Value,
) -> Result<TrustRootSignature, TrustDeclarationError> {
let map = match v {
Value::Map(e) => e,
_ => return Err(TrustDeclarationError::Malformed("signature not a map".into())),
};
let get = |key: &str| -> Result<&Value, TrustDeclarationError> {
map.iter()
.find_map(|(k, val)| match k {
Value::Text(s) if s == key => Some(val),
_ => None,
})
.ok_or_else(|| {
TrustDeclarationError::Malformed(format!("missing signature field: {key}"))
})
};
let alg_str = match get("alg")? {
Value::Text(s) => s.as_str(),
_ => return Err(TrustDeclarationError::Malformed("signature.alg not text".into())),
};
let algorithm = match alg_str {
"Ed25519" => SignatureAlgorithm::Ed25519,
"Es256" => SignatureAlgorithm::Es256,
"Es256K" => SignatureAlgorithm::Es256K,
_ => {
return Err(TrustDeclarationError::Malformed(format!(
"unknown signature alg: {alg_str}"
)))
}
};
let bytes = match get("bytes")? {
Value::Bytes(b) => b,
_ => return Err(TrustDeclarationError::Malformed("signature.bytes not bytes".into())),
};
let bytes_arr: [u8; 64] = bytes
.as_slice()
.try_into()
.map_err(|_| TrustDeclarationError::Malformed("signature.bytes wrong length".into()))?;
Ok(TrustRootSignature {
algorithm,
bytes: bytes_arr,
})
}
#[must_use]
pub fn encode_wire_bytes(declaration: &ServiceTrustDeclaration) -> Vec<u8> {
let map = Value::Map(vec![
(
Value::Text("declaration_id".into()),
Value::Bytes(declaration.declaration_id.as_bytes().to_vec()),
),
(
Value::Text("from_service".into()),
service_identity_value(&declaration.from_service),
),
(
Value::Text("to_service".into()),
service_identity_value(&declaration.to_service),
),
(
Value::Text("capabilities".into()),
capabilities_value(&declaration.capabilities),
),
(
Value::Text("resource_scope".into()),
resource_scope_value(&declaration.resource_scope),
),
(
Value::Text("issued_at".into()),
system_time_value(declaration.issued_at),
),
(
Value::Text("expires_at".into()),
system_time_value(declaration.expires_at),
),
(
Value::Text("trust_root".into()),
trust_root_value(&declaration.trust_root),
),
(
Value::Text("signature".into()),
trust_root_signature_value(&declaration.signature),
),
]);
crate::wire::canonical_cbor_encode(map)
}
#[allow(clippy::too_many_arguments)]
fn encode_canonical_payload(
declaration_id: &TrustDeclarationId,
from_service: &ServiceIdentity,
to_service: &ServiceIdentity,
capabilities: &CapabilitySet,
resource_scope: &ResourceScope,
issued_at: SystemTime,
expires_at: SystemTime,
trust_root: &TrustRootIdentity,
) -> Vec<u8> {
let map = Value::Map(vec![
(
Value::Text("declaration_id".into()),
Value::Bytes(declaration_id.as_bytes().to_vec()),
),
(
Value::Text("from_service".into()),
service_identity_value(from_service),
),
(
Value::Text("to_service".into()),
service_identity_value(to_service),
),
(
Value::Text("capabilities".into()),
capabilities_value(capabilities),
),
(
Value::Text("resource_scope".into()),
resource_scope_value(resource_scope),
),
(Value::Text("issued_at".into()), system_time_value(issued_at)),
(Value::Text("expires_at".into()), system_time_value(expires_at)),
(
Value::Text("trust_root".into()),
trust_root_value(trust_root),
),
]);
crate::wire::canonical_cbor_encode(map)
}
fn service_identity_value(s: &ServiceIdentity) -> Value {
Value::Map(vec![
(
Value::Text("did".into()),
Value::Text(s.service_did().as_str().to_string()),
),
(
Value::Text("key_id".into()),
Value::Bytes(s.key_id().as_bytes().to_vec()),
),
(
Value::Text("key_alg".into()),
Value::Text(signature_alg_name(s.key_material().algorithm).into()),
),
(
Value::Text("key_material".into()),
Value::Bytes(s.key_material().bytes.to_vec()),
),
])
}
fn capabilities_value(set: &CapabilitySet) -> Value {
Value::Array(
set.kinds()
.iter()
.map(|c| Value::Text(c.wire_name().to_string()))
.collect(),
)
}
fn resource_scope_value(s: &ResourceScope) -> Value {
match s {
ResourceScope::Resource(rid) => Value::Map(vec![
(Value::Text("kind".into()), Value::Text("resource".into())),
(
Value::Text("value".into()),
Value::Map(vec![
(
Value::Text("did".into()),
Value::Text(rid.did().as_str().to_string()),
),
(
Value::Text("nsid".into()),
Value::Text(rid.nsid().as_str().to_string()),
),
(
Value::Text("rkey".into()),
Value::Text(rid.rkey().as_str().to_string()),
),
]),
),
]),
ResourceScope::AllResourcesOwnedBy(did) => Value::Map(vec![
(
Value::Text("kind".into()),
Value::Text("all_resources_owned_by".into()),
),
(
Value::Text("value".into()),
Value::Text(did.as_str().to_string()),
),
]),
ResourceScope::ClassWideAdministrative => Value::Map(vec![(
Value::Text("kind".into()),
Value::Text("class_wide_administrative".into()),
)]),
}
}
fn trust_root_value(tr: &TrustRootIdentity) -> Value {
Value::Map(vec![
(
Value::Text("root_key_id".into()),
Value::Bytes(tr.root_key_id.as_bytes().to_vec()),
),
(
Value::Text("root_key_alg".into()),
Value::Text(signature_alg_name(tr.root_key.algorithm).into()),
),
(
Value::Text("root_key_material".into()),
Value::Bytes(tr.root_key.bytes.to_vec()),
),
])
}
fn trust_root_signature_value(sig: &TrustRootSignature) -> Value {
Value::Map(vec![
(
Value::Text("alg".into()),
Value::Text(signature_alg_name(sig.algorithm).into()),
),
(Value::Text("bytes".into()), Value::Bytes(sig.bytes.to_vec())),
])
}
fn signature_alg_name(a: SignatureAlgorithm) -> &'static str {
match a {
SignatureAlgorithm::Ed25519 => "Ed25519",
SignatureAlgorithm::Es256 => "Es256",
SignatureAlgorithm::Es256K => "Es256K",
}
}
fn system_time_value(t: SystemTime) -> Value {
let secs = t
.duration_since(SystemTime::UNIX_EPOCH)
.expect("SystemTime before UNIX_EPOCH not supported")
.as_secs();
Value::Integer(secs.into())
}
fn verify_signature(
signing_input: &[u8],
signature: &TrustRootSignature,
public_key: &PublicKey,
) -> Result<(), TrustDeclarationError> {
match signature.algorithm {
SignatureAlgorithm::Ed25519 => {
let sig = Ed25519Signature::from_bytes(&signature.bytes);
let key = VerifyingKey::from_bytes(&public_key.bytes)
.map_err(|_| TrustDeclarationError::SignatureInvalid)?;
key.verify(signing_input, &sig)
.map_err(|_| TrustDeclarationError::SignatureInvalid)
}
SignatureAlgorithm::Es256 | SignatureAlgorithm::Es256K => {
Err(TrustDeclarationError::SignatureInvalid)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use ed25519_dalek::{Signer, SigningKey};
fn fixed_root_signing_key() -> SigningKey {
SigningKey::from_bytes(&[42u8; 32])
}
fn fixed_trust_root() -> TrustRootIdentity {
let signing = fixed_root_signing_key();
TrustRootIdentity {
root_key_id: KeyId::from_bytes([0xCA; 32]),
root_key: PublicKey {
algorithm: SignatureAlgorithm::Ed25519,
bytes: signing.verifying_key().to_bytes(),
},
}
}
fn fixed_service_identity(did: &str) -> ServiceIdentity {
let signing = SigningKey::from_bytes(&[7u8; 32]);
ServiceIdentity::new_internal(
Did::new(did).unwrap(),
KeyId::from_bytes([1; 32]),
PublicKey {
algorithm: SignatureAlgorithm::Ed25519,
bytes: signing.verifying_key().to_bytes(),
},
None,
)
}
fn build_signed_wire(
issued_at: SystemTime,
expires_at: SystemTime,
trust_root: TrustRootIdentity,
) -> Vec<u8> {
let payload_bytes = encode_canonical_payload(
&TrustDeclarationId::from_bytes([0xDD; 16]),
&fixed_service_identity("did:web:from.example"),
&fixed_service_identity("did:web:to.example"),
&CapabilitySet::from_kinds([CapabilityKind::ViewPrivate]),
&ResourceScope::ClassWideAdministrative,
issued_at,
expires_at,
&trust_root,
);
let mut signing_input =
Vec::with_capacity(TRUST_DECLARATION_DOMAIN_TAG.len() + payload_bytes.len());
signing_input.extend_from_slice(TRUST_DECLARATION_DOMAIN_TAG);
signing_input.extend_from_slice(&payload_bytes);
let sig = fixed_root_signing_key().sign(&signing_input);
let signature = TrustRootSignature {
algorithm: SignatureAlgorithm::Ed25519,
bytes: sig.to_bytes(),
};
let provisional = ServiceTrustDeclaration {
declaration_id: TrustDeclarationId::from_bytes([0xDD; 16]),
from_service: fixed_service_identity("did:web:from.example"),
to_service: fixed_service_identity("did:web:to.example"),
capabilities: CapabilitySet::from_kinds([CapabilityKind::ViewPrivate]),
resource_scope: ResourceScope::ClassWideAdministrative,
issued_at,
expires_at,
trust_root,
signature,
_private: PhantomData,
};
encode_wire_bytes(&provisional)
}
fn now() -> SystemTime {
SystemTime::UNIX_EPOCH + Duration::from_secs(1_700_000_000)
}
#[test]
fn max_trust_declaration_validity_pinned_at_30_days() {
assert_eq!(
MAX_TRUST_DECLARATION_VALIDITY,
Duration::from_secs(30 * 86400)
);
}
#[test]
fn trust_declaration_domain_tag_pinned_per_7_4() {
assert_eq!(
TRUST_DECLARATION_DOMAIN_TAG,
b"kryphocron/v1/service-trust-declaration/"
);
}
#[test]
fn happy_path_verifies_a_signed_declaration() {
let trust_root = fixed_trust_root();
let issued_at = now();
let expires_at = issued_at + Duration::from_secs(86400);
let wire = build_signed_wire(issued_at, expires_at, trust_root);
let decl = verify_trust_declaration(
&wire,
&[trust_root],
Duration::from_secs(30),
now(),
)
.unwrap();
assert_eq!(
decl.declaration_id().as_bytes(),
&[0xDD; 16],
);
assert_eq!(decl.expires_at(), expires_at);
}
#[test]
fn unknown_trust_root_returns_unknown_trust_root() {
let trust_root = fixed_trust_root();
let other_root = TrustRootIdentity {
root_key_id: KeyId::from_bytes([0x99; 32]),
root_key: PublicKey {
algorithm: SignatureAlgorithm::Ed25519,
bytes: [0; 32],
},
};
let issued_at = now();
let expires_at = issued_at + Duration::from_secs(60);
let wire = build_signed_wire(issued_at, expires_at, trust_root);
let err =
verify_trust_declaration(&wire, &[other_root], Duration::from_secs(30), now())
.unwrap_err();
assert!(matches!(err, TrustDeclarationError::UnknownTrustRoot));
}
#[test]
fn signature_invalid_returns_signature_invalid() {
let trust_root = fixed_trust_root();
let issued_at = now();
let expires_at = issued_at + Duration::from_secs(60);
let mut wire = build_signed_wire(issued_at, expires_at, trust_root);
let head = wire
.windows(2)
.position(|w| w == [0x58, 0x40])
.expect("signature byte-string must be present");
let sig_start = head + 2;
for b in &mut wire[sig_start..sig_start + 64] {
*b = 0;
}
let err =
verify_trust_declaration(&wire, &[trust_root], Duration::from_secs(30), now())
.unwrap_err();
assert!(matches!(err, TrustDeclarationError::SignatureInvalid));
}
#[test]
fn expired_declaration_returns_expired() {
let trust_root = fixed_trust_root();
let issued_at = now() - Duration::from_secs(7200);
let expires_at = issued_at + Duration::from_secs(60);
let wire = build_signed_wire(issued_at, expires_at, trust_root);
let err =
verify_trust_declaration(&wire, &[trust_root], Duration::from_secs(30), now())
.unwrap_err();
assert!(matches!(err, TrustDeclarationError::Expired { .. }));
}
#[test]
fn future_dated_declaration_returns_not_yet_valid() {
let trust_root = fixed_trust_root();
let issued_at = now() + Duration::from_secs(3600);
let expires_at = issued_at + Duration::from_secs(60);
let wire = build_signed_wire(issued_at, expires_at, trust_root);
let err =
verify_trust_declaration(&wire, &[trust_root], Duration::from_secs(30), now())
.unwrap_err();
assert!(matches!(err, TrustDeclarationError::NotYetValid { .. }));
}
#[test]
fn over_max_validity_window_returns_validity_window_too_long() {
let trust_root = fixed_trust_root();
let issued_at = now();
let expires_at = issued_at + Duration::from_secs(31 * 86400);
let wire = build_signed_wire(issued_at, expires_at, trust_root);
let err =
verify_trust_declaration(&wire, &[trust_root], Duration::from_secs(30), now())
.unwrap_err();
assert!(matches!(
err,
TrustDeclarationError::ValidityWindowTooLong { .. }
));
}
#[test]
fn malformed_cbor_returns_malformed() {
let err = verify_trust_declaration(
&[0xFF, 0xFF, 0xFF],
&[fixed_trust_root()],
Duration::from_secs(30),
now(),
)
.unwrap_err();
assert!(matches!(err, TrustDeclarationError::Malformed(_)));
}
#[test]
fn non_canonical_cbor_returns_malformed() {
let non_canonical: Vec<u8> = vec![
0xA2, 0x65, 0x7A, 0x65, 0x62, 0x72, 0x61, 0x01, 0x65, 0x61, 0x70, 0x70,
0x6C, 0x65, 0x02,
];
let err = verify_trust_declaration(
&non_canonical,
&[fixed_trust_root()],
Duration::from_secs(30),
now(),
)
.unwrap_err();
assert!(matches!(err, TrustDeclarationError::Malformed(_)));
}
#[test]
fn signature_without_domain_tag_fails_verification() {
let trust_root = fixed_trust_root();
let issued_at = now();
let expires_at = issued_at + Duration::from_secs(86400);
let payload_bytes = encode_canonical_payload(
&TrustDeclarationId::from_bytes([0xDD; 16]),
&fixed_service_identity("did:web:from.example"),
&fixed_service_identity("did:web:to.example"),
&CapabilitySet::from_kinds([CapabilityKind::ViewPrivate]),
&ResourceScope::ClassWideAdministrative,
issued_at,
expires_at,
&trust_root,
);
let sig_no_tag = fixed_root_signing_key().sign(&payload_bytes);
let signature = TrustRootSignature {
algorithm: SignatureAlgorithm::Ed25519,
bytes: sig_no_tag.to_bytes(),
};
let provisional = ServiceTrustDeclaration {
declaration_id: TrustDeclarationId::from_bytes([0xDD; 16]),
from_service: fixed_service_identity("did:web:from.example"),
to_service: fixed_service_identity("did:web:to.example"),
capabilities: CapabilitySet::from_kinds([CapabilityKind::ViewPrivate]),
resource_scope: ResourceScope::ClassWideAdministrative,
issued_at,
expires_at,
trust_root,
signature,
_private: PhantomData,
};
let wire = encode_wire_bytes(&provisional);
let err = verify_trust_declaration(
&wire,
&[trust_root],
Duration::from_secs(30),
now(),
)
.unwrap_err();
assert!(matches!(err, TrustDeclarationError::SignatureInvalid));
}
}