use crate::courierust_tls::TlsError;
use alloc::string::String;
use alloc::vec::Vec;
#[derive(Debug, Clone)]
pub struct Certificate {
pub der: Vec<u8>,
pub tbs: Vec<u8>,
pub sig_alg: SigAlg,
pub signature: Vec<u8>,
pub spki: Spki,
pub serial: Vec<u8>,
pub issuer_der: Vec<u8>,
pub subject_der: Vec<u8>,
pub not_before: i64,
pub not_after: i64,
pub dns_names: Vec<String>,
pub ip_names: Vec<Vec<u8>>,
pub is_ca: Option<bool>,
pub key_usage: KeyUsage,
pub eku: Vec<Vec<u8>>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SigAlg {
RsaSha256,
RsaSha384,
RsaSha512,
EcdsaSha256,
EcdsaSha384,
Ed25519,
Unknown,
}
#[derive(Debug, Clone)]
pub struct Spki {
pub oid: Vec<u8>,
pub key: Vec<u8>,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct KeyUsage {
pub digital_signature: bool,
pub content_commitment: bool,
pub key_encipherment: bool,
pub data_encipherment: bool,
pub key_agreement: bool,
pub key_cert_sign: bool,
pub crl_sign: bool,
pub encipher_only: bool,
pub decipher_only: bool,
}
impl KeyUsage {
fn from_bits(bits: &[u8]) -> Self {
let mut u = Self::default();
if bits.is_empty() {
return u;
}
let b0 = bits[0];
let b1 = bits.get(1).copied().unwrap_or(0);
u.digital_signature = b0 & 0x80 != 0;
u.content_commitment = b0 & 0x40 != 0;
u.key_encipherment = b0 & 0x20 != 0;
u.data_encipherment = b0 & 0x10 != 0;
u.key_agreement = b0 & 0x08 != 0;
u.key_cert_sign = b0 & 0x04 != 0;
u.crl_sign = b0 & 0x02 != 0;
u.encipher_only = b0 & 0x01 != 0;
u.decipher_only = b1 & 0x80 != 0;
u
}
}
#[derive(Debug, Clone)]
pub struct RootCert {
pub der: Vec<u8>,
}
#[derive(Debug, Clone, Default)]
pub struct RootStore {
roots: Vec<RootCert>,
}
impl RootStore {
pub fn new() -> Self {
Self { roots: Vec::new() }
}
pub fn add_der(&mut self, der: Vec<u8>) {
self.roots.push(RootCert { der });
}
pub fn add_pem(&mut self, pem: &str) -> crate::courierust_tls::TlsResult<usize> {
let certs = parse_pem_certificates(pem)?;
let n = certs.len();
for c in certs {
self.roots.push(RootCert { der: c });
}
Ok(n)
}
pub fn len(&self) -> usize {
self.roots.len()
}
pub fn is_empty(&self) -> bool {
self.roots.is_empty()
}
pub fn roots(&self) -> &[RootCert] {
&self.roots
}
}
pub fn parse_pem_certificates(pem: &str) -> crate::courierust_tls::TlsResult<Vec<Vec<u8>>> {
let mut out = Vec::new();
let mut current: Option<Vec<u8>> = None;
let mut in_cert = false;
for line in pem.lines() {
let line = line.trim();
if line.starts_with("-----BEGIN CERTIFICATE-----") {
in_cert = true;
current = Some(Vec::new());
} else if line.starts_with("-----END CERTIFICATE-----") {
if let Some(mut b64) = current.take() {
let raw: String = b64
.drain(..)
.filter(|c| !c.is_ascii_whitespace())
.map(|c| c as char)
.collect();
let der = base64_decode(&raw)
.ok_or_else(|| TlsError::Protocol("invalid PEM base64".into()))?;
out.push(der);
}
in_cert = false;
} else if in_cert {
if let Some(buf) = current.as_mut() {
buf.extend_from_slice(line.as_bytes());
}
}
}
Ok(out)
}
fn base64_decode(s: &str) -> Option<Vec<u8>> {
let mut out = Vec::new();
let mut acc: u32 = 0;
let mut nbits = 0;
for c in s.bytes() {
let v = match c {
b'A'..=b'Z' => c - b'A',
b'a'..=b'z' => c - b'a' + 26,
b'0'..=b'9' => c - b'0' + 52,
b'+' => 62,
b'/' => 63,
b'=' => break,
_ => return None,
};
acc = (acc << 6) | v as u32;
nbits += 6;
if nbits >= 8 {
nbits -= 8;
out.push((acc >> nbits) as u8);
}
}
Some(out)
}
pub(crate) mod der;
pub fn parse_certificate(der: &[u8]) -> crate::courierust_tls::TlsResult<Certificate> {
der::parse_certificate(der)
}
pub fn hostname_matches(name: &str, dns_names: &[String], ip_names: &[Vec<u8>]) -> bool {
let name = name.trim_end_matches('.');
if let Ok(ip) = name.parse::<std::net::IpAddr>() {
let bytes = match ip {
std::net::IpAddr::V4(v4) => v4.octets().to_vec(),
std::net::IpAddr::V6(v6) => v6.octets().to_vec(),
};
return ip_names.contains(&bytes);
}
if dns_names.is_empty() {
return false;
}
let lower = name.to_ascii_lowercase();
for d in dns_names {
let d = d.trim_end_matches('.');
if dns_match(&lower, &d.to_ascii_lowercase()) {
return true;
}
}
false
}
fn dns_match(name: &str, pattern: &str) -> bool {
if name == pattern {
return true;
}
if !pattern.starts_with("*.") {
return false;
}
let suffix = &pattern[2..];
if suffix.contains('*') {
return false;
}
let Some(stripped) = name.strip_suffix(suffix) else {
return false;
};
if !stripped.ends_with('.') {
return false;
}
let label = &stripped[..stripped.len() - 1];
!label.is_empty() && !label.contains('.')
}
const EKU_SERVER_AUTH: &[u8] = &[0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x01];
const EKU_ANY: &[u8] = &[0x55, 0x1d, 0x25, 0x00];
pub fn has_server_auth_eku(cert: &Certificate) -> bool {
if cert.eku.is_empty() {
return true;
}
cert.eku
.iter()
.any(|e| e.as_slice() == EKU_SERVER_AUTH || e.as_slice() == EKU_ANY)
}
pub fn validate_chain(
roots: &RootStore,
chain: &[Vec<u8>],
now: i64,
) -> crate::courierust_tls::TlsResult<()> {
if chain.is_empty() {
return Err(TlsError::Certificate("empty certificate chain".into()));
}
let certs: Vec<Certificate> = chain
.iter()
.map(|d| der::parse_certificate(d))
.collect::<crate::courierust_tls::TlsResult<Vec<_>>>()?;
for (i, c) in certs.iter().enumerate() {
if now < c.not_before || now > c.not_after {
return Err(TlsError::Certificate(format!(
"certificate {} outside validity window",
i
)));
}
}
for i in 0..certs.len() - 1 {
let child = &certs[i];
let parent = &certs[i + 1];
if child.issuer_der != parent.subject_der {
return Err(TlsError::Certificate("issuer/subject name mismatch".into()));
}
if !verify_cert_signature(child, &parent.spki) {
return Err(TlsError::Certificate(
"certificate signature invalid".into(),
));
}
if child.is_ca != Some(true) {
return Err(TlsError::Certificate("intermediate is not a CA".into()));
}
if child.key_usage.digital_signature
&& !child.key_usage.key_cert_sign
&& !child.is_ca.is_none()
{
if !child.key_usage.key_cert_sign {
return Err(TlsError::Certificate("CA lacks keyCertSign".into()));
}
}
}
let last = certs.last().unwrap();
let anchored = {
let mut found = false;
for root in roots.roots() {
if root.der == last.der {
found = true;
break;
}
}
found
};
if !anchored {
let mut verified = false;
for root in roots.roots() {
let Ok(root_cert) = der::parse_certificate(&root.der) else {
continue;
};
if last.issuer_der != root_cert.subject_der {
continue;
}
if verify_cert_signature(last, &root_cert.spki) {
if root_cert.issuer_der == root_cert.subject_der {
verified = true;
break;
}
}
}
if !verified {
return Err(TlsError::Certificate("no trusted root found".into()));
}
} else if last.issuer_der != last.subject_der {
if !verify_cert_signature(last, &last.spki) {
return Err(TlsError::Certificate("root is not self-signed".into()));
}
}
if certs[0].is_ca == Some(true) && certs.len() == 1 {
if !anchored {
return Err(TlsError::Certificate("leaf is a CA".into()));
}
}
Ok(())
}
fn verify_cert_signature(cert: &Certificate, issuer: &Spki) -> bool {
use crate::courierust_tls::crypto::hash::{Digest, Sha256, Sha384};
use crate::courierust_tls::crypto::rsa::{
RsaPublicKey, DIGEST_INFO_SHA256, DIGEST_INFO_SHA384, DIGEST_INFO_SHA512,
};
use crate::courierust_tls::crypto::{ecdsa, ed25519};
use crate::courierust_tls::x509::der::{
parse_rsa_public_key, OID_EC_PUBLIC_KEY, OID_ED25519, OID_RSA_ENCRYPTION,
};
match cert.sig_alg {
SigAlg::RsaSha256 | SigAlg::RsaSha384 | SigAlg::RsaSha512 => {
if issuer.oid != OID_RSA_ENCRYPTION {
return false;
}
let Some((n, e)) = parse_rsa_public_key(&issuer.key) else {
return false;
};
let key = RsaPublicKey { n, e };
let digest = match cert.sig_alg {
SigAlg::RsaSha256 => {
let mut h = Sha256::new();
h.update(&cert.tbs);
h.finalize()
}
SigAlg::RsaSha384 => {
let mut h = Sha384::new();
h.update(&cert.tbs);
h.finalize()
}
_ => {
let mut h = Sha512Digest::create();
h.update(&cert.tbs);
h.finalize()
}
};
let digest_info = match cert.sig_alg {
SigAlg::RsaSha256 => DIGEST_INFO_SHA256,
SigAlg::RsaSha384 => DIGEST_INFO_SHA384,
_ => DIGEST_INFO_SHA512,
};
key.verify_pkcs1v15(digest_info, &digest, &cert.signature)
}
SigAlg::EcdsaSha256 | SigAlg::EcdsaSha384 => {
if issuer.oid != OID_EC_PUBLIC_KEY || issuer.key.len() != 65 || issuer.key[0] != 0x04 {
return false;
}
let mut qx = [0u8; 32];
let mut qy = [0u8; 32];
qx.copy_from_slice(&issuer.key[1..33]);
qy.copy_from_slice(&issuer.key[33..65]);
match cert.sig_alg {
SigAlg::EcdsaSha256 => {
let mut h = Sha256::new();
h.update(&cert.tbs);
let d = h.finalize();
ecdsa::verify_der(&qx, &qy, &d, &cert.signature)
}
_ => {
let mut h = Sha384::new();
h.update(&cert.tbs);
let d = h.finalize();
ecdsa::verify_der(&qx, &qy, &d, &cert.signature)
}
}
}
SigAlg::Ed25519 => {
if issuer.oid != OID_ED25519 || issuer.key.len() != 32 {
return false;
}
let mut pk = [0u8; 32];
pk.copy_from_slice(&issuer.key);
let mut sig = [0u8; 64];
if cert.signature.len() != 64 {
return false;
}
sig.copy_from_slice(&cert.signature);
ed25519::verify(&pk, &cert.tbs, &sig)
}
SigAlg::Unknown => false,
}
}
struct Sha512Digest;
impl Sha512Digest {
fn create() -> crate::courierust_tls::crypto::ed25519::Sha512 {
crate::courierust_tls::crypto::ed25519::Sha512::new()
}
}