x509_cert/
serial_number.rs

1//! X.509 serial number
2
3use core::{fmt::Display, marker::PhantomData};
4
5use der::{
6    asn1::{self, Int},
7    DecodeValue, EncodeValue, ErrorKind, FixedTag, Header, Length, Reader, Result, Tag, ValueOrd,
8    Writer,
9};
10
11use crate::certificate::{Profile, Rfc5280};
12
13/// [RFC 5280 Section 4.1.2.2.]  Serial Number
14///
15///   The serial number MUST be a positive integer assigned by the CA to
16///   each certificate.  It MUST be unique for each certificate issued by a
17///   given CA (i.e., the issuer name and serial number identify a unique
18///   certificate).  CAs MUST force the serialNumber to be a non-negative
19///   integer.
20///
21///   Given the uniqueness requirements above, serial numbers can be
22///   expected to contain long integers.  Certificate users MUST be able to
23///   handle serialNumber values up to 20 octets.  Conforming CAs MUST NOT
24///   use serialNumber values longer than 20 octets.
25///
26///   Note: Non-conforming CAs may issue certificates with serial numbers
27///   that are negative or zero.  Certificate users SHOULD be prepared to
28///   gracefully handle such certificates.
29#[derive(Clone, Debug, Eq, PartialEq, ValueOrd, PartialOrd, Ord)]
30pub struct SerialNumber<P: Profile = Rfc5280> {
31    pub(crate) inner: Int,
32    _profile: PhantomData<P>,
33}
34
35impl<P: Profile> SerialNumber<P> {
36    /// Maximum length in bytes for a [`SerialNumber`]
37    pub const MAX_LEN: Length = Length::new(20);
38
39    /// See notes in `SerialNumber::new` and `SerialNumber::decode_value`.
40    pub(crate) const MAX_DECODE_LEN: Length = Length::new(21);
41
42    /// Create a new [`SerialNumber`] from a byte slice.
43    ///
44    /// The byte slice **must** represent a positive integer.
45    pub fn new(bytes: &[u8]) -> Result<Self> {
46        let inner = asn1::Uint::new(bytes)?;
47
48        // The user might give us a 20 byte unsigned integer with a high MSB,
49        // which we'd then encode with 21 octets to preserve the sign bit.
50        // RFC 5280 is ambiguous about whether this is valid, so we limit
51        // `SerialNumber` *encodings* to 20 bytes or fewer while permitting
52        // `SerialNumber` *decodings* to have up to 21 bytes below.
53        if inner.value_len()? > Self::MAX_LEN {
54            return Err(ErrorKind::Overlength.into());
55        }
56
57        Ok(Self {
58            inner: inner.into(),
59            _profile: PhantomData,
60        })
61    }
62
63    /// Borrow the inner byte slice which contains the least significant bytes
64    /// of a big endian integer value with all leading zeros stripped.
65    pub fn as_bytes(&self) -> &[u8] {
66        self.inner.as_bytes()
67    }
68}
69
70impl<P: Profile> EncodeValue for SerialNumber<P> {
71    fn value_len(&self) -> Result<Length> {
72        self.inner.value_len()
73    }
74
75    fn encode_value(&self, writer: &mut impl Writer) -> Result<()> {
76        self.inner.encode_value(writer)
77    }
78}
79
80impl<'a, P: Profile> DecodeValue<'a> for SerialNumber<P> {
81    fn decode_value<R: Reader<'a>>(reader: &mut R, header: Header) -> Result<Self> {
82        let inner = Int::decode_value(reader, header)?;
83        let serial = Self {
84            inner,
85            _profile: PhantomData,
86        };
87
88        P::check_serial_number(&serial)?;
89
90        Ok(serial)
91    }
92}
93
94impl<P: Profile> FixedTag for SerialNumber<P> {
95    const TAG: Tag = <Int as FixedTag>::TAG;
96}
97
98impl Display for SerialNumber {
99    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
100        let mut iter = self.as_bytes().iter().peekable();
101
102        while let Some(byte) = iter.next() {
103            match iter.peek() {
104                Some(_) => write!(f, "{:02X}:", byte)?,
105                None => write!(f, "{:02X}", byte)?,
106            }
107        }
108
109        Ok(())
110    }
111}
112
113macro_rules! impl_from {
114    ($source:ty) => {
115        impl From<$source> for SerialNumber {
116            fn from(inner: $source) -> SerialNumber {
117                let serial_number = &inner.to_be_bytes()[..];
118                let serial_number = asn1::Uint::new(serial_number).unwrap();
119
120                // This could only fail if the big endian representation was to be more than 20
121                // bytes long. Because it's only implemented for up to u64 / usize (8 bytes).
122                SerialNumber::new(serial_number.as_bytes()).unwrap()
123            }
124        }
125    };
126}
127
128impl_from!(u8);
129impl_from!(u16);
130impl_from!(u32);
131impl_from!(u64);
132impl_from!(usize);
133
134// Implement by hand because the derive would create invalid values.
135// Use the constructor to create a valid value.
136#[cfg(feature = "arbitrary")]
137impl<'a, P: Profile> arbitrary::Arbitrary<'a> for SerialNumber<P> {
138    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
139        let len = u.int_in_range(0u32..=Self::MAX_LEN.into())?;
140
141        Self::new(u.bytes(len as usize)?).map_err(|_| arbitrary::Error::IncorrectFormat)
142    }
143
144    fn size_hint(depth: usize) -> (usize, Option<usize>) {
145        arbitrary::size_hint::and(u32::size_hint(depth), (0, None))
146    }
147}
148
149#[cfg(test)]
150#[allow(clippy::unwrap_used)]
151mod tests {
152    use alloc::string::ToString;
153
154    use super::*;
155
156    #[test]
157    fn serial_number_invariants() {
158        // Creating a new serial with an oversized encoding (due to high MSB) fails.
159        {
160            let too_big = [0x80; 20];
161            assert!(SerialNumber::<Rfc5280>::new(&too_big).is_err());
162        }
163
164        // Creating a new serial with the maximum encoding succeeds.
165        {
166            let just_enough = [
167                0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
168                0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
169            ];
170            assert!(SerialNumber::<Rfc5280>::new(&just_enough).is_ok());
171        }
172    }
173
174    #[test]
175    fn serial_number_display() {
176        {
177            let sn = SerialNumber::new(&[0x11, 0x22, 0x33]).unwrap();
178
179            assert_eq!(sn.to_string(), "11:22:33")
180        }
181
182        {
183            let sn = SerialNumber::new(&[0xAA, 0xBB, 0xCC, 0x01, 0x10, 0x00, 0x11]).unwrap();
184
185            // We force the user's serial to be positive if they give us a negative one.
186            assert_eq!(sn.to_string(), "00:AA:BB:CC:01:10:00:11")
187        }
188
189        {
190            let sn = SerialNumber::new(&[0x00, 0x00, 0x01]).unwrap();
191
192            // Leading zeroes are ignored, due to canonicalization.
193            assert_eq!(sn.to_string(), "01")
194        }
195    }
196}