//! ASN.1 DER (Distinguished Encoding Rules) parser. //! //! Parses TLV (Tag-Length-Value) structures from DER-encoded byte slices. //! Supports all types needed for X.509 certificate and PKCS#1 key parsing. use core::fmt; // --------------------------------------------------------------------------- // Error type // --------------------------------------------------------------------------- #[derive(Debug, Clone, PartialEq, Eq)] pub enum Asn1Error { /// Not enough data to read the tag/length/value. Truncated, /// Indefinite-length encoding is forbidden in DER. IndefiniteLength, /// Length encoding is not minimal (DER violation). NonMinimalLength, /// Integer encoding is not minimal (leading zero or negative without sign byte). NonMinimalInteger, /// Unexpected tag encountered. UnexpectedTag { expected: u8, got: u8 }, /// Trailing data after a complete parse. TrailingData, /// Invalid OID encoding. InvalidOid, /// Invalid boolean value in DER (must be 0x00 or 0xFF). InvalidBoolean, /// Invalid UTF-8 in string value. InvalidUtf8, /// Invalid time string format. InvalidTime, /// Bit string unused bits > 7 or non-zero unused bits with empty content. InvalidBitString, /// Content length exceeds available data. LengthOverflow, } impl fmt::Display for Asn1Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Truncated => write!(f, "truncated data"), Self::IndefiniteLength => write!(f, "indefinite length not allowed in DER"), Self::NonMinimalLength => write!(f, "non-minimal length encoding"), Self::NonMinimalInteger => write!(f, "non-minimal integer encoding"), Self::UnexpectedTag { expected, got } => { write!( f, "unexpected tag: expected 0x{expected:02x}, got 0x{got:02x}" ) } Self::TrailingData => write!(f, "trailing data after value"), Self::InvalidOid => write!(f, "invalid OID encoding"), Self::InvalidBoolean => write!(f, "invalid DER boolean"), Self::InvalidUtf8 => write!(f, "invalid UTF-8"), Self::InvalidTime => write!(f, "invalid time string"), Self::InvalidBitString => write!(f, "invalid bit string"), Self::LengthOverflow => write!(f, "length overflow"), } } } pub type Result = core::result::Result; // --------------------------------------------------------------------------- // Tag constants // --------------------------------------------------------------------------- // Tag class bits (bits 7-6) pub const TAG_CLASS_UNIVERSAL: u8 = 0x00; pub const TAG_CLASS_APPLICATION: u8 = 0x40; pub const TAG_CLASS_CONTEXT: u8 = 0x80; pub const TAG_CLASS_PRIVATE: u8 = 0xC0; // Constructed bit (bit 5) pub const TAG_CONSTRUCTED: u8 = 0x20; // Universal type tags pub const TAG_BOOLEAN: u8 = 0x01; pub const TAG_INTEGER: u8 = 0x02; pub const TAG_BIT_STRING: u8 = 0x03; pub const TAG_OCTET_STRING: u8 = 0x04; pub const TAG_NULL: u8 = 0x05; pub const TAG_OID: u8 = 0x06; pub const TAG_UTF8_STRING: u8 = 0x0C; pub const TAG_SEQUENCE: u8 = 0x30; // constructed pub const TAG_SET: u8 = 0x31; // constructed pub const TAG_PRINTABLE_STRING: u8 = 0x13; pub const TAG_IA5_STRING: u8 = 0x16; pub const TAG_UTC_TIME: u8 = 0x17; pub const TAG_GENERALIZED_TIME: u8 = 0x18; // --------------------------------------------------------------------------- // OID — Object Identifier // --------------------------------------------------------------------------- /// An ASN.1 Object Identifier, stored as raw DER-encoded bytes. #[derive(Clone, PartialEq, Eq)] pub struct Oid<'a> { /// The raw DER-encoded OID value bytes (without tag and length). bytes: &'a [u8], } impl<'a> Oid<'a> { /// Create an OID from raw DER-encoded bytes. pub fn from_der_bytes(bytes: &'a [u8]) -> Self { Self { bytes } } /// Return the raw DER-encoded bytes. pub fn as_bytes(&self) -> &[u8] { self.bytes } /// Decode to a vector of arc components (e.g., [1, 2, 840, 113549, ...]). pub fn components(&self) -> Result> { if self.bytes.is_empty() { return Err(Asn1Error::InvalidOid); } let mut out = Vec::new(); let first = self.bytes[0]; out.push((first / 40) as u32); out.push((first % 40) as u32); let mut i = 1; while i < self.bytes.len() { let mut value: u32 = 0; loop { if i >= self.bytes.len() { return Err(Asn1Error::InvalidOid); } let byte = self.bytes[i]; i += 1; value = value .checked_shl(7) .and_then(|v| v.checked_add((byte & 0x7F) as u32)) .ok_or(Asn1Error::InvalidOid)?; if byte & 0x80 == 0 { break; } } out.push(value); } Ok(out) } /// Encode from dotted-notation components. pub fn encode_components(components: &[u32]) -> Result> { if components.len() < 2 { return Err(Asn1Error::InvalidOid); } if components[0] > 2 || (components[0] < 2 && components[1] >= 40) { return Err(Asn1Error::InvalidOid); } let mut out = Vec::new(); let first = (components[0] * 40 + components[1]) as u8; out.push(first); for &c in &components[2..] { encode_base128(&mut out, c); } Ok(out) } /// Compare against a known OID given as a byte slice of DER-encoded value. pub fn matches(&self, other: &[u8]) -> bool { self.bytes == other } } impl fmt::Debug for Oid<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self.components() { Ok(comps) => { let s: Vec = comps.iter().map(|c| c.to_string()).collect(); write!(f, "OID({})", s.join(".")) } Err(_) => write!(f, "OID()"), } } } impl fmt::Display for Oid<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self.components() { Ok(comps) => { let s: Vec = comps.iter().map(|c| c.to_string()).collect(); write!(f, "{}", s.join(".")) } Err(_) => write!(f, ""), } } } fn encode_base128(out: &mut Vec, mut value: u32) { if value == 0 { out.push(0); return; } // Find out how many bytes we need let mut tmp = Vec::new(); while value > 0 { tmp.push((value & 0x7F) as u8); value >>= 7; } tmp.reverse(); for (i, byte) in tmp.iter().enumerate() { if i < tmp.len() - 1 { out.push(byte | 0x80); } else { out.push(*byte); } } } // --------------------------------------------------------------------------- // Well-known OIDs (DER-encoded value bytes) // --------------------------------------------------------------------------- /// sha256WithRSAEncryption (1.2.840.113549.1.1.11) pub const OID_SHA256_WITH_RSA: &[u8] = &[0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0B]; /// sha384WithRSAEncryption (1.2.840.113549.1.1.12) pub const OID_SHA384_WITH_RSA: &[u8] = &[0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0C]; /// sha512WithRSAEncryption (1.2.840.113549.1.1.13) pub const OID_SHA512_WITH_RSA: &[u8] = &[0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0D]; /// rsaEncryption (1.2.840.113549.1.1.1) pub const OID_RSA_ENCRYPTION: &[u8] = &[0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01]; /// ecPublicKey (1.2.840.10045.2.1) pub const OID_EC_PUBLIC_KEY: &[u8] = &[0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01]; /// prime256v1 / secp256r1 (1.2.840.10045.3.1.7) pub const OID_PRIME256V1: &[u8] = &[0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07]; /// secp384r1 (1.3.132.0.34) pub const OID_SECP384R1: &[u8] = &[0x2B, 0x81, 0x04, 0x00, 0x22]; /// ecdsaWithSHA256 (1.2.840.10045.4.3.2) pub const OID_ECDSA_WITH_SHA256: &[u8] = &[0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x04, 0x03, 0x02]; /// ecdsaWithSHA384 (1.2.840.10045.4.3.3) pub const OID_ECDSA_WITH_SHA384: &[u8] = &[0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x04, 0x03, 0x03]; /// sha256 (2.16.840.1.101.3.4.2.1) pub const OID_SHA256: &[u8] = &[0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01]; /// sha384 (2.16.840.1.101.3.4.2.2) pub const OID_SHA384: &[u8] = &[0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02]; /// sha512 (2.16.840.1.101.3.4.2.3) pub const OID_SHA512: &[u8] = &[0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03]; /// id-ce-subjectKeyIdentifier (2.5.29.14) pub const OID_SUBJECT_KEY_IDENTIFIER: &[u8] = &[0x55, 0x1D, 0x0E]; /// id-ce-keyUsage (2.5.29.15) pub const OID_KEY_USAGE: &[u8] = &[0x55, 0x1D, 0x0F]; /// id-ce-subjectAltName (2.5.29.17) pub const OID_SUBJECT_ALT_NAME: &[u8] = &[0x55, 0x1D, 0x11]; /// id-ce-basicConstraints (2.5.29.19) pub const OID_BASIC_CONSTRAINTS: &[u8] = &[0x55, 0x1D, 0x13]; /// id-ce-authorityKeyIdentifier (2.5.29.35) pub const OID_AUTHORITY_KEY_IDENTIFIER: &[u8] = &[0x55, 0x1D, 0x23]; /// id-at-commonName (2.5.4.3) pub const OID_COMMON_NAME: &[u8] = &[0x55, 0x04, 0x03]; /// id-at-countryName (2.5.4.6) pub const OID_COUNTRY_NAME: &[u8] = &[0x55, 0x04, 0x06]; /// id-at-organizationName (2.5.4.10) pub const OID_ORGANIZATION_NAME: &[u8] = &[0x55, 0x04, 0x0A]; /// id-at-organizationalUnitName (2.5.4.11) pub const OID_ORGANIZATIONAL_UNIT_NAME: &[u8] = &[0x55, 0x04, 0x0B]; // --------------------------------------------------------------------------- // TLV item — a parsed tag-length-value triple // --------------------------------------------------------------------------- /// A single parsed TLV (Tag-Length-Value) item. #[derive(Debug, Clone)] pub struct Tlv<'a> { /// The raw tag byte. pub tag: u8, /// The value bytes (not including tag and length encoding). pub value: &'a [u8], /// The full TLV encoding (tag + length + value). Useful for signature verification. pub raw: &'a [u8], } impl<'a> Tlv<'a> { /// True if this is a constructed (SEQUENCE, SET, context-specific constructed) type. pub fn is_constructed(&self) -> bool { self.tag & TAG_CONSTRUCTED != 0 } /// Return the tag class (UNIVERSAL, APPLICATION, CONTEXT, PRIVATE). pub fn tag_class(&self) -> u8 { self.tag & 0xC0 } /// Return the tag number (bits 4-0). pub fn tag_number(&self) -> u8 { self.tag & 0x1F } /// True if this is a context-specific tag (class bits = 10). pub fn is_context_specific(&self) -> bool { self.tag_class() == TAG_CLASS_CONTEXT } /// Return context-specific tag number, or None if not context-specific. pub fn context_tag(&self) -> Option { if self.is_context_specific() { Some(self.tag_number()) } else { None } } /// Parse the value as a SEQUENCE of TLV items. pub fn sequence_items(&self) -> Result>> { let mut parser = DerParser::new(self.value); let mut items = Vec::new(); while parser.has_more() { items.push(parser.read_tlv()?); } Ok(items) } /// Parse the value as a DER INTEGER and return the bytes (may have leading zero for sign). pub fn as_integer(&self) -> Result<&'a [u8]> { if self.tag != TAG_INTEGER { return Err(Asn1Error::UnexpectedTag { expected: TAG_INTEGER, got: self.tag, }); } validate_der_integer(self.value)?; Ok(self.value) } /// Parse the value as a positive DER INTEGER, stripping any leading zero byte. pub fn as_positive_integer(&self) -> Result<&'a [u8]> { let bytes = self.as_integer()?; if bytes.first() == Some(&0) && bytes.len() > 1 { Ok(&bytes[1..]) } else { Ok(bytes) } } /// Parse the value as a BOOLEAN. pub fn as_boolean(&self) -> Result { if self.tag != TAG_BOOLEAN { return Err(Asn1Error::UnexpectedTag { expected: TAG_BOOLEAN, got: self.tag, }); } if self.value.len() != 1 { return Err(Asn1Error::InvalidBoolean); } match self.value[0] { 0x00 => Ok(false), 0xFF => Ok(true), _ => Err(Asn1Error::InvalidBoolean), } } /// Parse the value as a NULL (must be empty). pub fn as_null(&self) -> Result<()> { if self.tag != TAG_NULL { return Err(Asn1Error::UnexpectedTag { expected: TAG_NULL, got: self.tag, }); } if !self.value.is_empty() { return Err(Asn1Error::TrailingData); } Ok(()) } /// Parse the value as an OID. pub fn as_oid(&self) -> Result> { if self.tag != TAG_OID { return Err(Asn1Error::UnexpectedTag { expected: TAG_OID, got: self.tag, }); } if self.value.is_empty() { return Err(Asn1Error::InvalidOid); } Ok(Oid::from_der_bytes(self.value)) } /// Parse the value as a BIT STRING. Returns (unused_bits, data). pub fn as_bit_string(&self) -> Result<(u8, &'a [u8])> { if self.tag != TAG_BIT_STRING { return Err(Asn1Error::UnexpectedTag { expected: TAG_BIT_STRING, got: self.tag, }); } if self.value.is_empty() { return Err(Asn1Error::InvalidBitString); } let unused = self.value[0]; if unused > 7 { return Err(Asn1Error::InvalidBitString); } let data = &self.value[1..]; if data.is_empty() && unused != 0 { return Err(Asn1Error::InvalidBitString); } // DER: unused bits in the last byte must be zero if unused > 0 && !data.is_empty() { let mask = (1u8 << unused) - 1; if data[data.len() - 1] & mask != 0 { return Err(Asn1Error::InvalidBitString); } } Ok((unused, data)) } /// Parse the value as an OCTET STRING. pub fn as_octet_string(&self) -> Result<&'a [u8]> { if self.tag != TAG_OCTET_STRING { return Err(Asn1Error::UnexpectedTag { expected: TAG_OCTET_STRING, got: self.tag, }); } Ok(self.value) } /// Parse the value as a UTF8String. pub fn as_utf8_string(&self) -> Result<&'a str> { if self.tag != TAG_UTF8_STRING { return Err(Asn1Error::UnexpectedTag { expected: TAG_UTF8_STRING, got: self.tag, }); } core::str::from_utf8(self.value).map_err(|_| Asn1Error::InvalidUtf8) } /// Parse the value as a PrintableString (ASCII subset). pub fn as_printable_string(&self) -> Result<&'a str> { if self.tag != TAG_PRINTABLE_STRING { return Err(Asn1Error::UnexpectedTag { expected: TAG_PRINTABLE_STRING, got: self.tag, }); } core::str::from_utf8(self.value).map_err(|_| Asn1Error::InvalidUtf8) } /// Parse the value as an IA5String (ASCII). pub fn as_ia5_string(&self) -> Result<&'a str> { if self.tag != TAG_IA5_STRING { return Err(Asn1Error::UnexpectedTag { expected: TAG_IA5_STRING, got: self.tag, }); } core::str::from_utf8(self.value).map_err(|_| Asn1Error::InvalidUtf8) } /// Parse as any string type (UTF8String, PrintableString, IA5String). pub fn as_string(&self) -> Result<&'a str> { match self.tag { TAG_UTF8_STRING | TAG_PRINTABLE_STRING | TAG_IA5_STRING => { core::str::from_utf8(self.value).map_err(|_| Asn1Error::InvalidUtf8) } _ => Err(Asn1Error::UnexpectedTag { expected: TAG_UTF8_STRING, got: self.tag, }), } } /// Parse the value as UTCTime (YYMMDDHHMMSSZ). pub fn as_utc_time(&self) -> Result<&'a str> { if self.tag != TAG_UTC_TIME { return Err(Asn1Error::UnexpectedTag { expected: TAG_UTC_TIME, got: self.tag, }); } let s = core::str::from_utf8(self.value).map_err(|_| Asn1Error::InvalidTime)?; // Basic validation: should be 13 chars ending in 'Z' (YYMMDDHHMMSSZ) if s.len() != 13 || !s.ends_with('Z') { return Err(Asn1Error::InvalidTime); } Ok(s) } /// Parse the value as GeneralizedTime (YYYYMMDDHHMMSSZ). pub fn as_generalized_time(&self) -> Result<&'a str> { if self.tag != TAG_GENERALIZED_TIME { return Err(Asn1Error::UnexpectedTag { expected: TAG_GENERALIZED_TIME, got: self.tag, }); } let s = core::str::from_utf8(self.value).map_err(|_| Asn1Error::InvalidTime)?; // Basic validation: should be 15 chars ending in 'Z' (YYYYMMDDHHMMSSZ) if s.len() != 15 || !s.ends_with('Z') { return Err(Asn1Error::InvalidTime); } Ok(s) } } // --------------------------------------------------------------------------- // DER integer validation // --------------------------------------------------------------------------- fn validate_der_integer(bytes: &[u8]) -> Result<()> { if bytes.is_empty() { return Err(Asn1Error::NonMinimalInteger); } // Check for non-minimal positive: leading 0x00 followed by a byte < 0x80 if bytes.len() > 1 && bytes[0] == 0x00 && bytes[1] & 0x80 == 0 { return Err(Asn1Error::NonMinimalInteger); } // Check for non-minimal negative: leading 0xFF followed by a byte >= 0x80 if bytes.len() > 1 && bytes[0] == 0xFF && bytes[1] & 0x80 != 0 { return Err(Asn1Error::NonMinimalInteger); } Ok(()) } // --------------------------------------------------------------------------- // DER parser // --------------------------------------------------------------------------- /// A streaming DER parser over a byte slice. pub struct DerParser<'a> { data: &'a [u8], pos: usize, } impl<'a> DerParser<'a> { /// Create a new parser over the given data. pub fn new(data: &'a [u8]) -> Self { Self { data, pos: 0 } } /// True if there is more data to parse. pub fn has_more(&self) -> bool { self.pos < self.data.len() } /// Remaining unparsed bytes. pub fn remaining(&self) -> &'a [u8] { &self.data[self.pos..] } /// Current position in the input. pub fn position(&self) -> usize { self.pos } /// Read the next TLV item. pub fn read_tlv(&mut self) -> Result> { let start = self.pos; // Read tag let tag = self.read_byte()?; // We only support single-byte tags (tag number < 31). // Multi-byte tags (tag number == 0x1F) are rare in X.509. if tag & 0x1F == 0x1F { // Long-form tag — skip continuation bytes // For simplicity, we don't decode these but we need to consume them. while self.pos < self.data.len() { let b = self.read_byte()?; if b & 0x80 == 0 { break; } } // This is a simplification; we store the first tag byte only. } // Read length let length = self.read_length()?; // Read value if self.pos + length > self.data.len() { return Err(Asn1Error::Truncated); } let value = &self.data[self.pos..self.pos + length]; self.pos += length; let raw = &self.data[start..self.pos]; Ok(Tlv { tag, value, raw }) } /// Read a TLV and expect a specific tag. pub fn read_expect(&mut self, expected_tag: u8) -> Result> { let tlv = self.read_tlv()?; if tlv.tag != expected_tag { return Err(Asn1Error::UnexpectedTag { expected: expected_tag, got: tlv.tag, }); } Ok(tlv) } /// Read an optional context-specific tagged value. Returns None if the next /// tag doesn't match. pub fn read_optional_context( &mut self, tag_number: u8, constructed: bool, ) -> Result>> { if !self.has_more() { return Ok(None); } let expected = TAG_CLASS_CONTEXT | if constructed { TAG_CONSTRUCTED } else { 0 } | tag_number; if self.peek_tag()? != expected { return Ok(None); } Ok(Some(self.read_tlv()?)) } /// Peek at the next tag byte without consuming it. pub fn peek_tag(&self) -> Result { if self.pos >= self.data.len() { return Err(Asn1Error::Truncated); } Ok(self.data[self.pos]) } /// Skip the next TLV item. pub fn skip(&mut self) -> Result<()> { self.read_tlv()?; Ok(()) } /// Read all remaining TLV items. pub fn read_all(&mut self) -> Result>> { let mut items = Vec::new(); while self.has_more() { items.push(self.read_tlv()?); } Ok(items) } // --- Internal helpers --- fn read_byte(&mut self) -> Result { if self.pos >= self.data.len() { return Err(Asn1Error::Truncated); } let b = self.data[self.pos]; self.pos += 1; Ok(b) } fn read_length(&mut self) -> Result { let first = self.read_byte()?; if first == 0x80 { // Indefinite length — forbidden in DER return Err(Asn1Error::IndefiniteLength); } if first & 0x80 == 0 { // Short form: length is the byte itself return Ok(first as usize); } // Long form: first byte tells how many length bytes follow let num_bytes = (first & 0x7F) as usize; if num_bytes == 0 { return Err(Asn1Error::IndefiniteLength); } if num_bytes > 4 { // We don't support lengths > 4 bytes (4 GB should be more than enough) return Err(Asn1Error::LengthOverflow); } let mut length: usize = 0; for i in 0..num_bytes { let b = self.read_byte()? as usize; // DER minimality: first byte must not be 0 in multi-byte encoding if i == 0 && b == 0 { return Err(Asn1Error::NonMinimalLength); } length = length .checked_shl(8) .and_then(|v| v.checked_add(b)) .ok_or(Asn1Error::LengthOverflow)?; } // DER minimality: if the length fits in short form (< 128), must use short form if length < 128 { return Err(Asn1Error::NonMinimalLength); } // DER minimality: if the length fits in fewer bytes, must use fewer bytes if num_bytes > 1 { let min_bytes = if length < 0x100 { 1 } else if length < 0x10000 { 2 } else if length < 0x1000000 { 3 } else { 4 }; if num_bytes > min_bytes { return Err(Asn1Error::NonMinimalLength); } } Ok(length) } } // --------------------------------------------------------------------------- // Convenience: parse a single top-level TLV from a byte slice // --------------------------------------------------------------------------- /// Parse a single DER-encoded TLV from the data, returning an error if there /// is trailing data. pub fn parse_one(data: &[u8]) -> Result> { let mut parser = DerParser::new(data); let tlv = parser.read_tlv()?; if parser.has_more() { return Err(Asn1Error::TrailingData); } Ok(tlv) } /// Parse a DER-encoded SEQUENCE and return its inner TLV items. pub fn parse_sequence(data: &[u8]) -> Result>> { let outer = parse_one(data)?; if outer.tag != TAG_SEQUENCE { return Err(Asn1Error::UnexpectedTag { expected: TAG_SEQUENCE, got: outer.tag, }); } outer.sequence_items() } // --------------------------------------------------------------------------- // Tests // --------------------------------------------------------------------------- #[cfg(test)] mod tests { use super::*; // --- Basic TLV parsing --- #[test] fn parse_null() { let data = [0x05, 0x00]; // NULL let tlv = parse_one(&data).unwrap(); assert_eq!(tlv.tag, TAG_NULL); assert!(tlv.value.is_empty()); tlv.as_null().unwrap(); } #[test] fn parse_boolean_true() { let data = [0x01, 0x01, 0xFF]; // BOOLEAN TRUE let tlv = parse_one(&data).unwrap(); assert!(tlv.as_boolean().unwrap()); } #[test] fn parse_boolean_false() { let data = [0x01, 0x01, 0x00]; // BOOLEAN FALSE let tlv = parse_one(&data).unwrap(); assert!(!tlv.as_boolean().unwrap()); } #[test] fn reject_invalid_boolean() { let data = [0x01, 0x01, 0x01]; // Not 0x00 or 0xFF let tlv = parse_one(&data).unwrap(); assert_eq!(tlv.as_boolean().unwrap_err(), Asn1Error::InvalidBoolean); } #[test] fn parse_integer_positive() { // INTEGER 127 let data = [0x02, 0x01, 0x7F]; let tlv = parse_one(&data).unwrap(); assert_eq!(tlv.as_integer().unwrap(), &[0x7F]); } #[test] fn parse_integer_with_leading_zero() { // INTEGER 128 (needs leading 0x00 to stay positive) let data = [0x02, 0x02, 0x00, 0x80]; let tlv = parse_one(&data).unwrap(); assert_eq!(tlv.as_integer().unwrap(), &[0x00, 0x80]); assert_eq!(tlv.as_positive_integer().unwrap(), &[0x80]); } #[test] fn reject_non_minimal_integer() { // Non-minimal: leading 0x00 before a byte < 0x80 let data = [0x02, 0x02, 0x00, 0x7F]; let tlv = parse_one(&data).unwrap(); assert_eq!(tlv.as_integer().unwrap_err(), Asn1Error::NonMinimalInteger); } #[test] fn parse_octet_string() { let data = [0x04, 0x03, 0x01, 0x02, 0x03]; let tlv = parse_one(&data).unwrap(); assert_eq!(tlv.as_octet_string().unwrap(), &[0x01, 0x02, 0x03]); } #[test] fn parse_bit_string() { // BIT STRING: 0 unused bits, data = [0xAB, 0xCD] let data = [0x03, 0x03, 0x00, 0xAB, 0xCD]; let tlv = parse_one(&data).unwrap(); let (unused, bits) = tlv.as_bit_string().unwrap(); assert_eq!(unused, 0); assert_eq!(bits, &[0xAB, 0xCD]); } #[test] fn parse_bit_string_with_unused_bits() { // BIT STRING: 4 unused bits, last byte has low 4 bits = 0 let data = [0x03, 0x02, 0x04, 0xF0]; let tlv = parse_one(&data).unwrap(); let (unused, bits) = tlv.as_bit_string().unwrap(); assert_eq!(unused, 4); assert_eq!(bits, &[0xF0]); } #[test] fn reject_invalid_bit_string_nonzero_unused() { // BIT STRING: 4 unused bits but low 4 bits are non-zero let data = [0x03, 0x02, 0x04, 0xF1]; let tlv = parse_one(&data).unwrap(); assert_eq!( tlv.as_bit_string().unwrap_err(), Asn1Error::InvalidBitString ); } // --- OID tests --- #[test] fn parse_oid_rsa_encryption() { // OID 1.2.840.113549.1.1.1 (rsaEncryption) let data = [ 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01, ]; let tlv = parse_one(&data).unwrap(); let oid = tlv.as_oid().unwrap(); assert!(oid.matches(OID_RSA_ENCRYPTION)); let comps = oid.components().unwrap(); assert_eq!(comps, vec![1, 2, 840, 113549, 1, 1, 1]); } #[test] fn parse_oid_sha256_with_rsa() { let data = [ 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0B, ]; let tlv = parse_one(&data).unwrap(); let oid = tlv.as_oid().unwrap(); assert!(oid.matches(OID_SHA256_WITH_RSA)); assert_eq!(oid.components().unwrap(), vec![1, 2, 840, 113549, 1, 1, 11]); } #[test] fn parse_oid_prime256v1() { let data = [0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07]; let tlv = parse_one(&data).unwrap(); let oid = tlv.as_oid().unwrap(); assert!(oid.matches(OID_PRIME256V1)); assert_eq!(oid.components().unwrap(), vec![1, 2, 840, 10045, 3, 1, 7]); } #[test] fn oid_display() { let oid = Oid::from_der_bytes(OID_RSA_ENCRYPTION); assert_eq!(format!("{}", oid), "1.2.840.113549.1.1.1"); } #[test] fn oid_encode_decode_roundtrip() { let components = [1u32, 2, 840, 113549, 1, 1, 1]; let encoded = Oid::encode_components(&components).unwrap(); assert_eq!(encoded.as_slice(), OID_RSA_ENCRYPTION); let oid = Oid::from_der_bytes(&encoded); assert_eq!(oid.components().unwrap(), components); } #[test] fn oid_encode_simple() { // 2.5.4.3 (id-at-commonName) let components = [2u32, 5, 4, 3]; let encoded = Oid::encode_components(&components).unwrap(); assert_eq!(encoded.as_slice(), OID_COMMON_NAME); } // --- String tests --- #[test] fn parse_utf8_string() { let data = [0x0C, 0x05, b'H', b'e', b'l', b'l', b'o']; let tlv = parse_one(&data).unwrap(); assert_eq!(tlv.as_utf8_string().unwrap(), "Hello"); } #[test] fn parse_printable_string() { let data = [0x13, 0x02, b'U', b'S']; let tlv = parse_one(&data).unwrap(); assert_eq!(tlv.as_printable_string().unwrap(), "US"); } #[test] fn parse_ia5_string() { let data = [0x16, 0x03, b'c', b'o', b'm']; let tlv = parse_one(&data).unwrap(); assert_eq!(tlv.as_ia5_string().unwrap(), "com"); } // --- Time tests --- #[test] fn parse_utc_time() { // "230101120000Z" let time_str = b"230101120000Z"; let mut data = vec![TAG_UTC_TIME, time_str.len() as u8]; data.extend_from_slice(time_str); let tlv = parse_one(&data).unwrap(); assert_eq!(tlv.as_utc_time().unwrap(), "230101120000Z"); } #[test] fn parse_generalized_time() { // "20230101120000Z" let time_str = b"20230101120000Z"; let mut data = vec![TAG_GENERALIZED_TIME, time_str.len() as u8]; data.extend_from_slice(time_str); let tlv = parse_one(&data).unwrap(); assert_eq!(tlv.as_generalized_time().unwrap(), "20230101120000Z"); } // --- SEQUENCE tests --- #[test] fn parse_sequence_of_integers() { // SEQUENCE { INTEGER 1, INTEGER 2 } #[rustfmt::skip] let data = [ 0x30, 0x06, // SEQUENCE, length 6 0x02, 0x01, 0x01, // INTEGER 1 0x02, 0x01, 0x02, // INTEGER 2 ]; let items = parse_sequence(&data).unwrap(); assert_eq!(items.len(), 2); assert_eq!(items[0].as_integer().unwrap(), &[0x01]); assert_eq!(items[1].as_integer().unwrap(), &[0x02]); } #[test] fn parse_nested_sequence() { // SEQUENCE { SEQUENCE { INTEGER 42 } } #[rustfmt::skip] let data = [ 0x30, 0x05, // outer SEQUENCE 0x30, 0x03, // inner SEQUENCE 0x02, 0x01, 0x2A, // INTEGER 42 ]; let outer = parse_sequence(&data).unwrap(); assert_eq!(outer.len(), 1); let inner = outer[0].sequence_items().unwrap(); assert_eq!(inner.len(), 1); assert_eq!(inner[0].as_integer().unwrap(), &[0x2A]); } // --- Context-specific tags --- #[test] fn parse_context_specific_explicit() { // [0] EXPLICIT { INTEGER 3 } #[rustfmt::skip] let data = [ 0xA0, 0x03, // context-specific [0] constructed 0x02, 0x01, 0x03, // INTEGER 3 ]; let tlv = parse_one(&data).unwrap(); assert!(tlv.is_context_specific()); assert_eq!(tlv.context_tag(), Some(0)); assert!(tlv.is_constructed()); // Parse inner content let mut inner = DerParser::new(tlv.value); let int_tlv = inner.read_tlv().unwrap(); assert_eq!(int_tlv.as_integer().unwrap(), &[0x03]); } #[test] fn parse_context_specific_implicit() { // [1] IMPLICIT OCTET STRING (primitive) let data = [0x81, 0x02, 0xAB, 0xCD]; let tlv = parse_one(&data).unwrap(); assert!(tlv.is_context_specific()); assert_eq!(tlv.context_tag(), Some(1)); assert!(!tlv.is_constructed()); assert_eq!(tlv.value, &[0xAB, 0xCD]); } #[test] fn read_optional_context_present() { #[rustfmt::skip] let data = [ 0xA0, 0x03, // [0] constructed 0x02, 0x01, 0x03, // INTEGER 3 ]; let mut parser = DerParser::new(&data); let opt = parser.read_optional_context(0, true).unwrap(); assert!(opt.is_some()); assert_eq!(opt.unwrap().context_tag(), Some(0)); } #[test] fn read_optional_context_absent() { let data = [0x02, 0x01, 0x42]; // INTEGER, not [0] let mut parser = DerParser::new(&data); let opt = parser.read_optional_context(0, true).unwrap(); assert!(opt.is_none()); // Parser should not have advanced assert_eq!(parser.position(), 0); } // --- Long-form length --- #[test] fn parse_long_form_length() { // OCTET STRING with 200 bytes (needs 2-byte length encoding: 0x81, 0xC8) let mut data = vec![0x04, 0x81, 0xC8]; data.extend_from_slice(&[0xAA; 200]); let tlv = parse_one(&data).unwrap(); assert_eq!(tlv.value.len(), 200); } #[test] fn parse_two_byte_long_form_length() { // OCTET STRING with 256 bytes (needs: 0x82, 0x01, 0x00) let mut data = vec![0x04, 0x82, 0x01, 0x00]; data.extend_from_slice(&[0xBB; 256]); let tlv = parse_one(&data).unwrap(); assert_eq!(tlv.value.len(), 256); } // --- DER violation rejection --- #[test] fn reject_indefinite_length() { let data = [0x30, 0x80, 0x00, 0x00]; // SEQUENCE with indefinite length assert_eq!(parse_one(&data).unwrap_err(), Asn1Error::IndefiniteLength); } #[test] fn reject_non_minimal_length() { // OCTET STRING, length 5 encoded as 0x81, 0x05 (should be just 0x05) let data = [0x04, 0x81, 0x05, 0x01, 0x02, 0x03, 0x04, 0x05]; assert_eq!(parse_one(&data).unwrap_err(), Asn1Error::NonMinimalLength); } #[test] fn reject_trailing_data() { let data = [0x05, 0x00, 0xFF]; // NULL + trailing byte assert_eq!(parse_one(&data).unwrap_err(), Asn1Error::TrailingData); } #[test] fn reject_truncated() { let data = [0x04, 0x05, 0x01, 0x02]; // OCTET STRING claims 5 bytes but only 2 given assert_eq!(parse_one(&data).unwrap_err(), Asn1Error::Truncated); } // --- Real certificate fragment --- #[test] fn parse_algorithm_identifier() { // AlgorithmIdentifier { algorithm: sha256WithRSA, parameters: NULL } #[rustfmt::skip] let data = [ 0x30, 0x0D, // SEQUENCE 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0B, // OID 0x05, 0x00, // NULL ]; let items = parse_sequence(&data).unwrap(); assert_eq!(items.len(), 2); let oid = items[0].as_oid().unwrap(); assert!(oid.matches(OID_SHA256_WITH_RSA)); items[1].as_null().unwrap(); } #[test] fn parse_rdn_sequence_fragment() { // A single RelativeDistinguishedName: CN = "Test" #[rustfmt::skip] let data = [ 0x30, 0x13, // SEQUENCE (Name) 0x31, 0x11, // SET (RDN) 0x30, 0x0F, // SEQUENCE (AttributeTypeAndValue) 0x06, 0x03, 0x55, 0x04, 0x03, // OID: id-at-commonName 0x0C, 0x08, b'T', b'e', b's', b't', b' ', b'C', b'A', b'0', // UTF8String "Test CA0" ]; let name_items = parse_sequence(&data).unwrap(); assert_eq!(name_items.len(), 1); // One RDN let rdn = name_items[0].sequence_items().unwrap(); assert_eq!(rdn.len(), 1); // One attribute let atv = rdn[0].sequence_items().unwrap(); assert_eq!(atv.len(), 2); let oid = atv[0].as_oid().unwrap(); assert!(oid.matches(OID_COMMON_NAME)); assert_eq!(atv[1].as_utf8_string().unwrap(), "Test CA0"); } // --- Raw field access --- #[test] fn raw_includes_full_encoding() { let data = [0x02, 0x01, 0x42]; // INTEGER 66 let tlv = parse_one(&data).unwrap(); assert_eq!(tlv.raw, &data); assert_eq!(tlv.value, &[0x42]); } // --- DerParser sequential reads --- #[test] fn parser_sequential_reads() { #[rustfmt::skip] let data = [ 0x02, 0x01, 0x01, // INTEGER 1 0x02, 0x01, 0x02, // INTEGER 2 0x05, 0x00, // NULL ]; let mut parser = DerParser::new(&data); let a = parser.read_expect(TAG_INTEGER).unwrap(); assert_eq!(a.as_integer().unwrap(), &[0x01]); let b = parser.read_expect(TAG_INTEGER).unwrap(); assert_eq!(b.as_integer().unwrap(), &[0x02]); let c = parser.read_expect(TAG_NULL).unwrap(); c.as_null().unwrap(); assert!(!parser.has_more()); } #[test] fn parser_read_expect_wrong_tag() { let data = [0x05, 0x00]; // NULL let mut parser = DerParser::new(&data); let err = parser.read_expect(TAG_INTEGER).unwrap_err(); assert_eq!( err, Asn1Error::UnexpectedTag { expected: TAG_INTEGER, got: TAG_NULL, } ); } // --- X.509 version field (context-specific [0] EXPLICIT INTEGER) --- #[test] fn parse_x509_version_field() { // [0] EXPLICIT { INTEGER 2 } — X.509 v3 #[rustfmt::skip] let data = [ 0xA0, 0x03, // [0] constructed 0x02, 0x01, 0x02, // INTEGER 2 ]; let tlv = parse_one(&data).unwrap(); assert!(tlv.is_context_specific()); assert!(tlv.is_constructed()); assert_eq!(tlv.context_tag(), Some(0)); let mut inner = DerParser::new(tlv.value); let version = inner.read_expect(TAG_INTEGER).unwrap(); assert_eq!(version.as_integer().unwrap(), &[0x02]); } // --- BasicConstraints extension value --- #[test] fn parse_basic_constraints() { // SEQUENCE { BOOLEAN TRUE, INTEGER 0 } #[rustfmt::skip] let data = [ 0x30, 0x06, // SEQUENCE 0x01, 0x01, 0xFF, // BOOLEAN TRUE (cA) 0x02, 0x01, 0x00, // INTEGER 0 (pathLenConstraint) ]; let items = parse_sequence(&data).unwrap(); assert_eq!(items.len(), 2); assert!(items[0].as_boolean().unwrap()); assert_eq!(items[1].as_integer().unwrap(), &[0x00]); } // --- Large OID component values --- #[test] fn parse_oid_large_components() { // 2.16.840.1.101.3.4.2.1 (sha256) let data = [ 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, ]; let tlv = parse_one(&data).unwrap(); let oid = tlv.as_oid().unwrap(); assert!(oid.matches(OID_SHA256)); assert_eq!( oid.components().unwrap(), vec![2, 16, 840, 1, 101, 3, 4, 2, 1] ); } // --- SET tag --- #[test] fn parse_set() { // SET { INTEGER 1, INTEGER 2 } #[rustfmt::skip] let data = [ 0x31, 0x06, // SET 0x02, 0x01, 0x01, // INTEGER 1 0x02, 0x01, 0x02, // INTEGER 2 ]; let tlv = parse_one(&data).unwrap(); assert_eq!(tlv.tag, TAG_SET); assert!(tlv.is_constructed()); let items = tlv.sequence_items().unwrap(); assert_eq!(items.len(), 2); } // --- read_all --- #[test] fn read_all_items() { #[rustfmt::skip] let data = [ 0x02, 0x01, 0x01, 0x02, 0x01, 0x02, 0x02, 0x01, 0x03, ]; let mut parser = DerParser::new(&data); let items = parser.read_all().unwrap(); assert_eq!(items.len(), 3); } // --- Empty data --- #[test] fn empty_data_returns_truncated() { assert_eq!(parse_one(&[]).unwrap_err(), Asn1Error::Truncated); } }