//! X.509 serial number use core::{fmt::Display, marker::PhantomData}; use der::{ asn1::{self, Int}, DecodeValue, EncodeValue, ErrorKind, FixedTag, Header, Length, Reader, Result, Tag, ValueOrd, Writer, }; use crate::certificate::{Profile, Rfc5280}; /// [RFC 5280 Section 4.1.2.2.] Serial Number /// /// The serial number MUST be a positive integer assigned by the CA to /// each certificate. It MUST be unique for each certificate issued by a /// given CA (i.e., the issuer name and serial number identify a unique /// certificate). CAs MUST force the serialNumber to be a non-negative /// integer. /// /// Given the uniqueness requirements above, serial numbers can be /// expected to contain long integers. Certificate users MUST be able to /// handle serialNumber values up to 20 octets. Conforming CAs MUST NOT /// use serialNumber values longer than 20 octets. /// /// Note: Non-conforming CAs may issue certificates with serial numbers /// that are negative or zero. Certificate users SHOULD be prepared to /// gracefully handle such certificates. #[derive(Clone, Debug, Eq, PartialEq, ValueOrd, PartialOrd, Ord)] pub struct SerialNumber { pub(crate) inner: Int, _profile: PhantomData

, } impl SerialNumber

{ /// Maximum length in bytes for a [`SerialNumber`] pub const MAX_LEN: Length = Length::new(20); /// See notes in `SerialNumber::new` and `SerialNumber::decode_value`. pub(crate) const MAX_DECODE_LEN: Length = Length::new(21); /// Create a new [`SerialNumber`] from a byte slice. /// /// The byte slice **must** represent a positive integer. pub fn new(bytes: &[u8]) -> Result { let inner = asn1::Uint::new(bytes)?; // The user might give us a 20 byte unsigned integer with a high MSB, // which we'd then encode with 21 octets to preserve the sign bit. // RFC 5280 is ambiguous about whether this is valid, so we limit // `SerialNumber` *encodings* to 20 bytes or fewer while permitting // `SerialNumber` *decodings* to have up to 21 bytes below. if inner.value_len()? > Self::MAX_LEN { return Err(ErrorKind::Overlength.into()); } Ok(Self { inner: inner.into(), _profile: PhantomData, }) } /// Borrow the inner byte slice which contains the least significant bytes /// of a big endian integer value with all leading zeros stripped. pub fn as_bytes(&self) -> &[u8] { self.inner.as_bytes() } } impl EncodeValue for SerialNumber

{ fn value_len(&self) -> Result { self.inner.value_len() } fn encode_value(&self, writer: &mut impl Writer) -> Result<()> { self.inner.encode_value(writer) } } impl<'a, P: Profile> DecodeValue<'a> for SerialNumber

{ fn decode_value>(reader: &mut R, header: Header) -> Result { let inner = Int::decode_value(reader, header)?; let serial = Self { inner, _profile: PhantomData, }; P::check_serial_number(&serial)?; Ok(serial) } } impl FixedTag for SerialNumber

{ const TAG: Tag = ::TAG; } impl Display for SerialNumber { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { let mut iter = self.as_bytes().iter().peekable(); while let Some(byte) = iter.next() { match iter.peek() { Some(_) => write!(f, "{:02X}:", byte)?, None => write!(f, "{:02X}", byte)?, } } Ok(()) } } macro_rules! impl_from { ($source:ty) => { impl From<$source> for SerialNumber { fn from(inner: $source) -> SerialNumber { let serial_number = &inner.to_be_bytes()[..]; let serial_number = asn1::Uint::new(serial_number).unwrap(); // This could only fail if the big endian representation was to be more than 20 // bytes long. Because it's only implemented for up to u64 / usize (8 bytes). SerialNumber::new(serial_number.as_bytes()).unwrap() } } }; } impl_from!(u8); impl_from!(u16); impl_from!(u32); impl_from!(u64); impl_from!(usize); // Implement by hand because the derive would create invalid values. // Use the constructor to create a valid value. #[cfg(feature = "arbitrary")] impl<'a, P: Profile> arbitrary::Arbitrary<'a> for SerialNumber

{ fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { let len = u.int_in_range(0u32..=Self::MAX_LEN.into())?; Self::new(u.bytes(len as usize)?).map_err(|_| arbitrary::Error::IncorrectFormat) } fn size_hint(depth: usize) -> (usize, Option) { arbitrary::size_hint::and(u32::size_hint(depth), (0, None)) } } #[cfg(test)] mod tests { use alloc::string::ToString; use super::*; #[test] fn serial_number_invariants() { // Creating a new serial with an oversized encoding (due to high MSB) fails. { let too_big = [0x80; 20]; assert!(SerialNumber::::new(&too_big).is_err()); } // Creating a new serial with the maximum encoding succeeds. { let just_enough = [ 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, ]; assert!(SerialNumber::::new(&just_enough).is_ok()); } } #[test] fn serial_number_display() { { let sn = SerialNumber::new(&[0x11, 0x22, 0x33]).unwrap(); assert_eq!(sn.to_string(), "11:22:33") } { let sn = SerialNumber::new(&[0xAA, 0xBB, 0xCC, 0x01, 0x10, 0x00, 0x11]).unwrap(); // We force the user's serial to be positive if they give us a negative one. assert_eq!(sn.to_string(), "00:AA:BB:CC:01:10:00:11") } { let sn = SerialNumber::new(&[0x00, 0x00, 0x01]).unwrap(); // Leading zeroes are ignored, due to canonicalization. assert_eq!(sn.to_string(), "01") } } }