use ring::rand::SystemRandom;
use ring::signature::{EcdsaKeyPair, ECDSA_P256_SHA256_ASN1_SIGNING};
use crate::error::{Error, Result};
use crate::{DistinguishedName, Extensions, MatterCertificate, MatterTime, PublicKey, Signature};
#[derive(Debug, Clone)]
pub struct TestCertFields {
pub serial: Vec<u8>,
pub issuer: DistinguishedName,
pub not_before: MatterTime,
pub not_after: MatterTime,
pub subject: DistinguishedName,
pub public_key: PublicKey,
pub extensions: Extensions,
pub signature: Signature,
}
#[must_use]
pub fn build_unsigned(fields: TestCertFields) -> MatterCertificate {
MatterCertificate::from_fields(
fields.serial,
fields.issuer,
fields.not_before,
fields.not_after,
fields.subject,
fields.public_key,
fields.extensions,
fields.signature,
)
}
pub fn build_x509_der(fields: TestCertFields, issuer_pkcs8: &[u8]) -> Result<Vec<u8>> {
let cert = build_unsigned(fields);
let tbs = cert.to_x509_tbs_der()?;
let rng = SystemRandom::new();
let key_pair = EcdsaKeyPair::from_pkcs8(&ECDSA_P256_SHA256_ASN1_SIGNING, issuer_pkcs8, &rng)
.map_err(|_| Error::TestX509SigningFailed("issuer PKCS#8 key rejected by ring"))?;
let sig_der = key_pair
.sign(&rng, &tbs)
.map_err(|_| Error::TestX509SigningFailed("ring ECDSA signing failed"))?;
let sig_bytes = sig_der.as_ref();
let mut sig_bit_string = Vec::with_capacity(sig_bytes.len() + 4);
sig_bit_string.push(0x03);
crate::x509::encode_definite_length(&mut sig_bit_string, sig_bytes.len() + 1);
sig_bit_string.push(0x00); sig_bit_string.extend_from_slice(sig_bytes);
let alg = crate::x509::encode_algorithm_identifier_ecdsa_sha256();
let mut inner = Vec::with_capacity(tbs.len() + alg.len() + sig_bit_string.len());
inner.extend_from_slice(&tbs);
inner.extend_from_slice(&alg);
inner.extend_from_slice(&sig_bit_string);
Ok(crate::x509::wrap_sequence(&inner))
}
#[must_use]
pub fn with_signature(cert: &MatterCertificate, signature: Signature) -> MatterCertificate {
MatterCertificate::from_fields(
cert.serial().to_vec(),
cert.issuer().clone(),
cert.not_before(),
cert.not_after(),
cert.subject().clone(),
cert.public_key().clone(),
cert.extensions().clone(),
signature,
)
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)] mod tests {
use ring::rand::SystemRandom;
use ring::signature::{EcdsaKeyPair, KeyPair, ECDSA_P256_SHA256_ASN1_SIGNING};
use super::*;
use crate::extensions::{BasicConstraints, KeyUsage};
use crate::DnAttribute;
fn gen_key() -> (Vec<u8>, PublicKey) {
let rng = SystemRandom::new();
let pkcs8 = EcdsaKeyPair::generate_pkcs8(&ECDSA_P256_SHA256_ASN1_SIGNING, &rng).unwrap();
let pkcs8 = pkcs8.as_ref().to_vec();
let kp = EcdsaKeyPair::from_pkcs8(&ECDSA_P256_SHA256_ASN1_SIGNING, &pkcs8, &rng).unwrap();
let pk = PublicKey::from_slice(kp.public_key().as_ref()).unwrap();
(pkcs8, pk)
}
#[test]
fn build_x509_der_emits_parseable_self_signed_cert() {
let (pkcs8, pk) = gen_key();
let dn = DistinguishedName::new(vec![DnAttribute::CommonName("matter-test root".into())]);
let fields = TestCertFields {
serial: vec![0x01],
issuer: dn.clone(),
not_before: MatterTime::from_unix_secs(1_700_000_000),
not_after: MatterTime::from_unix_secs(1_900_000_000),
subject: dn,
public_key: pk,
extensions: Extensions {
basic_constraints: Some(BasicConstraints {
is_ca: true,
path_len_constraint: None,
}),
key_usage: Some(KeyUsage::KEY_CERT_SIGN | KeyUsage::CRL_SIGN),
..Default::default()
},
signature: Signature::new([0u8; 64]),
};
let der = build_x509_der(fields, &pkcs8).expect("self-signed cert builds");
assert_eq!(der[0], 0x30, "outer Certificate must be a SEQUENCE");
assert!(der.len() > 100, "a real cert is well over 100 bytes");
}
#[test]
fn vendor_and_product_id_encode_as_printable_hex() {
let (_pkcs8, pk) = gen_key();
let subject = DistinguishedName::new(vec![
DnAttribute::CommonName("dac".into()),
DnAttribute::VendorId(0xFFF1),
DnAttribute::ProductId(0x8001),
]);
let cert = build_unsigned(TestCertFields {
serial: vec![0x01],
issuer: DistinguishedName::new(vec![DnAttribute::CommonName("pai".into())]),
not_before: MatterTime::from_unix_secs(1_700_000_000),
not_after: MatterTime::from_unix_secs(1_900_000_000),
subject,
public_key: pk,
extensions: Extensions::default(),
signature: Signature::new([0u8; 64]),
});
let tbs = cert.to_x509_tbs_der().unwrap();
assert!(
tbs.windows(6)
.any(|w| w == [0x13, 0x04, b'F', b'F', b'F', b'1']),
"VID must encode as PrintableString \"FFF1\""
);
assert!(
tbs.windows(6)
.any(|w| w == [0x13, 0x04, b'8', b'0', b'0', b'1']),
"PID must encode as PrintableString \"8001\""
);
}
#[test]
fn vendor_id_is_not_tlv_encodable() {
use matter_codec::{Tag, TlvWriter};
let dn = DistinguishedName::new(vec![DnAttribute::VendorId(0xFFF1)]);
let mut buf = Vec::new();
let mut w = TlvWriter::new(&mut buf);
let err = dn.write(&mut w, Tag::Anonymous).unwrap_err();
assert!(matches!(err, Error::DnAttributeNotTlvEncodable("VendorId")));
}
}