pub mod precheck;
use alloc::string::String;
use alloc::vec::Vec;
use minicbor::data::Type;
use minicbor::{Decoder, Encoder};
use crate::alg::{ContentAlgorithm, SignatureAlgorithm};
use crate::claims::{Claims, ProtectedHeaders};
use crate::error::DecodeError;
use crate::hash::RequestHash;
use crate::kdf::{KdfParties, PartyIdentity};
use crate::label::{self, canonical_sort_key};
use crate::types::{ContentType, KeyId, MessageId, ResponseSubject, Subject, UnixTime};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CodecError;
pub const TAG_SIGN1: u64 = 18;
pub const TAG_ENCRYPT: u64 = 96;
pub const NONCE_LEN: usize = 12;
pub const X25519_LEN: usize = 32;
const CLAIM_LABELS: [i64; 6] = [
label::HDR_CWT_CLAIMS,
label::IN_REPLY_TO,
label::REQUEST_HASH,
label::SENDER_KEY_ID,
label::RESPONSE_KEY_ID,
label::RESPONSE_SUBJECT,
];
type EncResult = Result<(), minicbor::encode::Error<core::convert::Infallible>>;
fn encode_with(
f: impl FnOnce(&mut Encoder<&mut Vec<u8>>) -> EncResult,
) -> Result<Vec<u8>, CodecError> {
let mut out = Vec::new();
let mut e = Encoder::new(&mut out);
f(&mut e).map_err(|_| CodecError)?;
Ok(out)
}
fn basil_labels(claims: &Claims) -> Vec<i64> {
let mut labels = Vec::new();
if claims.in_reply_to.is_some() {
labels.push(label::IN_REPLY_TO);
}
if claims.request_hash.is_some() {
labels.push(label::REQUEST_HASH);
}
if claims.sender_key_id.is_some() {
labels.push(label::SENDER_KEY_ID);
}
if claims.response_key_id.is_some() {
labels.push(label::RESPONSE_KEY_ID);
}
if claims.response_subject.is_some() {
labels.push(label::RESPONSE_SUBJECT);
}
labels
}
fn crit_labels(claims: Option<&Claims>, protected_headers: Option<&ProtectedHeaders>) -> Vec<i64> {
let mut crit = alloc::vec![label::HDR_CONTENT_TYPE];
if let Some(c) = claims {
crit.push(label::HDR_CWT_CLAIMS);
crit.extend(basil_labels(c));
}
if protected_headers.is_some_and(|headers| !headers.signer_certificates_jwt.is_empty()) {
crit.push(label::SIGNER_CERTIFICATES_JWT);
}
crit
}
fn write_cwt_map(e: &mut Encoder<&mut Vec<u8>>, claims: &Claims) -> EncResult {
let mut n = 2; n += u64::from(claims.issuer.is_some());
n += u64::from(claims.audience.is_some());
n += u64::from(claims.expires_at.is_some());
e.map(n)?;
if let Some(iss) = &claims.issuer {
e.i64(label::CWT_ISS)?.str(iss.as_str())?;
}
if let Some(aud) = &claims.audience {
e.i64(label::CWT_AUD)?.str(aud.as_str())?;
}
if let Some(UnixTime(exp)) = claims.expires_at {
e.i64(label::CWT_EXP)?.i64(exp)?;
}
e.i64(label::CWT_IAT)?.i64(claims.issued_at.0)?;
e.i64(label::CWT_CTI)?.bytes(claims.message_id.as_bytes())?;
Ok(())
}
fn write_claims_capable_tail(
e: &mut Encoder<&mut Vec<u8>>,
content_type: &ContentType,
claims: Option<&Claims>,
protected_headers: Option<&ProtectedHeaders>,
kid: Option<&KeyId>,
) -> EncResult {
let crit = crit_labels(claims, protected_headers);
e.i64(label::HDR_CRIT)?.array(crit.len() as u64)?;
for l in &crit {
e.i64(*l)?;
}
e.i64(label::HDR_CONTENT_TYPE)?.str(content_type.as_str())?;
if let Some(kid) = kid {
e.i64(label::HDR_KID)?.bytes(kid.as_bytes())?;
}
if let Some(c) = claims {
e.i64(label::HDR_CWT_CLAIMS)?;
write_cwt_map(e, c)?;
if let Some(v) = &c.in_reply_to {
e.i64(label::IN_REPLY_TO)?.bytes(v.as_bytes())?;
}
if let Some(RequestHash(h)) = &c.request_hash {
e.i64(label::REQUEST_HASH)?.bytes(h)?;
}
if let Some(v) = &c.sender_key_id {
e.i64(label::SENDER_KEY_ID)?.bytes(v.as_bytes())?;
}
if let Some(v) = &c.response_key_id {
match v.as_catalog_name() {
Some(name) => e.i64(label::RESPONSE_KEY_ID)?.str(name)?,
None => return Err(minicbor::encode::Error::message("response kid not text")),
};
}
if let Some(v) = &c.response_subject {
e.i64(label::RESPONSE_SUBJECT)?.str(v.as_str())?;
}
}
if let Some(headers) = protected_headers
&& !headers.signer_certificates_jwt.is_empty()
{
e.i64(label::SIGNER_CERTIFICATES_JWT)?
.array(headers.signer_certificates_jwt.len() as u64)?;
for jwt in &headers.signer_certificates_jwt {
e.str(jwt)?;
}
}
Ok(())
}
fn claims_capable_tail_len(
claims: Option<&Claims>,
protected_headers: Option<&ProtectedHeaders>,
) -> u64 {
2 + claims.map_or(0, |c| 1 + basil_labels(c).len() as u64)
+ u64::from(
protected_headers.is_some_and(|headers| !headers.signer_certificates_jwt.is_empty()),
)
}
pub fn encode_sign1_protected_bare_with_headers(
algorithm: SignatureAlgorithm,
kid: &KeyId,
content_type: &ContentType,
claims: Option<&Claims>,
protected_headers: Option<&ProtectedHeaders>,
) -> Result<Vec<u8>, CodecError> {
encode_with(|e| {
e.map(2 + claims_capable_tail_len(claims, protected_headers))?;
e.i64(label::HDR_ALG)?.i64(algorithm.codepoint())?;
write_claims_capable_tail(e, content_type, claims, protected_headers, Some(kid))
})
}
pub fn encode_sign1_protected_sealed_outer(
algorithm: SignatureAlgorithm,
kid: &KeyId,
) -> Result<Vec<u8>, CodecError> {
encode_with(|e| {
e.map(2)?;
e.i64(label::HDR_ALG)?.i64(algorithm.codepoint())?;
e.i64(label::HDR_KID)?.bytes(kid.as_bytes())?;
Ok(())
})
}
pub fn encode_encrypt_protected(
content_algorithm: ContentAlgorithm,
content_type: &ContentType,
claims: Option<&Claims>,
) -> Result<Vec<u8>, CodecError> {
encode_with(|e| {
e.map(1 + claims_capable_tail_len(claims, None))?;
e.i64(label::HDR_ALG)?.i64(content_algorithm.codepoint())?;
write_claims_capable_tail(e, content_type, claims, None, None)
})
}
fn party_labels(parties: &KdfParties) -> Vec<i64> {
let mut labels = Vec::new();
if parties.party_u.as_bytes().is_some() {
labels.push(label::HDR_PARTY_U_IDENTITY);
}
if parties.party_v.as_bytes().is_some() {
labels.push(label::HDR_PARTY_V_IDENTITY);
}
labels
}
pub fn encode_recipient_protected(parties: &KdfParties) -> Result<Vec<u8>, CodecError> {
encode_with(|e| {
let party = party_labels(parties);
let n = 1 + u64::from(!party.is_empty()) + party.len() as u64;
e.map(n)?;
e.i64(label::HDR_ALG)?
.i64(crate::alg::KeyAgreementAlgorithm::EcdhEsHkdf256.codepoint())?;
if !party.is_empty() {
e.i64(label::HDR_CRIT)?.array(party.len() as u64)?;
for l in &party {
e.i64(*l)?;
}
}
if let Some(id) = parties.party_u.as_bytes() {
e.i64(label::HDR_PARTY_U_IDENTITY)?.bytes(id)?;
}
if let Some(id) = parties.party_v.as_bytes() {
e.i64(label::HDR_PARTY_V_IDENTITY)?.bytes(id)?;
}
Ok(())
})
}
pub fn assemble_sign1(
protected: &[u8],
payload: &[u8],
signature: &[u8],
) -> Result<Vec<u8>, CodecError> {
encode_with(|e| {
e.tag(minicbor::data::Tag::new(TAG_SIGN1))?;
e.array(4)?;
e.bytes(protected)?;
e.map(0)?;
e.bytes(payload)?;
e.bytes(signature)?;
Ok(())
})
}
pub struct EncryptAssembly<'a> {
pub protected: &'a [u8],
pub iv: &'a [u8; NONCE_LEN],
pub ciphertext: &'a [u8],
pub recipient_protected: &'a [u8],
pub recipient_kid: &'a KeyId,
pub ephemeral_x: &'a [u8; X25519_LEN],
}
pub fn assemble_encrypt(a: &EncryptAssembly<'_>) -> Result<Vec<u8>, CodecError> {
encode_with(|e| {
e.tag(minicbor::data::Tag::new(TAG_ENCRYPT))?;
e.array(4)?;
e.bytes(a.protected)?;
e.map(1)?;
e.i64(label::HDR_IV)?.bytes(a.iv)?;
e.bytes(a.ciphertext)?;
e.array(1)?;
e.array(3)?;
e.bytes(a.recipient_protected)?;
e.map(2)?;
e.i64(label::HDR_KID)?.bytes(a.recipient_kid.as_bytes())?;
e.i64(label::HDR_EPHEMERAL_KEY)?;
e.map(3)?;
e.i64(1)?.i64(1)?; e.i64(-1)?.i64(4)?; e.i64(-2)?.bytes(a.ephemeral_x)?; e.null()?;
Ok(())
})
}
pub fn sig_structure(
protected: &[u8],
external_aad: &[u8],
payload: &[u8],
) -> Result<Vec<u8>, CodecError> {
encode_with(|e| {
e.array(4)?;
e.str("Signature1")?;
e.bytes(protected)?;
e.bytes(external_aad)?;
e.bytes(payload)?;
Ok(())
})
}
pub fn enc_structure(protected: &[u8], external_aad: &[u8]) -> Result<Vec<u8>, CodecError> {
encode_with(|e| {
e.array(3)?;
e.str("Encrypt")?;
e.bytes(protected)?;
e.bytes(external_aad)?;
Ok(())
})
}
pub fn kdf_context(
content_algorithm: ContentAlgorithm,
parties: &KdfParties,
recipient_protected: &[u8],
) -> Result<Vec<u8>, CodecError> {
encode_with(|e| {
e.array(4)?;
e.i64(content_algorithm.codepoint())?;
for identity in [&parties.party_u, &parties.party_v] {
e.array(3)?;
match identity.as_bytes() {
Some(id) => e.bytes(id)?,
None => e.null()?,
};
e.null()?;
e.null()?;
}
e.array(2)?;
e.u64(8 * content_algorithm.key_len() as u64)?;
e.bytes(recipient_protected)?;
Ok(())
})
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ClaimsExpectation {
Required,
Forbidden,
Optional,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Sign1Layer {
Bare,
SealedOuter,
}
#[derive(Debug, Clone)]
pub struct DecodedSign1 {
pub protected: Vec<u8>,
pub algorithm: SignatureAlgorithm,
pub kid: KeyId,
pub content_type: Option<ContentType>,
pub claims: Option<Claims>,
pub protected_headers: ProtectedHeaders,
pub payload: Vec<u8>,
pub signature: Vec<u8>,
}
#[derive(Debug, Clone)]
pub struct DecodedEncrypt {
pub protected: Vec<u8>,
pub content_algorithm: ContentAlgorithm,
pub content_type: ContentType,
pub claims: Option<Claims>,
pub iv: [u8; NONCE_LEN],
pub ciphertext: Vec<u8>,
pub recipient_protected: Vec<u8>,
pub recipient_kid: KeyId,
pub ephemeral_x: [u8; X25519_LEN],
pub parties: KdfParties,
}
fn dt(d: &Decoder<'_>) -> Result<Type, DecodeError> {
d.datatype().map_err(|_| DecodeError::Malformed)
}
const fn is_int(t: Type) -> bool {
matches!(
t,
Type::U8
| Type::U16
| Type::U32
| Type::U64
| Type::I8
| Type::I16
| Type::I32
| Type::I64
| Type::Int
)
}
fn read_label(d: &mut Decoder<'_>) -> Result<i64, DecodeError> {
let t = dt(d)?;
if matches!(t, Type::String | Type::StringIndef) {
return Err(DecodeError::TextLabel);
}
if !is_int(t) {
return Err(DecodeError::Malformed);
}
d.i64().map_err(|_| DecodeError::Malformed)
}
fn read_bytes(d: &mut Decoder<'_>, l: i64) -> Result<Vec<u8>, DecodeError> {
if dt(d)? != Type::Bytes {
return Err(DecodeError::WrongType { label: l });
}
Ok(d.bytes().map_err(|_| DecodeError::Malformed)?.to_vec())
}
fn read_str(d: &mut Decoder<'_>, l: i64) -> Result<String, DecodeError> {
if dt(d)? != Type::String {
return Err(DecodeError::WrongType { label: l });
}
Ok(String::from(d.str().map_err(|_| DecodeError::Malformed)?))
}
fn read_string_array(d: &mut Decoder<'_>, l: i64) -> Result<Vec<String>, DecodeError> {
let n = read_array_len(d)?;
let mut out = Vec::new();
for _ in 0..n {
out.push(read_str(d, l)?);
}
Ok(out)
}
fn read_int(d: &mut Decoder<'_>, l: i64) -> Result<i64, DecodeError> {
if !is_int(dt(d)?) {
return Err(DecodeError::WrongType { label: l });
}
d.i64().map_err(|_| DecodeError::Malformed)
}
fn read_time(d: &mut Decoder<'_>, l: i64) -> Result<i64, DecodeError> {
let t = dt(d)?;
if matches!(t, Type::F16 | Type::F32 | Type::F64) {
return Err(DecodeError::FractionalTime);
}
if !is_int(t) {
return Err(DecodeError::WrongType { label: l });
}
d.i64().map_err(|_| DecodeError::Malformed)
}
fn read_map_len(d: &mut Decoder<'_>) -> Result<u64, DecodeError> {
if dt(d)? != Type::Map {
return Err(DecodeError::Malformed);
}
d.map()
.map_err(|_| DecodeError::Malformed)?
.ok_or(DecodeError::IndefiniteLength)
}
fn read_array_len(d: &mut Decoder<'_>) -> Result<u64, DecodeError> {
if dt(d)? != Type::Array {
return Err(DecodeError::Malformed);
}
d.array()
.map_err(|_| DecodeError::Malformed)?
.ok_or(DecodeError::IndefiniteLength)
}
fn check_order(prev: Option<i64>, current: i64) -> Result<(), DecodeError> {
prev.map_or(Ok(()), |p| {
match canonical_sort_key(p).cmp(&canonical_sort_key(current)) {
core::cmp::Ordering::Less => Ok(()),
core::cmp::Ordering::Equal => Err(DecodeError::DuplicateLabel),
core::cmp::Ordering::Greater => Err(DecodeError::NonDeterministicEncoding),
}
})
}
#[derive(Debug, Default)]
struct ClaimsParts {
issuer: Option<Subject>,
audience: Option<Subject>,
expires_at: Option<UnixTime>,
issued_at: Option<UnixTime>,
message_id: Option<MessageId>,
in_reply_to: Option<MessageId>,
request_hash: Option<RequestHash>,
sender_key_id: Option<KeyId>,
response_key_id: Option<KeyId>,
response_subject: Option<ResponseSubject>,
cwt_present: bool,
basil_present: bool,
}
impl ClaimsParts {
fn into_claims(self) -> Result<Option<Claims>, DecodeError> {
if !self.cwt_present {
if self.basil_present {
return Err(DecodeError::MissingHeader {
label: label::HDR_CWT_CLAIMS,
});
}
return Ok(None);
}
let issued_at = self.issued_at.ok_or(DecodeError::MissingClaim {
claim: label::CWT_IAT,
})?;
let message_id = self.message_id.ok_or(DecodeError::MissingClaim {
claim: label::CWT_CTI,
})?;
Ok(Some(Claims {
issuer: self.issuer,
audience: self.audience,
expires_at: self.expires_at,
issued_at,
message_id,
sender_key_id: self.sender_key_id,
response_key_id: self.response_key_id,
response_subject: self.response_subject,
in_reply_to: self.in_reply_to,
request_hash: self.request_hash,
}))
}
}
fn parse_cwt_map(d: &mut Decoder<'_>, parts: &mut ClaimsParts) -> Result<(), DecodeError> {
let n = read_map_len(d)?;
let mut prev = None;
for _ in 0..n {
let key = read_label(d)?;
check_order(prev, key)?;
prev = Some(key);
match key {
label::CWT_ISS => {
parts.issuer = Some(Subject::new(read_str(d, key)?)?);
}
label::CWT_AUD => {
parts.audience = Some(Subject::new(read_str(d, key)?)?);
}
label::CWT_EXP => {
parts.expires_at = Some(UnixTime(read_time(d, key)?));
}
label::CWT_IAT => {
parts.issued_at = Some(UnixTime(read_time(d, key)?));
}
label::CWT_CTI => {
parts.message_id = Some(MessageId::from_bytes(read_bytes(d, key)?)?);
}
other => return Err(DecodeError::UnknownClaim { claim: other }),
}
}
parts.cwt_present = true;
Ok(())
}
fn parse_basil_label(
d: &mut Decoder<'_>,
l: i64,
parts: &mut ClaimsParts,
) -> Result<bool, DecodeError> {
match l {
label::IN_REPLY_TO => {
parts.in_reply_to = Some(MessageId::from_bytes(read_bytes(d, l)?)?);
}
label::REQUEST_HASH => {
let raw = read_bytes(d, l)?;
let arr: [u8; 32] =
raw.as_slice()
.try_into()
.map_err(|_| DecodeError::InvalidLength {
label: l,
expected: 32,
actual: raw.len(),
})?;
parts.request_hash = Some(RequestHash(arr));
}
label::SENDER_KEY_ID => {
parts.sender_key_id = Some(KeyId::from_bytes(read_bytes(d, l)?)?);
}
label::RESPONSE_KEY_ID => {
parts.response_key_id = Some(KeyId::from_text(&read_str(d, l)?)?);
}
label::RESPONSE_SUBJECT => {
parts.response_subject = Some(ResponseSubject::new(read_str(d, l)?)?);
}
_ => return Ok(false),
}
parts.basil_present = true;
Ok(true)
}
fn parse_crit(d: &mut Decoder<'_>) -> Result<Vec<i64>, DecodeError> {
let n = read_array_len(d)?;
if n == 0 {
return Err(DecodeError::WrongType {
label: label::HDR_CRIT,
});
}
let mut out = Vec::new();
for _ in 0..n {
out.push(read_label(d)?);
}
Ok(out)
}
fn check_crit(actual: Option<&Vec<i64>>, expected: &[i64]) -> Result<(), DecodeError> {
let Some(actual) = actual else {
return Err(DecodeError::CritMissing);
};
if actual.as_slice() == expected {
return Ok(());
}
for l in actual {
if !expected.contains(l) {
return Err(DecodeError::CritUnexpected { label: *l });
}
}
for l in expected {
if !actual.contains(l) {
return Err(DecodeError::CritIncomplete { label: *l });
}
}
Err(DecodeError::NonDeterministicEncoding)
}
#[derive(Debug)]
struct ClaimsCapableHeader {
alg: i64,
crit: Option<Vec<i64>>,
content_type: Option<ContentType>,
kid: Option<KeyId>,
claims: Option<Claims>,
protected_headers: ProtectedHeaders,
}
fn parse_claims_capable_header(
bytes: &[u8],
allow_kid: bool,
) -> Result<ClaimsCapableHeader, DecodeError> {
if bytes.is_empty() {
return Err(DecodeError::MissingHeader {
label: label::HDR_ALG,
});
}
precheck::scan(bytes)?;
let mut d = Decoder::new(bytes);
let n = read_map_len(&mut d)?;
let mut prev = None;
let mut alg = None;
let mut crit = None;
let mut content_type = None;
let mut kid = None;
let mut parts = ClaimsParts::default();
let mut protected_headers = ProtectedHeaders::default();
for _ in 0..n {
let l = read_label(&mut d)?;
check_order(prev, l)?;
prev = Some(l);
match l {
label::HDR_ALG => alg = Some(read_int(&mut d, l)?),
label::HDR_CRIT => crit = Some(parse_crit(&mut d)?),
label::HDR_CONTENT_TYPE => {
content_type = Some(ContentType::new(read_str(&mut d, l)?)?);
}
label::HDR_KID if allow_kid => {
kid = Some(KeyId::from_bytes(read_bytes(&mut d, l)?)?);
}
label::HDR_CWT_CLAIMS => parse_cwt_map(&mut d, &mut parts)?,
label::SIGNER_CERTIFICATES_JWT if allow_kid => {
protected_headers.signer_certificates_jwt = read_string_array(&mut d, l)?;
}
other => {
if !parse_basil_label(&mut d, other, &mut parts)? {
return Err(DecodeError::UnknownLabel { label: other });
}
}
}
}
let alg = alg.ok_or(DecodeError::MissingHeader {
label: label::HDR_ALG,
})?;
let claims = parts.into_claims()?;
Ok(ClaimsCapableHeader {
alg,
crit,
content_type,
kid,
claims,
protected_headers,
})
}
fn finish_claims_capable(
h: &ClaimsCapableHeader,
expectation: ClaimsExpectation,
) -> Result<(), DecodeError> {
if h.content_type.is_none() {
return Err(DecodeError::MissingHeader {
label: label::HDR_CONTENT_TYPE,
});
}
match (expectation, &h.claims) {
(ClaimsExpectation::Required, None) => {
return Err(DecodeError::MissingHeader {
label: label::HDR_CWT_CLAIMS,
});
}
(ClaimsExpectation::Forbidden, Some(_)) => {
return Err(DecodeError::UnknownLabel {
label: label::HDR_CWT_CLAIMS,
});
}
_ => {}
}
check_crit(
h.crit.as_ref(),
&crit_labels(h.claims.as_ref(), Some(&h.protected_headers)),
)
}
fn parse_unprotected<'b>(
d: &mut Decoder<'b>,
mut on_entry: impl FnMut(&mut Decoder<'b>, i64) -> Result<bool, DecodeError>,
) -> Result<(), DecodeError> {
let n = read_map_len(d)?;
let mut prev = None;
for _ in 0..n {
let l = read_label(d)?;
check_order(prev, l)?;
prev = Some(l);
if CLAIM_LABELS.contains(&l) {
return Err(DecodeError::ClaimsInUnprotected);
}
if !on_entry(d, l)? {
return Err(DecodeError::UnknownLabel { label: l });
}
}
Ok(())
}
const fn check_tag(scan: precheck::Scan, expected: u64) -> Result<(), DecodeError> {
match scan.top_tag {
None => Err(DecodeError::NotTagged),
Some(t) if t == expected => Ok(()),
Some(t) => Err(DecodeError::WrongTag {
expected,
actual: t,
}),
}
}
pub fn decode_sign1_strict(bytes: &[u8], layer: Sign1Layer) -> Result<DecodedSign1, DecodeError> {
check_tag(precheck::scan(bytes)?, TAG_SIGN1)?;
let mut d = Decoder::new(bytes);
d.tag().map_err(|_| DecodeError::Malformed)?;
if read_array_len(&mut d)? != 4 {
return Err(DecodeError::Malformed);
}
if dt(&d)? != Type::Bytes {
return Err(DecodeError::Malformed);
}
let protected = d.bytes().map_err(|_| DecodeError::Malformed)?.to_vec();
parse_unprotected(&mut d, |_, _| Ok(false))?;
let payload = match dt(&d)? {
Type::Bytes => d.bytes().map_err(|_| DecodeError::Malformed)?.to_vec(),
Type::Null => return Err(DecodeError::MissingPayload),
_ => return Err(DecodeError::Malformed),
};
let signature = read_bytes(&mut d, 0).map_err(|_| DecodeError::Malformed)?;
if signature.is_empty() {
return Err(DecodeError::Malformed);
}
let (algorithm, kid, content_type, claims, protected_headers, rebuilt_protected) = match layer {
Sign1Layer::Bare => {
let h = parse_claims_capable_header(&protected, true)?;
let algorithm = SignatureAlgorithm::from_codepoint(h.alg)
.ok_or(DecodeError::UnknownAlgorithm { alg: h.alg })?;
finish_claims_capable(&h, ClaimsExpectation::Optional)?;
let kid = h.kid.ok_or(DecodeError::MissingHeader {
label: label::HDR_KID,
})?;
let Some(content_type) = h.content_type else {
return Err(DecodeError::MissingHeader {
label: label::HDR_CONTENT_TYPE,
});
};
let rebuilt = encode_sign1_protected_bare_with_headers(
algorithm,
&kid,
&content_type,
h.claims.as_ref(),
Some(&h.protected_headers),
)
.map_err(|CodecError| DecodeError::NonDeterministicEncoding)?;
(
algorithm,
kid,
Some(content_type),
h.claims,
h.protected_headers,
rebuilt,
)
}
Sign1Layer::SealedOuter => {
let (algorithm, kid) = parse_sealed_outer_protected(&protected)?;
let rebuilt = encode_sign1_protected_sealed_outer(algorithm, &kid)
.map_err(|CodecError| DecodeError::NonDeterministicEncoding)?;
(
algorithm,
kid,
None,
None,
ProtectedHeaders::default(),
rebuilt,
)
}
};
let rebuilt = assemble_sign1(&rebuilt_protected, &payload, &signature)
.map_err(|CodecError| DecodeError::NonDeterministicEncoding)?;
if rebuilt != bytes {
return Err(DecodeError::NonDeterministicEncoding);
}
Ok(DecodedSign1 {
protected,
algorithm,
kid,
content_type,
claims,
protected_headers,
payload,
signature,
})
}
fn parse_sealed_outer_protected(bytes: &[u8]) -> Result<(SignatureAlgorithm, KeyId), DecodeError> {
if bytes.is_empty() {
return Err(DecodeError::MissingHeader {
label: label::HDR_ALG,
});
}
precheck::scan(bytes)?;
let mut d = Decoder::new(bytes);
let n = read_map_len(&mut d)?;
let mut prev = None;
let mut alg = None;
let mut kid = None;
for _ in 0..n {
let l = read_label(&mut d)?;
check_order(prev, l)?;
prev = Some(l);
match l {
label::HDR_ALG => alg = Some(read_int(&mut d, l)?),
label::HDR_KID => kid = Some(KeyId::from_bytes(read_bytes(&mut d, l)?)?),
other => return Err(DecodeError::UnknownLabel { label: other }),
}
}
let alg = alg.ok_or(DecodeError::MissingHeader {
label: label::HDR_ALG,
})?;
let algorithm =
SignatureAlgorithm::from_codepoint(alg).ok_or(DecodeError::UnknownAlgorithm { alg })?;
let kid = kid.ok_or(DecodeError::MissingHeader {
label: label::HDR_KID,
})?;
Ok((algorithm, kid))
}
fn parse_recipient_protected(bytes: &[u8]) -> Result<KdfParties, DecodeError> {
if bytes.is_empty() {
return Err(DecodeError::MissingHeader {
label: label::HDR_ALG,
});
}
precheck::scan(bytes)?;
let mut d = Decoder::new(bytes);
let n = read_map_len(&mut d)?;
let mut prev = None;
let mut alg = None;
let mut crit = None;
let mut party_u = PartyIdentity::nil();
let mut party_v = PartyIdentity::nil();
for _ in 0..n {
let l = read_label(&mut d)?;
check_order(prev, l)?;
prev = Some(l);
match l {
label::HDR_ALG => alg = Some(read_int(&mut d, l)?),
label::HDR_CRIT => crit = Some(parse_crit(&mut d)?),
label::HDR_PARTY_U_IDENTITY => {
party_u = PartyIdentity::from_bytes(read_bytes(&mut d, l)?)?;
}
label::HDR_PARTY_V_IDENTITY => {
party_v = PartyIdentity::from_bytes(read_bytes(&mut d, l)?)?;
}
other => return Err(DecodeError::UnknownLabel { label: other }),
}
}
let alg = alg.ok_or(DecodeError::MissingHeader {
label: label::HDR_ALG,
})?;
if crate::alg::KeyAgreementAlgorithm::from_codepoint(alg).is_none() {
return Err(DecodeError::UnknownAlgorithm { alg });
}
let parties = KdfParties { party_u, party_v };
let expected = party_labels(&parties);
if expected.is_empty() {
if let Some(crit) = crit {
let l = crit.first().copied().unwrap_or(label::HDR_CRIT);
return Err(DecodeError::CritUnexpected { label: l });
}
} else {
check_crit(crit.as_ref(), &expected)?;
}
Ok(parties)
}
fn parse_ephemeral_key(d: &mut Decoder<'_>) -> Result<[u8; X25519_LEN], DecodeError> {
if dt(d)? != Type::Map {
return Err(DecodeError::EphemeralKeyShape);
}
let n = d
.map()
.map_err(|_| DecodeError::Malformed)?
.ok_or(DecodeError::IndefiniteLength)?;
if n != 3 {
return Err(DecodeError::EphemeralKeyShape);
}
let mut prev = None;
let mut x: Option<[u8; X25519_LEN]> = None;
let mut kty_ok = false;
let mut crv_ok = false;
for _ in 0..n {
let l = read_label(d)?;
check_order(prev, l)?;
prev = Some(l);
match l {
1 => kty_ok = read_int(d, l)? == 1,
-1 => crv_ok = read_int(d, l)? == 4,
-2 => {
let raw = read_bytes(d, l)?;
x = Some(
raw.as_slice()
.try_into()
.map_err(|_| DecodeError::EphemeralKeyShape)?,
);
}
_ => return Err(DecodeError::EphemeralKeyShape),
}
}
if !kty_ok || !crv_ok {
return Err(DecodeError::EphemeralKeyShape);
}
x.ok_or(DecodeError::EphemeralKeyShape)
}
#[allow(clippy::too_many_lines)]
pub fn decode_encrypt_strict(
bytes: &[u8],
expectation: ClaimsExpectation,
) -> Result<DecodedEncrypt, DecodeError> {
check_tag(precheck::scan(bytes)?, TAG_ENCRYPT)?;
let mut d = Decoder::new(bytes);
d.tag().map_err(|_| DecodeError::Malformed)?;
if read_array_len(&mut d)? != 4 {
return Err(DecodeError::Malformed);
}
if dt(&d)? != Type::Bytes {
return Err(DecodeError::Malformed);
}
let protected = d.bytes().map_err(|_| DecodeError::Malformed)?.to_vec();
let mut iv: Option<[u8; NONCE_LEN]> = None;
parse_unprotected(&mut d, |d, l| {
if l != label::HDR_IV {
return Ok(false);
}
let raw = read_bytes(d, l)?;
let arr: [u8; NONCE_LEN] =
raw.as_slice()
.try_into()
.map_err(|_| DecodeError::InvalidLength {
label: l,
expected: NONCE_LEN,
actual: raw.len(),
})?;
iv = Some(arr);
Ok(true)
})?;
let iv = iv.ok_or(DecodeError::MissingHeader {
label: label::HDR_IV,
})?;
let ciphertext = match dt(&d)? {
Type::Bytes => d.bytes().map_err(|_| DecodeError::Malformed)?.to_vec(),
Type::Null => return Err(DecodeError::MissingPayload),
_ => return Err(DecodeError::Malformed),
};
let recipient_count = read_array_len(&mut d)?;
if recipient_count != 1 {
return Err(DecodeError::RecipientCount {
count: usize::try_from(recipient_count).unwrap_or(usize::MAX),
});
}
let recipient_len = read_array_len(&mut d)?;
if recipient_len == 4 {
return Err(DecodeError::NestedRecipients);
}
if recipient_len != 3 {
return Err(DecodeError::Malformed);
}
if dt(&d)? != Type::Bytes {
return Err(DecodeError::Malformed);
}
let recipient_protected = d.bytes().map_err(|_| DecodeError::Malformed)?.to_vec();
let mut recipient_kid: Option<KeyId> = None;
let mut ephemeral_x: Option<[u8; X25519_LEN]> = None;
parse_unprotected(&mut d, |d, l| match l {
label::HDR_KID => {
recipient_kid = Some(KeyId::from_bytes(read_bytes(d, l)?)?);
Ok(true)
}
label::HDR_EPHEMERAL_KEY => {
ephemeral_x = Some(parse_ephemeral_key(d)?);
Ok(true)
}
_ => Ok(false),
})?;
let recipient_kid = recipient_kid.ok_or(DecodeError::MissingHeader {
label: label::HDR_KID,
})?;
let ephemeral_x = ephemeral_x.ok_or(DecodeError::MissingHeader {
label: label::HDR_EPHEMERAL_KEY,
})?;
match dt(&d)? {
Type::Null => d.null().map_err(|_| DecodeError::Malformed)?,
_ => return Err(DecodeError::RecipientCiphertextPresent),
}
let h = parse_claims_capable_header(&protected, false)?;
let Some(content_algorithm) = ContentAlgorithm::from_codepoint(h.alg) else {
return Err(DecodeError::UnknownAlgorithm { alg: h.alg });
};
finish_claims_capable(&h, expectation)?;
let Some(content_type) = h.content_type.clone() else {
return Err(DecodeError::MissingHeader {
label: label::HDR_CONTENT_TYPE,
});
};
let parties = parse_recipient_protected(&recipient_protected)?;
let rebuilt_protected =
encode_encrypt_protected(content_algorithm, &content_type, h.claims.as_ref())
.map_err(|CodecError| DecodeError::NonDeterministicEncoding)?;
let rebuilt_recipient_protected = encode_recipient_protected(&parties)
.map_err(|CodecError| DecodeError::NonDeterministicEncoding)?;
let rebuilt = assemble_encrypt(&EncryptAssembly {
protected: &rebuilt_protected,
iv: &iv,
ciphertext: &ciphertext,
recipient_protected: &rebuilt_recipient_protected,
recipient_kid: &recipient_kid,
ephemeral_x: &ephemeral_x,
})
.map_err(|CodecError| DecodeError::NonDeterministicEncoding)?;
if rebuilt != bytes {
return Err(DecodeError::NonDeterministicEncoding);
}
Ok(DecodedEncrypt {
protected,
content_algorithm,
content_type,
claims: h.claims,
iv,
ciphertext,
recipient_protected,
recipient_kid,
ephemeral_x,
parties,
})
}