use ciborium::value::Value;
use super::{
ALG_EDDSA, CONTENT_TYPE, COSE_SIGN1_TAG, HEADER_LABEL_ALG, HEADER_LABEL_CONTENT_TYPE,
HEADER_LABEL_KID, KEY_LEN, MAX_SEQUENCE, PAYLOAD_LABEL_DELEGATED_KEY, PAYLOAD_LABEL_NOT_BEFORE,
PAYLOAD_LABEL_ROLE, PAYLOAD_LABEL_SEQUENCE, PAYLOAD_LABEL_SUBJECT_KIND,
PAYLOAD_LABEL_SUBJECT_VALUE,
};
use crate::delegation::artifact::{DelegationClaim, DelegationRole, DelegationSubjectKind};
use crate::error::{TrustError, TrustResult};
use crate::keys::identity::is_usable_ed25519_public_key;
pub(crate) struct DecodedFields {
pub(crate) root_public_key: [u8; KEY_LEN],
pub(crate) claim: DelegationClaim,
pub(crate) signature: [u8; 64],
}
fn reject() -> TrustError {
TrustError::DelegationVerification
}
fn parse_value(bytes: &[u8]) -> TrustResult<Value> {
ciborium::de::from_reader(bytes).map_err(|_err| reject())
}
fn fixed_bytes<const N: usize>(value: &Value) -> TrustResult<[u8; N]> {
let Value::Bytes(bytes) = value else {
return Err(reject());
};
bytes.as_slice().try_into().map_err(|_err| reject())
}
fn require_integer(value: &Value, expected: i128) -> TrustResult<()> {
let Value::Integer(int) = value else {
return Err(reject());
};
if i128::from(*int) == expected {
Ok(())
} else {
Err(reject())
}
}
fn unsigned(value: &Value) -> TrustResult<u64> {
let Value::Integer(int) = value else {
return Err(reject());
};
u64::try_from(i128::from(*int)).map_err(|_err| reject())
}
pub(super) fn decode_protected(protected_raw: &[u8]) -> TrustResult<[u8; KEY_LEN]> {
let Value::Map(protected) = parse_value(protected_raw)? else {
return Err(reject());
};
let [(alg_key, alg), (ct_key, ct), (kid_key, kid)] = protected.as_slice() else {
return Err(reject());
};
require_integer(alg_key, i128::from(HEADER_LABEL_ALG))?;
require_integer(alg, i128::from(ALG_EDDSA))?;
require_integer(ct_key, i128::from(HEADER_LABEL_CONTENT_TYPE))?;
let Value::Text(content_type) = ct else {
return Err(reject());
};
if content_type != CONTENT_TYPE {
return Err(reject());
}
require_integer(kid_key, i128::from(HEADER_LABEL_KID))?;
let root_public_key: [u8; KEY_LEN] = fixed_bytes(kid)?;
if !is_usable_ed25519_public_key(&root_public_key) {
return Err(reject());
}
Ok(root_public_key)
}
pub(super) fn decode_payload(payload_raw: &[u8]) -> TrustResult<DelegationClaim> {
let Value::Map(payload) = parse_value(payload_raw)? else {
return Err(reject());
};
let [
(subject_kind_key, subject_kind),
(subject_value_key, subject_value),
(delegated_key_key, delegated_key),
(role_key, role),
(not_before_key, not_before),
(sequence_key, sequence),
] = payload.as_slice()
else {
return Err(reject());
};
require_integer(subject_kind_key, i128::from(PAYLOAD_LABEL_SUBJECT_KIND))?;
let subject_kind =
DelegationSubjectKind::from_wire(unsigned(subject_kind)?).ok_or_else(reject)?;
require_integer(subject_value_key, i128::from(PAYLOAD_LABEL_SUBJECT_VALUE))?;
let Value::Text(subject_value) = subject_value else {
return Err(reject());
};
if subject_value.is_empty() {
return Err(reject());
}
require_integer(delegated_key_key, i128::from(PAYLOAD_LABEL_DELEGATED_KEY))?;
let delegated_public_key: [u8; KEY_LEN] = fixed_bytes(delegated_key)?;
if !is_usable_ed25519_public_key(&delegated_public_key) {
return Err(reject());
}
require_integer(role_key, i128::from(PAYLOAD_LABEL_ROLE))?;
let role = DelegationRole::from_wire(unsigned(role)?).ok_or_else(reject)?;
if !subject_kind.permits(role) {
return Err(reject());
}
require_integer(not_before_key, i128::from(PAYLOAD_LABEL_NOT_BEFORE))?;
let not_before_unix_ms = unsigned(not_before)?;
require_integer(sequence_key, i128::from(PAYLOAD_LABEL_SEQUENCE))?;
let sequence = unsigned(sequence)?;
if sequence > MAX_SEQUENCE {
return Err(reject());
}
Ok(DelegationClaim {
subject_kind,
subject_value: subject_value.clone(),
delegated_public_key,
role,
not_before_unix_ms,
sequence,
})
}
pub(crate) fn decode_fields(bytes: &[u8]) -> TrustResult<DecodedFields> {
let Value::Tag(COSE_SIGN1_TAG, boxed) = parse_value(bytes)? else {
return Err(reject());
};
let Value::Array(items) = *boxed else {
return Err(reject());
};
let [
protected_item,
unprotected_item,
payload_item,
signature_item,
] = items.as_slice()
else {
return Err(reject());
};
let Value::Map(unprotected) = unprotected_item else {
return Err(reject());
};
if !unprotected.is_empty() {
return Err(reject());
}
let signature: [u8; 64] = fixed_bytes(signature_item)?;
let Value::Bytes(protected_raw) = protected_item else {
return Err(reject());
};
let root_public_key = decode_protected(protected_raw)?;
let Value::Bytes(payload_raw) = payload_item else {
return Err(reject());
};
let claim = decode_payload(payload_raw)?;
Ok(DecodedFields {
root_public_key,
claim,
signature,
})
}