diff --git a/Cargo.lock b/Cargo.lock index c3ac2b1af..1bd00453f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1841,6 +1841,7 @@ dependencies = [ "const-oid 0.9.5", "der", "ecdsa", + "generic-array", "hex-literal 0.4.1", "p256", "rand", diff --git a/x509-cert/Cargo.toml b/x509-cert/Cargo.toml index b90401750..5271c8686 100644 --- a/x509-cert/Cargo.toml +++ b/x509-cert/Cargo.toml @@ -17,6 +17,7 @@ rust-version = "1.65" [dependencies] const-oid = { version = "0.9.3", features = ["db"] } der = { version = "0.7.6", features = ["alloc", "derive", "flagset", "oid"] } +generic-array = { version = "0.14.7", default-features = false } spki = { version = "0.7.2", features = ["alloc"] } # optional dependencies diff --git a/x509-cert/src/serial_number.rs b/x509-cert/src/serial_number.rs index 42952fd8c..8fa537720 100644 --- a/x509-cert/src/serial_number.rs +++ b/x509-cert/src/serial_number.rs @@ -7,6 +7,20 @@ use der::{ DecodeValue, EncodeValue, ErrorKind, FixedTag, Header, Length, Reader, Result, Tag, ValueOrd, Writer, }; +#[cfg(feature = "builder")] +use { + core::ops::Add, + generic_array::{ + typenum::{ + consts::{U20, U8}, + marker_traits::Unsigned, + type_operators::{Max, Min}, + uint::UTerm, + }, + ArrayLength, GenericArray, + }, + signature::rand_core::CryptoRngCore, +}; use crate::certificate::{Profile, Rfc5280}; @@ -65,6 +79,48 @@ impl SerialNumber

{ pub fn as_bytes(&self) -> &[u8] { self.inner.as_bytes() } + + /// Generates a random serial number from RNG. + /// + /// This follows the recommendation the CAB forum [ballot 164] and uses a minimum of 64 bits + /// of output from the CSPRNG. + /// + /// [ballot 164]: https://cabforum.org/2016/03/31/ballot-164/ + #[cfg(feature = "builder")] + pub fn generate(rng: &mut impl CryptoRngCore) -> Result + where + N: Unsigned + ArrayLength, + N: Min, // Rand minimum is 64 bits + N: Max, // Max length is 20 bytes + { + Self::generate_with_prefix::(GenericArray::default(), rng) + } + + /// Generates a random serial number from RNG. Include a prefix value. + /// + /// This follows the recommendation the CAB forum [ballot 164] and uses a minimum of 64 bits + /// of output from the CSPRNG. + /// + /// [ballot 164]: https://cabforum.org/2016/03/31/ballot-164/ + #[cfg(feature = "builder")] + pub fn generate_with_prefix( + prefix: GenericArray, + rng: &mut impl CryptoRngCore, + ) -> Result + where + N: Unsigned, + Prefix: Unsigned + ArrayLength, + Prefix: Add, + >::Output: ArrayLength, + N: Min, // Rand minimum is 64 bits + >::Output: Max, // Max length is 20 bytes + { + let mut buf = GenericArray::<_, >::Output>::default(); + buf[..Prefix::USIZE].copy_from_slice(&prefix); + rng.fill_bytes(&mut buf[Prefix::USIZE..]); + + Self::new(&buf) + } } impl EncodeValue for SerialNumber

{ @@ -151,6 +207,9 @@ impl<'a, P: Profile> arbitrary::Arbitrary<'a> for SerialNumber

{ mod tests { use alloc::string::ToString; + #[cfg(feature = "builder")] + use generic_array::{arr, typenum::consts::U17}; + use super::*; #[test] @@ -193,4 +252,22 @@ mod tests { assert_eq!(sn.to_string(), "01") } } + + #[cfg(feature = "builder")] + #[test] + fn serial_number_generate() { + let sn = SerialNumber::::generate::(&mut rand::thread_rng()).unwrap(); + + // Underlying storage uses signed int for compatibility reasons, + // we may need to prefix the value with 0x00 to make it an unsigned. + // in which case the length is going to be 18. + assert!(matches!(sn.as_bytes().len(), 17..=18)); + + let sn = SerialNumber::::generate_with_prefix::<_, U17>( + arr![u8; 1,2,3], + &mut rand::thread_rng(), + ) + .unwrap(); + assert_eq!(sn.as_bytes().len(), 20); + } }