use x509_cert::der::Decode;
use crate::data::{Date, RsaPublicKey, malformed};
use crate::error::Result;
#[derive(Clone)]
pub struct Certificate {
der: Vec<u8>,
inner: x509_cert::Certificate,
}
impl Certificate {
pub fn parse(der: &[u8]) -> Result<Self> {
let inner = x509_cert::Certificate::from_der(der)
.map_err(|e| malformed(&format!("not a DER X.509 certificate: {e}")))?;
Ok(Certificate {
der: der.to_vec(),
inner,
})
}
pub fn der(&self) -> &[u8] {
&self.der
}
pub fn inner(&self) -> &x509_cert::Certificate {
&self.inner
}
pub fn public_key(&self) -> Result<RsaPublicKey> {
let bits = self
.inner
.tbs_certificate()
.subject_public_key_info()
.subject_public_key
.as_bytes()
.ok_or_else(|| malformed("subject public key is not a whole number of bytes"))?;
use rsa::pkcs1::DecodeRsaPublicKey as _;
use rsa::traits::PublicKeyParts as _;
let key = rsa::RsaPublicKey::from_pkcs1_der(bits)
.map_err(|_| malformed("subject public key is not an RSA key"))?;
Ok(RsaPublicKey {
exponent: key.e().to_bytes_be(),
modulus: key.n().to_bytes_be(),
})
}
pub fn subject(&self) -> String {
self.inner.tbs_certificate().subject().to_string()
}
pub fn issuer(&self) -> String {
self.inner.tbs_certificate().issuer().to_string()
}
pub fn serial_number(&self) -> Vec<u8> {
self.inner
.tbs_certificate()
.serial_number()
.as_bytes()
.to_vec()
}
pub fn validity(&self) -> (Date, Date) {
let v = self.inner.tbs_certificate().validity();
(
Date::from_unix_seconds(v.not_before.to_unix_duration().as_secs() as i64),
Date::from_unix_seconds(v.not_after.to_unix_duration().as_secs() as i64),
)
}
pub fn is_valid_on(&self, date: Date) -> bool {
let (from, to) = self.validity();
from <= date && date <= to
}
pub fn signature_algorithm(&self) -> String {
self.inner.signature_algorithm().oid.to_string()
}
}
pub mod roots {
use super::Certificate;
use crate::error::Result;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Hierarchy {
Production,
Test,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Accept {
ProductionOnly,
ProductionAndTest,
}
impl Accept {
fn allows(self, hierarchy: Hierarchy) -> bool {
self == Accept::ProductionAndTest || hierarchy == Hierarchy::Production
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Purpose {
UserAuthentication,
DigitalSignature,
}
#[derive(Debug, Clone, Copy)]
pub struct Root {
pub purpose: Purpose,
pub hierarchy: Hierarchy,
pub generation: Option<u8>,
pub der: &'static [u8],
}
impl Root {
pub fn certificate(&self) -> Result<Certificate> {
Certificate::parse(self.der)
}
}
pub const KNOWN: &[Root] = &[
Root {
purpose: Purpose::UserAuthentication,
hierarchy: Hierarchy::Production,
generation: Some(1),
der: include_bytes!("../certs/authca01.cer"),
},
Root {
purpose: Purpose::UserAuthentication,
hierarchy: Hierarchy::Production,
generation: Some(2),
der: include_bytes!("../certs/authca02.cer"),
},
Root {
purpose: Purpose::UserAuthentication,
hierarchy: Hierarchy::Production,
generation: Some(3),
der: include_bytes!("../certs/authca03.cer"),
},
Root {
purpose: Purpose::DigitalSignature,
hierarchy: Hierarchy::Production,
generation: Some(1),
der: include_bytes!("../certs/signca01.cer"),
},
Root {
purpose: Purpose::DigitalSignature,
hierarchy: Hierarchy::Production,
generation: Some(2),
der: include_bytes!("../certs/signca02.cer"),
},
Root {
purpose: Purpose::DigitalSignature,
hierarchy: Hierarchy::Production,
generation: Some(3),
der: include_bytes!("../certs/signca03.cer"),
},
Root {
purpose: Purpose::UserAuthentication,
hierarchy: Hierarchy::Test,
generation: None,
der: include_bytes!("../certs/test/authca-test-2019.cer"),
},
Root {
purpose: Purpose::UserAuthentication,
hierarchy: Hierarchy::Test,
generation: None,
der: include_bytes!("../certs/test/authca-test-2023.cer"),
},
Root {
purpose: Purpose::DigitalSignature,
hierarchy: Hierarchy::Test,
generation: None,
der: include_bytes!("../certs/test/signca-test-2019.cer"),
},
Root {
purpose: Purpose::DigitalSignature,
hierarchy: Hierarchy::Test,
generation: None,
der: include_bytes!("../certs/test/signca-test-2024.cer"),
},
];
pub fn issuer_of(cert: &Certificate, accept: Accept) -> Result<Certificate> {
let issuer = cert.issuer();
for root in KNOWN.iter().filter(|r| accept.allows(r.hierarchy)) {
let candidate = root.certificate()?;
if candidate.subject() == issuer && cert.verify_signature(&candidate).is_ok() {
return Ok(candidate);
}
}
Err(crate::Error::SignatureInvalid(
"no root this crate carries signed this certificate",
))
}
}
impl std::fmt::Debug for Certificate {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let (from, to) = self.validity();
f.debug_struct("Certificate")
.field("subject", &self.subject())
.field("issuer", &self.issuer())
.field("validity", &format!("{from} .. {to}"))
.field("der_len", &self.der.len())
.finish()
}
}
#[cfg(feature = "verify")]
impl Certificate {
pub fn verify_to_root(&self, on: Date, accept: roots::Accept) -> Result<()> {
let root = roots::issuer_of(self, accept)?;
Certificate::verify_chain(&[self.clone(), root], on)
}
pub fn verify_chain(chain: &[Certificate], on: Date) -> Result<()> {
let (leaf, rest) = chain
.split_first()
.ok_or_else(|| malformed("an empty chain verifies nothing"))?;
let mut subject = leaf;
if !subject.is_valid_on(on) {
let (from, to) = subject.validity();
return Err(malformed(&format!(
"{} is valid {from} to {to}, not on {on}",
subject.subject()
)));
}
for issuer in rest {
if subject.issuer() != issuer.subject() {
return Err(malformed(&format!(
"chain is broken: {:?} names issuer {:?}, next is {:?}",
subject.subject(),
subject.issuer(),
issuer.subject()
)));
}
if !issuer.is_valid_on(on) {
let (from, to) = issuer.validity();
return Err(malformed(&format!(
"{} is valid {from} to {to}, not on {on}",
issuer.subject()
)));
}
subject.verify_signature(issuer)?;
subject = issuer;
}
Ok(())
}
pub fn verify_signature(&self, issuer: &Certificate) -> Result<()> {
use x509_cert::der::Encode as _;
const SHA256_WITH_RSA: &str = "1.2.840.113549.1.1.11";
if self.signature_algorithm() != SHA256_WITH_RSA {
return Err(crate::Error::SignatureInvalid(
"only sha256WithRSAEncryption is checked",
));
}
let tbs = self
.inner
.tbs_certificate()
.to_der()
.map_err(|_| malformed("re-encoding the TBSCertificate failed"))?;
let signature = self
.inner
.signature()
.as_bytes()
.ok_or_else(|| malformed("signature is not a whole number of bytes"))?;
issuer.public_key()?.verify_pkcs1_sha256(&tbs, signature)
}
}