#![forbid(unsafe_code)]
use crate::attestation::{AttestationError, ProductId, VendorId};
const CSA_TEST_CD_SIGNING_ROOT_PEM: &[u8] =
include_bytes!("./csa_cd_signing_roots/csa-test-cd-signing-root.pem");
const CHIP_TEST_CD_SIGNING_CERT_DER: &[u8] =
include_bytes!("./csa_cd_signing_roots/Chip-Test-CD-Signing-Cert.der");
const CSA_CD_SIGNING_KEY_001_DER: &[u8] =
include_bytes!("./csa_cd_signing_roots/CSA-CD-Signing-Key-001.der");
#[derive(Debug, Clone)]
pub struct CdSigningRoots {
public_keys: Vec<Vec<u8>>,
}
impl CdSigningRoots {
#[must_use]
pub fn with_example_device_roots() -> Self {
let mut public_keys = Vec::with_capacity(3);
if let Ok(pk) = parse_pem_public_key(CSA_TEST_CD_SIGNING_ROOT_PEM) {
public_keys.push(pk);
}
for der in [CHIP_TEST_CD_SIGNING_CERT_DER, CSA_CD_SIGNING_KEY_001_DER] {
if let Some(pk) = cert_der_public_key(der) {
public_keys.push(pk);
}
}
Self { public_keys }
}
pub fn from_pem(pems: &[&[u8]]) -> Result<Self, AttestationError> {
let mut public_keys = Vec::with_capacity(pems.len());
for raw in pems {
let pk = parse_pem_public_key(raw)?;
public_keys.push(pk);
}
Ok(Self { public_keys })
}
pub fn from_cert_der(certs: &[&[u8]]) -> Result<Self, AttestationError> {
let mut public_keys = Vec::with_capacity(certs.len());
for der in certs {
let pk = cert_der_public_key(der)
.ok_or(AttestationError::CertificationDeclarationMalformed)?;
public_keys.push(pk);
}
Ok(Self { public_keys })
}
#[must_use]
pub fn len(&self) -> usize {
self.public_keys.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.public_keys.is_empty()
}
fn keys(&self) -> &[Vec<u8>] {
&self.public_keys
}
}
#[allow(
clippy::similar_names,
reason = "expected_vid/expected_pid mirror the crate-wide VendorId/ProductId vocabulary"
)]
pub fn verify_certification_declaration(
cd_bytes: &[u8],
expected_vid: VendorId,
expected_pid: ProductId,
trust: &CdSigningRoots,
) -> Result<(), AttestationError> {
verify_certification_declaration_with_paa(cd_bytes, expected_vid, expected_pid, trust, None)
}
#[allow(
clippy::similar_names,
clippy::too_many_lines,
reason = "the `expected_vid`/`expected_pid` pair mirrors the public-API \
vocabulary used elsewhere in this crate (VendorId/ProductId); \
renaming would obscure intent at the call site. The function is a \
single linear six-step CMS+TLV verification, clearer inline than split."
)]
pub fn verify_certification_declaration_with_paa(
cd_bytes: &[u8],
expected_vid: VendorId,
expected_pid: ProductId,
trust: &CdSigningRoots,
device_paa_skid: Option<&[u8]>,
) -> Result<(), AttestationError> {
use cms::content_info::ContentInfo;
use cms::signed_data::SignedData;
use der::asn1::OctetString;
use der::{Decode, DecodeValue, Encode, Header, SliceReader, Tag as DerTag};
let content_info = ContentInfo::from_der(cd_bytes)
.map_err(|_| AttestationError::CertificationDeclarationMalformed)?;
let signed_data_der = content_info
.content
.to_der()
.map_err(|_| AttestationError::CertificationDeclarationMalformed)?;
let signed_data = SignedData::from_der(&signed_data_der)
.map_err(|_| AttestationError::CertificationDeclarationMalformed)?;
if signed_data.signer_infos.0.len() != 1 {
return Err(AttestationError::CertificationDeclarationMalformed);
}
let signer = signed_data
.signer_infos
.0
.iter()
.next()
.ok_or(AttestationError::CertificationDeclarationMalformed)?;
let econtent = signed_data
.encap_content_info
.econtent
.as_ref()
.ok_or(AttestationError::CertificationDeclarationMalformed)?;
let econtent_der = econtent
.to_der()
.map_err(|_| AttestationError::CertificationDeclarationMalformed)?;
let mut reader = SliceReader::new(&econtent_der)
.map_err(|_| AttestationError::CertificationDeclarationMalformed)?;
let header = Header::decode(&mut reader)
.map_err(|_| AttestationError::CertificationDeclarationMalformed)?;
if header.tag != DerTag::OctetString {
return Err(AttestationError::CertificationDeclarationMalformed);
}
let octet_string = OctetString::decode_value(&mut reader, header)
.map_err(|_| AttestationError::CertificationDeclarationMalformed)?;
let content_bytes = octet_string.as_bytes().to_vec();
let sig = signer.signature.as_bytes();
let mut accepted = false;
for key in trust.keys() {
if verify_ecdsa_p256_sha256(key, &content_bytes, sig).is_ok() {
accepted = true;
break;
}
}
if !accepted {
return Err(AttestationError::CertificationDeclarationSignatureInvalid);
}
let parsed = parse_inner_cd_tlv(&content_bytes)?;
if let (Some(origin_vid), Some(origin_pid)) =
(parsed.dac_origin_vendor_id, parsed.dac_origin_product_id)
{
if origin_vid != expected_vid {
return Err(AttestationError::CertificationDeclarationVidMismatch {
declared: origin_vid,
expected: expected_vid,
});
}
if origin_pid != expected_pid {
return Err(AttestationError::CertificationDeclarationPidMismatch(
expected_pid,
));
}
} else {
if parsed.vendor_id != expected_vid {
return Err(AttestationError::CertificationDeclarationVidMismatch {
declared: parsed.vendor_id,
expected: expected_vid,
});
}
if !parsed.product_ids.contains(&expected_pid) {
return Err(AttestationError::CertificationDeclarationPidMismatch(
expected_pid,
));
}
}
if !paa_is_authorized(parsed.authorized_paa_list.as_deref(), device_paa_skid) {
return Err(AttestationError::CertificationDeclarationPaaNotAuthorized);
}
Ok(())
}
#[derive(Debug)]
struct ParsedCd {
vendor_id: VendorId,
product_ids: Vec<ProductId>,
dac_origin_vendor_id: Option<VendorId>,
dac_origin_product_id: Option<ProductId>,
authorized_paa_list: Option<Vec<[u8; 20]>>,
}
#[allow(
clippy::too_many_lines,
reason = "one linear tag-dispatch loop over the CD's context-tagged fields; \
splitting the per-tag arms into helpers would scatter the decode logic."
)]
fn parse_inner_cd_tlv(tlv: &[u8]) -> Result<ParsedCd, AttestationError> {
use matter_codec::{ContainerKind, Element, Tag, TlvReader, Value};
let mut reader = TlvReader::new(tlv);
match reader
.next()
.map_err(|_| AttestationError::CertificationDeclarationTlvMalformed)?
{
Some(Element::ContainerStart {
tag: Tag::Anonymous,
kind: ContainerKind::Structure,
}) => {}
_ => return Err(AttestationError::CertificationDeclarationTlvMalformed),
}
let mut vid: Option<VendorId> = None;
let mut pids: Vec<ProductId> = Vec::new();
let mut origin_vendor: Option<VendorId> = None;
let mut origin_product: Option<ProductId> = None;
let mut authorized_paa_list: Option<Vec<[u8; 20]>> = None;
loop {
match reader
.next()
.map_err(|_| AttestationError::CertificationDeclarationTlvMalformed)?
{
None => return Err(AttestationError::CertificationDeclarationTlvMalformed),
Some(Element::ContainerEnd) => break,
Some(Element::Scalar {
tag: Tag::Context(1),
value: Value::Uint(v),
}) => {
if vid.is_some() {
return Err(AttestationError::CertificationDeclarationTlvMalformed);
}
let v16 = u16::try_from(v)
.map_err(|_| AttestationError::CertificationDeclarationTlvMalformed)?;
vid = Some(VendorId::new(v16));
}
Some(Element::ContainerStart {
tag: Tag::Context(2),
kind: ContainerKind::Array,
}) => {
if !pids.is_empty() {
return Err(AttestationError::CertificationDeclarationTlvMalformed);
}
loop {
match reader
.next()
.map_err(|_| AttestationError::CertificationDeclarationTlvMalformed)?
{
None => return Err(AttestationError::CertificationDeclarationTlvMalformed),
Some(Element::ContainerEnd) => break,
Some(Element::Scalar {
tag: Tag::Anonymous,
value: Value::Uint(p),
}) => {
let p16 = u16::try_from(p).map_err(|_| {
AttestationError::CertificationDeclarationTlvMalformed
})?;
pids.push(ProductId::new(p16));
}
Some(_) => {}
}
}
}
Some(Element::Scalar {
tag: Tag::Context(9),
value: Value::Uint(v),
}) => {
if origin_vendor.is_some() {
return Err(AttestationError::CertificationDeclarationTlvMalformed);
}
let v16 = u16::try_from(v)
.map_err(|_| AttestationError::CertificationDeclarationTlvMalformed)?;
origin_vendor = Some(VendorId::new(v16));
}
Some(Element::Scalar {
tag: Tag::Context(10),
value: Value::Uint(p),
}) => {
if origin_product.is_some() {
return Err(AttestationError::CertificationDeclarationTlvMalformed);
}
let p16 = u16::try_from(p)
.map_err(|_| AttestationError::CertificationDeclarationTlvMalformed)?;
origin_product = Some(ProductId::new(p16));
}
Some(Element::ContainerStart {
tag: Tag::Context(11),
kind: ContainerKind::Array,
}) => {
if authorized_paa_list.is_some() {
return Err(AttestationError::CertificationDeclarationTlvMalformed);
}
let mut list: Vec<[u8; 20]> = Vec::new();
loop {
match reader
.next()
.map_err(|_| AttestationError::CertificationDeclarationTlvMalformed)?
{
None => return Err(AttestationError::CertificationDeclarationTlvMalformed),
Some(Element::ContainerEnd) => break,
Some(Element::Scalar {
tag: Tag::Anonymous,
value: Value::Bytes(b),
}) => {
let skid: [u8; 20] = b.as_slice().try_into().map_err(|_| {
AttestationError::CertificationDeclarationTlvMalformed
})?;
list.push(skid);
}
Some(_) => {
return Err(AttestationError::CertificationDeclarationTlvMalformed)
}
}
}
authorized_paa_list = Some(list);
}
Some(_) => {}
}
}
let vendor_id = vid.ok_or(AttestationError::CertificationDeclarationTlvMalformed)?;
if pids.is_empty() {
return Err(AttestationError::CertificationDeclarationTlvMalformed);
}
Ok(ParsedCd {
vendor_id,
product_ids: pids,
dac_origin_vendor_id: origin_vendor,
dac_origin_product_id: origin_product,
authorized_paa_list,
})
}
fn paa_is_authorized(list: Option<&[[u8; 20]]>, device_paa_skid: Option<&[u8]>) -> bool {
match list {
None => true,
Some(entries) => match device_paa_skid {
Some(skid) => entries.iter().any(|e| e.as_slice() == skid),
None => false,
},
}
}
fn cert_der_public_key(der: &[u8]) -> Option<Vec<u8>> {
use x509_parser::prelude::{FromDer, X509Certificate};
let (_, cert) = X509Certificate::from_der(der).ok()?;
let pk = cert.public_key().subject_public_key.data.as_ref().to_vec();
(pk.len() == 65 && pk[0] == 0x04).then_some(pk)
}
fn parse_pem_public_key(pem: &[u8]) -> Result<Vec<u8>, AttestationError> {
use base64::Engine;
const HEADER: &str = "-----BEGIN PUBLIC KEY-----";
const FOOTER: &str = "-----END PUBLIC KEY-----";
let pem_str = std::str::from_utf8(pem)
.map_err(|_| AttestationError::CertificationDeclarationMalformed)?;
let header_start = pem_str
.find(HEADER)
.ok_or(AttestationError::CertificationDeclarationMalformed)?;
let body_start = header_start + HEADER.len();
let footer_start = pem_str
.find(FOOTER)
.ok_or(AttestationError::CertificationDeclarationMalformed)?;
if footer_start <= body_start {
return Err(AttestationError::CertificationDeclarationMalformed);
}
let body: String = pem_str[body_start..footer_start]
.chars()
.filter(|c| !c.is_whitespace())
.collect();
let der = base64::engine::general_purpose::STANDARD
.decode(body.as_bytes())
.map_err(|_| AttestationError::CertificationDeclarationMalformed)?;
if der.len() < 65 {
return Err(AttestationError::CertificationDeclarationMalformed);
}
let point = &der[der.len() - 65..];
if point[0] != 0x04 {
return Err(AttestationError::CertificationDeclarationMalformed);
}
Ok(point.to_vec())
}
fn verify_ecdsa_p256_sha256(
public_key: &[u8],
msg: &[u8],
sig: &[u8],
) -> Result<(), AttestationError> {
use ring::signature::{UnparsedPublicKey, ECDSA_P256_SHA256_ASN1, ECDSA_P256_SHA256_FIXED};
let asn1 = UnparsedPublicKey::new(&ECDSA_P256_SHA256_ASN1, public_key);
if asn1.verify(msg, sig).is_ok() {
return Ok(());
}
if sig.len() == 64 {
let fixed = UnparsedPublicKey::new(&ECDSA_P256_SHA256_FIXED, public_key);
if fixed.verify(msg, sig).is_ok() {
return Ok(());
}
}
Err(AttestationError::CertificationDeclarationSignatureInvalid)
}
#[cfg(test)]
mod tests {
#![allow(clippy::similar_names)]
use super::*;
#[test]
#[allow(clippy::unwrap_used, clippy::expect_used)] fn verify_accepts_der_encoded_ecdsa_signature() {
use ring::rand::SystemRandom;
use ring::signature::{EcdsaKeyPair, KeyPair, ECDSA_P256_SHA256_ASN1_SIGNING};
let rng = SystemRandom::new();
let pkcs8 = EcdsaKeyPair::generate_pkcs8(&ECDSA_P256_SHA256_ASN1_SIGNING, &rng).unwrap();
let kp = EcdsaKeyPair::from_pkcs8(&ECDSA_P256_SHA256_ASN1_SIGNING, pkcs8.as_ref(), &rng)
.unwrap();
let msg = b"certification declaration content";
let sig = kp.sign(&rng, msg).unwrap(); let pk = kp.public_key().as_ref();
verify_ecdsa_p256_sha256(pk, msg, sig.as_ref())
.expect("DER-encoded CMS signature must verify");
}
#[test]
fn with_example_device_roots_loads_bundled_roots() {
let trust = CdSigningRoots::with_example_device_roots();
assert_eq!(trust.len(), 3);
assert!(!trust.is_empty());
}
#[test]
#[allow(clippy::expect_used)] fn parse_pem_public_key_extracts_65_byte_sec1_point() {
const PEM: &[u8] = include_bytes!("./csa_cd_signing_roots/csa-test-cd-signing-root.pem");
let key = parse_pem_public_key(PEM).expect("happy path parses");
assert_eq!(key.len(), 65, "SEC1 uncompressed P-256 point");
assert_eq!(key[0], 0x04, "uncompressed-point marker byte");
}
#[test]
#[allow(clippy::expect_used)] fn parse_pem_public_key_rejects_garbage() {
let err = parse_pem_public_key(b"not a PEM").expect_err("garbage rejected");
assert!(matches!(
err,
AttestationError::CertificationDeclarationMalformed
));
}
#[test]
#[allow(clippy::unwrap_used)] fn from_pem_empty_input_yields_empty_trust_store() {
let trust = CdSigningRoots::from_pem(&[]).unwrap();
assert!(trust.is_empty());
assert_eq!(trust.len(), 0);
}
#[test]
#[allow(clippy::unwrap_used, clippy::expect_used)] fn from_cert_der_extracts_p256_pubkey_from_x509_cert() {
use matter_cert::test_support::{build_x509_der, TestCertFields};
use matter_cert::{
DistinguishedName, DnAttribute, Extensions, MatterTime, PublicKey, Signature,
};
use ring::rand::SystemRandom;
use ring::signature::{EcdsaKeyPair, KeyPair, ECDSA_P256_SHA256_ASN1_SIGNING};
let rng = SystemRandom::new();
let pkcs8 = EcdsaKeyPair::generate_pkcs8(&ECDSA_P256_SHA256_ASN1_SIGNING, &rng).unwrap();
let kp = EcdsaKeyPair::from_pkcs8(&ECDSA_P256_SHA256_ASN1_SIGNING, pkcs8.as_ref(), &rng)
.unwrap();
let expected = kp.public_key().as_ref().to_vec(); let pk = PublicKey::from_slice(&expected).unwrap();
let dn = DistinguishedName::new(vec![DnAttribute::CommonName(
"Test CD Signing Key (synthetic)".into(),
)]);
let der = build_x509_der(
TestCertFields {
serial: vec![0x01],
issuer: dn.clone(),
not_before: MatterTime::from_unix_secs(1_700_000_000),
not_after: MatterTime::NO_EXPIRY,
subject: dn,
public_key: pk,
extensions: Extensions::default(),
signature: Signature::new([0u8; 64]),
},
pkcs8.as_ref(), )
.expect("synthetic CD signing cert builds");
let trust = CdSigningRoots::from_cert_der(&[&der]).expect("cert parses");
assert_eq!(trust.len(), 1);
assert_eq!(
trust.public_keys[0], expected,
"extracted SEC1 public key must match the cert's subject key"
);
}
#[test]
#[allow(clippy::expect_used)] fn from_cert_der_rejects_non_certificate_bytes() {
let err = CdSigningRoots::from_cert_der(&[b"not a certificate"])
.expect_err("garbage DER rejected");
assert!(matches!(
err,
AttestationError::CertificationDeclarationMalformed
));
}
#[test]
fn parse_inner_cd_tlv_extracts_vendor_id_and_pid_list() {
#![allow(clippy::unwrap_used, clippy::expect_used)]
use matter_codec::{Tag, TlvWriter};
let mut buf = Vec::new();
let mut w = TlvWriter::new(&mut buf);
w.start_structure(Tag::Anonymous).unwrap();
w.put_uint(Tag::Context(0), 1).unwrap(); w.put_uint(Tag::Context(1), 0xFFF1).unwrap(); w.start_array(Tag::Context(2)).unwrap();
w.put_uint(Tag::Anonymous, 0x8001).unwrap();
w.put_uint(Tag::Anonymous, 0x8002).unwrap();
w.end_container().unwrap(); w.end_container().unwrap();
let parsed = parse_inner_cd_tlv(&buf).expect("happy path decodes");
assert_eq!(parsed.vendor_id, VendorId::new(0xFFF1));
assert_eq!(
parsed.product_ids,
vec![ProductId::new(0x8001), ProductId::new(0x8002)]
);
assert!(
parsed.authorized_paa_list.is_none(),
"no tag 11 → no PAA constraint"
);
}
#[cfg(test)]
fn cd_tlv_with_paa_list(entries: Option<&[&[u8]]>) -> Vec<u8> {
#![allow(clippy::unwrap_used)]
use matter_codec::{Tag, TlvWriter};
let mut buf = Vec::new();
let mut w = TlvWriter::new(&mut buf);
w.start_structure(Tag::Anonymous).unwrap();
w.put_uint(Tag::Context(1), 0xFFF1).unwrap();
w.start_array(Tag::Context(2)).unwrap();
w.put_uint(Tag::Anonymous, 0x8000).unwrap();
w.end_container().unwrap();
if let Some(entries) = entries {
w.start_array(Tag::Context(11)).unwrap();
for e in entries {
w.put_bytes(Tag::Anonymous, e).unwrap();
}
w.end_container().unwrap();
}
w.end_container().unwrap();
buf
}
#[test]
#[allow(clippy::expect_used)] fn parse_inner_cd_tlv_extracts_authorized_paa_list() {
let a = [0xAAu8; 20];
let b = [0xBBu8; 20];
let buf = cd_tlv_with_paa_list(Some(&[&a, &b]));
let parsed = parse_inner_cd_tlv(&buf).expect("tag 11 decodes");
assert_eq!(parsed.authorized_paa_list, Some(vec![a, b]));
}
#[test]
fn parse_inner_cd_tlv_rejects_wrong_length_paa_entry() {
let short = [0xAAu8; 19];
let buf = cd_tlv_with_paa_list(Some(&[&short]));
assert!(matches!(
parse_inner_cd_tlv(&buf),
Err(AttestationError::CertificationDeclarationTlvMalformed)
));
}
#[test]
fn paa_is_authorized_matrix() {
let a = [0xAAu8; 20];
let b = [0xBBu8; 20];
assert!(paa_is_authorized(None, Some(&a)));
assert!(paa_is_authorized(None, None));
assert!(paa_is_authorized(Some(&[a, b]), Some(&a)));
let c = [0xCCu8; 20];
assert!(!paa_is_authorized(Some(&[a, b]), Some(&c)));
assert!(!paa_is_authorized(Some(&[a, b]), None));
assert!(!paa_is_authorized(Some(&[]), Some(&a)));
}
#[test]
fn parse_inner_cd_tlv_ignores_forward_compat_fields() {
#![allow(clippy::unwrap_used, clippy::expect_used)]
use matter_codec::{Tag, TlvWriter};
let mut buf = Vec::new();
let mut w = TlvWriter::new(&mut buf);
w.start_structure(Tag::Anonymous).unwrap();
w.put_uint(Tag::Context(0), 1).unwrap();
w.put_uint(Tag::Context(1), 0xFFF1).unwrap();
w.start_array(Tag::Context(2)).unwrap();
w.put_uint(Tag::Anonymous, 0x8001).unwrap();
w.end_container().unwrap();
w.put_uint(Tag::Context(3), 0x0100).unwrap();
w.put_utf8(Tag::Context(4), "CSA-ID").unwrap();
w.put_uint(Tag::Context(5), 0).unwrap();
w.put_uint(Tag::Context(6), 0).unwrap();
w.put_uint(Tag::Context(7), 1).unwrap();
w.put_uint(Tag::Context(8), 0).unwrap();
w.put_uint(Tag::Context(99), 0xDEAD).unwrap(); w.end_container().unwrap();
let parsed = parse_inner_cd_tlv(&buf).expect("forward-compat decode");
assert_eq!(parsed.vendor_id, VendorId::new(0xFFF1));
assert_eq!(parsed.product_ids, vec![ProductId::new(0x8001)]);
}
#[test]
fn parse_inner_cd_tlv_rejects_missing_vid() {
#![allow(clippy::unwrap_used, clippy::expect_used)]
use matter_codec::{Tag, TlvWriter};
let mut buf = Vec::new();
let mut w = TlvWriter::new(&mut buf);
w.start_structure(Tag::Anonymous).unwrap();
w.start_array(Tag::Context(2)).unwrap();
w.put_uint(Tag::Anonymous, 0x8001).unwrap();
w.end_container().unwrap();
w.end_container().unwrap();
let err = parse_inner_cd_tlv(&buf).expect_err("missing vid rejected");
assert!(matches!(
err,
AttestationError::CertificationDeclarationTlvMalformed
));
}
#[test]
fn parse_inner_cd_tlv_rejects_garbage() {
#![allow(clippy::unwrap_used, clippy::expect_used)]
let err = parse_inner_cd_tlv(&[0xFF]).expect_err("garbage rejected");
assert!(matches!(
err,
AttestationError::CertificationDeclarationTlvMalformed
));
}
#[test]
fn parse_inner_cd_tlv_captures_dac_origin_fields() {
#![allow(clippy::unwrap_used, clippy::expect_used)]
let tlv = build_inner_cd_tlv(0xFFF1, 0x8001, Some(0x1234), Some(0x5678), &[0x8001]);
let parsed = parse_inner_cd_tlv(&tlv).expect("decodes with dac_origin");
assert_eq!(parsed.vendor_id, VendorId::new(0xFFF1));
assert_eq!(parsed.product_ids, vec![ProductId::new(0x8001)]);
assert_eq!(parsed.dac_origin_vendor_id, Some(VendorId::new(0x1234)));
assert_eq!(parsed.dac_origin_product_id, Some(ProductId::new(0x5678)));
}
const CSA_TEST_CD_SIGNING_KEY_PKCS8: &[u8] = include_bytes!(
"../../../../../test-vectors/commissioning/cd/csa-test-cd-signing-root.pkcs8.der"
);
#[allow(clippy::unwrap_used)] fn build_inner_cd_tlv(
vendor_id: u16,
product_id: u16,
dac_origin_vid: Option<u16>,
dac_origin_pid: Option<u16>,
product_id_array: &[u16],
) -> Vec<u8> {
use matter_codec::{Tag, TlvWriter};
let mut buf = Vec::new();
{
let mut w = TlvWriter::new(&mut buf);
w.start_structure(Tag::Anonymous).unwrap();
w.put_uint(Tag::Context(0), 1).unwrap(); w.put_uint(Tag::Context(1), u64::from(vendor_id)).unwrap(); w.start_array(Tag::Context(2)).unwrap(); let pids: Vec<u16> = if product_id_array.is_empty() {
vec![product_id]
} else {
product_id_array.to_vec()
};
for p in pids {
w.put_uint(Tag::Anonymous, u64::from(p)).unwrap();
}
w.end_container().unwrap();
w.put_uint(Tag::Context(3), 0x0100).unwrap(); w.put_utf8(Tag::Context(4), "CSA00000000000000").unwrap(); w.put_uint(Tag::Context(5), 0).unwrap(); w.put_uint(Tag::Context(6), 0).unwrap(); w.put_uint(Tag::Context(7), 1).unwrap(); w.put_uint(Tag::Context(8), 0).unwrap(); if let Some(v) = dac_origin_vid {
w.put_uint(Tag::Context(9), u64::from(v)).unwrap();
}
if let Some(p) = dac_origin_pid {
w.put_uint(Tag::Context(10), u64::from(p)).unwrap();
}
w.end_container().unwrap();
}
buf
}
#[allow(clippy::unwrap_used, clippy::expect_used)] fn sign_into_cms(content: &[u8]) -> Vec<u8> {
use cms::cert::IssuerAndSerialNumber;
use cms::content_info::{CmsVersion, ContentInfo};
use cms::signed_data::{
EncapsulatedContentInfo, SignedData, SignerIdentifier, SignerInfo, SignerInfos,
};
use const_oid::ObjectIdentifier;
use der::asn1::{Any, AnyRef, OctetString, SetOfVec};
use der::{Encode, Tag as DerTag};
use ring::rand::SystemRandom;
use ring::signature::{EcdsaKeyPair, ECDSA_P256_SHA256_FIXED_SIGNING};
use spki::AlgorithmIdentifierOwned;
use x509_cert::name::RdnSequence;
use x509_cert::serial_number::SerialNumber;
const ID_DATA: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.113549.1.7.1");
const ID_SIGNED_DATA: ObjectIdentifier =
ObjectIdentifier::new_unwrap("1.2.840.113549.1.7.2");
const ID_SHA_256: ObjectIdentifier = ObjectIdentifier::new_unwrap("2.16.840.1.101.3.4.2.1");
const ECDSA_WITH_SHA_256: ObjectIdentifier =
ObjectIdentifier::new_unwrap("1.2.840.10045.4.3.2");
let rng = SystemRandom::new();
let key = EcdsaKeyPair::from_pkcs8(
&ECDSA_P256_SHA256_FIXED_SIGNING,
CSA_TEST_CD_SIGNING_KEY_PKCS8,
&rng,
)
.expect("bundled CSA-test CD signing key loads");
let signature = key.sign(&rng, content).expect("sign eContent");
let econtent_any =
Any::new(DerTag::OctetString, content.to_vec()).expect("Any(OctetString)");
let encap = EncapsulatedContentInfo {
econtent_type: ID_DATA,
econtent: Some(econtent_any),
};
let sha256 = AlgorithmIdentifierOwned {
oid: ID_SHA_256,
parameters: None,
};
let digest_algorithms =
SetOfVec::try_from(vec![sha256.clone()]).expect("digest_algorithms");
let serial = SerialNumber::new(&[0x01]).expect("serial");
let sid = SignerIdentifier::IssuerAndSerialNumber(IssuerAndSerialNumber {
issuer: RdnSequence::default(),
serial_number: serial,
});
let signature_octets =
OctetString::new(signature.as_ref().to_vec()).expect("signature octets");
let signer_info = SignerInfo {
version: CmsVersion::V1,
sid,
digest_alg: sha256,
signed_attrs: None,
signature_algorithm: AlgorithmIdentifierOwned {
oid: ECDSA_WITH_SHA_256,
parameters: None,
},
signature: signature_octets,
unsigned_attrs: None,
};
let signer_infos = SignerInfos(SetOfVec::try_from(vec![signer_info]).expect("signer set"));
let signed_data = SignedData {
version: CmsVersion::V1,
digest_algorithms,
encap_content_info: encap,
certificates: None,
crls: None,
signer_infos,
};
let signed_data_der = signed_data.to_der().expect("SignedData der");
let signed_data_any =
Any::from(AnyRef::try_from(signed_data_der.as_slice()).expect("AnyRef"));
let content_info = ContentInfo {
content_type: ID_SIGNED_DATA,
content: signed_data_any,
};
content_info.to_der().expect("ContentInfo der")
}
#[test]
#[allow(clippy::expect_used)] fn dac_origin_present_dac_matches_origin_is_accepted() {
let tlv = build_inner_cd_tlv(0xFFF1, 0x8001, Some(0x1234), Some(0x5678), &[0x8001]);
let cd = sign_into_cms(&tlv);
let trust = CdSigningRoots::with_example_device_roots();
verify_certification_declaration(
&cd,
VendorId::new(0x1234),
ProductId::new(0x5678),
&trust,
)
.expect("DAC matching dac_origin VID/PID must be accepted");
}
#[test]
#[allow(clippy::expect_used)] fn dac_origin_present_dac_matches_neither_is_rejected() {
let tlv = build_inner_cd_tlv(0xFFF1, 0x8001, Some(0x1234), Some(0x5678), &[0x8001]);
let cd = sign_into_cms(&tlv);
let trust = CdSigningRoots::with_example_device_roots();
let err = verify_certification_declaration(
&cd,
VendorId::new(0xFFF1),
ProductId::new(0x8001),
&trust,
)
.expect_err("DAC not matching dac_origin must be rejected");
assert!(
matches!(
err,
AttestationError::CertificationDeclarationVidMismatch {
declared,
expected,
} if declared == VendorId::new(0x1234) && expected == VendorId::new(0xFFF1)
),
"expected VID mismatch against the dac_origin VID, got {err:?}"
);
}
#[test]
#[allow(clippy::expect_used)] fn no_dac_origin_uses_vendor_id_and_pid_array() {
let tlv = build_inner_cd_tlv(0xFFF1, 0x8001, None, None, &[0x8001, 0x8002]);
let cd = sign_into_cms(&tlv);
let trust = CdSigningRoots::with_example_device_roots();
verify_certification_declaration(
&cd,
VendorId::new(0xFFF1),
ProductId::new(0x8002),
&trust,
)
.expect("DAC matching vendor_id and a member of product_id_array accepted");
let err = verify_certification_declaration(
&cd,
VendorId::new(0xFFF1),
ProductId::new(0x9999),
&trust,
)
.expect_err("PID outside product_id_array rejected");
assert!(
matches!(err, AttestationError::CertificationDeclarationPidMismatch(p)
if p == ProductId::new(0x9999)),
"expected PID mismatch, got {err:?}"
);
}
}