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))?; }
let body = seq.read_unsigned_integer_bytes()?;
let magnitude_len = match body {
[0x00, rest @ ..] if !rest.is_empty() => rest.len(),
other => other.len(),
};
if magnitude_len > 20 {
return Err(Error::Malformed);
}
Ok(body)
}
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> {
super::AnyPublicKey::from_spki_der(self.spki_der()?)
}
pub fn spki_der(&self) -> Result<&[u8], Error> {
let mut seq = self.tbs_after_algid()?;
DistinguishedName::decode(&mut seq)?; Validity::decode(&mut seq)?; DistinguishedName::decode(&mut seq)?; Ok(seq.read_element()?) }
#[allow(dead_code)]
pub(crate) fn subject_public_key_bits(&self) -> Result<&[u8], 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()?; let mut spki_r = Reader::new(spki);
let mut spki_seq = spki_r.read_sequence()?;
spki_seq.read_sequence()?; Ok(spki_seq.read_bit_string()?)
}
pub fn verify_signature_with(&self, issuer: &super::AnyPublicKey) -> Result<(), Error> {
self.check_signature_algid_consistent()?;
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 name_constraints(&self) -> Result<Option<NameConstraints>, Error> {
let mut out: Option<NameConstraints> = None;
self.walk_extensions(|id, _critical, value| {
if id == oid::NAME_CONSTRAINTS {
out = Some(parse_name_constraints(value)?);
}
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..];
if let Some(&last) = bytes.last() {
if unused > 0 && (last & ((1u8 << unused) - 1)) != 0 {
return Err(Error::Malformed);
}
} else if unused != 0 {
return Err(Error::Malformed);
}
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 tbs_bytes = self.parts()?.tbs;
let version = {
let mut outer = Reader::new(tbs_bytes);
let mut seq = outer.read_sequence()?;
if seq.peek_tag() == Some(tag::context(0)) {
let body = seq.read_tlv(tag::context(0))?;
let mut vr = Reader::new(body);
let v = vr.read_integer_bytes()?;
vr.finish()?;
match v {
[0] => 1u8,
[1] => 2u8,
[2] => 3u8,
_ => return Err(Error::Malformed),
}
} else {
1u8
}
};
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(());
}
if version < 3 {
return Err(Error::Malformed);
}
let wrapper = seq.read_tlv(tag::context(3))?;
let mut outer = Reader::new(wrapper);
let mut exts = outer.read_sequence()?;
let mut seen: Vec<Vec<u64>> = Vec::new();
while !exts.is_empty() {
let mut ext = exts.read_sequence()?;
let id = parse_oid(ext.read_oid()?)?;
if seen.iter().any(|prior| prior.as_slice() == id.as_slice()) {
return Err(Error::Malformed);
}
seen.push(id.clone());
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)
}
#[derive(Clone, Debug, Default)]
pub struct NameConstraints {
pub permitted_dns: Vec<String>,
pub excluded_dns: Vec<String>,
pub permitted_ip: Vec<(Vec<u8>, Vec<u8>)>,
pub excluded_ip: Vec<(Vec<u8>, Vec<u8>)>,
pub has_unenforceable_permitted: bool,
pub has_unenforceable_excluded: bool,
}
fn parse_name_constraints(value: &[u8]) -> Result<NameConstraints, Error> {
let mut out = NameConstraints::default();
let mut r = Reader::new(value);
let mut seq = r.read_sequence()?;
if seq.peek_tag() == Some(tag::context(0)) {
let body = seq.read_tlv(tag::context(0))?;
parse_subtrees(
body,
&mut out.permitted_dns,
&mut out.permitted_ip,
&mut out.has_unenforceable_permitted,
)?;
}
if seq.peek_tag() == Some(tag::context(1)) {
let body = seq.read_tlv(tag::context(1))?;
parse_subtrees(
body,
&mut out.excluded_dns,
&mut out.excluded_ip,
&mut out.has_unenforceable_excluded,
)?;
}
seq.finish()?;
r.finish()?;
Ok(out)
}
fn is_contiguous_cidr_mask(mask: &[u8]) -> bool {
let mut seen_zero = false;
for &b in mask {
if seen_zero && b != 0 {
return false;
}
match b {
0x00 => seen_zero = true,
0xff => {}
other => {
seen_zero = true;
let inv = !other;
if inv & (inv.wrapping_add(1)) != 0 {
return false;
}
}
}
}
true
}
fn parse_subtrees(
body: &[u8],
dns_out: &mut Vec<String>,
ip_out: &mut Vec<(Vec<u8>, Vec<u8>)>,
unenforceable: &mut bool,
) -> Result<(), Error> {
let mut r = Reader::new(body);
while !r.is_empty() {
let mut subtree = r.read_sequence()?;
let (t, value) = subtree.read_any()?;
match 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)?;
dns_out.push(String::from(s));
}
0x87 => match value.len() {
8 | 32 => {
let half = value.len() / 2;
let mask = &value[half..];
if !is_contiguous_cidr_mask(mask) {
return Err(Error::Malformed);
}
ip_out.push((value[..half].to_vec(), mask.to_vec()));
}
_ => return Err(Error::Malformed),
},
_ => {
*unenforceable = true;
}
}
if !subtree.is_empty() {
return Err(Error::Malformed);
}
}
Ok(())
}
#[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 spki_der_matches_openssl_pin() {
use crate::hash::Digest;
const CERT_PEM: &str = "-----BEGIN CERTIFICATE-----\n\
MIIBgTCCASegAwIBAgIUaMKOLEGL7hmOfuMyATVi+TgNxSIwCgYIKoZIzj0EAwIw\n\
FjEUMBIGA1UEAwwLcGluLmV4YW1wbGUwHhcNMjYwNjA4MDk1MTMxWhcNMzYwNjA1\n\
MDk1MTMxWjAWMRQwEgYDVQQDDAtwaW4uZXhhbXBsZTBZMBMGByqGSM49AgEGCCqG\n\
SM49AwEHA0IABGhf2FQmn+VA2jfcICCeVevaiFBlw7EwWpX69NE1DRxUlva+zITd\n\
OLZSDu3v3Yro/59okcjXp++BtPhGdcGbo0ajUzBRMB0GA1UdDgQWBBQ5i0J8evfA\n\
qxirsPn/c4VFmp6HdDAfBgNVHSMEGDAWgBQ5i0J8evfAqxirsPn/c4VFmp6HdDAP\n\
BgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMCA0gAMEUCIQCVcvEwWVzOio4YkRpj\n\
Y1NEr6FN2fy1H0zXF9zOOKr4uwIgDxEtUqWUho9z6vzcpmKVCSk8QInuvwkVR40o\n\
tvee2FY=\n\
-----END CERTIFICATE-----\n";
let expected_pin: [u8; 32] = [
0x56, 0x8b, 0x4e, 0x3b, 0x17, 0x91, 0x8f, 0x83, 0x05, 0xca, 0x99, 0x7a, 0x0e, 0x77,
0xd8, 0x67, 0xb3, 0x48, 0x52, 0x00, 0x7d, 0xf5, 0xdb, 0xf2, 0x43, 0x1c, 0x04, 0x90,
0x1a, 0xe6, 0x22, 0x39,
];
let cert = Certificate::from_pem(CERT_PEM).unwrap();
let spki = cert.spki_der().unwrap();
assert_eq!(spki[0], 0x30, "spki_der starts with the SEQUENCE tag");
assert!(cert.subject_public_key().is_ok());
assert_eq!(
Sha256::digest(spki).as_ref(),
&expected_pin,
"SHA-256(spki_der) must match the OpenSSL/curl public-key pin",
);
}
#[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 verify_signature_with_rejects_inner_outer_algid_mismatch() {
use crate::der::{encode_bit_string, encode_integer, encode_sequence};
let key = rsa_test_key_a();
let name = DistinguishedName::common_name("algid mismatch");
let spki = rsa_spki(&key.public_key());
let inner_algid = algorithm_identifier(oid::SHA256_WITH_RSA, true);
let mut body = alloc::vec::Vec::new();
body.extend_from_slice(&encode_integer(&1u64.to_be_bytes())); body.extend_from_slice(&inner_algid);
body.extend_from_slice(&name.to_der());
body.extend_from_slice(&validity().to_der());
body.extend_from_slice(&name.to_der());
body.extend_from_slice(&spki);
let tbs = encode_sequence(&body);
let sig = key.sign_pkcs1v15::<Sha256>(&tbs).unwrap();
let outer_algid = algorithm_identifier(oid::SHA1_WITH_RSA, true);
let der = encode_sequence(&[tbs, outer_algid, encode_bit_string(&sig)].concat());
let cert = Certificate::from_der(der).unwrap();
let pub_key = cert.subject_public_key().unwrap();
assert!(matches!(
cert.check_signature_algid_consistent(),
Err(Error::Malformed)
));
assert!(matches!(
cert.verify_signature_with(&pub_key),
Err(Error::Malformed)
));
}
#[test]
fn ed448_self_signed_and_chain() {
use crate::ec::Ed448PrivateKey;
use crate::rng::HmacDrbg;
let mut rng = HmacDrbg::<crate::hash::Sha256>::new(b"ed448-ca", b"n", &[]);
let ca_key = Ed448PrivateKey::generate(&mut rng);
let ca_signer = crate::x509::CertSigner::Ed448(&ca_key);
let ca_name = DistinguishedName::common_name("ed448 root ca");
let ca_cert =
Certificate::self_signed_general(&ca_signer, &ca_name, &validity(), 1, true, &[])
.unwrap();
let ca_pub = ca_cert.subject_public_key().unwrap();
assert!(matches!(ca_pub, crate::x509::AnyPublicKey::Ed448(_)));
ca_cert.verify_signature_with(&ca_pub).unwrap();
ca_cert.check_well_formed().unwrap();
assert_eq!(
ca_cert.signature_algorithm_oid().unwrap().as_slice(),
oid::ID_ED448
);
let leaf_key = Ed448PrivateKey::generate(&mut rng);
let leaf_name = DistinguishedName::common_name("ed448 leaf");
let leaf_pub = crate::x509::AnyPublicKey::Ed448(leaf_key.public_key());
let leaf_cert = Certificate::issue_general(
&ca_signer,
&ca_name,
&leaf_name,
&leaf_pub,
&validity(),
2,
false,
&["ed448.example"],
)
.unwrap();
leaf_cert.verify_signature_with(&ca_pub).unwrap();
leaf_cert.check_well_formed().unwrap();
assert_eq!(leaf_cert.issuer().unwrap(), ca_name);
assert_eq!(leaf_cert.subject().unwrap(), leaf_name);
assert!(leaf_cert.verify_signature_with(&leaf_pub).is_err());
}
#[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 key_usage_rejects_nonzero_unused_trailing_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-trailing", b"n", &[]);
let key = BoxedEcdsaPrivateKey::generate(CurveId::P256, &mut rng);
let signer = crate::x509::CertSigner::Ecdsa(&key);
let name = DistinguishedName::common_name("ku-trailing");
let bad_bs = encode_tlv(tag::BIT_STRING, &[0x01, 0x81]);
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)));
let good_bs = encode_tlv(tag::BIT_STRING, &[0x01, 0x80]);
let ext = Extension {
oid: oid::KEY_USAGE.to_vec(),
critical: true,
value: good_bs,
};
let cert = Certificate::self_signed_with_extensions(&signer, &name, &validity(), 1, &[ext])
.unwrap();
assert_eq!(cert.key_usage().unwrap(), Some(0x80));
}
#[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());
}
#[test]
fn cidr_mask_accepts_canonical_and_rejects_non_contiguous() {
for n in 0..=32u32 {
let mut mask = [0u8; 4];
let mut bits = n;
for byte in mask.iter_mut() {
let take = bits.min(8);
*byte = if take == 0 { 0 } else { 0xffu8 << (8 - take) };
bits -= take;
}
assert!(
super::is_contiguous_cidr_mask(&mask),
"rejected canonical /{n}: {mask:02x?}"
);
}
assert!(!super::is_contiguous_cidr_mask(&[0xff, 0x00, 0xff, 0x00]));
assert!(!super::is_contiguous_cidr_mask(&[0x80, 0x80, 0x00, 0x00]));
assert!(!super::is_contiguous_cidr_mask(&[0xfe, 0xff, 0xff, 0xff])); assert!(!super::is_contiguous_cidr_mask(&[0xff, 0xff, 0x0f, 0x00]));
}
#[test]
fn name_constraints_rejects_non_contiguous_ip_mask() {
use crate::der::encode_sequence;
let bad = [10u8, 0, 0, 0, 0xff, 0x00, 0xff, 0x00];
let mut ip_tlv = alloc::vec::Vec::new();
ip_tlv.push(0x87);
ip_tlv.push(bad.len() as u8);
ip_tlv.extend_from_slice(&bad);
let subtree = encode_sequence(&ip_tlv);
let mut perm = alloc::vec::Vec::new();
perm.push(0xA0); perm.push(subtree.len() as u8);
perm.extend_from_slice(&subtree);
let body = encode_sequence(&perm);
assert!(super::parse_name_constraints(&body).is_err());
}
fn forge_cert_with_version_and_exts(version: u8, exts: &[Extension]) -> Certificate {
use crate::der::{encode_bit_string, encode_context, encode_integer, encode_sequence};
let key = rsa_test_key_a();
let name = DistinguishedName::common_name("forged");
let algid = algorithm_identifier(oid::SHA256_WITH_RSA, true);
let spki = rsa_spki(&key.public_key());
let mut body = alloc::vec::Vec::new();
body.extend_from_slice(&encode_context(0, &encode_integer(&[version])));
body.extend_from_slice(&encode_integer(&1u64.to_be_bytes()));
body.extend_from_slice(&algid);
body.extend_from_slice(&name.to_der());
body.extend_from_slice(&validity().to_der());
body.extend_from_slice(&name.to_der());
body.extend_from_slice(&spki);
body.extend_from_slice(&extension::encode_extensions_field(exts));
let tbs = encode_sequence(&body);
let sig = key.sign_pkcs1v15::<Sha256>(&tbs).unwrap();
let der = encode_sequence(&[tbs, algid, encode_bit_string(&sig)].concat());
Certificate { der }
}
#[test]
fn v1_cert_with_extensions_is_rejected() {
let ext = extension::basic_constraints(true, None);
let cert = forge_cert_with_version_and_exts(0, &[ext]);
assert!(matches!(cert.subject_alt_names(), Err(Error::Malformed)));
assert!(matches!(cert.basic_constraints(), Err(Error::Malformed)));
assert!(matches!(cert.extensions(), Err(Error::Malformed)));
assert!(matches!(cert.check_well_formed(), Err(Error::Malformed)));
}
#[test]
fn v2_cert_with_extensions_is_rejected() {
let ext = extension::basic_constraints(false, None);
let cert = forge_cert_with_version_and_exts(1, &[ext]);
assert!(matches!(cert.subject_alt_names(), Err(Error::Malformed)));
assert!(matches!(cert.basic_constraints(), Err(Error::Malformed)));
assert!(matches!(cert.extensions(), Err(Error::Malformed)));
assert!(matches!(cert.check_well_formed(), Err(Error::Malformed)));
}
#[test]
fn v3_cert_with_extensions_is_accepted() {
let ext = extension::basic_constraints(true, None);
let cert = forge_cert_with_version_and_exts(2, &[ext]);
cert.check_well_formed().unwrap();
let bc = cert.basic_constraints().unwrap().unwrap();
assert_eq!(bc, (true, None));
}
#[test]
fn serial_with_21_octet_magnitude_is_rejected() {
use crate::der::{encode_bit_string, encode_context, encode_integer, encode_sequence};
let key = rsa_test_key_a();
let name = DistinguishedName::common_name("serial-test");
let algid = algorithm_identifier(oid::SHA256_WITH_RSA, true);
let spki = rsa_spki(&key.public_key());
let serial_body = [0x01u8; 21];
let mut body = alloc::vec::Vec::new();
body.extend_from_slice(&encode_context(0, &encode_integer(&[2])));
body.extend_from_slice(&encode_integer(&serial_body));
body.extend_from_slice(&algid);
body.extend_from_slice(&name.to_der());
body.extend_from_slice(&validity().to_der());
body.extend_from_slice(&name.to_der());
body.extend_from_slice(&spki);
body.extend_from_slice(&extension::encode_extensions_field(&[
extension::basic_constraints(false, None),
]));
let tbs = encode_sequence(&body);
let sig = key.sign_pkcs1v15::<Sha256>(&tbs).unwrap();
let der = encode_sequence(&[tbs, algid, encode_bit_string(&sig)].concat());
let cert = Certificate { der };
assert!(matches!(cert.serial_bytes(), Err(Error::Malformed)));
}
#[test]
fn serial_with_20_octet_magnitude_and_leading_zero_is_accepted() {
use crate::der::{encode_bit_string, encode_context, encode_integer, encode_sequence};
let key = rsa_test_key_a();
let name = DistinguishedName::common_name("serial-test");
let algid = algorithm_identifier(oid::SHA256_WITH_RSA, true);
let spki = rsa_spki(&key.public_key());
let mut serial_body = [0xCCu8; 20];
serial_body[0] = 0x80;
let mut body = alloc::vec::Vec::new();
body.extend_from_slice(&encode_context(0, &encode_integer(&[2])));
body.extend_from_slice(&encode_integer(&serial_body));
body.extend_from_slice(&algid);
body.extend_from_slice(&name.to_der());
body.extend_from_slice(&validity().to_der());
body.extend_from_slice(&name.to_der());
body.extend_from_slice(&spki);
body.extend_from_slice(&extension::encode_extensions_field(&[
extension::basic_constraints(false, None),
]));
let tbs = encode_sequence(&body);
let sig = key.sign_pkcs1v15::<Sha256>(&tbs).unwrap();
let der = encode_sequence(&[tbs, algid, encode_bit_string(&sig)].concat());
let cert = Certificate { der };
let bytes = cert.serial_bytes().unwrap();
assert_eq!(bytes.len(), 21);
assert_eq!(bytes[0], 0x00);
assert_eq!(bytes[1], 0x80);
}
#[test]
fn negative_serial_is_rejected() {
use crate::der::{encode_bit_string, encode_context, encode_sequence, encode_tlv, tag};
let key = rsa_test_key_a();
let name = DistinguishedName::common_name("neg-serial");
let algid = algorithm_identifier(oid::SHA256_WITH_RSA, true);
let spki = rsa_spki(&key.public_key());
let neg = encode_tlv(tag::INTEGER, &[0xFF, 0x42]);
let mut body = alloc::vec::Vec::new();
body.extend_from_slice(&encode_context(0, &crate::der::encode_integer(&[2])));
body.extend_from_slice(&neg);
body.extend_from_slice(&algid);
body.extend_from_slice(&name.to_der());
body.extend_from_slice(&validity().to_der());
body.extend_from_slice(&name.to_der());
body.extend_from_slice(&spki);
body.extend_from_slice(&extension::encode_extensions_field(&[
extension::basic_constraints(false, None),
]));
let tbs = encode_sequence(&body);
let sig = key.sign_pkcs1v15::<Sha256>(&tbs).unwrap();
let der = encode_sequence(&[tbs, algid, encode_bit_string(&sig)].concat());
let cert = Certificate { der };
assert!(cert.serial_bytes().is_err());
}
#[test]
fn duplicate_extension_oid_is_rejected() {
let dup_a = extension::basic_constraints(true, Some(0));
let dup_b = extension::basic_constraints(false, None);
let cert = forge_cert_with_version_and_exts(2, &[dup_a, dup_b]);
assert!(matches!(cert.basic_constraints(), Err(Error::Malformed)));
assert!(matches!(cert.subject_alt_names(), Err(Error::Malformed)));
assert!(matches!(cert.extensions(), Err(Error::Malformed)));
assert!(matches!(cert.check_well_formed(), Err(Error::Malformed)));
}
#[test]
fn distinct_extensions_are_accepted() {
use crate::x509::extension::KeyUsageBits;
let exts = [
extension::basic_constraints(true, Some(0)),
extension::key_usage(KeyUsageBits::KEY_CERT_SIGN),
];
let cert = forge_cert_with_version_and_exts(2, &exts);
cert.check_well_formed().unwrap();
assert_eq!(cert.extensions().unwrap().len(), 2);
}
fn dns_subtree(name: &[u8]) -> alloc::vec::Vec<u8> {
let mut base = alloc::vec![0x82u8, name.len() as u8];
base.extend_from_slice(name);
let mut seq = alloc::vec![0x30u8, base.len() as u8];
seq.extend_from_slice(&base);
seq
}
#[test]
fn name_constraint_dns_subtree_rejects_control_chars() {
for bad in [
b"evil\x00.example".as_slice(),
b"evil\n.example".as_slice(),
b"".as_slice(),
] {
let body = dns_subtree(bad);
let mut dns = alloc::vec::Vec::new();
let mut ip = alloc::vec::Vec::new();
let mut unenforceable = false;
assert!(
super::parse_subtrees(&body, &mut dns, &mut ip, &mut unenforceable).is_err(),
"should reject {bad:?}"
);
}
}
#[test]
fn name_constraint_dns_subtree_accepts_normal_name() {
let body = dns_subtree(b".example.com");
let mut dns = alloc::vec::Vec::new();
let mut ip = alloc::vec::Vec::new();
let mut unenforceable = false;
super::parse_subtrees(&body, &mut dns, &mut ip, &mut unenforceable).unwrap();
assert_eq!(
dns,
alloc::vec![alloc::string::String::from(".example.com")]
);
}
}