use alloc::string::String;
use alloc::vec::Vec;
use super::extension::{self, Extension, GeneralName};
use super::{
AnyPublicKey, CertSigner, CertificationRequest, DistinguishedName, Error, Validity,
algorithm_identifier, oid,
};
use crate::der::{
Reader, encode_bit_string, encode_context, encode_integer, encode_sequence, parse_oid,
pem_decode, pem_encode, tag,
};
use crate::hash::Sha256;
use crate::rsa::{RsaPrivateKey, RsaPublicKey};
const PEM_LABEL: &str = "CERTIFICATE";
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Certificate {
der: Vec<u8>,
}
struct CertParts<'a> {
tbs: &'a [u8],
sig_alg: Vec<u64>,
signature: &'a [u8],
}
fn rsa_spki<const LIMBS: usize>(pk: &RsaPublicKey<LIMBS>) -> Vec<u8> {
let algid = algorithm_identifier(oid::RSA_ENCRYPTION, true);
let key = pk.to_pkcs1_der();
encode_sequence(&[algid, encode_bit_string(&key)].concat())
}
pub(crate) fn legacy_extensions(is_ca: bool, sans: &[&str]) -> Vec<Extension> {
let mut v = Vec::new();
v.push(extension::basic_constraints(is_ca, None));
if !sans.is_empty() {
let names: Vec<GeneralName> = sans
.iter()
.map(|s| {
if let Some(v4) = parse_ipv4(s) {
GeneralName::IpV4(v4)
} else {
GeneralName::Dns((*s).into())
}
})
.collect();
v.push(extension::subject_alt_name(&names));
}
v
}
fn extensions_explicit(exts: &[Extension]) -> Vec<u8> {
extension::encode_extensions_field(exts)
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn build_tbs_raw(
serial: u64,
issuer: &DistinguishedName,
subject: &DistinguishedName,
validity: &Validity,
spki_der: &[u8],
sig_algid: &[u8],
extensions: &[Extension],
) -> Vec<u8> {
let mut body = Vec::new();
body.extend_from_slice(&encode_context(0, &encode_integer(&[2]))); body.extend_from_slice(&encode_integer(&serial.to_be_bytes()));
body.extend_from_slice(sig_algid);
body.extend_from_slice(&issuer.to_der());
body.extend_from_slice(&validity.to_der());
body.extend_from_slice(&subject.to_der());
body.extend_from_slice(spki_der);
body.extend_from_slice(&extensions_explicit(extensions));
encode_sequence(&body)
}
#[allow(clippy::too_many_arguments)]
fn build_tbs<const LIMBS: usize>(
serial: u64,
issuer: &DistinguishedName,
subject: &DistinguishedName,
validity: &Validity,
subject_key: &RsaPublicKey<LIMBS>,
is_ca: bool,
dns_names: &[&str],
) -> Vec<u8> {
let exts = legacy_extensions(is_ca, dns_names);
build_tbs_raw(
serial,
issuer,
subject,
validity,
&rsa_spki(subject_key),
&algorithm_identifier(oid::SHA256_WITH_RSA, true),
&exts,
)
}
impl Certificate {
pub fn issue<const LIMBS: usize>(
issuer_key: &RsaPrivateKey<LIMBS>,
issuer: &DistinguishedName,
subject: &DistinguishedName,
subject_key: &RsaPublicKey<LIMBS>,
validity: &Validity,
serial: u64,
is_ca: bool,
) -> Result<Certificate, Error> {
Self::issue_with_sans(
issuer_key,
issuer,
subject,
subject_key,
validity,
serial,
is_ca,
&[],
)
}
#[allow(clippy::too_many_arguments)]
pub fn issue_with_sans<const LIMBS: usize>(
issuer_key: &RsaPrivateKey<LIMBS>,
issuer: &DistinguishedName,
subject: &DistinguishedName,
subject_key: &RsaPublicKey<LIMBS>,
validity: &Validity,
serial: u64,
is_ca: bool,
dns_names: &[&str],
) -> Result<Certificate, Error> {
let tbs = build_tbs(
serial,
issuer,
subject,
validity,
subject_key,
is_ca,
dns_names,
);
let sig = issuer_key.sign_pkcs1v15::<Sha256>(&tbs)?;
let der = encode_sequence(
&[
tbs,
algorithm_identifier(oid::SHA256_WITH_RSA, true),
encode_bit_string(&sig),
]
.concat(),
);
Ok(Certificate { der })
}
pub fn self_signed<const LIMBS: usize>(
key: &RsaPrivateKey<LIMBS>,
subject: &DistinguishedName,
validity: &Validity,
serial: u64,
is_ca: bool,
) -> Result<Certificate, Error> {
Self::issue(
key,
subject,
subject,
&key.public_key(),
validity,
serial,
is_ca,
)
}
pub fn self_signed_with_sans<const LIMBS: usize>(
key: &RsaPrivateKey<LIMBS>,
subject: &DistinguishedName,
validity: &Validity,
serial: u64,
is_ca: bool,
dns_names: &[&str],
) -> Result<Certificate, Error> {
Self::issue_with_sans(
key,
subject,
subject,
&key.public_key(),
validity,
serial,
is_ca,
dns_names,
)
}
#[allow(clippy::too_many_arguments)]
pub fn issue_general(
signer: &CertSigner,
issuer: &DistinguishedName,
subject: &DistinguishedName,
subject_key: &AnyPublicKey,
validity: &Validity,
serial: u64,
is_ca: bool,
dns_names: &[&str],
) -> Result<Certificate, Error> {
let exts = legacy_extensions(is_ca, dns_names);
Self::issue_with_extensions(
signer,
issuer,
subject,
subject_key,
validity,
serial,
&exts,
)
}
#[allow(clippy::too_many_arguments)]
pub fn issue_with_extensions(
signer: &CertSigner,
issuer: &DistinguishedName,
subject: &DistinguishedName,
subject_key: &AnyPublicKey,
validity: &Validity,
serial: u64,
extensions: &[Extension],
) -> Result<Certificate, Error> {
let algid = signer.algorithm_identifier();
let tbs = build_tbs_raw(
serial,
issuer,
subject,
validity,
&subject_key.to_spki_der(),
&algid,
extensions,
);
let sig = signer.sign(&tbs)?;
let der = encode_sequence(&[tbs, algid, encode_bit_string(&sig)].concat());
Ok(Certificate { der })
}
pub fn self_signed_with_extensions(
signer: &CertSigner,
subject: &DistinguishedName,
validity: &Validity,
serial: u64,
extensions: &[Extension],
) -> Result<Certificate, Error> {
let key = signer.public_key();
Self::issue_with_extensions(signer, subject, subject, &key, validity, serial, extensions)
}
pub fn self_signed_general(
signer: &CertSigner,
subject: &DistinguishedName,
validity: &Validity,
serial: u64,
is_ca: bool,
dns_names: &[&str],
) -> Result<Certificate, Error> {
let key = signer.public_key();
Self::issue_general(
signer, subject, subject, &key, validity, serial, is_ca, dns_names,
)
}
pub fn issue_from_csr(
signer: &CertSigner,
issuer: &DistinguishedName,
csr: &CertificationRequest,
validity: &Validity,
serial: u64,
is_ca: bool,
) -> Result<Certificate, Error> {
csr.verify_self_signed()?;
let subject = csr.subject()?;
let subject_key = csr.public_key()?;
let sans = csr.subject_alt_names()?;
let san_refs: Vec<&str> = sans.iter().map(|s| s.as_str()).collect();
Self::issue_general(
signer,
issuer,
&subject,
&subject_key,
validity,
serial,
is_ca,
&san_refs,
)
}
pub fn from_der(der: Vec<u8>) -> Result<Certificate, Error> {
let mut r = Reader::new(&der);
r.read_sequence()?;
r.finish()?;
Ok(Certificate { der })
}
pub fn from_pem(pem: &str) -> Result<Certificate, Error> {
Ok(Certificate {
der: pem_decode(pem, PEM_LABEL)?,
})
}
pub fn to_der(&self) -> &[u8] {
&self.der
}
pub fn to_pem(&self) -> String {
pem_encode(PEM_LABEL, &self.der)
}
fn parts(&self) -> Result<CertParts<'_>, Error> {
let mut outer = Reader::new(&self.der);
let mut cert = outer.read_sequence()?;
let tbs = cert.read_element()?;
let mut alg = cert.read_sequence()?;
let sig_alg = parse_oid(alg.read_oid()?)?;
let signature = cert.read_bit_string()?;
cert.finish()?;
Ok(CertParts {
tbs,
sig_alg,
signature,
})
}
fn tbs_after_algid(&self) -> Result<Reader<'_>, Error> {
let tbs = self.parts()?.tbs;
let mut outer = Reader::new(tbs);
let mut seq = outer.read_sequence()?;
if seq.peek_tag() == Some(tag::context(0)) {
seq.read_tlv(tag::context(0))?; }
seq.read_integer_bytes()?; seq.read_sequence()?; Ok(seq)
}
#[allow(dead_code)]
pub(crate) fn serial_bytes(&self) -> Result<&[u8], Error> {
let tbs = self.parts()?.tbs;
let mut outer = Reader::new(tbs);
let mut seq = outer.read_sequence()?;
if seq.peek_tag() == Some(tag::context(0)) {
seq.read_tlv(tag::context(0))?; }
Ok(seq.read_unsigned_integer_bytes()?)
}
pub(crate) fn inner_signature_algid_der(&self) -> Result<&[u8], Error> {
let tbs = self.parts()?.tbs;
let mut outer = Reader::new(tbs);
let mut seq = outer.read_sequence()?;
if seq.peek_tag() == Some(tag::context(0)) {
seq.read_tlv(tag::context(0))?;
}
seq.read_integer_bytes()?;
let bytes = seq.read_element()?;
Ok(bytes)
}
pub(crate) fn outer_signature_algid_der(&self) -> Result<&[u8], Error> {
let mut outer = Reader::new(&self.der);
let mut cert = outer.read_sequence()?;
cert.read_element()?; let bytes = cert.read_element()?;
Ok(bytes)
}
pub(crate) fn check_signature_algid_consistent(&self) -> Result<(), Error> {
let inner = self.inner_signature_algid_der()?;
let outer = self.outer_signature_algid_der()?;
if inner == outer {
Ok(())
} else {
Err(Error::Malformed)
}
}
pub fn issuer(&self) -> Result<DistinguishedName, Error> {
let mut seq = self.tbs_after_algid()?;
DistinguishedName::decode(&mut seq)
}
pub(crate) fn issuer_der(&self) -> Result<&[u8], Error> {
let mut seq = self.tbs_after_algid()?;
let bytes = seq.read_element()?;
Ok(bytes)
}
pub fn subject(&self) -> Result<DistinguishedName, Error> {
let mut seq = self.tbs_after_algid()?;
DistinguishedName::decode(&mut seq)?; Validity::decode(&mut seq)?; DistinguishedName::decode(&mut seq)
}
pub(crate) fn subject_der(&self) -> Result<&[u8], Error> {
let mut seq = self.tbs_after_algid()?;
seq.read_element()?; seq.read_element()?; let bytes = seq.read_element()?;
Ok(bytes)
}
pub fn public_key<const LIMBS: usize>(&self) -> Result<RsaPublicKey<LIMBS>, Error> {
let mut seq = self.tbs_after_algid()?;
DistinguishedName::decode(&mut seq)?; Validity::decode(&mut seq)?; DistinguishedName::decode(&mut seq)?; let mut spki = seq.read_sequence()?;
let alg = parse_oid(spki.read_sequence()?.read_oid()?)?;
if alg.as_slice() != oid::RSA_ENCRYPTION {
return Err(Error::UnsupportedAlgorithm);
}
let key_der = spki.read_bit_string()?;
Ok(RsaPublicKey::from_pkcs1_der(key_der)?)
}
pub fn verify_signature<const LIMBS: usize>(
&self,
issuer_key: &RsaPublicKey<LIMBS>,
) -> Result<(), Error> {
let parts = self.parts()?;
if parts.sig_alg.as_slice() != oid::SHA256_WITH_RSA {
return Err(Error::UnsupportedAlgorithm);
}
issuer_key.verify_pkcs1v15::<Sha256>(parts.tbs, parts.signature)?;
Ok(())
}
pub fn subject_public_key(&self) -> Result<super::AnyPublicKey, Error> {
let mut seq = self.tbs_after_algid()?;
DistinguishedName::decode(&mut seq)?; Validity::decode(&mut seq)?; DistinguishedName::decode(&mut seq)?; let spki = seq.read_element()?; super::AnyPublicKey::from_spki_der(spki)
}
pub fn verify_signature_with(&self, issuer: &super::AnyPublicKey) -> Result<(), Error> {
let parts = self.parts()?;
issuer.verify(&parts.sig_alg, parts.tbs, parts.signature)
}
pub fn signature_algorithm_oid(&self) -> Result<Vec<u64>, Error> {
Ok(self.parts()?.sig_alg)
}
pub fn validity(&self) -> Result<Validity, Error> {
let mut seq = self.tbs_after_algid()?;
DistinguishedName::decode(&mut seq)?; Validity::decode(&mut seq)
}
pub fn subject_alt_names(&self) -> Result<Vec<String>, Error> {
let mut names = Vec::new();
self.walk_extensions(|id, _critical, value| {
if id == oid::SUBJECT_ALT_NAME {
parse_dns_names(value, &mut names)?;
}
Ok(())
})?;
Ok(names)
}
pub fn subject_alt_ips(&self) -> Result<Vec<SanIp>, Error> {
let mut out = Vec::new();
self.walk_extensions(|id, _critical, value| {
if id == oid::SUBJECT_ALT_NAME {
parse_ip_addresses(value, &mut out)?;
}
Ok(())
})?;
Ok(out)
}
pub fn basic_constraints(&self) -> Result<Option<(bool, Option<u32>)>, Error> {
let mut out = None;
self.walk_extensions(|id, _critical, value| {
if id == oid::BASIC_CONSTRAINTS {
let mut r = Reader::new(value);
let mut seq = r.read_sequence()?;
let is_ca = if seq.peek_tag() == Some(tag::BOOLEAN) {
seq.read_boolean()?
} else {
false
};
let path_len = if !seq.is_empty() {
let bytes = seq.read_unsigned_integer_bytes()?;
let mag = if bytes.len() > 1 && bytes[0] == 0x00 {
&bytes[1..]
} else {
bytes
};
if mag.len() > 4 {
return Err(Error::Malformed);
}
let mut v: u32 = 0;
for &b in mag {
v = (v << 8) | b as u32;
}
Some(v)
} else {
None
};
out = Some((is_ca, path_len));
}
Ok(())
})?;
Ok(out)
}
pub fn key_usage(&self) -> Result<Option<u16>, Error> {
let mut out = None;
self.walk_extensions(|id, _critical, value| {
if id == oid::KEY_USAGE {
let mut r = Reader::new(value);
let raw = r.read_tlv(tag::BIT_STRING)?;
if raw.is_empty() {
return Err(Error::Malformed);
}
if raw[0] > 7 {
return Err(Error::Malformed);
}
let _unused = raw[0];
let bytes = &raw[1..];
let mut mask: u16 = 0;
if !bytes.is_empty() {
mask |= bytes[0] as u16;
}
if bytes.len() > 1 {
mask |= (bytes[1] as u16) << 8;
}
out = Some(mask);
}
Ok(())
})?;
Ok(out)
}
pub fn extended_key_usages(&self) -> Result<Vec<Vec<u64>>, Error> {
let mut out = Vec::new();
self.walk_extensions(|id, _critical, value| {
if id == oid::EXT_KEY_USAGE {
let mut r = Reader::new(value);
let mut seq = r.read_sequence()?;
while !seq.is_empty() {
let raw = seq.read_oid()?;
out.push(parse_oid(raw)?);
}
}
Ok(())
})?;
Ok(out)
}
pub fn extensions(&self) -> Result<Vec<Extension>, Error> {
let mut out = Vec::new();
self.walk_extensions(|id, critical, value| {
out.push(Extension {
oid: id.to_vec(),
critical,
value: value.to_vec(),
});
Ok(())
})?;
Ok(out)
}
pub(crate) fn critical_extension_oids(&self) -> Result<Vec<Vec<u64>>, Error> {
let mut out = Vec::new();
self.walk_extensions(|id, critical, _value| {
if critical {
out.push(id.to_vec());
}
Ok(())
})?;
Ok(out)
}
fn walk_extensions(
&self,
mut f: impl FnMut(&[u64], bool, &[u8]) -> Result<(), Error>,
) -> Result<(), Error> {
let mut seq = self.tbs_after_algid()?;
DistinguishedName::decode(&mut seq)?; Validity::decode(&mut seq)?; DistinguishedName::decode(&mut seq)?; seq.read_element()?;
while matches!(seq.peek_tag(), Some(0x81) | Some(0x82)) {
seq.read_element()?;
}
if seq.peek_tag() != Some(tag::context(3)) {
return Ok(());
}
let wrapper = seq.read_tlv(tag::context(3))?;
let mut outer = Reader::new(wrapper);
let mut exts = outer.read_sequence()?;
while !exts.is_empty() {
let mut ext = exts.read_sequence()?;
let id = parse_oid(ext.read_oid()?)?;
let critical = if ext.peek_tag() == Some(tag::BOOLEAN) {
ext.read_boolean()?
} else {
false
};
let value = ext.read_octet_string()?;
f(&id, critical, value)?;
}
Ok(())
}
pub fn check_well_formed(&self) -> Result<(), Error> {
self.parts()?; self.issuer()?;
self.validity()?;
self.subject()?;
self.subject_alt_names()?; Ok(())
}
}
pub(super) fn parse_dns_names(der: &[u8], out: &mut Vec<String>) -> Result<(), Error> {
let mut reader = Reader::new(der);
let mut seq = reader.read_sequence()?;
while !seq.is_empty() {
let (t, value) = seq.read_any()?;
if t == 0x82 {
if value.is_empty() {
return Err(Error::Malformed);
}
for &b in value {
if !(0x20..=0x7E).contains(&b) {
return Err(Error::Malformed);
}
}
let s = core::str::from_utf8(value).map_err(|_| Error::Malformed)?;
if looks_like_ip_literal(s) {
return Err(Error::Malformed);
}
out.push(String::from(s));
}
}
Ok(())
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum SanIp {
V4([u8; 4]),
V6([u8; 16]),
}
fn parse_ip_addresses(der: &[u8], out: &mut Vec<SanIp>) -> Result<(), Error> {
let mut reader = Reader::new(der);
let mut seq = reader.read_sequence()?;
while !seq.is_empty() {
let (t, value) = seq.read_any()?;
if t == 0x87 {
match value.len() {
4 => {
let mut v = [0u8; 4];
v.copy_from_slice(value);
out.push(SanIp::V4(v));
}
16 => {
if value[..10].iter().all(|&b| b == 0) && value[10] == 0xff && value[11] == 0xff
{
return Err(Error::Malformed);
}
let mut v = [0u8; 16];
v.copy_from_slice(value);
out.push(SanIp::V6(v));
}
_ => return Err(Error::Malformed),
}
}
}
Ok(())
}
pub(crate) fn looks_like_ip_literal(s: &str) -> bool {
if s.bytes().any(|b| b == b':') {
return true;
}
let mut count = 0usize;
for label in s.split('.') {
count += 1;
if count > 4 {
return false;
}
if label.is_empty() || label.len() > 3 {
return false;
}
if !label.bytes().all(|b| b.is_ascii_digit()) {
return false;
}
}
count == 4
}
pub(crate) fn parse_ipv4(s: &str) -> Option<[u8; 4]> {
let mut out = [0u8; 4];
let mut count = 0usize;
for label in s.split('.') {
if count >= 4 {
return None;
}
let n: u32 = label.parse().ok()?;
if n > 255 {
return None;
}
out[count] = n as u8;
count += 1;
}
if count != 4 {
return None;
}
Some(out)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_util::{rsa_test_key_a, rsa_test_key_b};
use crate::x509::Time;
use alloc::vec;
fn validity() -> Validity {
Validity::new(
Time::utc(2024, 1, 1, 0, 0, 0),
Time::utc(2034, 1, 1, 0, 0, 0),
)
}
#[test]
fn self_signed_roundtrip_and_verify() {
let key = rsa_test_key_a();
let name =
DistinguishedName::common_name("purecrypto test CA").with_organization("Karpelès Lab");
let cert = Certificate::self_signed(&key, &name, &validity(), 1, true).unwrap();
let pem = cert.to_pem();
assert_eq!(Certificate::from_pem(&pem).unwrap(), cert);
assert_eq!(cert.subject().unwrap(), name);
assert_eq!(cert.issuer().unwrap(), name);
let parsed_key = cert.public_key::<32>().unwrap();
assert_eq!(parsed_key, key.public_key());
cert.verify_signature::<32>(&parsed_key).unwrap();
}
#[test]
fn ca_signs_leaf() {
let ca_key = rsa_test_key_a();
let leaf_key = rsa_test_key_b();
let ca_name = DistinguishedName::common_name("Root CA");
let leaf_name = DistinguishedName::common_name("leaf.example");
let leaf = Certificate::issue(
&ca_key,
&ca_name,
&leaf_name,
&leaf_key.public_key(),
&validity(),
2,
false,
)
.unwrap();
assert_eq!(leaf.subject().unwrap(), leaf_name);
assert_eq!(leaf.issuer().unwrap(), ca_name);
leaf.verify_signature::<32>(&ca_key.public_key()).unwrap();
assert!(leaf.verify_signature::<32>(&leaf_key.public_key()).is_err());
}
#[test]
fn tampered_cert_fails_verification() {
let key = rsa_test_key_a();
let cert = Certificate::self_signed(
&key,
&DistinguishedName::common_name("x"),
&validity(),
1,
true,
)
.unwrap();
let mut der = cert.to_der().to_vec();
let idx = der.len() / 3;
der[idx] ^= 1;
let bad = Certificate::from_der(der).unwrap();
assert!(bad.verify_signature::<32>(&key.public_key()).is_err());
}
const OPENSSL_EC_CERT: &str = "-----BEGIN CERTIFICATE-----\n\
MIIBjjCCATWgAwIBAgIUdDq5AMJ2buWe3Zp8FzA8x1IJ/I4wCgYIKoZIzj0EAwIw\n\
HTEbMBkGA1UEAwwScHVyZWNyeXB0byBlYyB0ZXN0MB4XDTI2MDUyNTE1NTcwMloX\n\
DTM2MDUyMjE1NTcwMlowHTEbMBkGA1UEAwwScHVyZWNyeXB0byBlYyB0ZXN0MFkw\n\
EwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEI/Rjb2Q5+virvsM30rQD4uAVpo5XDfzp\n\
6QEzGS5q032wAZMNKRyj79yAAFn9UwJzHjtFjQ8dexLQ+yFTHj994KNTMFEwHQYD\n\
VR0OBBYEFDkJ9uOVaokxPfzPjax49XgMM02PMB8GA1UdIwQYMBaAFDkJ9uOVaokx\n\
PfzPjax49XgMM02PMA8GA1UdEwEB/wQFMAMBAf8wCgYIKoZIzj0EAwIDRwAwRAIg\n\
RENTjAEB2yR6Dd5XY5jNxLqSJH4fJUKeGH8lMauQh7YCIGf8bBLXdk+nCnKjuiZw\n\
3sC6s2rrQa4gzDiVjwYM2ggX\n\
-----END CERTIFICATE-----\n";
#[test]
fn parse_and_verify_openssl_ec_cert() {
let cert = Certificate::from_pem(OPENSSL_EC_CERT).unwrap();
assert_eq!(
cert.subject().unwrap(),
DistinguishedName::common_name("purecrypto ec test")
);
let key = cert.subject_public_key().unwrap();
assert!(matches!(key, crate::x509::AnyPublicKey::Ecdsa(_)));
cert.verify_signature_with(&key).unwrap();
}
#[test]
fn ec_self_signed_general() {
use crate::ec::{BoxedEcdsaPrivateKey, CurveId};
use crate::rng::HmacDrbg;
let mut rng = HmacDrbg::<crate::hash::Sha256>::new(b"ec-ca", b"n", &[]);
let key = BoxedEcdsaPrivateKey::generate(CurveId::P256, &mut rng);
let signer = crate::x509::CertSigner::Ecdsa(&key);
let name = DistinguishedName::common_name("ec self-signed");
let cert =
Certificate::self_signed_general(&signer, &name, &validity(), 1, true, &[]).unwrap();
assert_eq!(cert.subject().unwrap(), name);
assert_eq!(cert.issuer().unwrap(), name);
let key = cert.subject_public_key().unwrap();
assert!(matches!(key, crate::x509::AnyPublicKey::Ecdsa(_)));
cert.verify_signature_with(&key).unwrap();
cert.check_well_formed().unwrap();
}
#[test]
fn validity_and_well_formed() {
let key = rsa_test_key_a();
let cert = Certificate::self_signed(
&key,
&DistinguishedName::common_name("x"),
&validity(),
1,
false,
)
.unwrap();
cert.check_well_formed().unwrap();
let v = cert.validity().unwrap();
assert_eq!(v.not_before, Time::utc(2024, 1, 1, 0, 0, 0));
assert!(v.accepts(&Time::utc(2026, 5, 26, 0, 0, 0)));
assert!(cert.subject_alt_names().unwrap().is_empty());
}
#[test]
fn subject_alt_name_roundtrip() {
let key = rsa_test_key_a();
let cert = Certificate::self_signed_with_sans(
&key,
&DistinguishedName::common_name("ignored-cn"),
&validity(),
1,
false,
&["example.com", "*.example.com", "localhost"],
)
.unwrap();
cert.check_well_formed().unwrap();
assert_eq!(
cert.subject_alt_names().unwrap(),
["example.com", "*.example.com", "localhost"]
);
}
const OPENSSL_P384_CERT: &str = "-----BEGIN CERTIFICATE-----\n\
MIIB0DCCAVagAwIBAgIUFfDkHLsaJs8XpNp/X26eBwaW0GwwCgYIKoZIzj0EAwMw\n\
HzEdMBsGA1UEAwwUcHVyZWNyeXB0byBwMzg0IHRlc3QwHhcNMjYwNTI1MTc0MTA2\n\
WhcNMzYwNTIyMTc0MTA2WjAfMR0wGwYDVQQDDBRwdXJlY3J5cHRvIHAzODQgdGVz\n\
dDB2MBAGByqGSM49AgEGBSuBBAAiA2IABAaRzx9xN0xEjH+XylpvGcNzgATGjcJ5\n\
EG6ZcuaFhG77H9Mt9FZkSDSgExkKfyw4Ux+FucZyuqi/R1HAhvZQbsfDESwSzKaX\n\
eta82AAFlW21rNICGPlnbgcBWHdPRW75T6NTMFEwHQYDVR0OBBYEFMcuZqhquDSL\n\
HxHY+LHcSb+Q7JhiMB8GA1UdIwQYMBaAFMcuZqhquDSLHxHY+LHcSb+Q7JhiMA8G\n\
A1UdEwEB/wQFMAMBAf8wCgYIKoZIzj0EAwMDaAAwZQIxANhANplvbyG3UYpPKRBw\n\
zonaqEOgq726vkmse4rPtI3e2qssKRgyBnJ7eK3aw/QtZAIwN67oHB6vv9uYce3C\n\
ychU4nzuraYi2jNpgZhSF+plk2mEygHvRKTdSsvVFUfuVRIu\n\
-----END CERTIFICATE-----\n";
#[test]
fn issue_with_extensions_round_trip() {
use crate::ec::{BoxedEcdsaPrivateKey, CurveId};
use crate::rng::HmacDrbg;
use crate::x509::extension::{
KeyUsageBits, basic_constraints, extended_key_usage, key_usage,
};
let mut rng = HmacDrbg::<crate::hash::Sha256>::new(b"ext-test", b"n", &[]);
let key = BoxedEcdsaPrivateKey::generate(CurveId::P256, &mut rng);
let signer = crate::x509::CertSigner::Ecdsa(&key);
let name = DistinguishedName::common_name("ext-cert");
let exts = [
basic_constraints(true, Some(0)),
key_usage(KeyUsageBits::KEY_CERT_SIGN | KeyUsageBits::CRL_SIGN),
extended_key_usage(&[oid::ID_KP_SERVER_AUTH]),
];
let cert = Certificate::self_signed_with_extensions(&signer, &name, &validity(), 1, &exts)
.unwrap();
cert.check_well_formed().unwrap();
let bc = cert.basic_constraints().unwrap().unwrap();
assert_eq!(bc, (true, Some(0)));
let ku = cert.key_usage().unwrap().unwrap();
assert_eq!(ku, (KeyUsageBits::KEY_CERT_SIGN | KeyUsageBits::CRL_SIGN).0);
let eku = cert.extended_key_usages().unwrap();
assert_eq!(eku, vec![oid::ID_KP_SERVER_AUTH.to_vec()]);
let all = cert.extensions().unwrap();
assert_eq!(all.len(), 3);
assert_eq!(all[0].oid, oid::BASIC_CONSTRAINTS);
assert_eq!(all[1].oid, oid::KEY_USAGE);
assert_eq!(all[2].oid, oid::EXT_KEY_USAGE);
let pk = cert.subject_public_key().unwrap();
cert.verify_signature_with(&pk).unwrap();
}
#[test]
fn key_usage_rejects_invalid_unused_bits() {
use crate::der::{encode_tlv, tag};
use crate::ec::{BoxedEcdsaPrivateKey, CurveId};
use crate::rng::HmacDrbg;
use crate::x509::extension::Extension;
let mut rng = HmacDrbg::<crate::hash::Sha256>::new(b"ku-bad", b"n", &[]);
let key = BoxedEcdsaPrivateKey::generate(CurveId::P256, &mut rng);
let signer = crate::x509::CertSigner::Ecdsa(&key);
let name = DistinguishedName::common_name("ku-bad");
let bad_bs = encode_tlv(tag::BIT_STRING, &[0xff, 0x80]);
let ext = Extension {
oid: oid::KEY_USAGE.to_vec(),
critical: true,
value: bad_bs,
};
let cert = Certificate::self_signed_with_extensions(&signer, &name, &validity(), 1, &[ext])
.unwrap();
assert!(matches!(cert.key_usage(), Err(Error::Malformed)));
}
#[test]
fn parse_and_verify_openssl_p384_cert() {
let cert = Certificate::from_pem(OPENSSL_P384_CERT).unwrap();
cert.check_well_formed().unwrap();
let key = cert.subject_public_key().unwrap();
match &key {
crate::x509::AnyPublicKey::Ecdsa(k) => {
assert_eq!(k.curve(), crate::ec::CurveId::P384)
}
_ => panic!("expected ECDSA P-384"),
}
cert.verify_signature_with(&key).unwrap();
}
#[test]
fn cert_rejects_intra_sequence_trailing_bytes() {
use crate::der::{encode_sequence, encode_tlv};
let key = rsa_test_key_a();
let good = Certificate::self_signed(
&key,
&DistinguishedName::common_name("trail.example"),
&validity(),
1,
false,
)
.unwrap();
let mut outer = Reader::new(good.to_der());
let mut seq = outer.read_sequence().unwrap();
let tbs = seq.read_element().unwrap();
let algid = seq.read_element().unwrap();
let sig_bit = seq.read_element().unwrap();
let trailer = encode_tlv(0x01, &[0x00]);
let mut body = alloc::vec::Vec::new();
body.extend_from_slice(tbs);
body.extend_from_slice(algid);
body.extend_from_slice(sig_bit);
body.extend_from_slice(&trailer);
let tampered_der = encode_sequence(&body);
let tampered = Certificate::from_der(tampered_der).unwrap();
assert!(tampered.subject().is_err());
assert!(tampered.signature_algorithm_oid().is_err());
}
fn san_with_dns(value: &[u8]) -> alloc::vec::Vec<u8> {
let mut entry = alloc::vec![0x82u8];
assert!(value.len() < 128, "test helper assumes short value");
entry.push(value.len() as u8);
entry.extend_from_slice(value);
let mut seq = alloc::vec![0x30u8, entry.len() as u8];
seq.extend_from_slice(&entry);
seq
}
#[test]
fn san_dns_parser_rejects_embedded_nul() {
let der = san_with_dns(b"victim.example\x00attacker.example");
let mut out = alloc::vec::Vec::new();
assert!(super::parse_dns_names(&der, &mut out).is_err());
}
#[test]
fn san_dns_parser_rejects_control_characters() {
for bad in [
b"victim.example\nattacker.example".as_slice(),
b"victim.example\rinjection".as_slice(),
b"victim.example\x01ctrl".as_slice(),
b"victim.example\x7fdel".as_slice(),
] {
let der = san_with_dns(bad);
let mut out = alloc::vec::Vec::new();
assert!(
super::parse_dns_names(&der, &mut out).is_err(),
"should reject {bad:?}"
);
}
}
#[test]
fn san_dns_parser_rejects_empty_entry() {
let der = san_with_dns(b"");
let mut out = alloc::vec::Vec::new();
assert!(super::parse_dns_names(&der, &mut out).is_err());
}
#[test]
fn san_dns_parser_rejects_ipv4_literal() {
for bad in [
b"10.0.0.1".as_slice(),
b"127.0.0.1".as_slice(),
b"255.255.255.255".as_slice(),
b"0.0.0.0".as_slice(),
] {
let der = san_with_dns(bad);
let mut out = alloc::vec::Vec::new();
assert!(
super::parse_dns_names(&der, &mut out).is_err(),
"should reject {bad:?}"
);
}
}
#[test]
fn san_dns_parser_rejects_ipv6_literal() {
for bad in [
b"::1".as_slice(),
b"2001:db8::1".as_slice(),
b"::ffff:10.0.0.1".as_slice(),
b"fe80::1".as_slice(),
] {
let der = san_with_dns(bad);
let mut out = alloc::vec::Vec::new();
assert!(
super::parse_dns_names(&der, &mut out).is_err(),
"should reject {bad:?}"
);
}
}
#[test]
fn san_dns_parser_accepts_normal_names() {
for ok in [
b"example.com".as_slice(),
b"www.example.com".as_slice(),
b"*.example.com".as_slice(),
b"xn--bcher-kva.example.de".as_slice(), b"v6.example.com".as_slice(),
b"10.example".as_slice(), ] {
let der = san_with_dns(ok);
let mut out = alloc::vec::Vec::new();
super::parse_dns_names(&der, &mut out).expect("should accept");
assert_eq!(
out.last().map(String::as_str),
Some(core::str::from_utf8(ok).unwrap())
);
}
}
fn san_with_ip(bytes: &[u8]) -> alloc::vec::Vec<u8> {
let mut entry = alloc::vec![0x87u8, bytes.len() as u8];
entry.extend_from_slice(bytes);
let mut seq = alloc::vec![0x30u8, entry.len() as u8];
seq.extend_from_slice(&entry);
seq
}
#[test]
fn san_ip_parser_accepts_v4_and_v6() {
let v4_der = san_with_ip(&[10, 0, 0, 1]);
let mut out = alloc::vec::Vec::new();
super::parse_ip_addresses(&v4_der, &mut out).unwrap();
assert_eq!(out, alloc::vec![super::SanIp::V4([10, 0, 0, 1])]);
let mut v6 = [0u8; 16];
v6[..2].copy_from_slice(&[0x20, 0x01]); v6[2..4].copy_from_slice(&[0x0d, 0xb8]);
v6[15] = 1;
let v6_der = san_with_ip(&v6);
let mut out = alloc::vec::Vec::new();
super::parse_ip_addresses(&v6_der, &mut out).unwrap();
assert_eq!(out, alloc::vec![super::SanIp::V6(v6)]);
}
#[test]
fn san_ip_parser_rejects_ipv4_mapped_ipv6() {
let mut bytes = [0u8; 16];
bytes[10] = 0xff;
bytes[11] = 0xff;
bytes[12] = 10;
bytes[13] = 0;
bytes[14] = 0;
bytes[15] = 1;
let der = san_with_ip(&bytes);
let mut out = alloc::vec::Vec::new();
assert!(super::parse_ip_addresses(&der, &mut out).is_err());
}
#[test]
fn san_ip_parser_rejects_wrong_length() {
let der = san_with_ip(&[10, 0, 0, 1, 99]);
let mut out = alloc::vec::Vec::new();
assert!(super::parse_ip_addresses(&der, &mut out).is_err());
}
}