use crate::error::PdfError;
use super::der::{maybe_read_context, read_integer_bytes, read_sequence, read_tlv, tag, Class};
#[derive(Debug, Clone, Default)]
pub struct Certificate {
pub issuer_der: Vec<u8>,
pub serial: Vec<u8>,
pub spki_pubkey_bits: Option<Vec<u8>>,
pub validity: Option<(Vec<u8>, Vec<u8>)>,
pub spki_algorithm_oid: Option<Vec<u64>>,
pub spki_algorithm_params: Option<Vec<u8>>,
}
impl Certificate {
pub fn parse(der: &[u8]) -> Result<Self, PdfError> {
let (cert_body, rest) = read_sequence(der)?;
if !rest.is_empty() {
return Err(PdfError::other(
"X.509: trailing bytes after Certificate SEQUENCE",
));
}
let (tbs, _after_tbs) = read_sequence(cert_body)?;
let (_version_ctx, after_version) = maybe_read_context(tbs, 0)?;
let (serial_body, after_serial) = read_integer_bytes(after_version)?;
let (_alg_body, after_alg) = read_sequence(after_serial)?;
let (issuer_tlv, after_issuer) = read_tlv(after_alg)?;
if issuer_tlv.class != Class::Universal || issuer_tlv.tag_number != tag::SEQUENCE {
return Err(PdfError::other(
"X.509: tbsCertificate.issuer must be SEQUENCE",
));
}
let issuer_total = after_alg.len() - after_issuer.len();
let issuer_der = after_alg[..issuer_total].to_vec();
let mut validity_out: Option<(Vec<u8>, Vec<u8>)> = None;
let mut spki_alg_oid_out: Option<Vec<u64>> = None;
let mut spki_alg_params_out: Option<Vec<u8>> = None;
let spki_pubkey_bits = (|| -> Option<Vec<u8>> {
let (validity_body, after_validity) = match read_sequence(after_issuer) {
Ok(parts) => parts,
Err(_) => return None,
};
let nb = parse_time(validity_body).ok();
let after_nb = nb.as_ref().map(|(_, r)| *r).unwrap_or(validity_body);
let na = parse_time(after_nb).ok();
if let (Some((nb_bytes, _)), Some((na_bytes, _))) = (&nb, &na) {
validity_out = Some((nb_bytes.clone(), na_bytes.clone()));
}
let after_subject = match read_sequence(after_validity) {
Ok((_, rest)) => rest,
Err(_) => return None,
};
let (spki_body, _) = read_sequence(after_subject).ok()?;
let (alg_body, after_alg) = read_sequence(spki_body).ok()?;
if let Ok((alg_oid, alg_params)) = super::der::read_oid(alg_body) {
spki_alg_oid_out = Some(alg_oid);
spki_alg_params_out = Some(alg_params.to_vec());
}
let (bs, _) = read_tlv(after_alg).ok()?;
if bs.class != Class::Universal || bs.tag_number != tag::BIT_STRING {
return None;
}
if bs.body.is_empty() {
return None;
}
Some(bs.body[1..].to_vec())
})();
Ok(Self {
issuer_der,
serial: serial_body.to_vec(),
spki_pubkey_bits,
validity: validity_out,
spki_algorithm_oid: spki_alg_oid_out,
spki_algorithm_params: spki_alg_params_out,
})
}
pub fn subject_key_identifier(&self) -> Option<Vec<u8>> {
use sha1::Digest;
self.spki_pubkey_bits
.as_deref()
.map(|b| sha1::Sha1::digest(b).to_vec())
}
pub fn validity(&self) -> Option<(&[u8], &[u8])> {
self.validity
.as_ref()
.map(|(a, b)| (a.as_slice(), b.as_slice()))
}
}
fn parse_time(data: &[u8]) -> Result<(Vec<u8>, &[u8]), PdfError> {
let (tlv, rest) = read_tlv(data)?;
if tlv.class != Class::Universal {
return Err(PdfError::other(
"X.509 Time: expected universal tag (UTCTime or GeneralizedTime)",
));
}
let normalised = match tlv.tag_number {
23 => {
if tlv.body.len() < 13 {
return Err(PdfError::other(format!(
"X.509 UTCTime: body too short ({} bytes)",
tlv.body.len()
)));
}
let yy = std::str::from_utf8(&tlv.body[0..2])
.map_err(|_| PdfError::other("X.509 UTCTime: non-UTF-8 year digits"))?;
let yy_n: u32 = yy
.parse()
.map_err(|_| PdfError::other("X.509 UTCTime: invalid year digits"))?;
let yyyy = if yy_n >= 50 { 1900 + yy_n } else { 2000 + yy_n };
let mut out = format!("{:04}", yyyy).into_bytes();
out.extend_from_slice(&tlv.body[2..]);
out
}
24 => tlv.body.to_vec(),
other => {
return Err(PdfError::other(format!(
"X.509 Time: unexpected tag {other} (expected 23 UTCTime or 24 GeneralizedTime)"
)))
}
};
Ok((normalised, rest))
}
pub fn time_within(instant: &[u8], not_before: &[u8], not_after: &[u8]) -> bool {
instant >= not_before && instant <= not_after
}
#[cfg(test)]
mod tests {
use super::*;
use crate::pubsec::der::{
write_context_constructed, write_integer_bytes, write_integer_u64, write_oid,
write_sequence,
};
fn synth_cert(issuer_der: &[u8], serial: &[u8]) -> Vec<u8> {
let version = write_context_constructed(0, &write_integer_u64(2)); let serial_int = write_integer_bytes(serial);
let sig_alg = write_sequence(&{
let mut b = write_oid(&[1, 2, 840, 113549, 1, 1, 11]); b.extend_from_slice(&super::super::der::write_null());
b
});
let mut tbs = Vec::new();
tbs.extend_from_slice(&version);
tbs.extend_from_slice(&serial_int);
tbs.extend_from_slice(&sig_alg);
tbs.extend_from_slice(issuer_der);
let tbs_seq = write_sequence(&tbs);
write_sequence(&{
let mut b = tbs_seq;
b.extend_from_slice(&write_sequence(&write_oid(&[1, 2, 840, 113549, 1, 1, 11])));
b.extend_from_slice(&super::super::der::write_tlv(
Class::Universal,
false,
3, &[0x00, 0xAB, 0xCD],
));
b
})
}
#[test]
fn parse_synthetic_cert_extracts_issuer_and_serial() {
let issuer_der = write_sequence(b"O=Synthetic Test CA");
let serial = vec![0xDE, 0xAD, 0xBE, 0xEF];
let cert_der = synth_cert(&issuer_der, &serial);
let cert = Certificate::parse(&cert_der).expect("parse");
assert_eq!(cert.issuer_der, issuer_der);
assert_eq!(cert.serial, serial);
}
fn synth_cert_with_spki(
issuer_der: &[u8],
serial: &[u8],
spki_pubkey_contents: &[u8],
) -> Vec<u8> {
let version = write_context_constructed(0, &write_integer_u64(2)); let serial_int = write_integer_bytes(serial);
let sig_alg = write_sequence(&{
let mut b = write_oid(&[1, 2, 840, 113549, 1, 1, 11]);
b.extend_from_slice(&super::super::der::write_null());
b
});
let validity = write_sequence(&{
let mut b = super::super::der::write_tlv(Class::Universal, false, 23, b"260101000000Z");
b.extend_from_slice(&super::super::der::write_tlv(
Class::Universal,
false,
23,
b"360101000000Z",
));
b
});
let subject = write_sequence(b"");
let spki = write_sequence(&{
let mut b = sig_alg.clone();
let mut bs = vec![0x00];
bs.extend_from_slice(spki_pubkey_contents);
b.extend_from_slice(&super::super::der::write_tlv(
Class::Universal,
false,
3,
&bs,
));
b
});
let mut tbs = Vec::new();
tbs.extend_from_slice(&version);
tbs.extend_from_slice(&serial_int);
tbs.extend_from_slice(&sig_alg);
tbs.extend_from_slice(issuer_der);
tbs.extend_from_slice(&validity);
tbs.extend_from_slice(&subject);
tbs.extend_from_slice(&spki);
let tbs_seq = write_sequence(&tbs);
write_sequence(&{
let mut b = tbs_seq;
b.extend_from_slice(&write_sequence(&write_oid(&[1, 2, 840, 113549, 1, 1, 11])));
b.extend_from_slice(&super::super::der::write_tlv(
Class::Universal,
false,
3,
&[0x00, 0xAB, 0xCD],
));
b
})
}
#[test]
fn parse_synthetic_cert_extracts_validity_normalised_to_generalized_time() {
let issuer_der = write_sequence(b"O=Validity Test CA");
let serial = vec![0x99];
let pubkey = b"FakePubKeyBitsForValidityTest-X!";
let cert_der = synth_cert_with_spki(&issuer_der, &serial, pubkey);
let cert = Certificate::parse(&cert_der).expect("parse");
let (nb, na) = cert.validity().expect("validity slot present");
assert_eq!(nb, b"20260101000000Z");
assert_eq!(na, b"20360101000000Z");
assert!(super::time_within(b"20300101000000Z", nb, na));
assert!(!super::time_within(b"20100101000000Z", nb, na));
assert!(!super::time_within(b"20400101000000Z", nb, na));
}
#[test]
fn parse_synthetic_cert_extracts_spki_and_ski() {
let issuer_der = write_sequence(b"O=SKI Test CA");
let serial = vec![0x42];
let pubkey = b"FakePubKeyBitsForSKIHashing-32B!";
let cert_der = synth_cert_with_spki(&issuer_der, &serial, pubkey);
let cert = Certificate::parse(&cert_der).expect("parse");
assert_eq!(cert.spki_pubkey_bits.as_deref(), Some(&pubkey[..]));
let ski = cert.subject_key_identifier().expect("SKI");
use sha1::Digest;
let expected = sha1::Sha1::digest(pubkey).to_vec();
assert_eq!(ski, expected);
assert_eq!(ski.len(), 20);
}
}