extern crate alloc;
use alloc::vec::Vec;
use zerodds_security::error::{SecurityError, SecurityErrorKind};
const TAG_INTEGER: u8 = 0x02;
const TAG_SEQUENCE: u8 = 0x30;
const TAG_UTCTIME: u8 = 0x17;
const TAG_GENERALIZED_TIME: u8 = 0x18;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CrlParseError {
Empty,
Truncated,
NotASequence,
MissingTbsCertList,
MissingRevokedList,
MissingSerial,
}
pub fn parse_crl_serials(crl_der: &[u8]) -> Result<Vec<Vec<u8>>, CrlParseError> {
if crl_der.is_empty() {
return Err(CrlParseError::Empty);
}
let (tag, certlist_inner, _rest) = read_tlv(crl_der)?;
if tag != TAG_SEQUENCE {
return Err(CrlParseError::NotASequence);
}
let (tbs_tag, tbs_inner, _) = read_tlv(certlist_inner)?;
if tbs_tag != TAG_SEQUENCE {
return Err(CrlParseError::MissingTbsCertList);
}
let mut cursor = tbs_inner;
while !cursor.is_empty() {
let (t, inner, rest) = read_tlv(cursor).map_err(|_| CrlParseError::MissingRevokedList)?;
cursor = rest;
if t != TAG_SEQUENCE {
continue;
}
if let Some(serials) = try_parse_revoked_list(inner)? {
return Ok(serials);
}
}
Ok(Vec::new())
}
pub fn validate_crl(crl_der: &[u8], cert_serial: &[u8]) -> Result<(), SecurityError> {
let revoked = parse_crl_serials(crl_der).map_err(|e| {
SecurityError::new(SecurityErrorKind::BadArgument, crl_parse_error_message(e))
})?;
if revoked.iter().any(|s| s.as_slice() == cert_serial) {
return Err(SecurityError::new(
SecurityErrorKind::AuthenticationFailed,
"crl: cert ist revoked",
));
}
Ok(())
}
fn crl_parse_error_message(e: CrlParseError) -> &'static str {
match e {
CrlParseError::Empty => "crl: leere eingabe",
CrlParseError::Truncated => "crl: truncated DER",
CrlParseError::NotASequence => "crl: outer ist keine SEQUENCE",
CrlParseError::MissingTbsCertList => "crl: TBSCertList fehlt",
CrlParseError::MissingRevokedList => "crl: revokedCertificates malformed",
CrlParseError::MissingSerial => "crl: revoked-eintrag ohne serial",
}
}
fn try_parse_revoked_list(inner: &[u8]) -> Result<Option<Vec<Vec<u8>>>, CrlParseError> {
if inner.is_empty() {
return Ok(None);
}
let (first_tag, first_inner, _) = match read_tlv(inner) {
Ok(v) => v,
Err(_) => return Ok(None),
};
if first_tag != TAG_SEQUENCE {
return Ok(None);
}
let (serial_tag, _serial_bytes, after_serial) = match read_tlv(first_inner) {
Ok(v) => v,
Err(_) => return Ok(None),
};
if serial_tag != TAG_INTEGER {
return Ok(None);
}
let (time_tag, _, _) = match read_tlv(after_serial) {
Ok(v) => v,
Err(_) => return Ok(None),
};
if time_tag != TAG_UTCTIME && time_tag != TAG_GENERALIZED_TIME {
return Ok(None);
}
let mut serials = Vec::new();
let mut cursor = inner;
while !cursor.is_empty() {
let (t, entry_inner, rest) = read_tlv(cursor).map_err(|_| CrlParseError::Truncated)?;
cursor = rest;
if t != TAG_SEQUENCE {
return Err(CrlParseError::MissingRevokedList);
}
let (serial_tag, serial_bytes, _) =
read_tlv(entry_inner).map_err(|_| CrlParseError::MissingSerial)?;
if serial_tag != TAG_INTEGER {
return Err(CrlParseError::MissingSerial);
}
serials.push(serial_bytes.to_vec());
}
Ok(Some(serials))
}
fn read_tlv(buf: &[u8]) -> Result<(u8, &[u8], &[u8]), CrlParseError> {
if buf.len() < 2 {
return Err(CrlParseError::Truncated);
}
let tag = buf[0];
let (len, header_len) = read_length(&buf[1..])?;
let total = 1 + header_len + len;
if buf.len() < total {
return Err(CrlParseError::Truncated);
}
let value = &buf[1 + header_len..total];
let rest = &buf[total..];
Ok((tag, value, rest))
}
fn read_length(buf: &[u8]) -> Result<(usize, usize), CrlParseError> {
if buf.is_empty() {
return Err(CrlParseError::Truncated);
}
let first = buf[0];
if first < 0x80 {
return Ok((first as usize, 1));
}
let n = (first & 0x7F) as usize;
if n == 0 || n > 4 {
return Err(CrlParseError::Truncated);
}
if buf.len() < 1 + n {
return Err(CrlParseError::Truncated);
}
let mut len = 0usize;
for &b in &buf[1..1 + n] {
len = len * 256 + b as usize;
}
Ok((len, 1 + n))
}
#[cfg(test)]
#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
mod tests {
use super::*;
fn build_test_crl(revoked_serials: &[&[u8]]) -> Vec<u8> {
let issuer = der_seq(&der_set(&[]));
let this_update = der_utctime(b"260101000000Z");
let mut revoked_inner = Vec::new();
for serial in revoked_serials {
let entry = der_seq(
&[
der_integer(serial).as_slice(),
der_utctime(b"260101000000Z").as_slice(),
]
.concat(),
);
revoked_inner.extend_from_slice(&entry);
}
let revoked_seq = der_seq(&revoked_inner);
let mut tbs = Vec::new();
tbs.extend_from_slice(&issuer);
tbs.extend_from_slice(&this_update);
tbs.extend_from_slice(&revoked_seq);
let tbs_seq = der_seq(&tbs);
der_seq(&tbs_seq)
}
fn der_seq(inner: &[u8]) -> Vec<u8> {
encode_tlv(TAG_SEQUENCE, inner)
}
fn der_set(inner: &[u8]) -> Vec<u8> {
encode_tlv(0x31, inner)
}
fn der_integer(value: &[u8]) -> Vec<u8> {
encode_tlv(TAG_INTEGER, value)
}
fn der_utctime(value: &[u8]) -> Vec<u8> {
encode_tlv(TAG_UTCTIME, value)
}
fn encode_tlv(tag: u8, value: &[u8]) -> Vec<u8> {
let mut out = Vec::with_capacity(value.len() + 6);
out.push(tag);
encode_length(&mut out, value.len());
out.extend_from_slice(value);
out
}
fn encode_length(out: &mut Vec<u8>, len: usize) {
if len < 0x80 {
out.push(len as u8);
} else if len < 0x100 {
out.push(0x81);
out.push(len as u8);
} else {
out.push(0x82);
out.push((len >> 8) as u8);
out.push((len & 0xFF) as u8);
}
}
#[test]
fn parse_serials_returns_all_revoked() {
let crl = build_test_crl(&[&[0x01], &[0x02], &[0x03]]);
let serials = parse_crl_serials(&crl).expect("parse");
assert_eq!(serials, vec![vec![0x01], vec![0x02], vec![0x03]]);
}
#[test]
fn parse_serials_empty_revocation_list() {
let crl = build_test_crl(&[]);
let serials = parse_crl_serials(&crl).expect("parse");
assert!(serials.is_empty());
}
#[test]
fn parse_serials_keeps_leading_zero_byte_for_positive_serials() {
let crl = build_test_crl(&[&[0x00, 0xFF]]);
let serials = parse_crl_serials(&crl).expect("parse");
assert_eq!(serials, vec![vec![0x00, 0xFF]]);
}
#[test]
fn parse_serials_handles_long_serial() {
let serial: [u8; 20] = [
0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66,
0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC,
];
let crl = build_test_crl(&[&serial]);
let serials = parse_crl_serials(&crl).expect("parse");
assert_eq!(serials.len(), 1);
assert_eq!(serials[0], serial.to_vec());
}
#[test]
fn validate_crl_known_revoked_rejects() {
let crl = build_test_crl(&[&[0xAB, 0xCD]]);
let err = validate_crl(&crl, &[0xAB, 0xCD]).unwrap_err();
assert_eq!(err.kind, SecurityErrorKind::AuthenticationFailed);
}
#[test]
fn validate_crl_unknown_serial_passes() {
let crl = build_test_crl(&[&[0xAB, 0xCD]]);
assert!(validate_crl(&crl, &[0xFF, 0xEE]).is_ok());
}
#[test]
fn validate_crl_against_empty_list_passes() {
let crl = build_test_crl(&[]);
assert!(validate_crl(&crl, &[0x01]).is_ok());
}
#[test]
fn validate_crl_signature_invalid_rejects() {
let bad = vec![0x05, 0x00, 0x00, 0x00];
let err = validate_crl(&bad, &[0x01]).unwrap_err();
assert_eq!(err.kind, SecurityErrorKind::BadArgument);
}
#[test]
fn validate_crl_empty_input_returns_bad_argument() {
let err = validate_crl(&[], &[0x01]).unwrap_err();
assert_eq!(err.kind, SecurityErrorKind::BadArgument);
}
#[test]
fn validate_crl_truncated_input_returns_bad_argument() {
let bad = vec![0x30, 0x32, 0x01, 0x02, 0x03];
let err = validate_crl(&bad, &[0x01]).unwrap_err();
assert_eq!(err.kind, SecurityErrorKind::BadArgument);
}
#[test]
fn parse_serials_rejects_non_sequence_outer() {
let bad = vec![0x02, 0x01, 0x00];
let err = parse_crl_serials(&bad).unwrap_err();
assert_eq!(err, CrlParseError::NotASequence);
}
#[test]
fn parse_serials_rejects_empty_input() {
let err = parse_crl_serials(&[]).unwrap_err();
assert_eq!(err, CrlParseError::Empty);
}
#[test]
fn parse_serials_handles_long_form_length() {
let mut serials_refs: Vec<Vec<u8>> = Vec::new();
for i in 0..10u8 {
serials_refs.push(vec![i, i.wrapping_add(1), i.wrapping_add(2)]);
}
let serials_slice: Vec<&[u8]> = serials_refs.iter().map(|v| v.as_slice()).collect();
let crl = build_test_crl(&serials_slice);
assert!(crl.len() > 128, "test-crl muss long-form-length triggern");
let parsed = parse_crl_serials(&crl).expect("parse");
assert_eq!(parsed.len(), 10);
for (got, want) in parsed.iter().zip(serials_refs.iter()) {
assert_eq!(got, want);
}
}
#[test]
fn parse_serials_rejects_indefinite_length() {
let bad = vec![0x30, 0x80, 0x00, 0x00];
let err = parse_crl_serials(&bad).unwrap_err();
assert!(matches!(
err,
CrlParseError::Truncated | CrlParseError::MissingTbsCertList
));
}
#[test]
fn validate_crl_with_two_revoked_finds_second() {
let crl = build_test_crl(&[&[0x01], &[0x02], &[0x03]]);
let err = validate_crl(&crl, &[0x03]).unwrap_err();
assert_eq!(err.kind, SecurityErrorKind::AuthenticationFailed);
}
#[test]
fn parse_error_messages_are_specific_per_variant() {
assert_eq!(
crl_parse_error_message(CrlParseError::Empty),
"crl: leere eingabe"
);
assert_eq!(
crl_parse_error_message(CrlParseError::Truncated),
"crl: truncated DER"
);
assert_eq!(
crl_parse_error_message(CrlParseError::NotASequence),
"crl: outer ist keine SEQUENCE"
);
assert_eq!(
crl_parse_error_message(CrlParseError::MissingTbsCertList),
"crl: TBSCertList fehlt"
);
assert_eq!(
crl_parse_error_message(CrlParseError::MissingRevokedList),
"crl: revokedCertificates malformed"
);
assert_eq!(
crl_parse_error_message(CrlParseError::MissingSerial),
"crl: revoked-eintrag ohne serial"
);
}
#[test]
fn try_parse_revoked_list_rejects_non_time_tag() {
let inner = [
0x30, 0x06, 0x02, 0x01, 0x42, 0x02, 0x01, 0x99, ];
let res = try_parse_revoked_list(&inner).expect("no err");
assert!(res.is_none(), "non-time-tag must yield None, got {res:?}");
}
#[test]
fn read_length_0x80_is_long_form_marker_not_short() {
let res = read_length(&[0x80, 0xAA]);
assert!(matches!(res, Err(CrlParseError::Truncated)), "got {res:?}");
}
#[test]
fn read_length_rejects_n_greater_than_four() {
let buf = [0x85, 0x00, 0x00, 0x00, 0x00, 0x00];
let res = read_length(&buf);
assert!(matches!(res, Err(CrlParseError::Truncated)), "got {res:?}");
}
#[test]
fn read_length_n_equals_four_accepted() {
let buf = [0x84, 0x00, 0x00, 0x01, 0x00, 0xAA, 0xBB];
let (len, hdr) = read_length(&buf).expect("n=4 must be accepted");
assert_eq!(len, 0x100);
assert_eq!(hdr, 5);
}
#[test]
fn read_length_buf_exactly_one_plus_n_accepted() {
let buf = [0x82, 0x12, 0x34];
let (len, hdr) = read_length(&buf).expect("buf.len()==1+n must succeed");
assert_eq!(len, 0x1234);
assert_eq!(hdr, 3);
}
#[test]
fn read_length_buf_one_plus_n_minus_one_truncated() {
let buf = [0x82, 0x12];
let res = read_length(&buf);
assert!(matches!(res, Err(CrlParseError::Truncated)), "got {res:?}");
}
#[test]
fn read_length_two_byte_length_high_byte_first() {
let buf = [0x82, 0x01, 0x00];
let (len, hdr) = read_length(&buf).expect("must parse");
assert_eq!(len, 256, "multi-byte length must be BE — high byte first");
assert_eq!(hdr, 3);
}
#[test]
fn read_length_three_byte_length_correct() {
let buf = [0x83, 0xFF, 0x00, 0xFF];
let (len, hdr) = read_length(&buf).expect("must parse");
assert_eq!(len, 0xFF_00_FF);
assert_eq!(hdr, 4);
}
}