use std::fmt;
#[derive(Debug, PartialEq, Eq)]
pub enum X509ParseError {
Truncated,
LengthTooLarge,
Malformed,
}
impl fmt::Display for X509ParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
X509ParseError::Truncated => write!(f, "certificate ended unexpectedly (truncated)"),
X509ParseError::LengthTooLarge => write!(f, "DER length field is implausibly large"),
X509ParseError::Malformed => write!(f, "certificate structure did not match X.509"),
}
}
}
impl std::error::Error for X509ParseError {}
struct Tlv<'a> {
tag: u8,
content: &'a [u8],
}
fn read_tlv(input: &[u8]) -> Result<(Tlv<'_>, &[u8]), X509ParseError> {
let tag = *input.first().ok_or(X509ParseError::Truncated)?;
let len_byte = *input.get(1).ok_or(X509ParseError::Truncated)?;
let (len, header) = if len_byte & 0x80 == 0 {
(len_byte as usize, 2)
} else {
let num = (len_byte & 0x7f) as usize;
if num == 0 || num > 4 {
return Err(X509ParseError::LengthTooLarge);
}
let mut len = 0usize;
for i in 0..num {
let b = *input.get(2 + i).ok_or(X509ParseError::Truncated)?;
len = (len << 8) | b as usize;
}
(len, 2 + num)
};
let end = header
.checked_add(len)
.ok_or(X509ParseError::LengthTooLarge)?;
if end > input.len() {
return Err(X509ParseError::Truncated);
}
Ok((
Tlv {
tag,
content: &input[header..end],
},
&input[end..],
))
}
fn expect_tag(input: &[u8], expected: u8) -> Result<(Tlv<'_>, &[u8]), X509ParseError> {
let (tlv, rest) = read_tlv(input)?;
if tlv.tag != expected {
return Err(X509ParseError::Malformed);
}
Ok((tlv, rest))
}
fn decode_oid(content: &[u8]) -> Option<String> {
let first = *content.first()?;
let arc1 = (first / 40) as u32;
let arc2 = (first % 40) as u32;
let mut out = format!("{arc1}.{arc2}");
let mut value: u64 = 0;
let mut started = false;
for &b in &content[1..] {
started = true;
value = (value << 7) | (b & 0x7f) as u64;
if b & 0x80 == 0 {
out.push('.');
out.push_str(&value.to_string());
value = 0;
started = false;
}
}
if started {
return None;
}
Some(out)
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DnAttr {
CommonName,
Organization,
OrganizationalUnit,
Country,
Locality,
StateOrProvince,
SerialNumber,
EmailAddress,
Other(String),
}
impl DnAttr {
fn from_oid(oid: &str) -> DnAttr {
match oid {
"2.5.4.3" => DnAttr::CommonName,
"2.5.4.10" => DnAttr::Organization,
"2.5.4.11" => DnAttr::OrganizationalUnit,
"2.5.4.6" => DnAttr::Country,
"2.5.4.7" => DnAttr::Locality,
"2.5.4.8" => DnAttr::StateOrProvince,
"2.5.4.5" => DnAttr::SerialNumber,
"1.2.840.113549.1.9.1" => DnAttr::EmailAddress,
_ => DnAttr::Other(oid.to_string()),
}
}
pub fn label(&self) -> &str {
match self {
DnAttr::CommonName => "CN",
DnAttr::Organization => "O",
DnAttr::OrganizationalUnit => "OU",
DnAttr::Country => "C",
DnAttr::Locality => "L",
DnAttr::StateOrProvince => "ST",
DnAttr::SerialNumber => "serialNumber",
DnAttr::EmailAddress => "emailAddress",
DnAttr::Other(oid) => oid,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SubjectDn {
pub rdns: Vec<(DnAttr, String)>,
}
impl fmt::Display for SubjectDn {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut first = true;
for (attr, value) in &self.rdns {
if !first {
write!(f, ", ")?;
}
first = false;
write!(f, "{}={}", attr.label(), value)?;
}
Ok(())
}
}
fn decode_dn_value(content: &[u8]) -> String {
String::from_utf8_lossy(content).into_owned()
}
pub fn parse_subject_dn(cert_der: &[u8]) -> Result<SubjectDn, X509ParseError> {
let (cert, _) = expect_tag(cert_der, 0x30)?;
let (tbs, _) = expect_tag(cert.content, 0x30)?;
let mut rest = tbs.content;
{
let (peek, after) = read_tlv(rest)?;
if peek.tag == 0xA0 {
rest = after;
}
}
let (_, rest) = expect_tag(rest, 0x02)?;
let (_, rest) = expect_tag(rest, 0x30)?;
let (_, rest) = expect_tag(rest, 0x30)?;
let (_, rest) = expect_tag(rest, 0x30)?;
let (subject, _) = expect_tag(rest, 0x30)?;
parse_name(subject.content)
}
fn parse_name(mut input: &[u8]) -> Result<SubjectDn, X509ParseError> {
let mut rdns = Vec::new();
while !input.is_empty() {
let (set, after_set) = expect_tag(input, 0x31)?;
input = after_set;
let mut atv = set.content;
while !atv.is_empty() {
let (seq, after_seq) = expect_tag(atv, 0x30)?;
atv = after_seq;
let (oid_tlv, after_oid) = expect_tag(seq.content, 0x06)?;
let oid = decode_oid(oid_tlv.content).ok_or(X509ParseError::Malformed)?;
let (val_tlv, _) = read_tlv(after_oid)?;
let attr = DnAttr::from_oid(&oid);
rdns.push((attr, decode_dn_value(val_tlv.content)));
}
}
Ok(SubjectDn { rdns })
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn oid_decoder_common_values() {
assert_eq!(decode_oid(&[0x55, 0x04, 0x03]).as_deref(), Some("2.5.4.3"));
assert_eq!(
decode_oid(&[0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x09, 0x01]).as_deref(),
Some("1.2.840.113549.1.9.1")
);
assert_eq!(
decode_oid(&[0x09, 0x92, 0x26, 0x89, 0x93, 0xF2, 0x2C, 0x64, 0x01, 0x01]).as_deref(),
Some("0.9.2342.19200300.100.1.1")
);
}
#[test]
fn oid_decoder_rejects_unterminated() {
assert_eq!(decode_oid(&[0x55, 0x81]), None);
}
#[test]
fn tlv_long_form_length() {
let mut buf = vec![0x04, 0x81, 0x80];
buf.extend(std::iter::repeat(0xAB).take(128));
let (tlv, rest) = read_tlv(&buf).unwrap();
assert_eq!(tlv.tag, 0x04);
assert_eq!(tlv.content.len(), 128);
assert!(rest.is_empty());
}
#[test]
fn tlv_long_form_length_two_and_three_octets() {
let mut buf = vec![0x04, 0x82, 0x01, 0x40];
buf.extend(std::iter::repeat(0xCD).take(320));
let (tlv, rest) = read_tlv(&buf).unwrap();
assert_eq!(tlv.tag, 0x04);
assert_eq!(tlv.content.len(), 320);
assert!(rest.is_empty());
let mut buf = vec![0x04, 0x83, 0x01, 0x00, 0x00];
buf.extend(std::iter::repeat(0xEF).take(65536));
let (tlv, rest) = read_tlv(&buf).unwrap();
assert_eq!(tlv.tag, 0x04);
assert_eq!(tlv.content.len(), 65536);
assert!(rest.is_empty());
}
#[test]
fn tlv_rejects_five_octet_length() {
assert_eq!(
read_tlv(&[0x04, 0x85, 0x00, 0x00, 0x00, 0x00, 0x01]).err(),
Some(X509ParseError::LengthTooLarge)
);
assert_eq!(
read_tlv(&[0x30, 0x8F]).err(),
Some(X509ParseError::LengthTooLarge)
);
}
#[test]
fn parse_subject_dn_rejects_unterminated_oid() {
let oid = [0x06u8, 0x02, 0x55, 0x81]; let value = [0x13u8, 0x01, b'X']; let mut atv_content = Vec::new();
atv_content.extend_from_slice(&oid);
atv_content.extend_from_slice(&value);
let mut atv = vec![0x30, atv_content.len() as u8]; atv.extend_from_slice(&atv_content);
let mut set = vec![0x31, atv.len() as u8]; set.extend_from_slice(&atv);
let mut subject = vec![0x30, set.len() as u8];
subject.extend_from_slice(&set);
let serial = [0x02u8, 0x01, 0x01]; let sig_alg = [0x30u8, 0x00]; let issuer = [0x30u8, 0x00]; let validity = [0x30u8, 0x00];
let mut tbs_content = Vec::new();
tbs_content.extend_from_slice(&serial);
tbs_content.extend_from_slice(&sig_alg);
tbs_content.extend_from_slice(&issuer);
tbs_content.extend_from_slice(&validity);
tbs_content.extend_from_slice(&subject);
let mut tbs = vec![0x30, tbs_content.len() as u8];
tbs.extend_from_slice(&tbs_content);
let mut cert = vec![0x30, tbs.len() as u8];
cert.extend_from_slice(&tbs);
assert_eq!(
parse_subject_dn(&cert),
Err(X509ParseError::Malformed),
"unterminated OID in subject must be rejected as Malformed"
);
}
#[test]
fn tlv_rejects_truncated_and_indefinite() {
assert_eq!(read_tlv(&[]).err(), Some(X509ParseError::Truncated));
assert_eq!(read_tlv(&[0x30]).err(), Some(X509ParseError::Truncated));
assert_eq!(
read_tlv(&[0x04, 0x05, 0x00, 0x01]).err(),
Some(X509ParseError::Truncated)
);
assert_eq!(
read_tlv(&[0x30, 0x80]).err(),
Some(X509ParseError::LengthTooLarge)
);
}
}