+665
| //! ASN.1 string types | ||
| use std::{fmt, str::FromStr}; | ||
| use crate::{Error, InvalidAsn1String}; | ||
| /// ASN.1 `PrintableString` type. | ||
| /// | ||
| /// Supports a subset of the ASCII printable characters (described below). | ||
| /// | ||
| /// For the full ASCII character set, use | ||
| /// [`Ia5String`][`crate::Ia5String`]. | ||
| /// | ||
| /// # Examples | ||
| /// | ||
| /// You can create a `PrintableString` from [a literal string][`&str`] with [`PrintableString::try_from`]: | ||
| /// | ||
| /// ``` | ||
| /// use rcgen::string::PrintableString; | ||
| /// let hello = PrintableString::try_from("hello").unwrap(); | ||
| /// ``` | ||
| /// | ||
| /// # Supported characters | ||
| /// | ||
| /// PrintableString is a subset of the [ASCII printable characters]. | ||
| /// For instance, `'@'` is a printable character as per ASCII but can't be part of [ASN.1's `PrintableString`]. | ||
| /// | ||
| /// The following ASCII characters/ranges are supported: | ||
| /// | ||
| /// - `A..Z` | ||
| /// - `a..z` | ||
| /// - `0..9` | ||
| /// - "` `" (i.e. space) | ||
| /// - `\` | ||
| /// - `(` | ||
| /// - `)` | ||
| /// - `+` | ||
| /// - `,` | ||
| /// - `-` | ||
| /// - `.` | ||
| /// - `/` | ||
| /// - `:` | ||
| /// - `=` | ||
| /// - `?` | ||
| /// | ||
| /// [ASCII printable characters]: https://en.wikipedia.org/wiki/ASCII#Printable_characters | ||
| /// [ASN.1's `PrintableString`]: https://en.wikipedia.org/wiki/PrintableString | ||
| #[derive(Debug, PartialEq, Eq, Hash, Clone)] | ||
| pub struct PrintableString(String); | ||
| impl PrintableString { | ||
| /// Extracts a string slice containing the entire `PrintableString`. | ||
| pub fn as_str(&self) -> &str { | ||
| &self.0 | ||
| } | ||
| } | ||
| impl TryFrom<&str> for PrintableString { | ||
| type Error = Error; | ||
| /// Converts a `&str` to a [`PrintableString`]. | ||
| /// | ||
| /// Any character not in the [`PrintableString`] charset will be rejected. | ||
| /// See [`PrintableString`] documentation for more information. | ||
| /// | ||
| /// The result is allocated on the heap. | ||
| fn try_from(input: &str) -> Result<Self, Error> { | ||
| input.to_string().try_into() | ||
| } | ||
| } | ||
| impl TryFrom<String> for PrintableString { | ||
| type Error = Error; | ||
| /// Converts a [`String`][`std::string::String`] into a [`PrintableString`] | ||
| /// | ||
| /// Any character not in the [`PrintableString`] charset will be rejected. | ||
| /// See [`PrintableString`] documentation for more information. | ||
| /// | ||
| /// This conversion does not allocate or copy memory. | ||
| fn try_from(value: String) -> Result<Self, Self::Error> { | ||
| for &c in value.as_bytes() { | ||
| match c { | ||
| b'A'..=b'Z' | ||
| | b'a'..=b'z' | ||
| | b'0'..=b'9' | ||
| | b' ' | ||
| | b'\'' | ||
| | b'(' | ||
| | b')' | ||
| | b'+' | ||
| | b',' | ||
| | b'-' | ||
| | b'.' | ||
| | b'/' | ||
| | b':' | ||
| | b'=' | ||
| | b'?' => (), | ||
| _ => { | ||
| return Err(Error::InvalidAsn1String( | ||
| InvalidAsn1String::PrintableString(value), | ||
| )) | ||
| }, | ||
| } | ||
| } | ||
| Ok(Self(value)) | ||
| } | ||
| } | ||
| impl FromStr for PrintableString { | ||
| type Err = Error; | ||
| fn from_str(s: &str) -> Result<Self, Self::Err> { | ||
| s.try_into() | ||
| } | ||
| } | ||
| impl AsRef<str> for PrintableString { | ||
| fn as_ref(&self) -> &str { | ||
| &self.0 | ||
| } | ||
| } | ||
| impl fmt::Display for PrintableString { | ||
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
| fmt::Display::fmt(self.as_str(), f) | ||
| } | ||
| } | ||
| impl PartialEq<str> for PrintableString { | ||
| fn eq(&self, other: &str) -> bool { | ||
| self.as_str() == other | ||
| } | ||
| } | ||
| impl PartialEq<String> for PrintableString { | ||
| fn eq(&self, other: &String) -> bool { | ||
| self.as_str() == other.as_str() | ||
| } | ||
| } | ||
| impl PartialEq<&str> for PrintableString { | ||
| fn eq(&self, other: &&str) -> bool { | ||
| self.as_str() == *other | ||
| } | ||
| } | ||
| impl PartialEq<&String> for PrintableString { | ||
| fn eq(&self, other: &&String) -> bool { | ||
| self.as_str() == other.as_str() | ||
| } | ||
| } | ||
| /// ASN.1 `IA5String` type. | ||
| /// | ||
| /// # Examples | ||
| /// | ||
| /// You can create a `Ia5String` from [a literal string][`&str`] with [`Ia5String::try_from`]: | ||
| /// | ||
| /// ``` | ||
| /// use rcgen::string::Ia5String; | ||
| /// let hello = Ia5String::try_from("hello").unwrap(); | ||
| /// ``` | ||
| /// | ||
| /// # Supported characters | ||
| /// | ||
| /// Supports the [International Alphabet No. 5 (IA5)] character encoding, i.e. | ||
| /// the 128 characters of the ASCII alphabet. (Note: IA5 is now | ||
| /// technically known as the International Reference Alphabet or IRA as | ||
| /// specified in the ITU-T's T.50 recommendation). | ||
| /// | ||
| /// For UTF-8, use [`String`][`std::string::String`]. | ||
| /// | ||
| /// [International Alphabet No. 5 (IA5)]: https://en.wikipedia.org/wiki/T.50_(standard) | ||
| #[derive(Debug, PartialEq, Eq, Hash, Clone)] | ||
| pub struct Ia5String(String); | ||
| impl Ia5String { | ||
| /// Extracts a string slice containing the entire `Ia5String`. | ||
| pub fn as_str(&self) -> &str { | ||
| &self.0 | ||
| } | ||
| } | ||
| impl TryFrom<&str> for Ia5String { | ||
| type Error = Error; | ||
| /// Converts a `&str` to a [`Ia5String`]. | ||
| /// | ||
| /// Any character not in the [`Ia5String`] charset will be rejected. | ||
| /// See [`Ia5String`] documentation for more information. | ||
| /// | ||
| /// The result is allocated on the heap. | ||
| fn try_from(input: &str) -> Result<Self, Error> { | ||
| input.to_string().try_into() | ||
| } | ||
| } | ||
| impl TryFrom<String> for Ia5String { | ||
| type Error = Error; | ||
| /// Converts a [`String`][`std::string::String`] into a [`Ia5String`] | ||
| /// | ||
| /// Any character not in the [`Ia5String`] charset will be rejected. | ||
| /// See [`Ia5String`] documentation for more information. | ||
| fn try_from(input: String) -> Result<Self, Error> { | ||
| if !input.is_ascii() { | ||
| return Err(Error::InvalidAsn1String(InvalidAsn1String::Ia5String( | ||
| input, | ||
| ))); | ||
| } | ||
| Ok(Self(input)) | ||
| } | ||
| } | ||
| impl FromStr for Ia5String { | ||
| type Err = Error; | ||
| fn from_str(s: &str) -> Result<Self, Self::Err> { | ||
| s.try_into() | ||
| } | ||
| } | ||
| impl AsRef<str> for Ia5String { | ||
| fn as_ref(&self) -> &str { | ||
| &self.0 | ||
| } | ||
| } | ||
| impl fmt::Display for Ia5String { | ||
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
| fmt::Display::fmt(self.as_str(), f) | ||
| } | ||
| } | ||
| impl PartialEq<str> for Ia5String { | ||
| fn eq(&self, other: &str) -> bool { | ||
| self.as_str() == other | ||
| } | ||
| } | ||
| impl PartialEq<String> for Ia5String { | ||
| fn eq(&self, other: &String) -> bool { | ||
| self.as_str() == other.as_str() | ||
| } | ||
| } | ||
| impl PartialEq<&str> for Ia5String { | ||
| fn eq(&self, other: &&str) -> bool { | ||
| self.as_str() == *other | ||
| } | ||
| } | ||
| impl PartialEq<&String> for Ia5String { | ||
| fn eq(&self, other: &&String) -> bool { | ||
| self.as_str() == other.as_str() | ||
| } | ||
| } | ||
| /// ASN.1 `TeletexString` type. | ||
| /// | ||
| /// # Examples | ||
| /// | ||
| /// You can create a `TeletexString` from [a literal string][`&str`] with [`TeletexString::try_from`]: | ||
| /// | ||
| /// ``` | ||
| /// use rcgen::string::TeletexString; | ||
| /// let hello = TeletexString::try_from("hello").unwrap(); | ||
| /// ``` | ||
| /// | ||
| /// # Supported characters | ||
| /// | ||
| /// The standard defines a complex character set allowed in this type. However, quoting the ASN.1 | ||
| /// [mailing list], "a sizable volume of software in the world treats TeletexString (T61String) as a | ||
| /// simple 8-bit string with mostly Windows Latin 1 (superset of iso-8859-1) encoding". | ||
| /// | ||
| /// `TeletexString` is included for backward compatibility, [RFC 5280] say it | ||
| /// SHOULD NOT be used for certificates for new subjects. | ||
| /// | ||
| /// [mailing list]: https://www.mail-archive.com/asn1@asn1.org/msg00460.html | ||
| /// [RFC 5280]: https://datatracker.ietf.org/doc/html/rfc5280#page-25 | ||
| #[derive(Debug, PartialEq, Eq, Hash, Clone)] | ||
| pub struct TeletexString(String); | ||
| impl TeletexString { | ||
| /// Extracts a string slice containing the entire `TeletexString`. | ||
| pub fn as_str(&self) -> &str { | ||
| &self.0 | ||
| } | ||
| /// Returns a byte slice of this `TeletexString`’s contents. | ||
| pub fn as_bytes(&self) -> &[u8] { | ||
| self.0.as_bytes() | ||
| } | ||
| } | ||
| impl TryFrom<&str> for TeletexString { | ||
| type Error = Error; | ||
| /// Converts a `&str` to a [`TeletexString`]. | ||
| /// | ||
| /// Any character not in the [`TeletexString`] charset will be rejected. | ||
| /// See [`TeletexString`] documentation for more information. | ||
| /// | ||
| /// The result is allocated on the heap. | ||
| fn try_from(input: &str) -> Result<Self, Error> { | ||
| input.to_string().try_into() | ||
| } | ||
| } | ||
| impl TryFrom<String> for TeletexString { | ||
| type Error = Error; | ||
| /// Converts a [`String`][`std::string::String`] into a [`TeletexString`] | ||
| /// | ||
| /// Any character not in the [`TeletexString`] charset will be rejected. | ||
| /// See [`TeletexString`] documentation for more information. | ||
| /// | ||
| /// This conversion does not allocate or copy memory. | ||
| fn try_from(input: String) -> Result<Self, Error> { | ||
| // Check all bytes are visible | ||
| if !input.as_bytes().iter().all(|b| (0x20..=0x7f).contains(b)) { | ||
| return Err(Error::InvalidAsn1String(InvalidAsn1String::TeletexString( | ||
| input, | ||
| ))); | ||
| } | ||
| Ok(Self(input)) | ||
| } | ||
| } | ||
| impl FromStr for TeletexString { | ||
| type Err = Error; | ||
| fn from_str(s: &str) -> Result<Self, Self::Err> { | ||
| s.try_into() | ||
| } | ||
| } | ||
| impl AsRef<str> for TeletexString { | ||
| fn as_ref(&self) -> &str { | ||
| &self.0 | ||
| } | ||
| } | ||
| impl fmt::Display for TeletexString { | ||
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
| fmt::Display::fmt(self.as_str(), f) | ||
| } | ||
| } | ||
| impl PartialEq<str> for TeletexString { | ||
| fn eq(&self, other: &str) -> bool { | ||
| self.as_str() == other | ||
| } | ||
| } | ||
| impl PartialEq<String> for TeletexString { | ||
| fn eq(&self, other: &String) -> bool { | ||
| self.as_str() == other.as_str() | ||
| } | ||
| } | ||
| impl PartialEq<&str> for TeletexString { | ||
| fn eq(&self, other: &&str) -> bool { | ||
| self.as_str() == *other | ||
| } | ||
| } | ||
| impl PartialEq<&String> for TeletexString { | ||
| fn eq(&self, other: &&String) -> bool { | ||
| self.as_str() == other.as_str() | ||
| } | ||
| } | ||
| /// ASN.1 `BMPString` type. | ||
| /// | ||
| /// # Examples | ||
| /// | ||
| /// You can create a `BmpString` from [a literal string][`&str`] with [`BmpString::try_from`]: | ||
| /// | ||
| /// ``` | ||
| /// use rcgen::string::BmpString; | ||
| /// let hello = BmpString::try_from("hello").unwrap(); | ||
| /// ``` | ||
| /// | ||
| /// # Supported characters | ||
| /// | ||
| /// Encodes Basic Multilingual Plane (BMP) subset of Unicode (ISO 10646), | ||
| /// a.k.a. UCS-2. | ||
| /// | ||
| /// Bytes are encoded as UTF-16 big-endian. | ||
| /// | ||
| /// `BMPString` is included for backward compatibility, [RFC 5280] say it | ||
| /// SHOULD NOT be used for certificates for new subjects. | ||
| /// | ||
| /// [RFC 5280]: https://datatracker.ietf.org/doc/html/rfc5280#page-25 | ||
| #[derive(Debug, PartialEq, Eq, Hash, Clone)] | ||
| pub struct BmpString(Vec<u8>); | ||
| impl BmpString { | ||
| /// Returns a byte slice of this `BmpString`'s contents. | ||
| /// | ||
| /// The inverse of this method is [`from_utf16be`]. | ||
| /// | ||
| /// [`from_utf16be`]: BmpString::from_utf16be | ||
| /// | ||
| /// # Examples | ||
| /// | ||
| /// ``` | ||
| /// use rcgen::string::BmpString; | ||
| /// let s = BmpString::try_from("hello").unwrap(); | ||
| /// | ||
| /// assert_eq!(&[0, 104, 0, 101, 0, 108, 0, 108, 0, 111], s.as_bytes()); | ||
| /// ``` | ||
| pub fn as_bytes(&self) -> &[u8] { | ||
| &self.0 | ||
| } | ||
| /// Decode a UTF-16BE–encoded vector `vec` into a `BmpString`, returning [Err](`std::result::Result::Err`) if `vec` contains any invalid data. | ||
| pub fn from_utf16be(vec: Vec<u8>) -> Result<Self, Error> { | ||
| if vec.len() % 2 != 0 { | ||
| return Err(Error::InvalidAsn1String(InvalidAsn1String::BmpString( | ||
| "Invalid UTF-16 encoding".to_string(), | ||
| ))); | ||
| } | ||
| // FIXME: Update this when `array_chunks` is stabilized. | ||
| for maybe_char in char::decode_utf16( | ||
| vec.chunks_exact(2) | ||
| .map(|chunk| u16::from_be_bytes([chunk[0], chunk[1]])), | ||
| ) { | ||
| // We check we only use the BMP subset of Unicode (the first 65 536 code points) | ||
| match maybe_char { | ||
| // Character is in the Basic Multilingual Plane | ||
| Ok(c) if (c as u64) < u64::from(u16::MAX) => (), | ||
| // Characters outside Basic Multilingual Plane or unpaired surrogates | ||
| _ => { | ||
| return Err(Error::InvalidAsn1String(InvalidAsn1String::BmpString( | ||
| "Invalid UTF-16 encoding".to_string(), | ||
| ))); | ||
| }, | ||
| } | ||
| } | ||
| Ok(Self(vec.to_vec())) | ||
| } | ||
| } | ||
| impl TryFrom<&str> for BmpString { | ||
| type Error = Error; | ||
| /// Converts a `&str` to a [`BmpString`]. | ||
| /// | ||
| /// Any character not in the [`BmpString`] charset will be rejected. | ||
| /// See [`BmpString`] documentation for more information. | ||
| /// | ||
| /// The result is allocated on the heap. | ||
| fn try_from(value: &str) -> Result<Self, Self::Error> { | ||
| let capacity = value.len().checked_mul(2).ok_or_else(|| { | ||
| Error::InvalidAsn1String(InvalidAsn1String::BmpString(value.to_string())) | ||
| })?; | ||
| let mut bytes = Vec::with_capacity(capacity); | ||
| for code_point in value.encode_utf16() { | ||
| bytes.extend(code_point.to_be_bytes()); | ||
| } | ||
| BmpString::from_utf16be(bytes) | ||
| } | ||
| } | ||
| impl TryFrom<String> for BmpString { | ||
| type Error = Error; | ||
| /// Converts a [`String`][`std::string::String`] into a [`BmpString`] | ||
| /// | ||
| /// Any character not in the [`BmpString`] charset will be rejected. | ||
| /// See [`BmpString`] documentation for more information. | ||
| /// | ||
| /// Parsing a `BmpString` allocates memory since the UTF-8 to UTF-16 conversion requires a memory allocation. | ||
| fn try_from(value: String) -> Result<Self, Self::Error> { | ||
| value.as_str().try_into() | ||
| } | ||
| } | ||
| impl FromStr for BmpString { | ||
| type Err = Error; | ||
| fn from_str(s: &str) -> Result<Self, Self::Err> { | ||
| s.try_into() | ||
| } | ||
| } | ||
| /// ASN.1 `UniversalString` type. | ||
| /// | ||
| /// # Examples | ||
| /// | ||
| /// You can create a `UniversalString` from [a literal string][`&str`] with [`UniversalString::try_from`]: | ||
| /// | ||
| /// ``` | ||
| /// use rcgen::string::UniversalString; | ||
| /// let hello = UniversalString::try_from("hello").unwrap(); | ||
| /// ``` | ||
| /// | ||
| /// # Supported characters | ||
| /// | ||
| /// The characters which can appear in the `UniversalString` type are any of the characters allowed by | ||
| /// ISO/IEC 10646 (Unicode). | ||
| /// | ||
| /// Bytes are encoded like UTF-32 big-endian. | ||
| /// | ||
| /// `UniversalString` is included for backward compatibility, [RFC 5280] say it | ||
| /// SHOULD NOT be used for certificates for new subjects. | ||
| /// | ||
| /// [RFC 5280]: https://datatracker.ietf.org/doc/html/rfc5280#page-25 | ||
| #[derive(Debug, PartialEq, Eq, Hash, Clone)] | ||
| pub struct UniversalString(Vec<u8>); | ||
| impl UniversalString { | ||
| /// Returns a byte slice of this `UniversalString`'s contents. | ||
| /// | ||
| /// The inverse of this method is [`from_utf32be`]. | ||
| /// | ||
| /// [`from_utf32be`]: UniversalString::from_utf32be | ||
| /// | ||
| /// # Examples | ||
| /// | ||
| /// ``` | ||
| /// use rcgen::string::UniversalString; | ||
| /// let s = UniversalString::try_from("hello").unwrap(); | ||
| /// | ||
| /// assert_eq!(&[0, 0, 0, 104, 0, 0, 0, 101, 0, 0, 0, 108, 0, 0, 0, 108, 0, 0, 0, 111], s.as_bytes()); | ||
| /// ``` | ||
| pub fn as_bytes(&self) -> &[u8] { | ||
| &self.0 | ||
| } | ||
| /// Decode a UTF-32BE–encoded vector `vec` into a `UniversalString`, returning [Err](`std::result::Result::Err`) if `vec` contains any invalid data. | ||
| pub fn from_utf32be(vec: Vec<u8>) -> Result<UniversalString, Error> { | ||
| if vec.len() % 4 != 0 { | ||
| return Err(Error::InvalidAsn1String( | ||
| InvalidAsn1String::UniversalString("Invalid UTF-32 encoding".to_string()), | ||
| )); | ||
| } | ||
| // FIXME: Update this when `array_chunks` is stabilized. | ||
| for maybe_char in vec | ||
| .chunks_exact(4) | ||
| .map(|chunk| u32::from_be_bytes([chunk[0], chunk[1], chunk[2], chunk[3]])) | ||
| { | ||
| if core::char::from_u32(maybe_char).is_none() { | ||
| return Err(Error::InvalidAsn1String( | ||
| InvalidAsn1String::UniversalString("Invalid UTF-32 encoding".to_string()), | ||
| )); | ||
| } | ||
| } | ||
| Ok(Self(vec)) | ||
| } | ||
| } | ||
| impl TryFrom<&str> for UniversalString { | ||
| type Error = Error; | ||
| /// Converts a `&str` to a [`UniversalString`]. | ||
| /// | ||
| /// Any character not in the [`UniversalString`] charset will be rejected. | ||
| /// See [`UniversalString`] documentation for more information. | ||
| /// | ||
| /// The result is allocated on the heap. | ||
| fn try_from(value: &str) -> Result<Self, Self::Error> { | ||
| let capacity = value.len().checked_mul(4).ok_or_else(|| { | ||
| Error::InvalidAsn1String(InvalidAsn1String::UniversalString(value.to_string())) | ||
| })?; | ||
| let mut bytes = Vec::with_capacity(capacity); | ||
| // A `char` is any ‘Unicode code point’ other than a surrogate code point. | ||
| // The code units for UTF-32 correspond exactly to Unicode code points. | ||
| // (https://www.unicode.org/reports/tr19/tr19-9.html#Introduction) | ||
| // So any `char` is a valid UTF-32, we just cast it to perform the convertion. | ||
| for char in value.chars().map(|char| char as u32) { | ||
| bytes.extend(char.to_be_bytes()) | ||
| } | ||
| UniversalString::from_utf32be(bytes) | ||
| } | ||
| } | ||
| impl TryFrom<String> for UniversalString { | ||
| type Error = Error; | ||
| /// Converts a [`String`][`std::string::String`] into a [`UniversalString`] | ||
| /// | ||
| /// Any character not in the [`UniversalString`] charset will be rejected. | ||
| /// See [`UniversalString`] documentation for more information. | ||
| /// | ||
| /// Parsing a `UniversalString` allocates memory since the UTF-8 to UTF-32 conversion requires a memory allocation. | ||
| fn try_from(value: String) -> Result<Self, Self::Error> { | ||
| value.as_str().try_into() | ||
| } | ||
| } | ||
| #[cfg(test)] | ||
| #[allow(clippy::unwrap_used)] | ||
| mod tests { | ||
| use crate::{BmpString, Ia5String, PrintableString, TeletexString, UniversalString}; | ||
| #[test] | ||
| fn printable_string() { | ||
| const EXAMPLE_UTF8: &str = "CertificateTemplate"; | ||
| let printable_string = PrintableString::try_from(EXAMPLE_UTF8).unwrap(); | ||
| assert_eq!(printable_string, EXAMPLE_UTF8); | ||
| assert!(PrintableString::try_from("@").is_err()); | ||
| assert!(PrintableString::try_from("*").is_err()); | ||
| } | ||
| #[test] | ||
| fn ia5_string() { | ||
| const EXAMPLE_UTF8: &str = "CertificateTemplate"; | ||
| let ia5_string = Ia5String::try_from(EXAMPLE_UTF8).unwrap(); | ||
| assert_eq!(ia5_string, EXAMPLE_UTF8); | ||
| assert!(Ia5String::try_from(String::from('\u{7F}')).is_ok()); | ||
| assert!(Ia5String::try_from(String::from('\u{8F}')).is_err()); | ||
| } | ||
| #[test] | ||
| fn teletext_string() { | ||
| const EXAMPLE_UTF8: &str = "CertificateTemplate"; | ||
| let teletext_string = TeletexString::try_from(EXAMPLE_UTF8).unwrap(); | ||
| assert_eq!(teletext_string, EXAMPLE_UTF8); | ||
| assert!(Ia5String::try_from(String::from('\u{7F}')).is_ok()); | ||
| assert!(Ia5String::try_from(String::from('\u{8F}')).is_err()); | ||
| } | ||
| #[test] | ||
| fn bmp_string() { | ||
| const EXPECTED_BYTES: &[u8] = &[ | ||
| 0x00, 0x43, 0x00, 0x65, 0x00, 0x72, 0x00, 0x74, 0x00, 0x69, 0x00, 0x66, 0x00, 0x69, | ||
| 0x00, 0x63, 0x00, 0x61, 0x00, 0x74, 0x00, 0x65, 0x00, 0x54, 0x00, 0x65, 0x00, 0x6d, | ||
| 0x00, 0x70, 0x00, 0x6c, 0x00, 0x61, 0x00, 0x74, 0x00, 0x65, | ||
| ]; | ||
| const EXAMPLE_UTF8: &str = "CertificateTemplate"; | ||
| let bmp_string = BmpString::try_from(EXAMPLE_UTF8).unwrap(); | ||
| assert_eq!(bmp_string.as_bytes(), EXPECTED_BYTES); | ||
| assert!(BmpString::try_from(String::from('\u{FFFE}')).is_ok()); | ||
| assert!(BmpString::try_from(String::from('\u{FFFF}')).is_err()); | ||
| } | ||
| #[test] | ||
| fn universal_string() { | ||
| const EXPECTED_BYTES: &[u8] = &[ | ||
| 0x00, 0x00, 0x00, 0x43, 0x00, 0x00, 0x00, 0x65, 0x00, 0x00, 0x00, 0x72, 0x00, 0x00, | ||
| 0x00, 0x74, 0x00, 0x00, 0x00, 0x69, 0x00, 0x00, 0x00, 0x66, 0x00, 0x00, 0x00, 0x69, | ||
| 0x00, 0x00, 0x00, 0x63, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00, 0x74, 0x00, 0x00, | ||
| 0x00, 0x65, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x00, 0x65, 0x00, 0x00, 0x00, 0x6d, | ||
| 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0x6c, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00, | ||
| 0x00, 0x74, 0x00, 0x00, 0x00, 0x65, | ||
| ]; | ||
| const EXAMPLE_UTF8: &str = "CertificateTemplate"; | ||
| let universal_string = UniversalString::try_from(EXAMPLE_UTF8).unwrap(); | ||
| assert_eq!(universal_string.as_bytes(), EXPECTED_BYTES); | ||
| } | ||
| } |
| { | ||
| "git": { | ||
| "sha1": "9025af3c1bb011847524957f0fa750a17f75432e" | ||
| "sha1": "4154ef340e86d2007cb122a8d220c13c5f138be0" | ||
| }, | ||
| "path_in_vcs": "rcgen" | ||
| } |
+180
-85
@@ -16,5 +16,5 @@ # This file is automatically @generated by Cargo. | ||
| name = "asn1-rs" | ||
| version = "0.7.0" | ||
| version = "0.7.1" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "607495ec7113b178fbba7a6166a27f99e774359ef4823adbefd756b5b81d7970" | ||
| checksum = "56624a96882bb8c26d61312ae18cb45868e5a9992ea73c58e45c3101e56a1e60" | ||
| dependencies = [ | ||
@@ -62,5 +62,5 @@ "asn1-rs-derive", | ||
| name = "aws-lc-fips-sys" | ||
| version = "0.13.3" | ||
| version = "0.13.6" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "29003a681b2b9465c1139bfb726da452a841a8b025f35953f3bce71139f10b21" | ||
| checksum = "e99d74bb793a19f542ae870a6edafbc5ecf0bc0ba01d4636b7f7e0aba9ee9bd3" | ||
| dependencies = [ | ||
@@ -72,3 +72,2 @@ "bindgen", | ||
| "fs_extra", | ||
| "paste", | ||
| "regex", | ||
@@ -79,9 +78,8 @@ ] | ||
| name = "aws-lc-rs" | ||
| version = "1.12.5" | ||
| version = "1.13.1" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "5e4e8200b9a4a5801a769d50eeabc05670fec7e959a8cb7a63a93e4e519942ae" | ||
| checksum = "93fcc8f365936c834db5514fc45aee5b1202d677e6b40e48468aaaa8183ca8c7" | ||
| dependencies = [ | ||
| "aws-lc-fips-sys", | ||
| "aws-lc-sys", | ||
| "paste", | ||
| "zeroize", | ||
@@ -92,5 +90,5 @@ ] | ||
| name = "aws-lc-sys" | ||
| version = "0.26.0" | ||
| version = "0.29.0" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "0f9dd2e03ee80ca2822dd6ea431163d2ef259f2066a4d6ccaca6d9dcb386aa43" | ||
| checksum = "61b1d86e7705efe1be1b569bab41d4fa1e14e220b60a160f78de2db687add079" | ||
| dependencies = [ | ||
@@ -102,3 +100,2 @@ "bindgen", | ||
| "fs_extra", | ||
| "paste", | ||
| ] | ||
@@ -137,11 +134,11 @@ | ||
| name = "bitflags" | ||
| version = "2.9.0" | ||
| version = "2.9.1" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" | ||
| checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" | ||
| [[package]] | ||
| name = "botan" | ||
| version = "0.11.0" | ||
| version = "0.11.1" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "51dd08fd73549772ae128c8e20f5318c328322cb21dedef57925634fe13cfe25" | ||
| checksum = "24d4c7647d67c53194fa0740404c6c508880aef2bfe99a9868dbb4b86f090377" | ||
| dependencies = [ | ||
@@ -153,11 +150,11 @@ "botan-sys", | ||
| name = "botan-src" | ||
| version = "0.30500.1" | ||
| version = "0.30701.2" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "dddc9b565df44dc13011eda5b70af00fae4a2ab886f66bd0e08725fe576fa460" | ||
| checksum = "8c4e1c7910f3b4712aed10e4259eca77e79ed84c1b023098c8eac596b993fc44" | ||
| [[package]] | ||
| name = "botan-sys" | ||
| version = "0.11.0" | ||
| version = "0.11.1" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "382a150feefeb20d1f4c6f1ca8ba46ba4c005905ec69c19eda4b3a27a3effe90" | ||
| checksum = "04285fa0c094cc9961fe435b1b279183db9394844ad82ce483aa6196c0e6da38" | ||
| dependencies = [ | ||
@@ -169,5 +166,5 @@ "botan-src", | ||
| name = "cc" | ||
| version = "1.2.16" | ||
| version = "1.2.23" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c" | ||
| checksum = "5f4ac86a9e5bc1e2b3449ab9d7d3a6a405e3d1bb28d7b9be8614f55846ae3766" | ||
| dependencies = [ | ||
@@ -216,5 +213,5 @@ "jobserver", | ||
| name = "data-encoding" | ||
| version = "2.8.0" | ||
| version = "2.9.0" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "575f75dfd25738df5b91b8e43e14d44bda14637a58fae779fd2b064f8bf3e010" | ||
| checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" | ||
@@ -237,5 +234,5 @@ [[package]] | ||
| name = "deranged" | ||
| version = "0.3.11" | ||
| version = "0.4.0" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" | ||
| checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" | ||
| dependencies = [ | ||
@@ -270,5 +267,5 @@ "powerfmt", | ||
| name = "errno" | ||
| version = "0.3.10" | ||
| version = "0.3.12" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" | ||
| checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" | ||
| dependencies = [ | ||
@@ -302,12 +299,24 @@ "libc", | ||
| name = "getrandom" | ||
| version = "0.2.15" | ||
| version = "0.2.16" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" | ||
| checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" | ||
| dependencies = [ | ||
| "cfg-if", | ||
| "libc", | ||
| "wasi", | ||
| "wasi 0.11.0+wasi-snapshot-preview1", | ||
| ] | ||
| [[package]] | ||
| name = "getrandom" | ||
| version = "0.3.3" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" | ||
| dependencies = [ | ||
| "cfg-if", | ||
| "libc", | ||
| "r-efi", | ||
| "wasi 0.14.2+wasi-0.2.4", | ||
| ] | ||
| [[package]] | ||
| name = "glob" | ||
@@ -344,6 +353,7 @@ version = "0.3.2" | ||
| name = "jobserver" | ||
| version = "0.1.32" | ||
| version = "0.1.33" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" | ||
| checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" | ||
| dependencies = [ | ||
| "getrandom 0.3.3", | ||
| "libc", | ||
@@ -366,14 +376,14 @@ ] | ||
| name = "libc" | ||
| version = "0.2.171" | ||
| version = "0.2.172" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" | ||
| checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" | ||
| [[package]] | ||
| name = "libloading" | ||
| version = "0.8.6" | ||
| version = "0.8.7" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" | ||
| checksum = "6a793df0d7afeac54f95b471d3af7f0d4fb975699f972341a4b76988d49cdf0c" | ||
| dependencies = [ | ||
| "cfg-if", | ||
| "windows-targets", | ||
| "windows-targets 0.53.0", | ||
| ] | ||
@@ -389,5 +399,5 @@ | ||
| name = "log" | ||
| version = "0.4.26" | ||
| version = "0.4.27" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" | ||
| checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" | ||
@@ -461,11 +471,11 @@ [[package]] | ||
| name = "once_cell" | ||
| version = "1.21.0" | ||
| version = "1.21.3" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "cde51589ab56b20a6f686b2c68f7a0bd6add753d697abf720d63f8db3ab7b1ad" | ||
| checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" | ||
| [[package]] | ||
| name = "openssl" | ||
| version = "0.10.71" | ||
| version = "0.10.72" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "5e14130c6a98cd258fdcb0fb6d744152343ff729cbfcb28c656a9d12b999fbcd" | ||
| checksum = "fedfea7d58a1f73118430a55da6a286e7b044961736ce96a16a17068ea25e5da" | ||
| dependencies = [ | ||
@@ -494,5 +504,5 @@ "bitflags", | ||
| name = "openssl-sys" | ||
| version = "0.9.106" | ||
| version = "0.9.108" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "8bb61ea9811cc39e3c2069f40b8b8e2e70d8569b361f879786cc7ed48b777cdd" | ||
| checksum = "e145e1651e858e820e4860f7b9c5e169bc1d8ce1c86043be79fa7b7634821847" | ||
| dependencies = [ | ||
@@ -506,8 +516,2 @@ "cc", | ||
| [[package]] | ||
| name = "paste" | ||
| version = "1.0.15" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" | ||
| [[package]] | ||
| name = "pem" | ||
@@ -536,5 +540,5 @@ version = "3.0.5" | ||
| name = "prettyplease" | ||
| version = "0.2.30" | ||
| version = "0.2.32" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "f1ccf34da56fc294e7d4ccf69a85992b7dfb826b7cf57bac6a70bba3494cc08a" | ||
| checksum = "664ec5419c51e34154eec046ebcba56312d5a2fc3b09a06da188e1ad21afadf6" | ||
| dependencies = [ | ||
@@ -547,5 +551,5 @@ "proc-macro2", | ||
| name = "proc-macro2" | ||
| version = "1.0.94" | ||
| version = "1.0.95" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" | ||
| checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" | ||
| dependencies = [ | ||
@@ -557,5 +561,5 @@ "unicode-ident", | ||
| name = "quote" | ||
| version = "1.0.39" | ||
| version = "1.0.40" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "c1f1914ce909e1658d9907913b4b91947430c7d9be598b15a1912935b8c04801" | ||
| checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" | ||
| dependencies = [ | ||
@@ -566,4 +570,10 @@ "proc-macro2", | ||
| [[package]] | ||
| name = "r-efi" | ||
| version = "5.2.0" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" | ||
| [[package]] | ||
| name = "rcgen" | ||
| version = "0.13.3" | ||
| version = "0.14.0" | ||
| dependencies = [ | ||
@@ -614,9 +624,9 @@ "aws-lc-rs", | ||
| name = "ring" | ||
| version = "0.17.13" | ||
| version = "0.17.14" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "70ac5d832aa16abd7d1def883a8545280c20a60f523a370aa3a9617c2b8550ee" | ||
| checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" | ||
| dependencies = [ | ||
| "cc", | ||
| "cfg-if", | ||
| "getrandom", | ||
| "getrandom 0.2.16", | ||
| "libc", | ||
@@ -657,11 +667,14 @@ "untrusted", | ||
| name = "rustls-pki-types" | ||
| version = "1.11.0" | ||
| version = "1.12.0" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" | ||
| checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" | ||
| dependencies = [ | ||
| "zeroize", | ||
| ] | ||
| [[package]] | ||
| name = "rustls-webpki" | ||
| version = "0.103.0" | ||
| version = "0.103.3" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "0aa4eeac2588ffff23e9d7a7e9b3f971c5fb5b7ebc9452745e0c232c64f83b2f" | ||
| checksum = "e4a72fe2bcf7a6ac6fd7d0b9e5cb68aeb7d4c0a0271730218b3e92d43b4eb435" | ||
| dependencies = [ | ||
@@ -701,5 +714,5 @@ "ring", | ||
| name = "syn" | ||
| version = "2.0.100" | ||
| version = "2.0.101" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" | ||
| checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" | ||
| dependencies = [ | ||
@@ -713,5 +726,5 @@ "proc-macro2", | ||
| name = "synstructure" | ||
| version = "0.13.1" | ||
| version = "0.13.2" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" | ||
| checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" | ||
| dependencies = [ | ||
@@ -745,5 +758,5 @@ "proc-macro2", | ||
| name = "time" | ||
| version = "0.3.39" | ||
| version = "0.3.41" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "dad298b01a40a23aac4580b67e3dbedb7cc8402f3592d7f49469de2ea4aecdd8" | ||
| checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" | ||
| dependencies = [ | ||
@@ -761,11 +774,11 @@ "deranged", | ||
| name = "time-core" | ||
| version = "0.1.3" | ||
| version = "0.1.4" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "765c97a5b985b7c11d7bc27fa927dc4fe6af3a6dfb021d28deb60d3bf51e76ef" | ||
| checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" | ||
| [[package]] | ||
| name = "time-macros" | ||
| version = "0.2.20" | ||
| version = "0.2.22" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "e8093bc3e81c3bc5f7879de09619d06c9a5a5e45ca44dfeeb7225bae38005c5c" | ||
| checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" | ||
| dependencies = [ | ||
@@ -801,2 +814,11 @@ "num-conv", | ||
| [[package]] | ||
| name = "wasi" | ||
| version = "0.14.2+wasi-0.2.4" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" | ||
| dependencies = [ | ||
| "wit-bindgen-rt", | ||
| ] | ||
| [[package]] | ||
| name = "which" | ||
@@ -819,3 +841,3 @@ version = "4.4.2" | ||
| dependencies = [ | ||
| "windows-targets", | ||
| "windows-targets 0.52.6", | ||
| ] | ||
@@ -829,3 +851,3 @@ | ||
| dependencies = [ | ||
| "windows-targets", | ||
| "windows-targets 0.52.6", | ||
| ] | ||
@@ -839,13 +861,29 @@ | ||
| dependencies = [ | ||
| "windows_aarch64_gnullvm", | ||
| "windows_aarch64_msvc", | ||
| "windows_i686_gnu", | ||
| "windows_i686_gnullvm", | ||
| "windows_i686_msvc", | ||
| "windows_x86_64_gnu", | ||
| "windows_x86_64_gnullvm", | ||
| "windows_x86_64_msvc", | ||
| "windows_aarch64_gnullvm 0.52.6", | ||
| "windows_aarch64_msvc 0.52.6", | ||
| "windows_i686_gnu 0.52.6", | ||
| "windows_i686_gnullvm 0.52.6", | ||
| "windows_i686_msvc 0.52.6", | ||
| "windows_x86_64_gnu 0.52.6", | ||
| "windows_x86_64_gnullvm 0.52.6", | ||
| "windows_x86_64_msvc 0.52.6", | ||
| ] | ||
| [[package]] | ||
| name = "windows-targets" | ||
| version = "0.53.0" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b" | ||
| dependencies = [ | ||
| "windows_aarch64_gnullvm 0.53.0", | ||
| "windows_aarch64_msvc 0.53.0", | ||
| "windows_i686_gnu 0.53.0", | ||
| "windows_i686_gnullvm 0.53.0", | ||
| "windows_i686_msvc 0.53.0", | ||
| "windows_x86_64_gnu 0.53.0", | ||
| "windows_x86_64_gnullvm 0.53.0", | ||
| "windows_x86_64_msvc 0.53.0", | ||
| ] | ||
| [[package]] | ||
| name = "windows_aarch64_gnullvm" | ||
@@ -857,2 +895,8 @@ version = "0.52.6" | ||
| [[package]] | ||
| name = "windows_aarch64_gnullvm" | ||
| version = "0.53.0" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" | ||
| [[package]] | ||
| name = "windows_aarch64_msvc" | ||
@@ -864,2 +908,8 @@ version = "0.52.6" | ||
| [[package]] | ||
| name = "windows_aarch64_msvc" | ||
| version = "0.53.0" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" | ||
| [[package]] | ||
| name = "windows_i686_gnu" | ||
@@ -871,2 +921,8 @@ version = "0.52.6" | ||
| [[package]] | ||
| name = "windows_i686_gnu" | ||
| version = "0.53.0" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" | ||
| [[package]] | ||
| name = "windows_i686_gnullvm" | ||
@@ -878,2 +934,8 @@ version = "0.52.6" | ||
| [[package]] | ||
| name = "windows_i686_gnullvm" | ||
| version = "0.53.0" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" | ||
| [[package]] | ||
| name = "windows_i686_msvc" | ||
@@ -885,2 +947,8 @@ version = "0.52.6" | ||
| [[package]] | ||
| name = "windows_i686_msvc" | ||
| version = "0.53.0" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" | ||
| [[package]] | ||
| name = "windows_x86_64_gnu" | ||
@@ -892,2 +960,8 @@ version = "0.52.6" | ||
| [[package]] | ||
| name = "windows_x86_64_gnu" | ||
| version = "0.53.0" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" | ||
| [[package]] | ||
| name = "windows_x86_64_gnullvm" | ||
@@ -899,2 +973,8 @@ version = "0.52.6" | ||
| [[package]] | ||
| name = "windows_x86_64_gnullvm" | ||
| version = "0.53.0" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" | ||
| [[package]] | ||
| name = "windows_x86_64_msvc" | ||
@@ -906,2 +986,17 @@ version = "0.52.6" | ||
| [[package]] | ||
| name = "windows_x86_64_msvc" | ||
| version = "0.53.0" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" | ||
| [[package]] | ||
| name = "wit-bindgen-rt" | ||
| version = "0.39.0" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" | ||
| dependencies = [ | ||
| "bitflags", | ||
| ] | ||
| [[package]] | ||
| name = "x509-parser" | ||
@@ -908,0 +1003,0 @@ version = "0.17.0" |
+4
-4
@@ -15,3 +15,3 @@ # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO | ||
| name = "rcgen" | ||
| version = "0.13.3" | ||
| version = "0.14.0" | ||
| build = false | ||
@@ -152,5 +152,2 @@ autolib = false | ||
| [dev-dependencies.openssl] | ||
| version = "0.10" | ||
| [dev-dependencies.pki-types] | ||
@@ -173,1 +170,4 @@ version = "1" | ||
| features = ["verify"] | ||
| [target."cfg(unix)".dev-dependencies.openssl] | ||
| version = "0.10" |
@@ -75,3 +75,3 @@ # Rcgen 0.12 to 0.13 Migration Guide | ||
| `CertificateSigningRequest::serialize_der_with_signer()` or | ||
| `CertificateSigningRequest::serialize_pem_with_signer(). In the new API, call | ||
| `CertificateSigningRequest::serialize_pem_with_signer()`. In the new API, call | ||
| `CertificateSigningRequestParams::signed_by()` and provide an issuer | ||
@@ -78,0 +78,0 @@ `Certificate` and `KeyPair`. |
@@ -0,1 +1,2 @@ | ||
| #[cfg(unix)] | ||
| fn main() -> Result<(), Box<dyn std::error::Error>> { | ||
@@ -34,1 +35,9 @@ use rcgen::{date_time_ymd, CertificateParams, DistinguishedName}; | ||
| } | ||
| #[cfg(not(unix))] | ||
| fn main() -> Result<(), Box<dyn std::error::Error>> { | ||
| // Due to the support burden of running OpenSSL on Windows, | ||
| // we only support the OpenSSL backend on Unix-like systems. | ||
| // It should still work on Windows if you have OpenSSL installed. | ||
| unimplemented!("OpenSSL backend is not supported on Windows"); | ||
| } |
| use rcgen::{ | ||
| BasicConstraints, Certificate, CertificateParams, DnType, DnValue::PrintableString, | ||
| ExtendedKeyUsagePurpose, IsCa, KeyPair, KeyUsagePurpose, | ||
| ExtendedKeyUsagePurpose, IsCa, Issuer, KeyPair, KeyUsagePurpose, | ||
| }; | ||
@@ -9,4 +9,4 @@ use time::{Duration, OffsetDateTime}; | ||
| fn main() { | ||
| let (ca, ca_key) = new_ca(); | ||
| let end_entity = new_end_entity(&ca, &ca_key); | ||
| let (ca, issuer) = new_ca(); | ||
| let end_entity = new_end_entity(&issuer); | ||
@@ -20,3 +20,3 @@ let end_entity_pem = end_entity.pem(); | ||
| fn new_ca() -> (Certificate, KeyPair) { | ||
| fn new_ca() -> (Certificate, Issuer<'static, KeyPair>) { | ||
| let mut params = | ||
@@ -41,6 +41,7 @@ CertificateParams::new(Vec::default()).expect("empty subject alt name can't produce error"); | ||
| let key_pair = KeyPair::generate().unwrap(); | ||
| (params.self_signed(&key_pair).unwrap(), key_pair) | ||
| let cert = params.self_signed(&key_pair).unwrap(); | ||
| (cert, Issuer::new(params, key_pair)) | ||
| } | ||
| fn new_end_entity(ca: &Certificate, ca_key: &KeyPair) -> Certificate { | ||
| fn new_end_entity(issuer: &Issuer<'static, KeyPair>) -> Certificate { | ||
| let name = "entity.other.host"; | ||
@@ -59,3 +60,3 @@ let mut params = CertificateParams::new(vec![name.into()]).expect("we know the name is valid"); | ||
| let key_pair = KeyPair::generate().unwrap(); | ||
| params.signed_by(&key_pair, ca, ca_key).unwrap() | ||
| params.signed_by(&key_pair, issuer).unwrap() | ||
| } | ||
@@ -62,0 +63,0 @@ |
+441
-486
@@ -9,7 +9,7 @@ use std::net::IpAddr; | ||
| use yasna::models::ObjectIdentifier; | ||
| use yasna::{DERWriter, Tag}; | ||
| use yasna::{DERWriter, DERWriterSeq, Tag}; | ||
| use crate::crl::CrlDistributionPoint; | ||
| use crate::csr::CertificateSigningRequest; | ||
| use crate::key_pair::{serialize_public_key_der, PublicKeyData}; | ||
| use crate::key_pair::{serialize_public_key_der, sign_der, PublicKeyData}; | ||
| #[cfg(feature = "crypto")] | ||
@@ -22,10 +22,8 @@ use crate::ring_like::digest; | ||
| write_x509_authority_key_identifier, write_x509_extension, DistinguishedName, Error, Issuer, | ||
| KeyIdMethod, KeyPair, KeyUsagePurpose, SanType, SerialNumber, | ||
| KeyIdMethod, KeyUsagePurpose, SanType, SerialNumber, SigningKey, | ||
| }; | ||
| /// An issued certificate together with the parameters used to generate it. | ||
| #[derive(Debug, Clone)] | ||
| /// An issued certificate | ||
| #[derive(Debug, Clone, PartialEq, Eq)] | ||
| pub struct Certificate { | ||
| pub(crate) params: CertificateParams, | ||
| pub(crate) subject_public_key_info: Vec<u8>, | ||
| pub(crate) der: CertificateDer<'static>, | ||
@@ -35,13 +33,2 @@ } | ||
| impl Certificate { | ||
| /// Returns the certificate parameters | ||
| pub fn params(&self) -> &CertificateParams { | ||
| &self.params | ||
| } | ||
| /// Calculates a subject key identifier for the certificate subject's public key. | ||
| /// This key identifier is used in the SubjectKeyIdentifier X.509v3 extension. | ||
| pub fn key_identifier(&self) -> Vec<u8> { | ||
| self.params | ||
| .key_identifier_method | ||
| .derive(&self.subject_public_key_info) | ||
| } | ||
| /// Get the certificate in DER encoded format. | ||
@@ -54,2 +41,3 @@ /// | ||
| } | ||
| /// Get the certificate in PEM encoded format. | ||
@@ -68,8 +56,2 @@ #[cfg(feature = "pem")] | ||
| impl AsRef<CertificateParams> for Certificate { | ||
| fn as_ref(&self) -> &CertificateParams { | ||
| &self.params | ||
| } | ||
| } | ||
| /// Parameters used for certificate generation | ||
@@ -107,4 +89,4 @@ #[allow(missing_docs)] | ||
| // not_before and not_after set to reasonably long dates | ||
| let not_before = date_time_ymd(1975, 01, 01); | ||
| let not_after = date_time_ymd(4096, 01, 01); | ||
| let not_before = date_time_ymd(1975, 1, 1); | ||
| let not_after = date_time_ymd(4096, 1, 1); | ||
| let mut distinguished_name = DistinguishedName::new(); | ||
@@ -165,21 +147,8 @@ distinguished_name.push(DnType::CommonName, "rcgen self signed cert"); | ||
| pub fn signed_by( | ||
| self, | ||
| &self, | ||
| public_key: &impl PublicKeyData, | ||
| issuer: &impl AsRef<CertificateParams>, | ||
| issuer_key: &KeyPair, | ||
| issuer: &Issuer<'_, impl SigningKey>, | ||
| ) -> Result<Certificate, Error> { | ||
| let issuer = Issuer { | ||
| distinguished_name: &issuer.as_ref().distinguished_name, | ||
| key_identifier_method: &issuer.as_ref().key_identifier_method, | ||
| key_usages: &issuer.as_ref().key_usages, | ||
| key_pair: issuer_key, | ||
| }; | ||
| let subject_public_key_info = | ||
| yasna::construct_der(|writer| serialize_public_key_der(public_key, writer)); | ||
| let der = self.serialize_der_with_signer(public_key, issuer)?; | ||
| Ok(Certificate { | ||
| params: self, | ||
| subject_public_key_info, | ||
| der, | ||
| der: self.serialize_der_with_signer(public_key, issuer)?, | ||
| }) | ||
@@ -192,255 +161,36 @@ } | ||
| /// [`Certificate::pem`]. | ||
| pub fn self_signed(self, key_pair: &KeyPair) -> Result<Certificate, Error> { | ||
| let issuer = Issuer { | ||
| distinguished_name: &self.distinguished_name, | ||
| key_identifier_method: &self.key_identifier_method, | ||
| key_usages: &self.key_usages, | ||
| key_pair, | ||
| }; | ||
| let subject_public_key_info = key_pair.public_key_der(); | ||
| let der = self.serialize_der_with_signer(key_pair, issuer)?; | ||
| pub fn self_signed(&self, signing_key: &impl SigningKey) -> Result<Certificate, Error> { | ||
| let issuer = Issuer::from_params(self, signing_key); | ||
| Ok(Certificate { | ||
| params: self, | ||
| subject_public_key_info, | ||
| der, | ||
| der: self.serialize_der_with_signer(signing_key, &issuer)?, | ||
| }) | ||
| } | ||
| /// Parses an existing ca certificate from the ASCII PEM format. | ||
| /// | ||
| /// See [`from_ca_cert_der`](Self::from_ca_cert_der) for more details. | ||
| #[cfg(all(feature = "pem", feature = "x509-parser"))] | ||
| pub fn from_ca_cert_pem(pem_str: &str) -> Result<Self, Error> { | ||
| let certificate = pem::parse(pem_str).or(Err(Error::CouldNotParseCertificate))?; | ||
| Self::from_ca_cert_der(&certificate.contents().into()) | ||
| /// Calculates a subject key identifier for the certificate subject's public key. | ||
| /// This key identifier is used in the SubjectKeyIdentifier X.509v3 extension. | ||
| pub fn key_identifier(&self, key: &impl PublicKeyData) -> Vec<u8> { | ||
| self.key_identifier_method | ||
| .derive(key.subject_public_key_info()) | ||
| } | ||
| /// Parses an existing ca certificate from the DER format. | ||
| /// | ||
| /// This function is only of use if you have an existing CA certificate | ||
| /// you would like to use to sign a certificate generated by `rcgen`. | ||
| /// By providing the constructed [`CertificateParams`] and the [`KeyPair`] | ||
| /// associated with your existing `ca_cert` you can use [`CertificateParams::signed_by()`] | ||
| /// or [`crate::CertificateSigningRequestParams::signed_by()`] to issue new certificates | ||
| /// using the CA cert. | ||
| /// | ||
| /// In general this function only extracts the information needed for signing. | ||
| /// Other attributes of the [`Certificate`] may be left as defaults. | ||
| /// | ||
| /// This function assumes the provided certificate is a CA. It will not check | ||
| /// for the presence of the `BasicConstraints` extension, or perform any other | ||
| /// validation. | ||
| /// | ||
| /// [`rustls_pemfile::certs()`] is often used to obtain a [`CertificateDer`] from PEM input. | ||
| /// If you already have a byte slice containing DER, it can trivially be converted into | ||
| /// [`CertificateDer`] using the [`Into`] trait. | ||
| /// | ||
| /// [`rustls_pemfile::certs()`]: https://docs.rs/rustls-pemfile/latest/rustls_pemfile/fn.certs.html | ||
| #[cfg(feature = "x509-parser")] | ||
| pub fn from_ca_cert_der(ca_cert: &CertificateDer<'_>) -> Result<Self, Error> { | ||
| #[cfg(all(test, feature = "x509-parser"))] | ||
| pub(crate) fn from_ca_cert_der(ca_cert: &CertificateDer<'_>) -> Result<Self, Error> { | ||
| let (_remainder, x509) = x509_parser::parse_x509_certificate(ca_cert) | ||
| .or(Err(Error::CouldNotParseCertificate))?; | ||
| .map_err(|_| Error::CouldNotParseCertificate)?; | ||
| let dn = DistinguishedName::from_name(&x509.tbs_certificate.subject)?; | ||
| let is_ca = Self::convert_x509_is_ca(&x509)?; | ||
| let validity = x509.validity(); | ||
| let subject_alt_names = Self::convert_x509_subject_alternative_name(&x509)?; | ||
| let key_usages = Self::convert_x509_key_usages(&x509)?; | ||
| let extended_key_usages = Self::convert_x509_extended_key_usages(&x509)?; | ||
| let name_constraints = Self::convert_x509_name_constraints(&x509)?; | ||
| let serial_number = Some(x509.serial.to_bytes_be().into()); | ||
| let key_identifier_method = | ||
| x509.iter_extensions() | ||
| .find_map(|ext| match ext.parsed_extension() { | ||
| x509_parser::extensions::ParsedExtension::SubjectKeyIdentifier(key_id) => { | ||
| Some(KeyIdMethod::PreSpecified(key_id.0.into())) | ||
| }, | ||
| _ => None, | ||
| }); | ||
| let key_identifier_method = match key_identifier_method { | ||
| Some(method) => method, | ||
| None => { | ||
| #[cfg(not(feature = "crypto"))] | ||
| return Err(Error::UnsupportedSignatureAlgorithm); | ||
| #[cfg(feature = "crypto")] | ||
| KeyIdMethod::Sha256 | ||
| }, | ||
| }; | ||
| Ok(CertificateParams { | ||
| is_ca, | ||
| subject_alt_names, | ||
| key_usages, | ||
| extended_key_usages, | ||
| name_constraints, | ||
| serial_number, | ||
| key_identifier_method, | ||
| distinguished_name: dn, | ||
| not_before: validity.not_before.to_datetime(), | ||
| not_after: validity.not_after.to_datetime(), | ||
| is_ca: IsCa::from_x509(&x509)?, | ||
| subject_alt_names: SanType::from_x509(&x509)?, | ||
| key_usages: KeyUsagePurpose::from_x509(&x509)?, | ||
| extended_key_usages: ExtendedKeyUsagePurpose::from_x509(&x509)?, | ||
| name_constraints: NameConstraints::from_x509(&x509)?, | ||
| serial_number: Some(x509.serial.to_bytes_be().into()), | ||
| key_identifier_method: KeyIdMethod::from_x509(&x509)?, | ||
| distinguished_name: DistinguishedName::from_name(&x509.tbs_certificate.subject)?, | ||
| not_before: x509.validity().not_before.to_datetime(), | ||
| not_after: x509.validity().not_after.to_datetime(), | ||
| ..Default::default() | ||
| }) | ||
| } | ||
| #[cfg(feature = "x509-parser")] | ||
| fn convert_x509_is_ca( | ||
| x509: &x509_parser::certificate::X509Certificate<'_>, | ||
| ) -> Result<IsCa, Error> { | ||
| use x509_parser::extensions::BasicConstraints as B; | ||
| let basic_constraints = x509 | ||
| .basic_constraints() | ||
| .or(Err(Error::CouldNotParseCertificate))? | ||
| .map(|ext| ext.value); | ||
| let is_ca = match basic_constraints { | ||
| Some(B { | ||
| ca: true, | ||
| path_len_constraint: Some(n), | ||
| }) if *n <= u8::MAX as u32 => IsCa::Ca(BasicConstraints::Constrained(*n as u8)), | ||
| Some(B { | ||
| ca: true, | ||
| path_len_constraint: Some(_), | ||
| }) => return Err(Error::CouldNotParseCertificate), | ||
| Some(B { | ||
| ca: true, | ||
| path_len_constraint: None, | ||
| }) => IsCa::Ca(BasicConstraints::Unconstrained), | ||
| Some(B { ca: false, .. }) => IsCa::ExplicitNoCa, | ||
| None => IsCa::NoCa, | ||
| }; | ||
| Ok(is_ca) | ||
| } | ||
| #[cfg(feature = "x509-parser")] | ||
| fn convert_x509_subject_alternative_name( | ||
| x509: &x509_parser::certificate::X509Certificate<'_>, | ||
| ) -> Result<Vec<SanType>, Error> { | ||
| let sans = x509 | ||
| .subject_alternative_name() | ||
| .or(Err(Error::CouldNotParseCertificate))? | ||
| .map(|ext| &ext.value.general_names); | ||
| if let Some(sans) = sans { | ||
| let mut subject_alt_names = Vec::with_capacity(sans.len()); | ||
| for san in sans { | ||
| subject_alt_names.push(SanType::try_from_general(san)?); | ||
| } | ||
| Ok(subject_alt_names) | ||
| } else { | ||
| Ok(Vec::new()) | ||
| } | ||
| } | ||
| #[cfg(feature = "x509-parser")] | ||
| fn convert_x509_key_usages( | ||
| x509: &x509_parser::certificate::X509Certificate<'_>, | ||
| ) -> Result<Vec<KeyUsagePurpose>, Error> { | ||
| let key_usage = x509 | ||
| .key_usage() | ||
| .or(Err(Error::CouldNotParseCertificate))? | ||
| .map(|ext| ext.value); | ||
| // This x509 parser stores flags in reversed bit BIT STRING order | ||
| let flags = key_usage.map_or(0u16, |k| k.flags).reverse_bits(); | ||
| Ok(KeyUsagePurpose::from_u16(flags)) | ||
| } | ||
| #[cfg(feature = "x509-parser")] | ||
| fn convert_x509_extended_key_usages( | ||
| x509: &x509_parser::certificate::X509Certificate<'_>, | ||
| ) -> Result<Vec<ExtendedKeyUsagePurpose>, Error> { | ||
| let extended_key_usage = x509 | ||
| .extended_key_usage() | ||
| .or(Err(Error::CouldNotParseCertificate))? | ||
| .map(|ext| ext.value); | ||
| let mut extended_key_usages = Vec::new(); | ||
| if let Some(extended_key_usage) = extended_key_usage { | ||
| if extended_key_usage.any { | ||
| extended_key_usages.push(ExtendedKeyUsagePurpose::Any); | ||
| } | ||
| if extended_key_usage.server_auth { | ||
| extended_key_usages.push(ExtendedKeyUsagePurpose::ServerAuth); | ||
| } | ||
| if extended_key_usage.client_auth { | ||
| extended_key_usages.push(ExtendedKeyUsagePurpose::ClientAuth); | ||
| } | ||
| if extended_key_usage.code_signing { | ||
| extended_key_usages.push(ExtendedKeyUsagePurpose::CodeSigning); | ||
| } | ||
| if extended_key_usage.email_protection { | ||
| extended_key_usages.push(ExtendedKeyUsagePurpose::EmailProtection); | ||
| } | ||
| if extended_key_usage.time_stamping { | ||
| extended_key_usages.push(ExtendedKeyUsagePurpose::TimeStamping); | ||
| } | ||
| if extended_key_usage.ocsp_signing { | ||
| extended_key_usages.push(ExtendedKeyUsagePurpose::OcspSigning); | ||
| } | ||
| } | ||
| Ok(extended_key_usages) | ||
| } | ||
| #[cfg(feature = "x509-parser")] | ||
| fn convert_x509_name_constraints( | ||
| x509: &x509_parser::certificate::X509Certificate<'_>, | ||
| ) -> Result<Option<NameConstraints>, Error> { | ||
| let constraints = x509 | ||
| .name_constraints() | ||
| .or(Err(Error::CouldNotParseCertificate))? | ||
| .map(|ext| ext.value); | ||
| if let Some(constraints) = constraints { | ||
| let permitted_subtrees = if let Some(permitted) = &constraints.permitted_subtrees { | ||
| Self::convert_x509_general_subtrees(permitted)? | ||
| } else { | ||
| Vec::new() | ||
| }; | ||
| let excluded_subtrees = if let Some(excluded) = &constraints.excluded_subtrees { | ||
| Self::convert_x509_general_subtrees(excluded)? | ||
| } else { | ||
| Vec::new() | ||
| }; | ||
| let name_constraints = NameConstraints { | ||
| permitted_subtrees, | ||
| excluded_subtrees, | ||
| }; | ||
| Ok(Some(name_constraints)) | ||
| } else { | ||
| Ok(None) | ||
| } | ||
| } | ||
| #[cfg(feature = "x509-parser")] | ||
| fn convert_x509_general_subtrees( | ||
| subtrees: &[x509_parser::extensions::GeneralSubtree<'_>], | ||
| ) -> Result<Vec<GeneralSubtree>, Error> { | ||
| use x509_parser::extensions::GeneralName; | ||
| let mut result = Vec::new(); | ||
| for subtree in subtrees { | ||
| let subtree = match &subtree.base { | ||
| GeneralName::RFC822Name(s) => GeneralSubtree::Rfc822Name(s.to_string()), | ||
| GeneralName::DNSName(s) => GeneralSubtree::DnsName(s.to_string()), | ||
| GeneralName::DirectoryName(n) => { | ||
| GeneralSubtree::DirectoryName(DistinguishedName::from_name(n)?) | ||
| }, | ||
| GeneralName::IPAddress(bytes) if bytes.len() == 8 => { | ||
| let addr: [u8; 4] = bytes[..4].try_into().unwrap(); | ||
| let mask: [u8; 4] = bytes[4..].try_into().unwrap(); | ||
| GeneralSubtree::IpAddress(CidrSubnet::V4(addr, mask)) | ||
| }, | ||
| GeneralName::IPAddress(bytes) if bytes.len() == 32 => { | ||
| let addr: [u8; 16] = bytes[..16].try_into().unwrap(); | ||
| let mask: [u8; 16] = bytes[16..].try_into().unwrap(); | ||
| GeneralSubtree::IpAddress(CidrSubnet::V6(addr, mask)) | ||
| }, | ||
| _ => continue, | ||
| }; | ||
| result.push(subtree); | ||
| } | ||
| Ok(result) | ||
| } | ||
| /// Write a CSR extension request attribute as defined in [RFC 2985]. | ||
@@ -554,3 +304,3 @@ /// | ||
| &self, | ||
| subject_key: &KeyPair, | ||
| subject_key: &impl SigningKey, | ||
| ) -> Result<CertificateSigningRequest, Error> { | ||
@@ -573,3 +323,3 @@ self.serialize_request_with_attributes(subject_key, Vec::new()) | ||
| &self, | ||
| subject_key: &KeyPair, | ||
| subject_key: &impl SigningKey, | ||
| attrs: Vec<Attribute>, | ||
@@ -594,3 +344,3 @@ ) -> Result<CertificateSigningRequest, Error> { | ||
| } = self; | ||
| // - alg and key_pair will be used by the caller | ||
| // - subject_key will be used by the caller | ||
| // - not_before and not_after cannot be put in a CSR | ||
@@ -623,3 +373,3 @@ // - key_identifier_method is here because self.write_extended_key_usage uses it | ||
| let der = subject_key.sign_der(|writer| { | ||
| let der = sign_der(subject_key, |writer| { | ||
| // Write version | ||
@@ -642,3 +392,3 @@ writer.next().write_u8(0); | ||
| writer.next().write_sequence(|writer| { | ||
| writer.next().write_oid(&ObjectIdentifier::from_slice(&oid)); | ||
| writer.next().write_oid(&ObjectIdentifier::from_slice(oid)); | ||
| writer.next().write_der(&values); | ||
@@ -661,7 +411,6 @@ }); | ||
| pub_key: &K, | ||
| issuer: Issuer<'_>, | ||
| issuer: &Issuer<'_, impl SigningKey>, | ||
| ) -> Result<CertificateDer<'static>, Error> { | ||
| let der = issuer.key_pair.sign_der(|writer| { | ||
| let pub_key_spki = | ||
| yasna::construct_der(|writer| serialize_public_key_der(pub_key, writer)); | ||
| let der = sign_der(&*issuer.signing_key, |writer| { | ||
| let pub_key_spki = pub_key.subject_public_key_info(); | ||
| // Write version | ||
@@ -689,5 +438,8 @@ writer.next().write_tagged(Tag::context(0), |writer| { | ||
| // Write signature algorithm | ||
| issuer.key_pair.alg.write_alg_ident(writer.next()); | ||
| issuer | ||
| .signing_key | ||
| .algorithm() | ||
| .write_alg_ident(writer.next()); | ||
| // Write issuer name | ||
| write_distinguished_name(writer.next(), &issuer.distinguished_name); | ||
| write_distinguished_name(writer.next(), issuer.distinguished_name.as_ref()); | ||
| // Write validity | ||
@@ -718,146 +470,136 @@ writer.next().write_sequence(|writer| { | ||
| writer.next().write_tagged(Tag::context(3), |writer| { | ||
| writer.write_sequence(|writer| self.write_extensions(writer, &pub_key_spki, issuer)) | ||
| })?; | ||
| Ok(()) | ||
| })?; | ||
| Ok(der.into()) | ||
| } | ||
| fn write_extensions( | ||
| &self, | ||
| writer: &mut DERWriterSeq, | ||
| pub_key_spki: &[u8], | ||
| issuer: &Issuer<'_, impl SigningKey>, | ||
| ) -> Result<(), Error> { | ||
| if self.use_authority_key_identifier_extension { | ||
| write_x509_authority_key_identifier( | ||
| writer.next(), | ||
| match issuer.key_identifier_method.as_ref() { | ||
| KeyIdMethod::PreSpecified(aki) => aki.clone(), | ||
| #[cfg(feature = "crypto")] | ||
| _ => issuer | ||
| .key_identifier_method | ||
| .derive(issuer.signing_key.subject_public_key_info()), | ||
| }, | ||
| ); | ||
| } | ||
| // Write subject_alt_names | ||
| self.write_subject_alt_names(writer.next()); | ||
| // Write standard key usage | ||
| self.write_key_usage(writer.next()); | ||
| // Write extended key usage | ||
| if !self.extended_key_usages.is_empty() { | ||
| write_x509_extension(writer.next(), oid::EXT_KEY_USAGE, false, |writer| { | ||
| writer.write_sequence(|writer| { | ||
| if self.use_authority_key_identifier_extension { | ||
| write_x509_authority_key_identifier( | ||
| writer.next(), | ||
| match issuer.key_identifier_method { | ||
| KeyIdMethod::PreSpecified(aki) => aki.clone(), | ||
| #[cfg(feature = "crypto")] | ||
| _ => issuer | ||
| .key_identifier_method | ||
| .derive(issuer.key_pair.public_key_der()), | ||
| }, | ||
| ); | ||
| for usage in self.extended_key_usages.iter() { | ||
| let oid = ObjectIdentifier::from_slice(usage.oid()); | ||
| writer.next().write_oid(&oid); | ||
| } | ||
| // Write subject_alt_names | ||
| if !self.subject_alt_names.is_empty() { | ||
| self.write_subject_alt_names(writer.next()); | ||
| } | ||
| }); | ||
| }); | ||
| } | ||
| // Write standard key usage | ||
| self.write_key_usage(writer.next()); | ||
| // Write extended key usage | ||
| if !self.extended_key_usages.is_empty() { | ||
| write_x509_extension(writer.next(), oid::EXT_KEY_USAGE, false, |writer| { | ||
| writer.write_sequence(|writer| { | ||
| for usage in self.extended_key_usages.iter() { | ||
| let oid = ObjectIdentifier::from_slice(usage.oid()); | ||
| writer.next().write_oid(&oid); | ||
| } | ||
| }); | ||
| }); | ||
| } | ||
| if let Some(name_constraints) = &self.name_constraints { | ||
| // If both trees are empty, the extension must be omitted. | ||
| if !name_constraints.is_empty() { | ||
| write_x509_extension( | ||
| if let Some(name_constraints) = &self.name_constraints { | ||
| // If both trees are empty, the extension must be omitted. | ||
| if !name_constraints.is_empty() { | ||
| write_x509_extension(writer.next(), oid::NAME_CONSTRAINTS, true, |writer| { | ||
| writer.write_sequence(|writer| { | ||
| if !name_constraints.permitted_subtrees.is_empty() { | ||
| write_general_subtrees( | ||
| writer.next(), | ||
| oid::NAME_CONSTRAINTS, | ||
| true, | ||
| |writer| { | ||
| writer.write_sequence(|writer| { | ||
| if !name_constraints.permitted_subtrees.is_empty() { | ||
| write_general_subtrees( | ||
| writer.next(), | ||
| 0, | ||
| &name_constraints.permitted_subtrees, | ||
| ); | ||
| } | ||
| if !name_constraints.excluded_subtrees.is_empty() { | ||
| write_general_subtrees( | ||
| writer.next(), | ||
| 1, | ||
| &name_constraints.excluded_subtrees, | ||
| ); | ||
| } | ||
| }); | ||
| }, | ||
| 0, | ||
| &name_constraints.permitted_subtrees, | ||
| ); | ||
| } | ||
| } | ||
| if !self.crl_distribution_points.is_empty() { | ||
| write_x509_extension( | ||
| writer.next(), | ||
| oid::CRL_DISTRIBUTION_POINTS, | ||
| false, | ||
| |writer| { | ||
| writer.write_sequence(|writer| { | ||
| for distribution_point in &self.crl_distribution_points { | ||
| distribution_point.write_der(writer.next()); | ||
| } | ||
| }) | ||
| }, | ||
| ); | ||
| } | ||
| match self.is_ca { | ||
| IsCa::Ca(ref constraint) => { | ||
| // Write subject_key_identifier | ||
| write_x509_extension( | ||
| if !name_constraints.excluded_subtrees.is_empty() { | ||
| write_general_subtrees( | ||
| writer.next(), | ||
| oid::SUBJECT_KEY_IDENTIFIER, | ||
| false, | ||
| |writer| { | ||
| writer.write_bytes( | ||
| &self.key_identifier_method.derive(pub_key_spki), | ||
| ); | ||
| }, | ||
| 1, | ||
| &name_constraints.excluded_subtrees, | ||
| ); | ||
| // Write basic_constraints | ||
| write_x509_extension( | ||
| writer.next(), | ||
| oid::BASIC_CONSTRAINTS, | ||
| true, | ||
| |writer| { | ||
| writer.write_sequence(|writer| { | ||
| writer.next().write_bool(true); // cA flag | ||
| if let BasicConstraints::Constrained(path_len_constraint) = | ||
| constraint | ||
| { | ||
| writer.next().write_u8(*path_len_constraint); | ||
| } | ||
| }); | ||
| }, | ||
| ); | ||
| }, | ||
| IsCa::ExplicitNoCa => { | ||
| // Write subject_key_identifier | ||
| write_x509_extension( | ||
| writer.next(), | ||
| oid::SUBJECT_KEY_IDENTIFIER, | ||
| false, | ||
| |writer| { | ||
| writer.write_bytes( | ||
| &self.key_identifier_method.derive(pub_key_spki), | ||
| ); | ||
| }, | ||
| ); | ||
| // Write basic_constraints | ||
| write_x509_extension( | ||
| writer.next(), | ||
| oid::BASIC_CONSTRAINTS, | ||
| true, | ||
| |writer| { | ||
| writer.write_sequence(|writer| { | ||
| writer.next().write_bool(false); // cA flag | ||
| }); | ||
| }, | ||
| ); | ||
| }, | ||
| IsCa::NoCa => {}, | ||
| } | ||
| } | ||
| }); | ||
| }); | ||
| } | ||
| } | ||
| // Write the custom extensions | ||
| for ext in &self.custom_extensions { | ||
| write_x509_extension(writer.next(), &ext.oid, ext.critical, |writer| { | ||
| writer.write_der(ext.content()) | ||
| }); | ||
| } | ||
| if !self.crl_distribution_points.is_empty() { | ||
| write_x509_extension( | ||
| writer.next(), | ||
| oid::CRL_DISTRIBUTION_POINTS, | ||
| false, | ||
| |writer| { | ||
| writer.write_sequence(|writer| { | ||
| for distribution_point in &self.crl_distribution_points { | ||
| distribution_point.write_der(writer.next()); | ||
| } | ||
| }) | ||
| }, | ||
| ); | ||
| } | ||
| match self.is_ca { | ||
| IsCa::Ca(ref constraint) => { | ||
| // Write subject_key_identifier | ||
| write_x509_extension( | ||
| writer.next(), | ||
| oid::SUBJECT_KEY_IDENTIFIER, | ||
| false, | ||
| |writer| { | ||
| writer.write_bytes(&self.key_identifier_method.derive(pub_key_spki)); | ||
| }, | ||
| ); | ||
| // Write basic_constraints | ||
| write_x509_extension(writer.next(), oid::BASIC_CONSTRAINTS, true, |writer| { | ||
| writer.write_sequence(|writer| { | ||
| writer.next().write_bool(true); // cA flag | ||
| if let BasicConstraints::Constrained(path_len_constraint) = constraint { | ||
| writer.next().write_u8(*path_len_constraint); | ||
| } | ||
| }); | ||
| }); | ||
| }, | ||
| IsCa::ExplicitNoCa => { | ||
| // Write subject_key_identifier | ||
| write_x509_extension( | ||
| writer.next(), | ||
| oid::SUBJECT_KEY_IDENTIFIER, | ||
| false, | ||
| |writer| { | ||
| writer.write_bytes(&self.key_identifier_method.derive(pub_key_spki)); | ||
| }, | ||
| ); | ||
| // Write basic_constraints | ||
| write_x509_extension(writer.next(), oid::BASIC_CONSTRAINTS, true, |writer| { | ||
| writer.write_sequence(|writer| { | ||
| writer.next().write_bool(false); // cA flag | ||
| }); | ||
| }); | ||
| }, | ||
| IsCa::NoCa => {}, | ||
| } | ||
| // Write the custom extensions | ||
| for ext in &self.custom_extensions { | ||
| write_x509_extension(writer.next(), &ext.oid, ext.critical, |writer| { | ||
| writer.write_der(ext.content()) | ||
| }); | ||
| } | ||
| Ok(()) | ||
| })?; | ||
| Ok(der.into()) | ||
| Ok(()) | ||
| } | ||
@@ -1048,2 +790,37 @@ | ||
| impl ExtendedKeyUsagePurpose { | ||
| #[cfg(all(test, feature = "x509-parser"))] | ||
| fn from_x509(x509: &x509_parser::certificate::X509Certificate<'_>) -> Result<Vec<Self>, Error> { | ||
| let extended_key_usage = x509 | ||
| .extended_key_usage() | ||
| .map_err(|_| Error::CouldNotParseCertificate)? | ||
| .map(|ext| ext.value); | ||
| let mut extended_key_usages = Vec::new(); | ||
| if let Some(extended_key_usage) = extended_key_usage { | ||
| if extended_key_usage.any { | ||
| extended_key_usages.push(Self::Any); | ||
| } | ||
| if extended_key_usage.server_auth { | ||
| extended_key_usages.push(Self::ServerAuth); | ||
| } | ||
| if extended_key_usage.client_auth { | ||
| extended_key_usages.push(Self::ClientAuth); | ||
| } | ||
| if extended_key_usage.code_signing { | ||
| extended_key_usages.push(Self::CodeSigning); | ||
| } | ||
| if extended_key_usage.email_protection { | ||
| extended_key_usages.push(Self::EmailProtection); | ||
| } | ||
| if extended_key_usage.time_stamping { | ||
| extended_key_usages.push(Self::TimeStamping); | ||
| } | ||
| if extended_key_usage.ocsp_signing { | ||
| extended_key_usages.push(Self::OcspSigning); | ||
| } | ||
| } | ||
| Ok(extended_key_usages) | ||
| } | ||
| fn oid(&self) -> &[u64] { | ||
@@ -1079,2 +856,33 @@ use ExtendedKeyUsagePurpose::*; | ||
| impl NameConstraints { | ||
| #[cfg(all(test, feature = "x509-parser"))] | ||
| fn from_x509( | ||
| x509: &x509_parser::certificate::X509Certificate<'_>, | ||
| ) -> Result<Option<Self>, Error> { | ||
| let constraints = x509 | ||
| .name_constraints() | ||
| .map_err(|_| Error::CouldNotParseCertificate)? | ||
| .map(|ext| ext.value); | ||
| let Some(constraints) = constraints else { | ||
| return Ok(None); | ||
| }; | ||
| let permitted_subtrees = if let Some(permitted) = &constraints.permitted_subtrees { | ||
| GeneralSubtree::from_x509(permitted)? | ||
| } else { | ||
| Vec::new() | ||
| }; | ||
| let excluded_subtrees = if let Some(excluded) = &constraints.excluded_subtrees { | ||
| GeneralSubtree::from_x509(excluded)? | ||
| } else { | ||
| Vec::new() | ||
| }; | ||
| Ok(Some(Self { | ||
| permitted_subtrees, | ||
| excluded_subtrees, | ||
| })) | ||
| } | ||
| fn is_empty(&self) -> bool { | ||
@@ -1102,2 +910,34 @@ self.permitted_subtrees.is_empty() && self.excluded_subtrees.is_empty() | ||
| impl GeneralSubtree { | ||
| #[cfg(all(test, feature = "x509-parser"))] | ||
| fn from_x509( | ||
| subtrees: &[x509_parser::extensions::GeneralSubtree<'_>], | ||
| ) -> Result<Vec<Self>, Error> { | ||
| use x509_parser::extensions::GeneralName; | ||
| let mut result = Vec::new(); | ||
| for subtree in subtrees { | ||
| let subtree = match &subtree.base { | ||
| GeneralName::RFC822Name(s) => Self::Rfc822Name(s.to_string()), | ||
| GeneralName::DNSName(s) => Self::DnsName(s.to_string()), | ||
| GeneralName::DirectoryName(n) => { | ||
| Self::DirectoryName(DistinguishedName::from_name(n)?) | ||
| }, | ||
| GeneralName::IPAddress(bytes) if bytes.len() == 8 => { | ||
| let addr: [u8; 4] = bytes[..4].try_into().unwrap(); | ||
| let mask: [u8; 4] = bytes[4..].try_into().unwrap(); | ||
| Self::IpAddress(CidrSubnet::V4(addr, mask)) | ||
| }, | ||
| GeneralName::IPAddress(bytes) if bytes.len() == 32 => { | ||
| let addr: [u8; 16] = bytes[..16].try_into().unwrap(); | ||
| let mask: [u8; 16] = bytes[16..].try_into().unwrap(); | ||
| Self::IpAddress(CidrSubnet::V6(addr, mask)) | ||
| }, | ||
| _ => continue, | ||
| }; | ||
| result.push(subtree); | ||
| } | ||
| Ok(result) | ||
| } | ||
| fn tag(&self) -> u64 { | ||
@@ -1120,3 +960,3 @@ // Defined in the GeneralName list in | ||
| #[derive(Debug, PartialEq, Eq, Hash, Clone)] | ||
| #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] | ||
| #[allow(missing_docs)] | ||
@@ -1138,3 +978,3 @@ /// CIDR subnet, as per [RFC 4632](https://tools.ietf.org/html/rfc4632) | ||
| ($t:ty, $d:expr) => {{ | ||
| let v = <$t>::max_value(); | ||
| let v = <$t>::MAX; | ||
| let v = v.checked_shr($d as u32).unwrap_or(0); | ||
@@ -1175,12 +1015,12 @@ (!v).to_be_bytes() | ||
| } | ||
| fn to_bytes(&self) -> Vec<u8> { | ||
| fn to_bytes(self) -> Vec<u8> { | ||
| let mut res = Vec::new(); | ||
| match self { | ||
| CidrSubnet::V4(addr, mask) => { | ||
| res.extend_from_slice(addr); | ||
| res.extend_from_slice(mask); | ||
| res.extend_from_slice(&addr); | ||
| res.extend_from_slice(&mask); | ||
| }, | ||
| CidrSubnet::V6(addr, mask) => { | ||
| res.extend_from_slice(addr); | ||
| res.extend_from_slice(mask); | ||
| res.extend_from_slice(&addr); | ||
| res.extend_from_slice(&mask); | ||
| }, | ||
@@ -1235,3 +1075,3 @@ } | ||
| /// Whether the certificate is allowed to sign other certificates | ||
| #[derive(Debug, PartialEq, Eq, Hash, Clone)] | ||
| #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] | ||
| pub enum IsCa { | ||
@@ -1246,2 +1086,31 @@ /// The certificate can only sign itself | ||
| impl IsCa { | ||
| #[cfg(all(test, feature = "x509-parser"))] | ||
| fn from_x509(x509: &x509_parser::certificate::X509Certificate<'_>) -> Result<Self, Error> { | ||
| use x509_parser::extensions::BasicConstraints as B; | ||
| let basic_constraints = x509 | ||
| .basic_constraints() | ||
| .map_err(|_| Error::CouldNotParseCertificate)? | ||
| .map(|ext| ext.value); | ||
| Ok(match basic_constraints { | ||
| Some(B { | ||
| ca: true, | ||
| path_len_constraint: Some(n), | ||
| }) if *n <= u8::MAX as u32 => Self::Ca(BasicConstraints::Constrained(*n as u8)), | ||
| Some(B { | ||
| ca: true, | ||
| path_len_constraint: Some(_), | ||
| }) => return Err(Error::CouldNotParseCertificate), | ||
| Some(B { | ||
| ca: true, | ||
| path_len_constraint: None, | ||
| }) => Self::Ca(BasicConstraints::Unconstrained), | ||
| Some(B { ca: false, .. }) => Self::ExplicitNoCa, | ||
| None => Self::NoCa, | ||
| }) | ||
| } | ||
| } | ||
| /// The path length constraint (only relevant for CA certificates) | ||
@@ -1251,3 +1120,3 @@ /// | ||
| /// length allowed for this CA certificate (not including the end entity certificate). | ||
| #[derive(Debug, PartialEq, Eq, Hash, Clone)] | ||
| #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] | ||
| pub enum BasicConstraints { | ||
@@ -1262,4 +1131,14 @@ /// No constraint | ||
| mod tests { | ||
| #[cfg(feature = "x509-parser")] | ||
| use std::net::Ipv4Addr; | ||
| #[cfg(feature = "x509-parser")] | ||
| use pki_types::pem::PemObject; | ||
| #[cfg(feature = "pem")] | ||
| use super::*; | ||
| #[cfg(feature = "x509-parser")] | ||
| use crate::DnValue; | ||
| #[cfg(feature = "crypto")] | ||
| use crate::KeyPair; | ||
@@ -1269,14 +1148,14 @@ #[cfg(feature = "crypto")] | ||
| fn test_with_key_usages() { | ||
| let mut params: CertificateParams = Default::default(); | ||
| let params = CertificateParams { | ||
| // Set key usages | ||
| key_usages: vec![ | ||
| KeyUsagePurpose::DigitalSignature, | ||
| KeyUsagePurpose::KeyEncipherment, | ||
| KeyUsagePurpose::ContentCommitment, | ||
| ], | ||
| // This can sign things! | ||
| is_ca: IsCa::Ca(BasicConstraints::Constrained(0)), | ||
| ..CertificateParams::default() | ||
| }; | ||
| // Set key_usages | ||
| params.key_usages = vec![ | ||
| KeyUsagePurpose::DigitalSignature, | ||
| KeyUsagePurpose::KeyEncipherment, | ||
| KeyUsagePurpose::ContentCommitment, | ||
| ]; | ||
| // This can sign things! | ||
| params.is_ca = IsCa::Ca(BasicConstraints::Constrained(0)); | ||
| // Make the cert | ||
@@ -1297,8 +1176,7 @@ let key_pair = KeyPair::generate().unwrap(); | ||
| if key_usage_oid_str == ext.oid.to_id_string() { | ||
| match ext.parsed_extension() { | ||
| x509_parser::extensions::ParsedExtension::KeyUsage(usage) => { | ||
| assert!(usage.flags == 7); | ||
| found = true; | ||
| }, | ||
| _ => {}, | ||
| if let x509_parser::extensions::ParsedExtension::KeyUsage(usage) = | ||
| ext.parsed_extension() | ||
| { | ||
| assert!(usage.flags == 7); | ||
| found = true; | ||
| } | ||
@@ -1314,10 +1192,10 @@ } | ||
| fn test_with_key_usages_decipheronly_only() { | ||
| let mut params: CertificateParams = Default::default(); | ||
| let params = CertificateParams { | ||
| // Set key usages | ||
| key_usages: vec![KeyUsagePurpose::DecipherOnly], | ||
| // This can sign things! | ||
| is_ca: IsCa::Ca(BasicConstraints::Constrained(0)), | ||
| ..CertificateParams::default() | ||
| }; | ||
| // Set key_usages | ||
| params.key_usages = vec![KeyUsagePurpose::DecipherOnly]; | ||
| // This can sign things! | ||
| params.is_ca = IsCa::Ca(BasicConstraints::Constrained(0)); | ||
| // Make the cert | ||
@@ -1338,8 +1216,7 @@ let key_pair = KeyPair::generate().unwrap(); | ||
| if key_usage_oid_str == ext.oid.to_id_string() { | ||
| match ext.parsed_extension() { | ||
| x509_parser::extensions::ParsedExtension::KeyUsage(usage) => { | ||
| assert!(usage.flags == 256); | ||
| found = true; | ||
| }, | ||
| _ => {}, | ||
| if let x509_parser::extensions::ParsedExtension::KeyUsage(usage) = | ||
| ext.parsed_extension() | ||
| { | ||
| assert!(usage.flags == 256); | ||
| found = true; | ||
| } | ||
@@ -1355,7 +1232,7 @@ } | ||
| fn test_with_extended_key_usages_any() { | ||
| let mut params: CertificateParams = Default::default(); | ||
| let params = CertificateParams { | ||
| extended_key_usages: vec![ExtendedKeyUsagePurpose::Any], | ||
| ..CertificateParams::default() | ||
| }; | ||
| // Set extended_key_usages | ||
| params.extended_key_usages = vec![ExtendedKeyUsagePurpose::Any]; | ||
| // Make the cert | ||
@@ -1378,11 +1255,12 @@ let key_pair = KeyPair::generate().unwrap(); | ||
| use x509_parser::der_parser::asn1_rs::Oid; | ||
| let mut params: CertificateParams = Default::default(); | ||
| const OID_1: &[u64] = &[1, 2, 3, 4]; | ||
| const OID_2: &[u64] = &[1, 2, 3, 4, 5, 6]; | ||
| // Set extended_key_usages | ||
| params.extended_key_usages = vec![ | ||
| ExtendedKeyUsagePurpose::Other(Vec::from(OID_1)), | ||
| ExtendedKeyUsagePurpose::Other(Vec::from(OID_2)), | ||
| ]; | ||
| let params = CertificateParams { | ||
| extended_key_usages: vec![ | ||
| ExtendedKeyUsagePurpose::Other(Vec::from(OID_1)), | ||
| ExtendedKeyUsagePurpose::Other(Vec::from(OID_2)), | ||
| ], | ||
| ..CertificateParams::default() | ||
| }; | ||
@@ -1425,3 +1303,80 @@ // Make the cert | ||
| #[cfg(all(feature = "pem", feature = "x509-parser"))] | ||
| #[cfg(feature = "x509-parser")] | ||
| #[test] | ||
| fn parse_other_name_alt_name() { | ||
| // Create and serialize a certificate with an alternative name containing an "OtherName". | ||
| let mut params = CertificateParams::default(); | ||
| let other_name = SanType::OtherName((vec![1, 2, 3, 4], "Foo".into())); | ||
| params.subject_alt_names.push(other_name.clone()); | ||
| let key_pair = KeyPair::generate().unwrap(); | ||
| let cert = params.self_signed(&key_pair).unwrap(); | ||
| // We should be able to parse the certificate with x509-parser. | ||
| assert!(x509_parser::parse_x509_certificate(cert.der()).is_ok()); | ||
| // We should be able to reconstitute params from the DER using x509-parser. | ||
| let params_from_cert = CertificateParams::from_ca_cert_der(cert.der()).unwrap(); | ||
| // We should find the expected distinguished name in the reconstituted params. | ||
| let expected_alt_names = &[&other_name]; | ||
| let subject_alt_names = params_from_cert | ||
| .subject_alt_names | ||
| .iter() | ||
| .collect::<Vec<_>>(); | ||
| assert_eq!(subject_alt_names, expected_alt_names); | ||
| } | ||
| #[cfg(feature = "x509-parser")] | ||
| #[test] | ||
| fn parse_ia5string_subject() { | ||
| // Create and serialize a certificate with a subject containing an IA5String email address. | ||
| let email_address_dn_type = DnType::CustomDnType(vec![1, 2, 840, 113549, 1, 9, 1]); // id-emailAddress | ||
| let email_address_dn_value = DnValue::Ia5String("foo@bar.com".try_into().unwrap()); | ||
| let mut params = CertificateParams::new(vec!["crabs".to_owned()]).unwrap(); | ||
| params.distinguished_name = DistinguishedName::new(); | ||
| params.distinguished_name.push( | ||
| email_address_dn_type.clone(), | ||
| email_address_dn_value.clone(), | ||
| ); | ||
| let key_pair = KeyPair::generate().unwrap(); | ||
| let cert = params.self_signed(&key_pair).unwrap(); | ||
| // We should be able to parse the certificate with x509-parser. | ||
| assert!(x509_parser::parse_x509_certificate(cert.der()).is_ok()); | ||
| // We should be able to reconstitute params from the DER using x509-parser. | ||
| let params_from_cert = CertificateParams::from_ca_cert_der(cert.der()).unwrap(); | ||
| // We should find the expected distinguished name in the reconstituted params. | ||
| let expected_names = &[(&email_address_dn_type, &email_address_dn_value)]; | ||
| let names = params_from_cert | ||
| .distinguished_name | ||
| .iter() | ||
| .collect::<Vec<(_, _)>>(); | ||
| assert_eq!(names, expected_names); | ||
| } | ||
| #[cfg(feature = "x509-parser")] | ||
| #[test] | ||
| fn converts_from_ip() { | ||
| let ip = Ipv4Addr::new(2, 4, 6, 8); | ||
| let ip_san = SanType::IpAddress(IpAddr::V4(ip)); | ||
| let mut params = CertificateParams::new(vec!["crabs".to_owned()]).unwrap(); | ||
| let ca_key = KeyPair::generate().unwrap(); | ||
| // Add the SAN we want to test the parsing for | ||
| params.subject_alt_names.push(ip_san.clone()); | ||
| // Because we're using a function for CA certificates | ||
| params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained); | ||
| // Serialize our cert that has our chosen san, so we can testing parsing/deserializing it. | ||
| let cert = params.self_signed(&ca_key).unwrap(); | ||
| let actual = CertificateParams::from_ca_cert_der(cert.der()).unwrap(); | ||
| assert!(actual.subject_alt_names.contains(&ip_san)); | ||
| } | ||
| #[cfg(feature = "x509-parser")] | ||
| mod test_key_identifier_from_ca { | ||
@@ -1516,3 +1471,4 @@ use super::*; | ||
| let params = CertificateParams::from_ca_cert_pem(ca_cert).unwrap(); | ||
| let ca_kp = KeyPair::from_pem(ca_key).unwrap(); | ||
| let ca = Issuer::from_ca_cert_pem(ca_cert, ca_kp).unwrap(); | ||
| let ca_ski = vec![ | ||
@@ -1524,11 +1480,8 @@ 0x97, 0xD4, 0x76, 0xA1, 0x9B, 0x1A, 0x71, 0x35, 0x2A, 0xC7, 0xF4, 0xA1, 0x84, 0x12, | ||
| assert_eq!( | ||
| KeyIdMethod::PreSpecified(ca_ski.clone()), | ||
| params.key_identifier_method | ||
| &KeyIdMethod::PreSpecified(ca_ski.clone()), | ||
| ca.key_identifier_method.as_ref() | ||
| ); | ||
| let ca_kp = KeyPair::from_pem(ca_key).unwrap(); | ||
| let ca_cert = params.self_signed(&ca_kp).unwrap(); | ||
| assert_eq!(&ca_ski, &ca_cert.key_identifier()); | ||
| let (_, x509_ca) = x509_parser::parse_x509_certificate(ca_cert.der()).unwrap(); | ||
| let ca_cert_der = CertificateDer::from_pem_slice(ca_cert.as_bytes()).unwrap(); | ||
| let (_, x509_ca) = x509_parser::parse_x509_certificate(ca_cert_der.as_ref()).unwrap(); | ||
| assert_eq!( | ||
@@ -1548,5 +1501,7 @@ &ca_ski, | ||
| let ee_key = KeyPair::generate().unwrap(); | ||
| let mut ee_params = CertificateParams::default(); | ||
| ee_params.use_authority_key_identifier_extension = true; | ||
| let ee_cert = ee_params.signed_by(&ee_key, &ca_cert, &ee_key).unwrap(); | ||
| let ee_params = CertificateParams { | ||
| use_authority_key_identifier_extension: true, | ||
| ..CertificateParams::default() | ||
| }; | ||
| let ee_cert = ee_params.signed_by(&ee_key, &ca).unwrap(); | ||
@@ -1553,0 +1508,0 @@ let (_, x509_ee) = x509_parser::parse_x509_certificate(ee_cert.der()).unwrap(); |
+26
-35
@@ -8,2 +8,3 @@ #[cfg(feature = "pem")] | ||
| use crate::key_pair::sign_der; | ||
| #[cfg(feature = "pem")] | ||
@@ -13,4 +14,4 @@ use crate::ENCODE_CONFIG; | ||
| oid, write_distinguished_name, write_dt_utc_or_generalized, | ||
| write_x509_authority_key_identifier, write_x509_extension, CertificateParams, Error, Issuer, | ||
| KeyIdMethod, KeyPair, KeyUsagePurpose, SerialNumber, | ||
| write_x509_authority_key_identifier, write_x509_extension, Error, Issuer, KeyIdMethod, | ||
| KeyUsagePurpose, SerialNumber, SigningKey, | ||
| }; | ||
@@ -29,5 +30,8 @@ | ||
| /// #[cfg(not(feature = "crypto"))] | ||
| /// impl RemoteKeyPair for MyKeyPair { | ||
| /// fn public_key(&self) -> &[u8] { &self.public_key } | ||
| /// impl SigningKey for MyKeyPair { | ||
| /// fn sign(&self, _: &[u8]) -> Result<Vec<u8>, rcgen::Error> { Ok(vec![]) } | ||
| /// } | ||
| /// #[cfg(not(feature = "crypto"))] | ||
| /// impl PublicKeyData for MyKeyPair { | ||
| /// fn der_bytes(&self) -> &[u8] { &self.public_key } | ||
| /// fn algorithm(&self) -> &'static SignatureAlgorithm { &PKCS_ED25519 } | ||
@@ -44,6 +48,5 @@ /// } | ||
| /// #[cfg(not(feature = "crypto"))] | ||
| /// let remote_key_pair = MyKeyPair { public_key: vec![] }; | ||
| /// #[cfg(not(feature = "crypto"))] | ||
| /// let key_pair = KeyPair::from_remote(Box::new(remote_key_pair)).unwrap(); | ||
| /// let issuer = issuer_params.self_signed(&key_pair).unwrap(); | ||
| /// let key_pair = MyKeyPair { public_key: vec![] }; | ||
| /// let issuer = Issuer::new(issuer_params, key_pair); | ||
| /// | ||
| /// // Describe a revoked certificate. | ||
@@ -67,7 +70,6 @@ /// let revoked_cert = RevokedCertParams{ | ||
| /// key_identifier_method: KeyIdMethod::PreSpecified(vec![]), | ||
| /// }.signed_by(&issuer, &key_pair).unwrap(); | ||
| /// }.signed_by(&issuer).unwrap(); | ||
| ///# } | ||
| #[derive(Debug)] | ||
| #[derive(Clone, Debug, PartialEq, Eq)] | ||
| pub struct CertificateRevocationList { | ||
| params: CertificateRevocationListParams, | ||
| der: CertificateRevocationListDer<'static>, | ||
@@ -77,7 +79,2 @@ } | ||
| impl CertificateRevocationList { | ||
| /// Returns the certificate revocation list (CRL) parameters. | ||
| pub fn params(&self) -> &CertificateRevocationListParams { | ||
| &self.params | ||
| } | ||
| /// Get the CRL in PEM encoded format. | ||
@@ -172,3 +169,3 @@ #[cfg(feature = "pem")] | ||
| /// Parameters used for certificate revocation list (CRL) generation | ||
| #[derive(Debug)] | ||
| #[derive(Clone, Debug, PartialEq, Eq)] | ||
| pub struct CertificateRevocationListParams { | ||
@@ -199,5 +196,4 @@ /// Issue date of the CRL. | ||
| pub fn signed_by( | ||
| self, | ||
| issuer: &impl AsRef<CertificateParams>, | ||
| issuer_key: &KeyPair, | ||
| &self, | ||
| issuer: &Issuer<'_, impl SigningKey>, | ||
| ) -> Result<CertificateRevocationList, Error> { | ||
@@ -208,9 +204,2 @@ if self.next_update.le(&self.this_update) { | ||
| let issuer = Issuer { | ||
| distinguished_name: &issuer.as_ref().distinguished_name, | ||
| key_identifier_method: &issuer.as_ref().key_identifier_method, | ||
| key_usages: &issuer.as_ref().key_usages, | ||
| key_pair: issuer_key, | ||
| }; | ||
| if !issuer.key_usages.is_empty() && !issuer.key_usages.contains(&KeyUsagePurpose::CrlSign) { | ||
@@ -222,8 +211,7 @@ return Err(Error::IssuerNotCrlSigner); | ||
| der: self.serialize_der(issuer)?.into(), | ||
| params: self, | ||
| }) | ||
| } | ||
| fn serialize_der(&self, issuer: Issuer) -> Result<Vec<u8>, Error> { | ||
| issuer.key_pair.sign_der(|writer| { | ||
| fn serialize_der(&self, issuer: &Issuer<'_, impl SigningKey>) -> Result<Vec<u8>, Error> { | ||
| sign_der(&*issuer.signing_key, |writer| { | ||
| // Write CRL version. | ||
@@ -244,3 +232,6 @@ // RFC 5280 §5.1.2.1: | ||
| // signatureAlgorithm field in the sequence CertificateList | ||
| issuer.key_pair.alg.write_alg_ident(writer.next()); | ||
| issuer | ||
| .signing_key | ||
| .algorithm() | ||
| .write_alg_ident(writer.next()); | ||
@@ -250,3 +241,3 @@ // Write issuer. | ||
| // The issuer field MUST contain a non-empty X.500 distinguished name (DN). | ||
| write_distinguished_name(writer.next(), &issuer.distinguished_name); | ||
| write_distinguished_name(writer.next(), issuer.distinguished_name.as_ref()); | ||
@@ -290,3 +281,3 @@ // Write thisUpdate date. | ||
| self.key_identifier_method | ||
| .derive(issuer.key_pair.public_key_der()), | ||
| .derive(issuer.signing_key.subject_public_key_info()), | ||
| ); | ||
@@ -320,3 +311,3 @@ | ||
| /// [issuing distribution point extension](https://datatracker.ietf.org/doc/html/rfc5280#section-5.2.5). | ||
| #[derive(Debug)] | ||
| #[derive(Clone, Debug, PartialEq, Eq)] | ||
| pub struct CrlIssuingDistributionPoint { | ||
@@ -364,3 +355,3 @@ /// The CRL's distribution point, containing a sequence of URIs the CRL can be retrieved from. | ||
| /// Parameters used for describing a revoked certificate included in a [`CertificateRevocationList`]. | ||
| #[derive(Debug)] | ||
| #[derive(Clone, Debug, PartialEq, Eq)] | ||
| pub struct RevokedCertParams { | ||
@@ -367,0 +358,0 @@ /// Serial number identifying the revoked certificate. |
+50
-30
@@ -10,4 +10,3 @@ use std::hash::Hash; | ||
| use crate::{ | ||
| key_pair::serialize_public_key_der, Certificate, CertificateParams, Error, Issuer, KeyPair, | ||
| PublicKeyData, SignatureAlgorithm, | ||
| Certificate, CertificateParams, Error, Issuer, PublicKeyData, SignatureAlgorithm, SigningKey, | ||
| }; | ||
@@ -18,3 +17,3 @@ #[cfg(feature = "x509-parser")] | ||
| /// A public key, extracted from a CSR | ||
| #[derive(Debug, PartialEq, Eq, Hash)] | ||
| #[derive(Clone, Debug, PartialEq, Eq, Hash)] | ||
| pub struct PublicKey { | ||
@@ -37,3 +36,3 @@ raw: Vec<u8>, | ||
| fn algorithm(&self) -> &SignatureAlgorithm { | ||
| fn algorithm(&self) -> &'static SignatureAlgorithm { | ||
| self.alg | ||
@@ -44,3 +43,3 @@ } | ||
| /// A certificate signing request (CSR) that can be encoded to PEM or DER. | ||
| #[derive(Debug)] | ||
| #[derive(Clone, Debug, PartialEq, Eq)] | ||
| pub struct CertificateSigningRequest { | ||
@@ -74,3 +73,3 @@ pub(crate) der: CertificateSigningRequestDer<'static>, | ||
| /// Parameters for a certificate signing request | ||
| #[derive(Debug)] | ||
| #[derive(Clone, Debug, PartialEq, Eq)] | ||
| pub struct CertificateSigningRequestParams { | ||
@@ -89,3 +88,3 @@ /// Parameters for the certificate to be signed. | ||
| pub fn from_pem(pem_str: &str) -> Result<Self, Error> { | ||
| let csr = pem::parse(pem_str).or(Err(Error::CouldNotParseCertificationRequest))?; | ||
| let csr = pem::parse(pem_str).map_err(|_| Error::CouldNotParseCertificationRequest)?; | ||
| Self::from_der(&csr.contents().into()) | ||
@@ -203,4 +202,3 @@ } | ||
| /// `issuer`, and the authority key identifier extension will be populated using the subject | ||
| /// public key of `issuer` (typically either a [`CertificateParams`] or | ||
| /// [`Certificate`]). It will be signed by `issuer_key`. | ||
| /// public key of `issuer`. It will be signed by `issuer_key`. | ||
| /// | ||
@@ -212,26 +210,48 @@ /// Note that no validation of the `issuer` certificate is performed. Rcgen will not require | ||
| /// [`Certificate::pem`]. | ||
| pub fn signed_by( | ||
| self, | ||
| issuer: &impl AsRef<CertificateParams>, | ||
| issuer_key: &KeyPair, | ||
| ) -> Result<Certificate, Error> { | ||
| let issuer = Issuer { | ||
| distinguished_name: &issuer.as_ref().distinguished_name, | ||
| key_identifier_method: &issuer.as_ref().key_identifier_method, | ||
| key_usages: &issuer.as_ref().key_usages, | ||
| key_pair: issuer_key, | ||
| }; | ||
| let der = self | ||
| .params | ||
| .serialize_der_with_signer(&self.public_key, issuer)?; | ||
| let subject_public_key_info = yasna::construct_der(|writer| { | ||
| serialize_public_key_der(&self.public_key, writer); | ||
| }); | ||
| pub fn signed_by(&self, issuer: &Issuer<impl SigningKey>) -> Result<Certificate, Error> { | ||
| Ok(Certificate { | ||
| params: self.params, | ||
| subject_public_key_info, | ||
| der, | ||
| der: self | ||
| .params | ||
| .serialize_der_with_signer(&self.public_key, issuer)?, | ||
| }) | ||
| } | ||
| } | ||
| #[cfg(all(test, feature = "x509-parser"))] | ||
| mod tests { | ||
| use crate::{CertificateParams, ExtendedKeyUsagePurpose, KeyPair, KeyUsagePurpose}; | ||
| use x509_parser::certification_request::X509CertificationRequest; | ||
| use x509_parser::prelude::{FromDer, ParsedExtension}; | ||
| #[test] | ||
| fn dont_write_sans_extension_if_no_sans_are_present() { | ||
| let mut params = CertificateParams::default(); | ||
| params.key_usages.push(KeyUsagePurpose::DigitalSignature); | ||
| let key_pair = KeyPair::generate().unwrap(); | ||
| let csr = params.serialize_request(&key_pair).unwrap(); | ||
| let (_, parsed_csr) = X509CertificationRequest::from_der(csr.der()).unwrap(); | ||
| assert!(!parsed_csr | ||
| .requested_extensions() | ||
| .unwrap() | ||
| .any(|ext| matches!(ext, ParsedExtension::SubjectAlternativeName(_)))); | ||
| } | ||
| #[test] | ||
| fn write_extension_request_if_ekus_are_present() { | ||
| let mut params = CertificateParams::default(); | ||
| params | ||
| .extended_key_usages | ||
| .push(ExtendedKeyUsagePurpose::ClientAuth); | ||
| let key_pair = KeyPair::generate().unwrap(); | ||
| let csr = params.serialize_request(&key_pair).unwrap(); | ||
| let (_, parsed_csr) = X509CertificationRequest::from_der(csr.der()).unwrap(); | ||
| let requested_extensions = parsed_csr | ||
| .requested_extensions() | ||
| .unwrap() | ||
| .collect::<Vec<_>>(); | ||
| assert!(matches!( | ||
| requested_extensions.first().unwrap(), | ||
| ParsedExtension::ExtendedKeyUsage(_) | ||
| )); | ||
| } | ||
| } |
+10
-10
| use std::fmt; | ||
| #[derive(Debug, PartialEq, Eq)] | ||
| #[derive(Clone, Debug, PartialEq, Eq)] | ||
| #[non_exhaustive] | ||
@@ -66,3 +66,3 @@ /// The error type of the rcgen crate | ||
| InvalidNameType => write!(f, "Invalid subject alternative name type")?, | ||
| InvalidAsn1String(e) => write!(f, "{}", e)?, | ||
| InvalidAsn1String(e) => write!(f, "{e}")?, | ||
| InvalidIpAddressOctetLength(actual) => { | ||
@@ -84,3 +84,3 @@ write!(f, "Invalid IP address octet length of {actual} bytes")? | ||
| RingUnspecified => write!(f, "Unspecified ring error")?, | ||
| RingKeyRejected(e) => write!(f, "Key rejected by ring: {}", e)?, | ||
| RingKeyRejected(e) => write!(f, "Key rejected by ring: {e}")?, | ||
@@ -90,3 +90,3 @@ Time => write!(f, "Time error")?, | ||
| #[cfg(feature = "pem")] | ||
| PemError(e) => write!(f, "PEM error: {}", e)?, | ||
| PemError(e) => write!(f, "PEM error: {e}")?, | ||
| UnsupportedInCsr => write!(f, "Certificate parameter unsupported in CSR")?, | ||
@@ -110,3 +110,3 @@ InvalidCrlNextUpdate => write!(f, "Invalid CRL next update parameter")?, | ||
| /// Invalid ASN.1 string type | ||
| #[derive(Debug, PartialEq, Eq)] | ||
| #[derive(Clone, Debug, PartialEq, Eq)] | ||
| #[non_exhaustive] | ||
@@ -130,7 +130,7 @@ pub enum InvalidAsn1String { | ||
| match self { | ||
| PrintableString(s) => write!(f, "Invalid PrintableString: '{}'", s)?, | ||
| Ia5String(s) => write!(f, "Invalid IA5String: '{}'", s)?, | ||
| BmpString(s) => write!(f, "Invalid BMPString: '{}'", s)?, | ||
| UniversalString(s) => write!(f, "Invalid UniversalString: '{}'", s)?, | ||
| TeletexString(s) => write!(f, "Invalid TeletexString: '{}'", s)?, | ||
| PrintableString(s) => write!(f, "Invalid PrintableString: '{s}'")?, | ||
| Ia5String(s) => write!(f, "Invalid IA5String: '{s}'")?, | ||
| BmpString(s) => write!(f, "Invalid BMPString: '{s}'")?, | ||
| UniversalString(s) => write!(f, "Invalid UniversalString: '{s}'")?, | ||
| TeletexString(s) => write!(f, "Invalid TeletexString: '{s}'")?, | ||
| }; | ||
@@ -137,0 +137,0 @@ Ok(()) |
+79
-137
@@ -0,1 +1,2 @@ | ||
| #[cfg(feature = "crypto")] | ||
| use std::fmt; | ||
@@ -32,26 +33,19 @@ | ||
| #[allow(clippy::large_enum_variant)] | ||
| #[cfg(feature = "crypto")] | ||
| pub(crate) enum KeyPairKind { | ||
| /// A Ecdsa key pair | ||
| #[cfg(feature = "crypto")] | ||
| Ec(EcdsaKeyPair), | ||
| /// A Ed25519 key pair | ||
| #[cfg(feature = "crypto")] | ||
| Ed(Ed25519KeyPair), | ||
| /// A RSA key pair | ||
| #[cfg(feature = "crypto")] | ||
| Rsa(RsaKeyPair, &'static dyn RsaEncoding), | ||
| /// A remote key pair | ||
| Remote(Box<dyn RemoteKeyPair + Send + Sync>), | ||
| } | ||
| #[cfg(feature = "crypto")] | ||
| impl fmt::Debug for KeyPairKind { | ||
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||
| match self { | ||
| #[cfg(feature = "crypto")] | ||
| Self::Ec(key_pair) => write!(f, "{:?}", key_pair), | ||
| #[cfg(feature = "crypto")] | ||
| Self::Ed(key_pair) => write!(f, "{:?}", key_pair), | ||
| #[cfg(feature = "crypto")] | ||
| Self::Rsa(key_pair, _) => write!(f, "{:?}", key_pair), | ||
| Self::Remote(_) => write!(f, "Box<dyn RemotePrivateKey>"), | ||
| Self::Ec(key_pair) => write!(f, "{key_pair:?}"), | ||
| Self::Ed(key_pair) => write!(f, "{key_pair:?}"), | ||
| Self::Rsa(key_pair, _) => write!(f, "{key_pair:?}"), | ||
| } | ||
@@ -68,2 +62,3 @@ } | ||
| /// and conversion between the formats. | ||
| #[cfg(feature = "crypto")] | ||
| pub struct KeyPair { | ||
@@ -75,2 +70,3 @@ pub(crate) kind: KeyPairKind, | ||
| #[cfg(feature = "crypto")] | ||
| impl fmt::Debug for KeyPair { | ||
@@ -86,2 +82,3 @@ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
| #[cfg(feature = "crypto")] | ||
| impl KeyPair { | ||
@@ -194,11 +191,2 @@ /// Generate a new random [`PKCS_ECDSA_P256_SHA256`] key pair | ||
| /// Obtains the key pair from a raw public key and a remote private key | ||
| pub fn from_remote(key_pair: Box<dyn RemoteKeyPair + Send + Sync>) -> Result<Self, Error> { | ||
| Ok(Self { | ||
| alg: key_pair.algorithm(), | ||
| kind: KeyPairKind::Remote(key_pair), | ||
| serialized_der: Vec::new(), | ||
| }) | ||
| } | ||
| /// Obtains the key pair from a DER formatted key | ||
@@ -416,63 +404,2 @@ /// using the specified [`SignatureAlgorithm`] | ||
| pub(crate) fn sign_der( | ||
| &self, | ||
| f: impl FnOnce(&mut DERWriterSeq<'_>) -> Result<(), Error>, | ||
| ) -> Result<Vec<u8>, Error> { | ||
| yasna::try_construct_der(|writer| { | ||
| writer.write_sequence(|writer| { | ||
| let data = yasna::try_construct_der(|writer| writer.write_sequence(f))?; | ||
| writer.next().write_der(&data); | ||
| // Write signatureAlgorithm | ||
| self.alg.write_alg_ident(writer.next()); | ||
| // Write signature | ||
| self.sign(&data, writer.next())?; | ||
| Ok(()) | ||
| }) | ||
| }) | ||
| } | ||
| pub(crate) fn sign(&self, msg: &[u8], writer: DERWriter) -> Result<(), Error> { | ||
| match &self.kind { | ||
| #[cfg(feature = "crypto")] | ||
| KeyPairKind::Ec(kp) => { | ||
| let system_random = SystemRandom::new(); | ||
| let signature = kp.sign(&system_random, msg)._err()?; | ||
| let sig = &signature.as_ref(); | ||
| writer.write_bitvec_bytes(sig, &sig.len() * 8); | ||
| }, | ||
| #[cfg(feature = "crypto")] | ||
| KeyPairKind::Ed(kp) => { | ||
| let signature = kp.sign(msg); | ||
| let sig = &signature.as_ref(); | ||
| writer.write_bitvec_bytes(sig, &sig.len() * 8); | ||
| }, | ||
| #[cfg(feature = "crypto")] | ||
| KeyPairKind::Rsa(kp, padding_alg) => { | ||
| let system_random = SystemRandom::new(); | ||
| let mut signature = vec![0; rsa_key_pair_public_modulus_len(kp)]; | ||
| kp.sign(*padding_alg, &system_random, msg, &mut signature) | ||
| ._err()?; | ||
| let sig = &signature.as_ref(); | ||
| writer.write_bitvec_bytes(sig, &sig.len() * 8); | ||
| }, | ||
| KeyPairKind::Remote(kp) => { | ||
| let signature = kp.sign(msg)?; | ||
| writer.write_bitvec_bytes(&signature, &signature.len() * 8); | ||
| }, | ||
| } | ||
| Ok(()) | ||
| } | ||
| /// Return the key pair's public key in DER format | ||
| /// | ||
| /// The key is formatted according to the SubjectPublicKeyInfo struct of | ||
| /// X.509. | ||
| /// See [RFC 5280 section 4.1](https://tools.ietf.org/html/rfc5280#section-4.1). | ||
| pub fn public_key_der(&self) -> Vec<u8> { | ||
| yasna::construct_der(|writer| serialize_public_key_der(self, writer)) | ||
| } | ||
| /// Return the key pair's public key in PEM format | ||
@@ -483,3 +410,3 @@ /// | ||
| pub fn public_key_pem(&self) -> String { | ||
| let contents = self.public_key_der(); | ||
| let contents = self.subject_public_key_info(); | ||
| let p = Pem::new("PUBLIC KEY", contents); | ||
@@ -490,10 +417,3 @@ pem::encode_config(&p, ENCODE_CONFIG) | ||
| /// Serializes the key pair (including the private key) in PKCS#8 format in DER | ||
| /// | ||
| /// Panics if called on a remote key pair. | ||
| pub fn serialize_der(&self) -> Vec<u8> { | ||
| #[cfg_attr(not(feature = "crypto"), allow(irrefutable_let_patterns))] | ||
| if let KeyPairKind::Remote(_) = self.kind { | ||
| panic!("Serializing a remote key pair is not supported") | ||
| } | ||
| self.serialized_der.clone() | ||
@@ -504,23 +424,6 @@ } | ||
| /// in PKCS#8 format in DER | ||
| /// | ||
| /// Panics if called on a remote key pair. | ||
| pub fn serialized_der(&self) -> &[u8] { | ||
| #[cfg_attr(not(feature = "crypto"), allow(irrefutable_let_patterns))] | ||
| if let KeyPairKind::Remote(_) = self.kind { | ||
| panic!("Serializing a remote key pair is not supported") | ||
| } | ||
| &self.serialized_der | ||
| } | ||
| /// Access the remote key pair if it is a remote one | ||
| pub fn as_remote(&self) -> Option<&(dyn RemoteKeyPair + Send + Sync)> { | ||
| #[cfg_attr(not(feature = "crypto"), allow(irrefutable_let_patterns))] | ||
| if let KeyPairKind::Remote(remote) = &self.kind { | ||
| Some(remote.as_ref()) | ||
| } else { | ||
| None | ||
| } | ||
| } | ||
| /// Serializes the key pair (including the private key) in PKCS#8 format in PEM | ||
@@ -536,2 +439,38 @@ #[cfg(feature = "pem")] | ||
| #[cfg(feature = "crypto")] | ||
| impl SigningKey for KeyPair { | ||
| fn sign(&self, msg: &[u8]) -> Result<Vec<u8>, Error> { | ||
| Ok(match &self.kind { | ||
| KeyPairKind::Ec(kp) => { | ||
| let system_random = SystemRandom::new(); | ||
| let signature = kp.sign(&system_random, msg)._err()?; | ||
| signature.as_ref().to_owned() | ||
| }, | ||
| KeyPairKind::Ed(kp) => kp.sign(msg).as_ref().to_owned(), | ||
| KeyPairKind::Rsa(kp, padding_alg) => { | ||
| let system_random = SystemRandom::new(); | ||
| let mut signature = vec![0; rsa_key_pair_public_modulus_len(kp)]; | ||
| kp.sign(*padding_alg, &system_random, msg, &mut signature) | ||
| ._err()?; | ||
| signature | ||
| }, | ||
| }) | ||
| } | ||
| } | ||
| #[cfg(feature = "crypto")] | ||
| impl PublicKeyData for KeyPair { | ||
| fn der_bytes(&self) -> &[u8] { | ||
| match &self.kind { | ||
| KeyPairKind::Ec(kp) => kp.public_key().as_ref(), | ||
| KeyPairKind::Ed(kp) => kp.public_key().as_ref(), | ||
| KeyPairKind::Rsa(kp, _) => kp.public_key().as_ref(), | ||
| } | ||
| } | ||
| fn algorithm(&self) -> &'static SignatureAlgorithm { | ||
| self.alg | ||
| } | ||
| } | ||
| #[cfg(feature = "crypto")] | ||
| impl TryFrom<&[u8]> for KeyPair { | ||
@@ -658,33 +597,28 @@ type Error = Error; | ||
| impl PublicKeyData for KeyPair { | ||
| fn der_bytes(&self) -> &[u8] { | ||
| match &self.kind { | ||
| #[cfg(feature = "crypto")] | ||
| KeyPairKind::Ec(kp) => kp.public_key().as_ref(), | ||
| #[cfg(feature = "crypto")] | ||
| KeyPairKind::Ed(kp) => kp.public_key().as_ref(), | ||
| #[cfg(feature = "crypto")] | ||
| KeyPairKind::Rsa(kp, _) => kp.public_key().as_ref(), | ||
| KeyPairKind::Remote(kp) => kp.public_key(), | ||
| } | ||
| } | ||
| pub(crate) fn sign_der( | ||
| key: &impl SigningKey, | ||
| f: impl FnOnce(&mut DERWriterSeq<'_>) -> Result<(), Error>, | ||
| ) -> Result<Vec<u8>, Error> { | ||
| yasna::try_construct_der(|writer| { | ||
| writer.write_sequence(|writer| { | ||
| let data = yasna::try_construct_der(|writer| writer.write_sequence(f))?; | ||
| writer.next().write_der(&data); | ||
| fn algorithm(&self) -> &SignatureAlgorithm { | ||
| self.alg | ||
| } | ||
| // Write signatureAlgorithm | ||
| key.algorithm().write_alg_ident(writer.next()); | ||
| // Write signature | ||
| let sig = key.sign(&data)?; | ||
| let writer = writer.next(); | ||
| writer.write_bitvec_bytes(&sig, sig.len() * 8); | ||
| Ok(()) | ||
| }) | ||
| }) | ||
| } | ||
| /// A private key that is not directly accessible, but can be used to sign messages | ||
| /// | ||
| /// Trait objects based on this trait can be passed to the [`KeyPair::from_remote`] function for generating certificates | ||
| /// from a remote and raw private key, for example an HSM. | ||
| pub trait RemoteKeyPair { | ||
| /// Returns the public key of this key pair in the binary format as in [`KeyPair::public_key_raw`] | ||
| fn public_key(&self) -> &[u8]; | ||
| /// A key that can be used to sign messages | ||
| pub trait SigningKey: PublicKeyData { | ||
| /// Signs `msg` using the selected algorithm | ||
| fn sign(&self, msg: &[u8]) -> Result<Vec<u8>, Error>; | ||
| /// Reveals the algorithm to be used when calling `sign()` | ||
| fn algorithm(&self) -> &'static SignatureAlgorithm; | ||
| } | ||
@@ -770,3 +704,3 @@ | ||
| fn algorithm(&self) -> &SignatureAlgorithm { | ||
| fn algorithm(&self) -> &'static SignatureAlgorithm { | ||
| self.alg | ||
@@ -778,2 +712,10 @@ } | ||
| pub trait PublicKeyData { | ||
| /// The public key data in DER format | ||
| /// | ||
| /// The key is formatted according to the X.509 SubjectPublicKeyInfo struct. | ||
| /// See [RFC 5280 section 4.1](https://tools.ietf.org/html/rfc5280#section-4.1). | ||
| fn subject_public_key_info(&self) -> Vec<u8> { | ||
| yasna::construct_der(|writer| serialize_public_key_der(self, writer)) | ||
| } | ||
| /// The public key in DER format | ||
@@ -783,6 +725,6 @@ fn der_bytes(&self) -> &[u8]; | ||
| /// The algorithm used by the key pair | ||
| fn algorithm(&self) -> &SignatureAlgorithm; | ||
| fn algorithm(&self) -> &'static SignatureAlgorithm; | ||
| } | ||
| pub(crate) fn serialize_public_key_der(key: &impl PublicKeyData, writer: DERWriter) { | ||
| pub(crate) fn serialize_public_key_der(key: &(impl PublicKeyData + ?Sized), writer: DERWriter) { | ||
| writer.write_sequence(|writer| { | ||
@@ -818,3 +760,3 @@ key.algorithm().write_oids_sign_alg(writer.next()); | ||
| let pem = kp.public_key_pem(); | ||
| let der = kp.public_key_der(); | ||
| let der = kp.subject_public_key_info(); | ||
@@ -821,0 +763,0 @@ let pkd_pem = SubjectPublicKeyInfo::from_pem(&pem).expect("from pem"); |
+189
-25
@@ -23,5 +23,5 @@ /*! | ||
| let CertifiedKey { cert, key_pair } = generate_simple_self_signed(subject_alt_names).unwrap(); | ||
| let CertifiedKey { cert, signing_key } = generate_simple_self_signed(subject_alt_names).unwrap(); | ||
| println!("{}", cert.pem()); | ||
| println!("{}", key_pair.serialize_pem()); | ||
| println!("{}", signing_key.serialize_pem()); | ||
| # } | ||
@@ -33,6 +33,6 @@ ```"## | ||
| #![deny(missing_docs)] | ||
| #![allow(clippy::complexity, clippy::style, clippy::pedantic)] | ||
| #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] | ||
| #![warn(unreachable_pub)] | ||
| use std::borrow::Cow; | ||
| use std::collections::HashMap; | ||
@@ -44,3 +44,6 @@ use std::fmt; | ||
| use std::net::{Ipv4Addr, Ipv6Addr}; | ||
| use std::ops::Deref; | ||
| #[cfg(feature = "x509-parser")] | ||
| use pki_types::CertificateDer; | ||
| use time::{OffsetDateTime, Time}; | ||
@@ -53,2 +56,4 @@ use yasna::models::ObjectIdentifier; | ||
| use crate::string::{BmpString, Ia5String, PrintableString, TeletexString, UniversalString}; | ||
| pub use certificate::{ | ||
@@ -64,6 +69,8 @@ date_time_ymd, Attribute, BasicConstraints, Certificate, CertificateParams, CidrSubnet, | ||
| pub use error::{Error, InvalidAsn1String}; | ||
| #[cfg(feature = "crypto")] | ||
| pub use key_pair::KeyPair; | ||
| pub use key_pair::PublicKeyData; | ||
| #[cfg(all(feature = "crypto", feature = "aws_lc_rs"))] | ||
| pub use key_pair::RsaKeySize; | ||
| pub use key_pair::{KeyPair, RemoteKeyPair, SubjectPublicKeyInfo}; | ||
| pub use key_pair::{SigningKey, SubjectPublicKeyInfo}; | ||
| #[cfg(feature = "crypto")] | ||
@@ -73,3 +80,2 @@ use ring_like::digest; | ||
| pub use sign_algo::SignatureAlgorithm; | ||
| pub use string_types::*; | ||
@@ -84,3 +90,3 @@ mod certificate; | ||
| mod sign_algo; | ||
| mod string_types; | ||
| pub mod string; | ||
@@ -94,7 +100,8 @@ /// Type-alias for the old name of [`Error`]. | ||
| /// An issued certificate, together with the subject keypair. | ||
| pub struct CertifiedKey { | ||
| #[derive(PartialEq, Eq)] | ||
| pub struct CertifiedKey<S: SigningKey> { | ||
| /// An issued certificate. | ||
| pub cert: Certificate, | ||
| /// The certificate's subject key pair. | ||
| pub key_pair: KeyPair, | ||
| /// The certificate's subject signing key. | ||
| pub signing_key: S, | ||
| } | ||
@@ -123,7 +130,7 @@ | ||
| let CertifiedKey { cert, key_pair } = generate_simple_self_signed(subject_alt_names).unwrap(); | ||
| let CertifiedKey { cert, signing_key } = generate_simple_self_signed(subject_alt_names).unwrap(); | ||
| // The certificate is now valid for localhost and the domain "hello.world.example" | ||
| println!("{}", cert.pem()); | ||
| println!("{}", key_pair.serialize_pem()); | ||
| println!("{}", signing_key.serialize_pem()); | ||
| # } | ||
@@ -135,15 +142,118 @@ ``` | ||
| subject_alt_names: impl Into<Vec<String>>, | ||
| ) -> Result<CertifiedKey, Error> { | ||
| let key_pair = KeyPair::generate()?; | ||
| let cert = CertificateParams::new(subject_alt_names)?.self_signed(&key_pair)?; | ||
| Ok(CertifiedKey { cert, key_pair }) | ||
| ) -> Result<CertifiedKey<KeyPair>, Error> { | ||
| let signing_key = KeyPair::generate()?; | ||
| let cert = CertificateParams::new(subject_alt_names)?.self_signed(&signing_key)?; | ||
| Ok(CertifiedKey { cert, signing_key }) | ||
| } | ||
| struct Issuer<'a> { | ||
| distinguished_name: &'a DistinguishedName, | ||
| key_identifier_method: &'a KeyIdMethod, | ||
| key_usages: &'a [KeyUsagePurpose], | ||
| key_pair: &'a KeyPair, | ||
| /// An issuer that can sign certificates. | ||
| /// | ||
| /// Encapsulates the distinguished name, key identifier method, key usages and signing key | ||
| /// of the issuing certificate. | ||
| pub struct Issuer<'a, S> { | ||
| distinguished_name: Cow<'a, DistinguishedName>, | ||
| key_identifier_method: Cow<'a, KeyIdMethod>, | ||
| key_usages: Cow<'a, [KeyUsagePurpose]>, | ||
| signing_key: MaybeOwned<'a, S>, | ||
| } | ||
| impl<'a, S: SigningKey> Issuer<'a, S> { | ||
| /// Create a new issuer from the given parameters and signing key. | ||
| pub fn new(params: CertificateParams, signing_key: S) -> Self { | ||
| Self { | ||
| distinguished_name: Cow::Owned(params.distinguished_name), | ||
| key_identifier_method: Cow::Owned(params.key_identifier_method), | ||
| key_usages: Cow::Owned(params.key_usages), | ||
| signing_key: MaybeOwned::Owned(signing_key), | ||
| } | ||
| } | ||
| fn from_params(params: &'a CertificateParams, signing_key: &'a S) -> Self { | ||
| Self { | ||
| distinguished_name: Cow::Borrowed(¶ms.distinguished_name), | ||
| key_identifier_method: Cow::Borrowed(¶ms.key_identifier_method), | ||
| key_usages: Cow::Borrowed(¶ms.key_usages), | ||
| signing_key: MaybeOwned::Borrowed(signing_key), | ||
| } | ||
| } | ||
| /// Parses an existing CA certificate from the ASCII PEM format. | ||
| /// | ||
| /// See [`from_ca_cert_der`](Self::from_ca_cert_der) for more details. | ||
| #[cfg(all(feature = "pem", feature = "x509-parser"))] | ||
| pub fn from_ca_cert_pem(pem_str: &str, signing_key: S) -> Result<Self, Error> { | ||
| let certificate = pem::parse(pem_str).map_err(|_| Error::CouldNotParseCertificate)?; | ||
| Self::from_ca_cert_der(&certificate.contents().into(), signing_key) | ||
| } | ||
| /// Parses an existing CA certificate from the DER format. | ||
| /// | ||
| /// This function assumes the provided certificate is a CA. It will not check | ||
| /// for the presence of the `BasicConstraints` extension, or perform any other | ||
| /// validation. | ||
| /// | ||
| /// If you already have a byte slice containing DER, it can trivially be converted into | ||
| /// [`CertificateDer`] using the [`Into`] trait. | ||
| #[cfg(feature = "x509-parser")] | ||
| pub fn from_ca_cert_der(ca_cert: &CertificateDer<'_>, signing_key: S) -> Result<Self, Error> { | ||
| let (_remainder, x509) = x509_parser::parse_x509_certificate(ca_cert) | ||
| .map_err(|_| Error::CouldNotParseCertificate)?; | ||
| Ok(Self { | ||
| key_usages: Cow::Owned(KeyUsagePurpose::from_x509(&x509)?), | ||
| key_identifier_method: Cow::Owned(KeyIdMethod::from_x509(&x509)?), | ||
| distinguished_name: Cow::Owned(DistinguishedName::from_name( | ||
| &x509.tbs_certificate.subject, | ||
| )?), | ||
| signing_key: MaybeOwned::Owned(signing_key), | ||
| }) | ||
| } | ||
| /// Allowed key usages for this issuer. | ||
| pub fn key_usages(&self) -> &[KeyUsagePurpose] { | ||
| &self.key_usages | ||
| } | ||
| /// Yield a reference to the signing key. | ||
| pub fn key(&self) -> &S { | ||
| &self.signing_key | ||
| } | ||
| } | ||
| impl<'a, S: SigningKey> fmt::Debug for Issuer<'a, S> { | ||
| /// Formats the issuer information without revealing the key pair. | ||
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||
| // The key pair is omitted from the debug output as it contains secret information. | ||
| let Issuer { | ||
| distinguished_name, | ||
| key_identifier_method, | ||
| key_usages, | ||
| signing_key: _, | ||
| } = self; | ||
| f.debug_struct("Issuer") | ||
| .field("distinguished_name", distinguished_name) | ||
| .field("key_identifier_method", key_identifier_method) | ||
| .field("key_usages", key_usages) | ||
| .field("signing_key", &"[elided]") | ||
| .finish() | ||
| } | ||
| } | ||
| enum MaybeOwned<'a, T> { | ||
| Owned(T), | ||
| Borrowed(&'a T), | ||
| } | ||
| impl<T> Deref for MaybeOwned<'_, T> { | ||
| type Target = T; | ||
| fn deref(&self) -> &Self::Target { | ||
| match self { | ||
| MaybeOwned::Owned(t) => t, | ||
| MaybeOwned::Borrowed(t) => t, | ||
| } | ||
| } | ||
| } | ||
| // https://tools.ietf.org/html/rfc5280#section-4.1.1 | ||
@@ -176,2 +286,22 @@ | ||
| impl SanType { | ||
| #[cfg(all(test, feature = "x509-parser"))] | ||
| fn from_x509(x509: &x509_parser::certificate::X509Certificate<'_>) -> Result<Vec<Self>, Error> { | ||
| let sans = x509 | ||
| .subject_alternative_name() | ||
| .map_err(|_| Error::CouldNotParseCertificate)? | ||
| .map(|ext| &ext.value.general_names); | ||
| let Some(sans) = sans else { | ||
| return Ok(Vec::new()); | ||
| }; | ||
| let mut subject_alt_names = Vec::with_capacity(sans.len()); | ||
| for san in sans { | ||
| subject_alt_names.push(Self::try_from_general(san)?); | ||
| } | ||
| Ok(subject_alt_names) | ||
| } | ||
| } | ||
| /// An `OtherName` value, defined in [RFC 5280§4.1.2.4]. | ||
@@ -410,2 +540,3 @@ /// | ||
| */ | ||
| #[derive(Clone, Debug)] | ||
| pub struct DistinguishedNameIterator<'a> { | ||
@@ -450,5 +581,16 @@ distinguished_name: &'a DistinguishedName, | ||
| impl KeyUsagePurpose { | ||
| #[cfg(feature = "x509-parser")] | ||
| fn from_x509(x509: &x509_parser::certificate::X509Certificate<'_>) -> Result<Vec<Self>, Error> { | ||
| let key_usage = x509 | ||
| .key_usage() | ||
| .map_err(|_| Error::CouldNotParseCertificate)? | ||
| .map(|ext| ext.value); | ||
| // This x509 parser stores flags in reversed bit BIT STRING order | ||
| let flags = key_usage.map_or(0u16, |k| k.flags).reverse_bits(); | ||
| Ok(Self::from_u16(flags)) | ||
| } | ||
| /// Encode a key usage as the value of a BIT STRING as defined by RFC 5280. | ||
| /// [`u16`] is sufficient to encode the largest possible key usage value (two bytes). | ||
| fn to_u16(&self) -> u16 { | ||
| fn to_u16(self) -> u16 { | ||
| const FLAG: u16 = 0b1000_0000_0000_0000; | ||
@@ -521,2 +663,24 @@ FLAG >> match self { | ||
| impl KeyIdMethod { | ||
| #[cfg(feature = "x509-parser")] | ||
| fn from_x509(x509: &x509_parser::certificate::X509Certificate<'_>) -> Result<Self, Error> { | ||
| let key_identifier_method = | ||
| x509.iter_extensions() | ||
| .find_map(|ext| match ext.parsed_extension() { | ||
| x509_parser::extensions::ParsedExtension::SubjectKeyIdentifier(key_id) => { | ||
| Some(KeyIdMethod::PreSpecified(key_id.0.into())) | ||
| }, | ||
| _ => None, | ||
| }); | ||
| Ok(match key_identifier_method { | ||
| Some(method) => method, | ||
| None => { | ||
| #[cfg(not(feature = "crypto"))] | ||
| return Err(Error::UnsupportedSignatureAlgorithm); | ||
| #[cfg(feature = "crypto")] | ||
| KeyIdMethod::Sha256 | ||
| }, | ||
| }) | ||
| } | ||
| /// Derive a key identifier for the provided subject public key info using the key ID method. | ||
@@ -531,2 +695,3 @@ /// | ||
| pub(crate) fn derive(&self, subject_public_key_info: impl AsRef<[u8]>) -> Vec<u8> { | ||
| #[cfg_attr(not(feature = "crypto"), expect(clippy::let_unit_value))] | ||
| let digest_method = match &self { | ||
@@ -684,2 +849,3 @@ #[cfg(feature = "crypto")] | ||
| #[expect(clippy::len_without_is_empty)] | ||
| impl SerialNumber { | ||
@@ -705,3 +871,3 @@ /// Create a serial number from the given byte slice. | ||
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { | ||
| let hex: Vec<_> = self.inner.iter().map(|b| format!("{:02x}", b)).collect(); | ||
| let hex: Vec<_> = self.inner.iter().map(|b| format!("{b:02x}")).collect(); | ||
| write!(f, "{}", hex.join(":")) | ||
@@ -788,5 +954,3 @@ } | ||
| i == j, | ||
| "Algorighm relationship mismatch for algorithm index pair {} and {}", | ||
| i, | ||
| j | ||
| "Algorithm relationship mismatch for algorithm index pair {i} and {j}" | ||
| ); | ||
@@ -793,0 +957,0 @@ } |
+2
-2
@@ -24,3 +24,3 @@ #[cfg(all(feature = "crypto", feature = "aws_lc_rs"))] | ||
| { | ||
| Ok(signature::EcdsaKeyPair::from_pkcs8(alg, pkcs8)._err()?) | ||
| signature::EcdsaKeyPair::from_pkcs8(alg, pkcs8)._err() | ||
| } | ||
@@ -34,3 +34,3 @@ } | ||
| ) -> Result<signature::EcdsaKeyPair, Error> { | ||
| Ok(signature::EcdsaKeyPair::from_private_key_der(alg, key)._err()?) | ||
| signature::EcdsaKeyPair::from_private_key_der(alg, key)._err() | ||
| } | ||
@@ -37,0 +37,0 @@ |
+10
-8
@@ -13,2 +13,3 @@ use std::fmt; | ||
| #[cfg(feature = "crypto")] | ||
| #[derive(Clone, Copy, Debug)] | ||
| pub(crate) enum SignAlgo { | ||
@@ -20,3 +21,3 @@ EcDsa(&'static EcdsaSigningAlgorithm), | ||
| #[derive(PartialEq, Eq, Hash)] | ||
| #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] | ||
| pub(crate) enum SignatureAlgorithmParams { | ||
@@ -35,2 +36,3 @@ /// Omit the parameters | ||
| /// Signature algorithm type | ||
| #[derive(Clone)] | ||
| pub struct SignatureAlgorithm { | ||
@@ -123,3 +125,3 @@ oids_sign_alg: &'static [&'static [u64]], | ||
| pub static PKCS_RSA_SHA256: SignatureAlgorithm = SignatureAlgorithm { | ||
| oids_sign_alg: &[&RSA_ENCRYPTION], | ||
| oids_sign_alg: &[RSA_ENCRYPTION], | ||
| #[cfg(feature = "crypto")] | ||
@@ -134,3 +136,3 @@ sign_alg: SignAlgo::Rsa(&signature::RSA_PKCS1_SHA256), | ||
| pub static PKCS_RSA_SHA384: SignatureAlgorithm = SignatureAlgorithm { | ||
| oids_sign_alg: &[&RSA_ENCRYPTION], | ||
| oids_sign_alg: &[RSA_ENCRYPTION], | ||
| #[cfg(feature = "crypto")] | ||
@@ -145,3 +147,3 @@ sign_alg: SignAlgo::Rsa(&signature::RSA_PKCS1_SHA384), | ||
| pub static PKCS_RSA_SHA512: SignatureAlgorithm = SignatureAlgorithm { | ||
| oids_sign_alg: &[&RSA_ENCRYPTION], | ||
| oids_sign_alg: &[RSA_ENCRYPTION], | ||
| #[cfg(feature = "crypto")] | ||
@@ -163,3 +165,3 @@ sign_alg: SignAlgo::Rsa(&signature::RSA_PKCS1_SHA512), | ||
| // to use ID-RSASSA-PSS if possible. | ||
| oids_sign_alg: &[&RSASSA_PSS], | ||
| oids_sign_alg: &[RSASSA_PSS], | ||
| #[cfg(feature = "crypto")] | ||
@@ -178,3 +180,3 @@ sign_alg: SignAlgo::Rsa(&signature::RSA_PSS_SHA256), | ||
| pub static PKCS_ECDSA_P256_SHA256: SignatureAlgorithm = SignatureAlgorithm { | ||
| oids_sign_alg: &[&EC_PUBLIC_KEY, &EC_SECP_256_R1], | ||
| oids_sign_alg: &[EC_PUBLIC_KEY, EC_SECP_256_R1], | ||
| #[cfg(feature = "crypto")] | ||
@@ -189,3 +191,3 @@ sign_alg: SignAlgo::EcDsa(&signature::ECDSA_P256_SHA256_ASN1_SIGNING), | ||
| pub static PKCS_ECDSA_P384_SHA384: SignatureAlgorithm = SignatureAlgorithm { | ||
| oids_sign_alg: &[&EC_PUBLIC_KEY, &EC_SECP_384_R1], | ||
| oids_sign_alg: &[EC_PUBLIC_KEY, EC_SECP_384_R1], | ||
| #[cfg(feature = "crypto")] | ||
@@ -201,3 +203,3 @@ sign_alg: SignAlgo::EcDsa(&signature::ECDSA_P384_SHA384_ASN1_SIGNING), | ||
| pub static PKCS_ECDSA_P521_SHA512: SignatureAlgorithm = SignatureAlgorithm { | ||
| oids_sign_alg: &[&EC_PUBLIC_KEY, &EC_SECP_521_R1], | ||
| oids_sign_alg: &[EC_PUBLIC_KEY, EC_SECP_521_R1], | ||
| #[cfg(feature = "crypto")] | ||
@@ -204,0 +206,0 @@ sign_alg: SignAlgo::EcDsa(&signature::ECDSA_P521_SHA512_ASN1_SIGNING), |
+16
-28
@@ -5,3 +5,3 @@ #![cfg(all(feature = "crypto", feature = "x509-parser"))] | ||
| use rcgen::{BasicConstraints, Certificate, CertificateParams, DnType, IsCa}; | ||
| use rcgen::{BasicConstraints, Certificate, CertificateParams, DnType, IsCa, Issuer}; | ||
| use rcgen::{CertificateRevocationListParams, RevocationReason, RevokedCertParams}; | ||
@@ -132,5 +132,5 @@ use rcgen::{DnValue, KeyPair}; | ||
| fn test_botan_separate_ca() { | ||
| let (mut params, ca_key) = default_params(); | ||
| params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained); | ||
| let ca_cert = params.self_signed(&ca_key).unwrap(); | ||
| let (mut ca_params, ca_key) = default_params(); | ||
| ca_params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained); | ||
| let ca_cert = ca_params.self_signed(&ca_key).unwrap(); | ||
@@ -148,3 +148,4 @@ let mut params = CertificateParams::new(vec!["crabs.crabs".to_string()]).unwrap(); | ||
| let key_pair = KeyPair::generate().unwrap(); | ||
| let cert = params.signed_by(&key_pair, &ca_cert, &ca_key).unwrap(); | ||
| let ca = Issuer::new(ca_params, ca_key); | ||
| let cert = params.signed_by(&key_pair, &ca).unwrap(); | ||
| check_cert_ca(cert.der(), &cert, ca_cert.der()); | ||
@@ -159,8 +160,5 @@ } | ||
| let ca_cert = params.self_signed(&ca_key).unwrap(); | ||
| let ca_cert_der = ca_cert.der(); | ||
| let ca = Issuer::from_ca_cert_der(ca_cert.der(), ca_key).unwrap(); | ||
| let imported_ca_cert_params = CertificateParams::from_ca_cert_der(ca_cert_der).unwrap(); | ||
| let imported_ca_cert = imported_ca_cert_params.self_signed(&ca_key).unwrap(); | ||
| let mut params = CertificateParams::new(vec!["crabs.crabs".to_string()]).unwrap(); | ||
@@ -177,5 +175,3 @@ params | ||
| let key_pair = KeyPair::generate().unwrap(); | ||
| let cert = params | ||
| .signed_by(&key_pair, &imported_ca_cert, &ca_key) | ||
| .unwrap(); | ||
| let cert = params.signed_by(&key_pair, &ca).unwrap(); | ||
| check_cert_ca(cert.der(), &cert, ca_cert_der); | ||
@@ -194,10 +190,4 @@ } | ||
| let ca_cert = params.self_signed(&imported_ca_key).unwrap(); | ||
| let ca = Issuer::from_ca_cert_der(ca_cert.der(), imported_ca_key).unwrap(); | ||
| let ca_cert_der = ca_cert.der(); | ||
| let imported_ca_cert_params = CertificateParams::from_ca_cert_der(ca_cert_der).unwrap(); | ||
| let imported_ca_cert = imported_ca_cert_params | ||
| .self_signed(&imported_ca_key) | ||
| .unwrap(); | ||
| let mut params = CertificateParams::new(vec!["crabs.crabs".to_string()]).unwrap(); | ||
@@ -213,7 +203,5 @@ params | ||
| let key_pair = KeyPair::generate().unwrap(); | ||
| let cert = params | ||
| .signed_by(&key_pair, &imported_ca_cert, &imported_ca_key) | ||
| .unwrap(); | ||
| let cert = params.signed_by(&key_pair, &ca).unwrap(); | ||
| check_cert_ca(cert.der(), &cert, ca_cert_der); | ||
| check_cert_ca(cert.der(), &cert, ca_cert.der()); | ||
| } | ||
@@ -233,3 +221,3 @@ | ||
| let issuer_key = KeyPair::generate_for(alg).unwrap(); | ||
| let issuer = issuer.self_signed(&issuer_key).unwrap(); | ||
| let ca = Issuer::new(issuer, issuer_key); | ||
@@ -243,4 +231,4 @@ // Create an end entity cert issued by the issuer. | ||
| let ee_key = KeyPair::generate_for(alg).unwrap(); | ||
| let ee = ee.signed_by(&ee_key, &issuer, &issuer_key).unwrap(); | ||
| let botan_ee = botan::Certificate::load(ee.der()).unwrap(); | ||
| let ee_cert = ee.signed_by(&ee_key, &ca).unwrap(); | ||
| let botan_ee = botan::Certificate::load(ee_cert.der()).unwrap(); | ||
@@ -255,3 +243,3 @@ // Generate a CRL with the issuer that revokes the EE cert. | ||
| revoked_certs: vec![RevokedCertParams { | ||
| serial_number: ee.params().serial_number.clone().unwrap(), | ||
| serial_number: ee.serial_number.clone().unwrap(), | ||
| revocation_time: now, | ||
@@ -264,3 +252,3 @@ reason_code: Some(RevocationReason::KeyCompromise), | ||
| let crl = crl.signed_by(&issuer, &issuer_key).unwrap(); | ||
| let crl = crl.signed_by(&ca).unwrap(); | ||
@@ -267,0 +255,0 @@ // We should be able to load the CRL in both serializations. |
+6
-102
@@ -42,30 +42,2 @@ #![cfg(feature = "crypto")] | ||
| #[cfg(feature = "x509-parser")] | ||
| mod test_convert_x509_subject_alternative_name { | ||
| use rcgen::{BasicConstraints, CertificateParams, IsCa, SanType}; | ||
| use std::net::{IpAddr, Ipv4Addr}; | ||
| #[test] | ||
| fn converts_from_ip() { | ||
| let ip = Ipv4Addr::new(2, 4, 6, 8); | ||
| let ip_san = SanType::IpAddress(IpAddr::V4(ip)); | ||
| let (mut params, ca_key) = super::util::default_params(); | ||
| // Add the SAN we want to test the parsing for | ||
| params.subject_alt_names.push(ip_san.clone()); | ||
| // Because we're using a function for CA certificates | ||
| params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained); | ||
| let cert = params.self_signed(&ca_key).unwrap(); | ||
| // Serialize our cert that has our chosen san, so we can testing parsing/deserializing it. | ||
| let ca_der = cert.der(); | ||
| let actual = CertificateParams::from_ca_cert_der(ca_der).unwrap(); | ||
| assert!(actual.subject_alt_names.contains(&ip_san)); | ||
| } | ||
| } | ||
| #[cfg(feature = "x509-parser")] | ||
| mod test_x509_custom_ext { | ||
@@ -111,3 +83,3 @@ use crate::util; | ||
| // Generate a CSR with the custom extension, parse it with x509-parser. | ||
| let test_cert_csr = test_cert.params().serialize_request(&test_key).unwrap(); | ||
| let test_cert_csr = params.serialize_request(&test_key).unwrap(); | ||
| let (_, x509_csr) = X509CertificationRequest::from_der(test_cert_csr.der()).unwrap(); | ||
@@ -209,4 +181,4 @@ | ||
| // Create a CRL with one revoked cert, and an issuer to sign the CRL. | ||
| let (crl, issuer) = util::test_crl(); | ||
| let revoked_cert = crl.params().revoked_certs.first().unwrap(); | ||
| let (crl_params, crl, issuer) = util::test_crl(); | ||
| let revoked_cert = crl_params.revoked_certs.first().unwrap(); | ||
| let revoked_cert_serial = BigUint::from_bytes_be(revoked_cert.serial_number.as_ref()); | ||
@@ -224,3 +196,3 @@ let (_, x509_issuer) = X509Certificate::from_der(issuer.der()).unwrap(); | ||
| x509_crl.last_update().to_datetime().unix_timestamp(), | ||
| crl.params().this_update.unix_timestamp() | ||
| crl_params.this_update.unix_timestamp() | ||
| ); | ||
@@ -233,5 +205,5 @@ assert_eq!( | ||
| .unix_timestamp(), | ||
| crl.params().next_update.unix_timestamp() | ||
| crl_params.next_update.unix_timestamp() | ||
| ); | ||
| let crl_number = BigUint::from_bytes_be(crl.params().crl_number.as_ref()); | ||
| let crl_number = BigUint::from_bytes_be(crl_params.crl_number.as_ref()); | ||
| assert_eq!(x509_crl.crl_number().unwrap(), &crl_number); | ||
@@ -356,70 +328,2 @@ | ||
| #[cfg(feature = "x509-parser")] | ||
| mod test_parse_ia5string_subject { | ||
| use crate::util; | ||
| use rcgen::DnType::CustomDnType; | ||
| use rcgen::{CertificateParams, DistinguishedName, DnValue}; | ||
| #[test] | ||
| fn parse_ia5string_subject() { | ||
| // Create and serialize a certificate with a subject containing an IA5String email address. | ||
| let email_address_dn_type = CustomDnType(vec![1, 2, 840, 113549, 1, 9, 1]); // id-emailAddress | ||
| let email_address_dn_value = DnValue::Ia5String("foo@bar.com".try_into().unwrap()); | ||
| let (mut params, key_pair) = util::default_params(); | ||
| params.distinguished_name = DistinguishedName::new(); | ||
| params.distinguished_name.push( | ||
| email_address_dn_type.clone(), | ||
| email_address_dn_value.clone(), | ||
| ); | ||
| let cert = params.self_signed(&key_pair).unwrap(); | ||
| let cert_der = cert.der(); | ||
| // We should be able to parse the certificate with x509-parser. | ||
| assert!(x509_parser::parse_x509_certificate(cert_der).is_ok()); | ||
| // We should be able to reconstitute params from the DER using x509-parser. | ||
| let params_from_cert = CertificateParams::from_ca_cert_der(cert_der).unwrap(); | ||
| // We should find the expected distinguished name in the reconstituted params. | ||
| let expected_names = &[(&email_address_dn_type, &email_address_dn_value)]; | ||
| let names = params_from_cert | ||
| .distinguished_name | ||
| .iter() | ||
| .collect::<Vec<(_, _)>>(); | ||
| assert_eq!(names, expected_names); | ||
| } | ||
| } | ||
| #[cfg(feature = "x509-parser")] | ||
| mod test_parse_other_name_alt_name { | ||
| use rcgen::{CertificateParams, KeyPair, SanType}; | ||
| #[test] | ||
| fn parse_other_name_alt_name() { | ||
| // Create and serialize a certificate with an alternative name containing an "OtherName". | ||
| let mut params = CertificateParams::default(); | ||
| let other_name = SanType::OtherName((vec![1, 2, 3, 4], "Foo".into())); | ||
| params.subject_alt_names.push(other_name.clone()); | ||
| let key_pair = KeyPair::generate().unwrap(); | ||
| let cert = params.self_signed(&key_pair).unwrap(); | ||
| let cert_der = cert.der(); | ||
| // We should be able to parse the certificate with x509-parser. | ||
| assert!(x509_parser::parse_x509_certificate(cert_der).is_ok()); | ||
| // We should be able to reconstitute params from the DER using x509-parser. | ||
| let params_from_cert = CertificateParams::from_ca_cert_der(cert_der).unwrap(); | ||
| // We should find the expected distinguished name in the reconstituted params. | ||
| let expected_alt_names = &[&other_name]; | ||
| let subject_alt_names = params_from_cert | ||
| .subject_alt_names | ||
| .iter() | ||
| .collect::<Vec<_>>(); | ||
| assert_eq!(subject_alt_names, expected_alt_names); | ||
| } | ||
| } | ||
| #[cfg(feature = "x509-parser")] | ||
| mod test_csr_extension_request { | ||
@@ -426,0 +330,0 @@ use rcgen::{CertificateParams, ExtendedKeyUsagePurpose, KeyPair, KeyUsagePurpose}; |
+43
-53
@@ -1,2 +0,2 @@ | ||
| #![cfg(feature = "pem")] | ||
| #![cfg(all(unix, feature = "pem"))] | ||
@@ -17,3 +17,3 @@ use std::cell::RefCell; | ||
| BasicConstraints, Certificate, CertificateParams, DnType, DnValue, GeneralSubtree, IsCa, | ||
| KeyPair, NameConstraints, | ||
| Issuer, KeyPair, NameConstraints, | ||
| }; | ||
@@ -44,6 +44,6 @@ | ||
| // deriving it | ||
| #[derive(Debug)] | ||
| #[derive(Clone, Debug)] | ||
| struct PipeInner([Vec<u8>; 2]); | ||
| #[derive(Debug)] | ||
| #[derive(Clone, Debug)] | ||
| struct PipeEnd { | ||
@@ -142,3 +142,3 @@ read_pos: usize, | ||
| Err(HandshakeError::WouldBlock(mh)) => cln_res = mh.handshake(), | ||
| Err(e) => panic!("Error: {:?}", e), | ||
| Err(e) => panic!("Error: {e:?}"), | ||
| } | ||
@@ -148,3 +148,3 @@ match srv_res { | ||
| Err(HandshakeError::WouldBlock(mh)) => srv_res = mh.handshake(), | ||
| Err(e) => panic!("Error: {:?}", e), | ||
| Err(e) => panic!("Error: {e:?}"), | ||
| } | ||
@@ -170,5 +170,4 @@ if ready == 3 { | ||
| fn verify_csr(cert: &Certificate, key_pair: &KeyPair) { | ||
| let csr = cert | ||
| .params() | ||
| fn verify_csr(params: &CertificateParams, key_pair: &KeyPair) { | ||
| let csr = params | ||
| .serialize_request(key_pair) | ||
@@ -195,4 +194,3 @@ .and_then(|csr| csr.pem()) | ||
| let (params, key_pair) = util::default_params(); | ||
| let cert = params.self_signed(&key_pair).unwrap(); | ||
| verify_csr(&cert, &key_pair); | ||
| verify_csr(¶ms, &key_pair); | ||
| } | ||
@@ -208,3 +206,3 @@ | ||
| verify_cert(&cert, &key_pair); | ||
| verify_csr(&cert, &key_pair); | ||
| verify_csr(¶ms, &key_pair); | ||
| } | ||
@@ -220,3 +218,3 @@ | ||
| verify_cert(&cert, &key_pair); | ||
| verify_csr(&cert, &key_pair); | ||
| verify_csr(¶ms, &key_pair); | ||
| } | ||
@@ -233,3 +231,3 @@ | ||
| verify_cert(&cert, &key_pair); | ||
| verify_csr(&cert, &key_pair); | ||
| verify_csr(¶ms, &key_pair); | ||
| } | ||
@@ -263,3 +261,3 @@ | ||
| verify_cert(&cert, &key_pair); | ||
| verify_csr(&cert, &key_pair); | ||
| verify_csr(¶ms, &key_pair); | ||
| } else { | ||
@@ -292,3 +290,3 @@ verify_cert_basic(&cert); | ||
| verify_cert(&cert, &key_pair); | ||
| verify_csr(&cert, &key_pair); | ||
| verify_csr(¶ms, &key_pair); | ||
| } | ||
@@ -313,3 +311,3 @@ | ||
| verify_cert(&cert, &key_pair); | ||
| verify_csr(&cert, &key_pair); | ||
| verify_csr(¶ms, &key_pair); | ||
| } else { | ||
@@ -325,6 +323,7 @@ // The PSS key types are not fully supported. | ||
| fn test_openssl_separate_ca() { | ||
| let (mut params, ca_key) = util::default_params(); | ||
| params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained); | ||
| let ca_cert = params.self_signed(&ca_key).unwrap(); | ||
| let (mut ca_params, ca_key) = util::default_params(); | ||
| ca_params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained); | ||
| let ca_cert = ca_params.self_signed(&ca_key).unwrap(); | ||
| let ca_cert_pem = ca_cert.pem(); | ||
| let ca = Issuer::new(ca_params, ca_key); | ||
@@ -339,3 +338,3 @@ let mut params = CertificateParams::new(vec!["crabs.crabs".to_string()]).unwrap(); | ||
| let cert_key = KeyPair::generate().unwrap(); | ||
| let cert = params.signed_by(&cert_key, &ca_cert, &ca_key).unwrap(); | ||
| let cert = params.signed_by(&cert_key, &ca).unwrap(); | ||
| let key = cert_key.serialize_der(); | ||
@@ -348,9 +347,9 @@ | ||
| fn test_openssl_separate_ca_with_printable_string() { | ||
| let (mut params, ca_key) = util::default_params(); | ||
| params.distinguished_name.push( | ||
| let (mut ca_params, ca_key) = util::default_params(); | ||
| ca_params.distinguished_name.push( | ||
| DnType::CountryName, | ||
| DnValue::PrintableString("US".try_into().unwrap()), | ||
| ); | ||
| params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained); | ||
| let ca_cert = params.self_signed(&ca_key).unwrap(); | ||
| ca_params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained); | ||
| let ca_cert = ca_params.self_signed(&ca_key).unwrap(); | ||
@@ -365,3 +364,4 @@ let mut params = CertificateParams::new(vec!["crabs.crabs".to_string()]).unwrap(); | ||
| let cert_key = KeyPair::generate().unwrap(); | ||
| let cert = params.signed_by(&cert_key, &ca_cert, &ca_key).unwrap(); | ||
| let ca = Issuer::new(ca_params, ca_key); | ||
| let cert = params.signed_by(&cert_key, &ca).unwrap(); | ||
| let key = cert_key.serialize_der(); | ||
@@ -374,6 +374,7 @@ | ||
| fn test_openssl_separate_ca_with_other_signing_alg() { | ||
| let (mut params, _) = util::default_params(); | ||
| params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained); | ||
| let (mut ca_params, _) = util::default_params(); | ||
| ca_params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained); | ||
| let ca_key = KeyPair::generate_for(&rcgen::PKCS_ECDSA_P256_SHA256).unwrap(); | ||
| let ca_cert = params.self_signed(&ca_key).unwrap(); | ||
| let ca_cert = ca_params.self_signed(&ca_key).unwrap(); | ||
| let ca = Issuer::new(ca_params, ca_key); | ||
@@ -388,3 +389,3 @@ let mut params = CertificateParams::new(vec!["crabs.crabs".to_string()]).unwrap(); | ||
| let cert_key = KeyPair::generate_for(&rcgen::PKCS_ECDSA_P384_SHA384).unwrap(); | ||
| let cert = params.signed_by(&cert_key, &ca_cert, &ca_key).unwrap(); | ||
| let cert = params.signed_by(&cert_key, &ca).unwrap(); | ||
| let key = cert_key.serialize_der(); | ||
@@ -397,8 +398,8 @@ | ||
| fn test_openssl_separate_ca_name_constraints() { | ||
| let (mut params, ca_key) = util::default_params(); | ||
| params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained); | ||
| let (mut ca_params, ca_key) = util::default_params(); | ||
| ca_params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained); | ||
| println!("openssl version: {:x}", openssl::version::number()); | ||
| params.name_constraints = Some(NameConstraints { | ||
| ca_params.name_constraints = Some(NameConstraints { | ||
| permitted_subtrees: vec![GeneralSubtree::DnsName("crabs.crabs".to_string())], | ||
@@ -410,3 +411,4 @@ //permitted_subtrees : vec![GeneralSubtree::DnsName("".to_string())], | ||
| }); | ||
| let ca_cert = params.self_signed(&ca_key).unwrap(); | ||
| let ca_cert = ca_params.self_signed(&ca_key).unwrap(); | ||
| let ca = Issuer::new(ca_params, ca_key); | ||
@@ -421,3 +423,3 @@ let mut params = CertificateParams::new(vec!["crabs.crabs".to_string()]).unwrap(); | ||
| let cert_key = KeyPair::generate().unwrap(); | ||
| let cert = params.signed_by(&cert_key, &ca_cert, &ca_key).unwrap(); | ||
| let cert = params.signed_by(&cert_key, &ca).unwrap(); | ||
| let key = cert_key.serialize_der(); | ||
@@ -431,4 +433,4 @@ | ||
| // Create a CRL with one revoked cert, and an issuer to sign the CRL. | ||
| let (crl, issuer) = util::test_crl(); | ||
| let revoked_cert = crl.params().revoked_certs.first().unwrap(); | ||
| let (crl_params, crl, issuer) = util::test_crl(); | ||
| let revoked_cert = crl_params.revoked_certs.first().unwrap(); | ||
| let revoked_cert_serial = &revoked_cert.serial_number; | ||
@@ -449,21 +451,9 @@ | ||
| #[allow(clippy::useless_conversion)] | ||
| let expected_last_update = Asn1Time::from_unix( | ||
| crl.params() | ||
| .this_update | ||
| .unix_timestamp() | ||
| .try_into() | ||
| .unwrap(), | ||
| ) | ||
| .unwrap(); | ||
| let expected_last_update = | ||
| Asn1Time::from_unix(crl_params.this_update.unix_timestamp().try_into().unwrap()).unwrap(); | ||
| assert!(openssl_crl.last_update().eq(&expected_last_update)); | ||
| // Asn1Time::from_unix takes i64 or i32 (depending on CPU architecture) | ||
| #[allow(clippy::useless_conversion)] | ||
| let expected_next_update = Asn1Time::from_unix( | ||
| crl.params() | ||
| .next_update | ||
| .unix_timestamp() | ||
| .try_into() | ||
| .unwrap(), | ||
| ) | ||
| .unwrap(); | ||
| let expected_next_update = | ||
| Asn1Time::from_unix(crl_params.next_update.unix_timestamp().try_into().unwrap()).unwrap(); | ||
| assert!(openssl_crl.next_update().unwrap().eq(&expected_next_update)); | ||
@@ -470,0 +460,0 @@ assert!(matches!( |
+12
-8
@@ -5,3 +5,3 @@ #![cfg(feature = "crypto")] | ||
| use rcgen::{BasicConstraints, Certificate, CertificateParams, KeyPair}; | ||
| use rcgen::{BasicConstraints, Certificate, CertificateParams, Issuer, KeyPair}; | ||
| use rcgen::{ | ||
@@ -88,3 +88,7 @@ CertificateRevocationList, CrlDistributionPoint, CrlIssuingDistributionPoint, CrlScope, | ||
| #[allow(unused)] // Used by openssl + x509-parser features. | ||
| pub fn test_crl() -> (CertificateRevocationList, Certificate) { | ||
| pub fn test_crl() -> ( | ||
| CertificateRevocationListParams, | ||
| CertificateRevocationList, | ||
| Certificate, | ||
| ) { | ||
| let (mut issuer, key_pair) = default_params(); | ||
@@ -97,3 +101,4 @@ issuer.is_ca = IsCa::Ca(BasicConstraints::Unconstrained); | ||
| ]; | ||
| let issuer = issuer.self_signed(&key_pair).unwrap(); | ||
| let issuer_cert = issuer.self_signed(&key_pair).unwrap(); | ||
| let ca = Issuer::new(issuer, key_pair); | ||
@@ -109,3 +114,3 @@ let now = OffsetDateTime::now_utc(); | ||
| let crl = CertificateRevocationListParams { | ||
| let params = CertificateRevocationListParams { | ||
| this_update: now, | ||
@@ -122,7 +127,6 @@ next_update: next_week, | ||
| key_identifier_method: KeyIdMethod::Sha256, | ||
| } | ||
| .signed_by(&issuer, &key_pair) | ||
| .unwrap(); | ||
| }; | ||
| (crl, issuer) | ||
| let crl = params.signed_by(&ca).unwrap(); | ||
| (params, crl, issuer_cert) | ||
| } | ||
@@ -129,0 +133,0 @@ |
+53
-60
@@ -17,3 +17,4 @@ #![cfg(feature = "crypto")] | ||
| use rcgen::{ | ||
| BasicConstraints, Certificate, CertificateParams, DnType, Error, IsCa, KeyPair, RemoteKeyPair, | ||
| BasicConstraints, Certificate, CertificateParams, DnType, Error, IsCa, Issuer, KeyPair, | ||
| PublicKeyData, SigningKey, | ||
| }; | ||
@@ -55,8 +56,8 @@ use rcgen::{CertificateRevocationListParams, RevocationReason, RevokedCertParams}; | ||
| fn check_cert<'a, 'b>( | ||
| fn check_cert<'a, 'b, S: SigningKey + 'a>( | ||
| cert_der: &CertificateDer<'_>, | ||
| cert: &'a Certificate, | ||
| cert_key: &'a KeyPair, | ||
| cert_key: &'a S, | ||
| alg: &dyn SignatureVerificationAlgorithm, | ||
| sign_fn: impl FnOnce(&'a KeyPair, &'b [u8]) -> Vec<u8>, | ||
| sign_fn: impl FnOnce(&'a S, &'b [u8]) -> Vec<u8>, | ||
| ) { | ||
@@ -70,9 +71,9 @@ #[cfg(feature = "pem")] | ||
| fn check_cert_ca<'a, 'b>( | ||
| fn check_cert_ca<'a, 'b, S: SigningKey + 'a>( | ||
| cert_der: &CertificateDer<'_>, | ||
| cert_key: &'a KeyPair, | ||
| cert_key: &'a S, | ||
| ca_der: &CertificateDer<'_>, | ||
| cert_alg: &dyn SignatureVerificationAlgorithm, | ||
| ca_alg: &dyn SignatureVerificationAlgorithm, | ||
| sign_fn: impl FnOnce(&'a KeyPair, &'b [u8]) -> Vec<u8>, | ||
| sign_fn: impl FnOnce(&'a S, &'b [u8]) -> Vec<u8>, | ||
| ) { | ||
@@ -267,5 +268,5 @@ let trust_anchor = anchor_from_trusted_cert(ca_der).unwrap(); | ||
| fn test_webpki_separate_ca() { | ||
| let (mut params, ca_key) = util::default_params(); | ||
| params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained); | ||
| let ca_cert = params.self_signed(&ca_key).unwrap(); | ||
| let (mut ca_params, ca_key) = util::default_params(); | ||
| ca_params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained); | ||
| let ca_cert = ca_params.self_signed(&ca_key).unwrap(); | ||
@@ -281,3 +282,4 @@ let mut params = CertificateParams::new(vec!["crabs.crabs".to_string()]).unwrap(); | ||
| let key_pair = KeyPair::generate().unwrap(); | ||
| let cert = params.signed_by(&key_pair, &ca_cert, &ca_key).unwrap(); | ||
| let ca = Issuer::new(ca_params, ca_key); | ||
| let cert = params.signed_by(&key_pair, &ca).unwrap(); | ||
| let sign_fn = |cert, msg| sign_msg_ecdsa(cert, msg, &signature::ECDSA_P256_SHA256_ASN1_SIGNING); | ||
@@ -296,6 +298,6 @@ check_cert_ca( | ||
| fn test_webpki_separate_ca_with_other_signing_alg() { | ||
| let (mut params, _) = util::default_params(); | ||
| params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained); | ||
| let (mut ca_params, _) = util::default_params(); | ||
| ca_params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained); | ||
| let ca_key = KeyPair::generate_for(&rcgen::PKCS_ECDSA_P256_SHA256).unwrap(); | ||
| let ca_cert = params.self_signed(&ca_key).unwrap(); | ||
| let ca_cert = ca_params.self_signed(&ca_key).unwrap(); | ||
@@ -311,3 +313,4 @@ let mut params = CertificateParams::new(vec!["crabs.crabs".to_string()]).unwrap(); | ||
| let key_pair = KeyPair::generate_for(&rcgen::PKCS_ED25519).unwrap(); | ||
| let cert = params.signed_by(&key_pair, &ca_cert, &ca_key).unwrap(); | ||
| let ca = Issuer::new(ca_params, ca_key); | ||
| let cert = params.signed_by(&key_pair, &ca).unwrap(); | ||
| check_cert_ca( | ||
@@ -327,7 +330,3 @@ cert.der(), | ||
| impl RemoteKeyPair for Remote { | ||
| fn public_key(&self) -> &[u8] { | ||
| self.0.public_key().as_ref() | ||
| } | ||
| impl SigningKey for Remote { | ||
| fn sign(&self, msg: &[u8]) -> Result<Vec<u8>, rcgen::Error> { | ||
@@ -340,3 +339,9 @@ let system_random = SystemRandom::new(); | ||
| } | ||
| } | ||
| impl PublicKeyData for Remote { | ||
| fn der_bytes(&self) -> &[u8] { | ||
| self.0.public_key().as_ref() | ||
| } | ||
| fn algorithm(&self) -> &'static rcgen::SignatureAlgorithm { | ||
@@ -361,3 +366,3 @@ &rcgen::PKCS_ECDSA_P256_SHA256 | ||
| .unwrap(); | ||
| let remote = KeyPair::from_remote(Box::new(Remote(remote))).unwrap(); | ||
| let remote = Remote(remote); | ||
@@ -428,11 +433,5 @@ let (params, _) = util::default_params(); | ||
| let ca_cert_der = ca_cert.der(); | ||
| let ca = Issuer::from_ca_cert_der(ca_cert.der(), ca_key).unwrap(); | ||
| assert_eq!(ca.key_usages(), &[KeyUsagePurpose::KeyCertSign]); | ||
| let imported_ca_cert_params = CertificateParams::from_ca_cert_der(ca_cert_der).unwrap(); | ||
| assert_eq!( | ||
| imported_ca_cert_params.key_usages, | ||
| vec![KeyUsagePurpose::KeyCertSign] | ||
| ); | ||
| let imported_ca_cert = imported_ca_cert_params.self_signed(&ca_key).unwrap(); | ||
| let mut params = CertificateParams::new(vec!["crabs.crabs".to_string()]).unwrap(); | ||
@@ -446,5 +445,3 @@ params | ||
| let cert_key = KeyPair::generate().unwrap(); | ||
| let cert = params | ||
| .signed_by(&cert_key, &imported_ca_cert, &ca_key) | ||
| .unwrap(); | ||
| let cert = params.signed_by(&cert_key, &ca).unwrap(); | ||
@@ -455,3 +452,3 @@ let sign_fn = |cert, msg| sign_msg_ecdsa(cert, msg, &signature::ECDSA_P256_SHA256_ASN1_SIGNING); | ||
| &cert_key, | ||
| ca_cert_der, | ||
| ca_cert.der(), | ||
| webpki::ring::ECDSA_P256_SHA256, | ||
@@ -473,8 +470,4 @@ webpki::ring::ECDSA_P256_SHA256, | ||
| let ca_cert = params.self_signed(&ca_key).unwrap(); | ||
| let ca = Issuer::from_ca_cert_der(ca_cert.der(), ca_key).unwrap(); | ||
| let ca_cert_der = ca_cert.der(); | ||
| let imported_ca_cert_params = CertificateParams::from_ca_cert_der(ca_cert_der).unwrap(); | ||
| let imported_ca_cert = imported_ca_cert_params.self_signed(&ca_key).unwrap(); | ||
| let mut params = CertificateParams::new(vec!["crabs.crabs".to_string()]).unwrap(); | ||
@@ -488,5 +481,3 @@ params | ||
| let cert_key = KeyPair::generate().unwrap(); | ||
| let cert = params | ||
| .signed_by(&cert_key, &imported_ca_cert, &ca_key) | ||
| .unwrap(); | ||
| let cert = params.signed_by(&cert_key, &ca).unwrap(); | ||
@@ -497,3 +488,3 @@ let sign_fn = |cert, msg| sign_msg_ecdsa(cert, msg, &signature::ECDSA_P256_SHA256_ASN1_SIGNING); | ||
| &cert_key, | ||
| ca_cert_der, | ||
| ca_cert.der(), | ||
| webpki::ring::ECDSA_P256_SHA256, | ||
@@ -538,8 +529,8 @@ webpki::ring::ECDSA_P256_SHA256, | ||
| let (mut params, ca_key) = util::default_params(); | ||
| params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained); | ||
| let (mut ca_params, ca_key) = util::default_params(); | ||
| ca_params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained); | ||
| for eku in &eku_test { | ||
| params.insert_extended_key_usage(eku.clone()); | ||
| ca_params.insert_extended_key_usage(eku.clone()); | ||
| } | ||
| let ekus_contained = ¶ms.extended_key_usages; | ||
| let ekus_contained = &ca_params.extended_key_usages; | ||
| for eku in &eku_test { | ||
@@ -549,5 +540,5 @@ assert!(ekus_contained.contains(eku)); | ||
| let ca_cert = params.self_signed(&ca_key).unwrap(); | ||
| let ca_cert = ca_params.self_signed(&ca_key).unwrap(); | ||
| let ekus_contained = &ca_cert.params().extended_key_usages; | ||
| let ekus_contained = &ca_params.extended_key_usages; | ||
| for eku in &eku_test { | ||
@@ -557,5 +548,7 @@ assert!(ekus_contained.contains(eku)); | ||
| let cert = csr.signed_by(&ca_cert, &ca_key).unwrap(); | ||
| let ekus = ca_params.extended_key_usages.clone(); | ||
| let ca = Issuer::new(ca_params, ca_key); | ||
| let cert = csr.signed_by(&ca).unwrap(); | ||
| let ekus_contained = &cert.params().extended_key_usages; | ||
| let ekus_contained = &csr.params.extended_key_usages; | ||
| for eku in &eku_test { | ||
@@ -565,5 +558,4 @@ assert!(ekus_contained.contains(eku)); | ||
| let eku_cert = &ca_cert.params().extended_key_usages; | ||
| for eku in &eku_test { | ||
| assert!(eku_cert.contains(eku)); | ||
| assert!(ekus.contains(eku)); | ||
| } | ||
@@ -603,4 +595,4 @@ | ||
| // Create a CRL with one revoked cert, and an issuer to sign the CRL. | ||
| let (crl, _) = util::test_crl(); | ||
| let revoked_cert = crl.params().revoked_certs.first().unwrap(); | ||
| let (crl_params, crl, _) = util::test_crl(); | ||
| let revoked_cert = crl_params.revoked_certs.first().unwrap(); | ||
@@ -645,3 +637,3 @@ // We should be able to parse the CRL DER without error. | ||
| let issuer_key = KeyPair::generate_for(alg).unwrap(); | ||
| let issuer = issuer.self_signed(&issuer_key).unwrap(); | ||
| let issuer_cert = issuer.self_signed(&issuer_key).unwrap(); | ||
@@ -654,8 +646,9 @@ // Create an end entity cert issued by the issuer. | ||
| let ee_key = KeyPair::generate_for(alg).unwrap(); | ||
| let ee = ee.signed_by(&ee_key, &issuer, &issuer_key).unwrap(); | ||
| let issuer = Issuer::new(issuer, issuer_key); | ||
| let ee_cert = ee.signed_by(&ee_key, &issuer).unwrap(); | ||
| // Set up webpki's verification requirements. | ||
| let trust_anchor = anchor_from_trusted_cert(issuer.der()).unwrap(); | ||
| let trust_anchor = anchor_from_trusted_cert(issuer_cert.der()).unwrap(); | ||
| let trust_anchor_list = &[trust_anchor]; | ||
| let end_entity_cert = EndEntityCert::try_from(ee.der()).unwrap(); | ||
| let end_entity_cert = EndEntityCert::try_from(ee_cert.der()).unwrap(); | ||
| let unix_time = 0x40_00_00_00; | ||
@@ -685,3 +678,3 @@ let time = UnixTime::since_unix_epoch(StdDuration::from_secs(unix_time)); | ||
| revoked_certs: vec![RevokedCertParams { | ||
| serial_number: ee.params().serial_number.clone().unwrap(), | ||
| serial_number: ee.serial_number.clone().unwrap(), | ||
| revocation_time: now, | ||
@@ -693,3 +686,3 @@ reason_code: Some(RevocationReason::KeyCompromise), | ||
| } | ||
| .signed_by(&issuer, &issuer_key) | ||
| .signed_by(&issuer) | ||
| .unwrap(); | ||
@@ -696,0 +689,0 @@ |
| use std::{fmt, str::FromStr}; | ||
| use crate::{Error, InvalidAsn1String}; | ||
| /// ASN.1 `PrintableString` type. | ||
| /// | ||
| /// Supports a subset of the ASCII printable characters (described below). | ||
| /// | ||
| /// For the full ASCII character set, use | ||
| /// [`Ia5String`][`crate::Ia5String`]. | ||
| /// | ||
| /// # Examples | ||
| /// | ||
| /// You can create a `PrintableString` from [a literal string][`&str`] with [`PrintableString::try_from`]: | ||
| /// | ||
| /// ``` | ||
| /// use rcgen::PrintableString; | ||
| /// let hello = PrintableString::try_from("hello").unwrap(); | ||
| /// ``` | ||
| /// | ||
| /// # Supported characters | ||
| /// | ||
| /// PrintableString is a subset of the [ASCII printable characters]. | ||
| /// For instance, `'@'` is a printable character as per ASCII but can't be part of [ASN.1's `PrintableString`]. | ||
| /// | ||
| /// The following ASCII characters/ranges are supported: | ||
| /// | ||
| /// - `A..Z` | ||
| /// - `a..z` | ||
| /// - `0..9` | ||
| /// - "` `" (i.e. space) | ||
| /// - `\` | ||
| /// - `(` | ||
| /// - `)` | ||
| /// - `+` | ||
| /// - `,` | ||
| /// - `-` | ||
| /// - `.` | ||
| /// - `/` | ||
| /// - `:` | ||
| /// - `=` | ||
| /// - `?` | ||
| /// | ||
| /// [ASCII printable characters]: https://en.wikipedia.org/wiki/ASCII#Printable_characters | ||
| /// [ASN.1's `PrintableString`]: https://en.wikipedia.org/wiki/PrintableString | ||
| #[derive(Debug, PartialEq, Eq, Hash, Clone)] | ||
| pub struct PrintableString(String); | ||
| impl PrintableString { | ||
| /// Extracts a string slice containing the entire `PrintableString`. | ||
| pub fn as_str(&self) -> &str { | ||
| &self.0 | ||
| } | ||
| } | ||
| impl TryFrom<&str> for PrintableString { | ||
| type Error = Error; | ||
| /// Converts a `&str` to a [`PrintableString`]. | ||
| /// | ||
| /// Any character not in the [`PrintableString`] charset will be rejected. | ||
| /// See [`PrintableString`] documentation for more information. | ||
| /// | ||
| /// The result is allocated on the heap. | ||
| fn try_from(input: &str) -> Result<Self, Error> { | ||
| input.to_string().try_into() | ||
| } | ||
| } | ||
| impl TryFrom<String> for PrintableString { | ||
| type Error = Error; | ||
| /// Converts a [`String`][`std::string::String`] into a [`PrintableString`] | ||
| /// | ||
| /// Any character not in the [`PrintableString`] charset will be rejected. | ||
| /// See [`PrintableString`] documentation for more information. | ||
| /// | ||
| /// This conversion does not allocate or copy memory. | ||
| fn try_from(value: String) -> Result<Self, Self::Error> { | ||
| for &c in value.as_bytes() { | ||
| match c { | ||
| b'A'..=b'Z' | ||
| | b'a'..=b'z' | ||
| | b'0'..=b'9' | ||
| | b' ' | ||
| | b'\'' | ||
| | b'(' | ||
| | b')' | ||
| | b'+' | ||
| | b',' | ||
| | b'-' | ||
| | b'.' | ||
| | b'/' | ||
| | b':' | ||
| | b'=' | ||
| | b'?' => (), | ||
| _ => { | ||
| return Err(Error::InvalidAsn1String( | ||
| InvalidAsn1String::PrintableString(value), | ||
| )) | ||
| }, | ||
| } | ||
| } | ||
| Ok(Self(value)) | ||
| } | ||
| } | ||
| impl FromStr for PrintableString { | ||
| type Err = Error; | ||
| fn from_str(s: &str) -> Result<Self, Self::Err> { | ||
| s.try_into() | ||
| } | ||
| } | ||
| impl AsRef<str> for PrintableString { | ||
| fn as_ref(&self) -> &str { | ||
| &self.0 | ||
| } | ||
| } | ||
| impl fmt::Display for PrintableString { | ||
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
| fmt::Display::fmt(self.as_str(), f) | ||
| } | ||
| } | ||
| impl PartialEq<str> for PrintableString { | ||
| fn eq(&self, other: &str) -> bool { | ||
| self.as_str() == other | ||
| } | ||
| } | ||
| impl PartialEq<String> for PrintableString { | ||
| fn eq(&self, other: &String) -> bool { | ||
| self.as_str() == other.as_str() | ||
| } | ||
| } | ||
| impl PartialEq<&str> for PrintableString { | ||
| fn eq(&self, other: &&str) -> bool { | ||
| self.as_str() == *other | ||
| } | ||
| } | ||
| impl PartialEq<&String> for PrintableString { | ||
| fn eq(&self, other: &&String) -> bool { | ||
| self.as_str() == other.as_str() | ||
| } | ||
| } | ||
| /// ASN.1 `IA5String` type. | ||
| /// | ||
| /// # Examples | ||
| /// | ||
| /// You can create a `Ia5String` from [a literal string][`&str`] with [`Ia5String::try_from`]: | ||
| /// | ||
| /// ``` | ||
| /// use rcgen::Ia5String; | ||
| /// let hello = Ia5String::try_from("hello").unwrap(); | ||
| /// ``` | ||
| /// | ||
| /// # Supported characters | ||
| /// | ||
| /// Supports the [International Alphabet No. 5 (IA5)] character encoding, i.e. | ||
| /// the 128 characters of the ASCII alphabet. (Note: IA5 is now | ||
| /// technically known as the International Reference Alphabet or IRA as | ||
| /// specified in the ITU-T's T.50 recommendation). | ||
| /// | ||
| /// For UTF-8, use [`String`][`std::string::String`]. | ||
| /// | ||
| /// [International Alphabet No. 5 (IA5)]: https://en.wikipedia.org/wiki/T.50_(standard) | ||
| #[derive(Debug, PartialEq, Eq, Hash, Clone)] | ||
| pub struct Ia5String(String); | ||
| impl Ia5String { | ||
| /// Extracts a string slice containing the entire `Ia5String`. | ||
| pub fn as_str(&self) -> &str { | ||
| &self.0 | ||
| } | ||
| } | ||
| impl TryFrom<&str> for Ia5String { | ||
| type Error = Error; | ||
| /// Converts a `&str` to a [`Ia5String`]. | ||
| /// | ||
| /// Any character not in the [`Ia5String`] charset will be rejected. | ||
| /// See [`Ia5String`] documentation for more information. | ||
| /// | ||
| /// The result is allocated on the heap. | ||
| fn try_from(input: &str) -> Result<Self, Error> { | ||
| input.to_string().try_into() | ||
| } | ||
| } | ||
| impl TryFrom<String> for Ia5String { | ||
| type Error = Error; | ||
| /// Converts a [`String`][`std::string::String`] into a [`Ia5String`] | ||
| /// | ||
| /// Any character not in the [`Ia5String`] charset will be rejected. | ||
| /// See [`Ia5String`] documentation for more information. | ||
| fn try_from(input: String) -> Result<Self, Error> { | ||
| if !input.is_ascii() { | ||
| return Err(Error::InvalidAsn1String(InvalidAsn1String::Ia5String( | ||
| input, | ||
| ))); | ||
| } | ||
| Ok(Self(input)) | ||
| } | ||
| } | ||
| impl FromStr for Ia5String { | ||
| type Err = Error; | ||
| fn from_str(s: &str) -> Result<Self, Self::Err> { | ||
| s.try_into() | ||
| } | ||
| } | ||
| impl AsRef<str> for Ia5String { | ||
| fn as_ref(&self) -> &str { | ||
| &self.0 | ||
| } | ||
| } | ||
| impl fmt::Display for Ia5String { | ||
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
| fmt::Display::fmt(self.as_str(), f) | ||
| } | ||
| } | ||
| impl PartialEq<str> for Ia5String { | ||
| fn eq(&self, other: &str) -> bool { | ||
| self.as_str() == other | ||
| } | ||
| } | ||
| impl PartialEq<String> for Ia5String { | ||
| fn eq(&self, other: &String) -> bool { | ||
| self.as_str() == other.as_str() | ||
| } | ||
| } | ||
| impl PartialEq<&str> for Ia5String { | ||
| fn eq(&self, other: &&str) -> bool { | ||
| self.as_str() == *other | ||
| } | ||
| } | ||
| impl PartialEq<&String> for Ia5String { | ||
| fn eq(&self, other: &&String) -> bool { | ||
| self.as_str() == other.as_str() | ||
| } | ||
| } | ||
| /// ASN.1 `TeletexString` type. | ||
| /// | ||
| /// # Examples | ||
| /// | ||
| /// You can create a `TeletexString` from [a literal string][`&str`] with [`TeletexString::try_from`]: | ||
| /// | ||
| /// ``` | ||
| /// use rcgen::TeletexString; | ||
| /// let hello = TeletexString::try_from("hello").unwrap(); | ||
| /// ``` | ||
| /// | ||
| /// # Supported characters | ||
| /// | ||
| /// The standard defines a complex character set allowed in this type. However, quoting the ASN.1 | ||
| /// [mailing list], "a sizable volume of software in the world treats TeletexString (T61String) as a | ||
| /// simple 8-bit string with mostly Windows Latin 1 (superset of iso-8859-1) encoding". | ||
| /// | ||
| /// `TeletexString` is included for backward compatibility, [RFC 5280] say it | ||
| /// SHOULD NOT be used for certificates for new subjects. | ||
| /// | ||
| /// [mailing list]: https://www.mail-archive.com/asn1@asn1.org/msg00460.html | ||
| /// [RFC 5280]: https://datatracker.ietf.org/doc/html/rfc5280#page-25 | ||
| #[derive(Debug, PartialEq, Eq, Hash, Clone)] | ||
| pub struct TeletexString(String); | ||
| impl TeletexString { | ||
| /// Extracts a string slice containing the entire `TeletexString`. | ||
| pub fn as_str(&self) -> &str { | ||
| &self.0 | ||
| } | ||
| /// Returns a byte slice of this `TeletexString`’s contents. | ||
| pub fn as_bytes(&self) -> &[u8] { | ||
| self.0.as_bytes() | ||
| } | ||
| } | ||
| impl TryFrom<&str> for TeletexString { | ||
| type Error = Error; | ||
| /// Converts a `&str` to a [`TeletexString`]. | ||
| /// | ||
| /// Any character not in the [`TeletexString`] charset will be rejected. | ||
| /// See [`TeletexString`] documentation for more information. | ||
| /// | ||
| /// The result is allocated on the heap. | ||
| fn try_from(input: &str) -> Result<Self, Error> { | ||
| input.to_string().try_into() | ||
| } | ||
| } | ||
| impl TryFrom<String> for TeletexString { | ||
| type Error = Error; | ||
| /// Converts a [`String`][`std::string::String`] into a [`TeletexString`] | ||
| /// | ||
| /// Any character not in the [`TeletexString`] charset will be rejected. | ||
| /// See [`TeletexString`] documentation for more information. | ||
| /// | ||
| /// This conversion does not allocate or copy memory. | ||
| fn try_from(input: String) -> Result<Self, Error> { | ||
| // Check all bytes are visible | ||
| if !input.as_bytes().iter().all(|b| (0x20..=0x7f).contains(b)) { | ||
| return Err(Error::InvalidAsn1String(InvalidAsn1String::TeletexString( | ||
| input, | ||
| ))); | ||
| } | ||
| Ok(Self(input)) | ||
| } | ||
| } | ||
| impl FromStr for TeletexString { | ||
| type Err = Error; | ||
| fn from_str(s: &str) -> Result<Self, Self::Err> { | ||
| s.try_into() | ||
| } | ||
| } | ||
| impl AsRef<str> for TeletexString { | ||
| fn as_ref(&self) -> &str { | ||
| &self.0 | ||
| } | ||
| } | ||
| impl fmt::Display for TeletexString { | ||
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
| fmt::Display::fmt(self.as_str(), f) | ||
| } | ||
| } | ||
| impl PartialEq<str> for TeletexString { | ||
| fn eq(&self, other: &str) -> bool { | ||
| self.as_str() == other | ||
| } | ||
| } | ||
| impl PartialEq<String> for TeletexString { | ||
| fn eq(&self, other: &String) -> bool { | ||
| self.as_str() == other.as_str() | ||
| } | ||
| } | ||
| impl PartialEq<&str> for TeletexString { | ||
| fn eq(&self, other: &&str) -> bool { | ||
| self.as_str() == *other | ||
| } | ||
| } | ||
| impl PartialEq<&String> for TeletexString { | ||
| fn eq(&self, other: &&String) -> bool { | ||
| self.as_str() == other.as_str() | ||
| } | ||
| } | ||
| /// ASN.1 `BMPString` type. | ||
| /// | ||
| /// # Examples | ||
| /// | ||
| /// You can create a `BmpString` from [a literal string][`&str`] with [`BmpString::try_from`]: | ||
| /// | ||
| /// ``` | ||
| /// use rcgen::BmpString; | ||
| /// let hello = BmpString::try_from("hello").unwrap(); | ||
| /// ``` | ||
| /// | ||
| /// # Supported characters | ||
| /// | ||
| /// Encodes Basic Multilingual Plane (BMP) subset of Unicode (ISO 10646), | ||
| /// a.k.a. UCS-2. | ||
| /// | ||
| /// Bytes are encoded as UTF-16 big-endian. | ||
| /// | ||
| /// `BMPString` is included for backward compatibility, [RFC 5280] say it | ||
| /// SHOULD NOT be used for certificates for new subjects. | ||
| /// | ||
| /// [RFC 5280]: https://datatracker.ietf.org/doc/html/rfc5280#page-25 | ||
| #[derive(Debug, PartialEq, Eq, Hash, Clone)] | ||
| pub struct BmpString(Vec<u8>); | ||
| impl BmpString { | ||
| /// Returns a byte slice of this `BmpString`'s contents. | ||
| /// | ||
| /// The inverse of this method is [`from_utf16be`]. | ||
| /// | ||
| /// [`from_utf16be`]: BmpString::from_utf16be | ||
| /// | ||
| /// # Examples | ||
| /// | ||
| /// ``` | ||
| /// use rcgen::BmpString; | ||
| /// let s = BmpString::try_from("hello").unwrap(); | ||
| /// | ||
| /// assert_eq!(&[0, 104, 0, 101, 0, 108, 0, 108, 0, 111], s.as_bytes()); | ||
| /// ``` | ||
| pub fn as_bytes(&self) -> &[u8] { | ||
| &self.0 | ||
| } | ||
| /// Decode a UTF-16BE–encoded vector `vec` into a `BmpString`, returning [Err](`std::result::Result::Err`) if `vec` contains any invalid data. | ||
| pub fn from_utf16be(vec: Vec<u8>) -> Result<Self, Error> { | ||
| if vec.len() % 2 != 0 { | ||
| return Err(Error::InvalidAsn1String(InvalidAsn1String::BmpString( | ||
| "Invalid UTF-16 encoding".to_string(), | ||
| ))); | ||
| } | ||
| // FIXME: Update this when `array_chunks` is stabilized. | ||
| for maybe_char in char::decode_utf16( | ||
| vec.chunks_exact(2) | ||
| .map(|chunk| u16::from_be_bytes([chunk[0], chunk[1]])), | ||
| ) { | ||
| // We check we only use the BMP subset of Unicode (the first 65 536 code points) | ||
| match maybe_char { | ||
| // Character is in the Basic Multilingual Plane | ||
| Ok(c) if (c as u64) < u64::from(u16::MAX) => (), | ||
| // Characters outside Basic Multilingual Plane or unpaired surrogates | ||
| _ => { | ||
| return Err(Error::InvalidAsn1String(InvalidAsn1String::BmpString( | ||
| "Invalid UTF-16 encoding".to_string(), | ||
| ))); | ||
| }, | ||
| } | ||
| } | ||
| Ok(Self(vec.to_vec())) | ||
| } | ||
| } | ||
| impl TryFrom<&str> for BmpString { | ||
| type Error = Error; | ||
| /// Converts a `&str` to a [`BmpString`]. | ||
| /// | ||
| /// Any character not in the [`BmpString`] charset will be rejected. | ||
| /// See [`BmpString`] documentation for more information. | ||
| /// | ||
| /// The result is allocated on the heap. | ||
| fn try_from(value: &str) -> Result<Self, Self::Error> { | ||
| let capacity = value.len().checked_mul(2).ok_or_else(|| { | ||
| Error::InvalidAsn1String(InvalidAsn1String::BmpString(value.to_string())) | ||
| })?; | ||
| let mut bytes = Vec::with_capacity(capacity); | ||
| for code_point in value.encode_utf16() { | ||
| bytes.extend(code_point.to_be_bytes()); | ||
| } | ||
| BmpString::from_utf16be(bytes) | ||
| } | ||
| } | ||
| impl TryFrom<String> for BmpString { | ||
| type Error = Error; | ||
| /// Converts a [`String`][`std::string::String`] into a [`BmpString`] | ||
| /// | ||
| /// Any character not in the [`BmpString`] charset will be rejected. | ||
| /// See [`BmpString`] documentation for more information. | ||
| /// | ||
| /// Parsing a `BmpString` allocates memory since the UTF-8 to UTF-16 conversion requires a memory allocation. | ||
| fn try_from(value: String) -> Result<Self, Self::Error> { | ||
| value.as_str().try_into() | ||
| } | ||
| } | ||
| impl FromStr for BmpString { | ||
| type Err = Error; | ||
| fn from_str(s: &str) -> Result<Self, Self::Err> { | ||
| s.try_into() | ||
| } | ||
| } | ||
| /// ASN.1 `UniversalString` type. | ||
| /// | ||
| /// # Examples | ||
| /// | ||
| /// You can create a `UniversalString` from [a literal string][`&str`] with [`UniversalString::try_from`]: | ||
| /// | ||
| /// ``` | ||
| /// use rcgen::UniversalString; | ||
| /// let hello = UniversalString::try_from("hello").unwrap(); | ||
| /// ``` | ||
| /// | ||
| /// # Supported characters | ||
| /// | ||
| /// The characters which can appear in the `UniversalString` type are any of the characters allowed by | ||
| /// ISO/IEC 10646 (Unicode). | ||
| /// | ||
| /// Bytes are encoded like UTF-32 big-endian. | ||
| /// | ||
| /// `UniversalString` is included for backward compatibility, [RFC 5280] say it | ||
| /// SHOULD NOT be used for certificates for new subjects. | ||
| /// | ||
| /// [RFC 5280]: https://datatracker.ietf.org/doc/html/rfc5280#page-25 | ||
| #[derive(Debug, PartialEq, Eq, Hash, Clone)] | ||
| pub struct UniversalString(Vec<u8>); | ||
| impl UniversalString { | ||
| /// Returns a byte slice of this `UniversalString`'s contents. | ||
| /// | ||
| /// The inverse of this method is [`from_utf32be`]. | ||
| /// | ||
| /// [`from_utf32be`]: UniversalString::from_utf32be | ||
| /// | ||
| /// # Examples | ||
| /// | ||
| /// ``` | ||
| /// use rcgen::UniversalString; | ||
| /// let s = UniversalString::try_from("hello").unwrap(); | ||
| /// | ||
| /// assert_eq!(&[0, 0, 0, 104, 0, 0, 0, 101, 0, 0, 0, 108, 0, 0, 0, 108, 0, 0, 0, 111], s.as_bytes()); | ||
| /// ``` | ||
| pub fn as_bytes(&self) -> &[u8] { | ||
| &self.0 | ||
| } | ||
| /// Decode a UTF-32BE–encoded vector `vec` into a `UniversalString`, returning [Err](`std::result::Result::Err`) if `vec` contains any invalid data. | ||
| pub fn from_utf32be(vec: Vec<u8>) -> Result<UniversalString, Error> { | ||
| if vec.len() % 4 != 0 { | ||
| return Err(Error::InvalidAsn1String( | ||
| InvalidAsn1String::UniversalString("Invalid UTF-32 encoding".to_string()), | ||
| )); | ||
| } | ||
| // FIXME: Update this when `array_chunks` is stabilized. | ||
| for maybe_char in vec | ||
| .chunks_exact(4) | ||
| .map(|chunk| u32::from_be_bytes([chunk[0], chunk[1], chunk[2], chunk[3]])) | ||
| { | ||
| if core::char::from_u32(maybe_char).is_none() { | ||
| return Err(Error::InvalidAsn1String( | ||
| InvalidAsn1String::UniversalString("Invalid UTF-32 encoding".to_string()), | ||
| )); | ||
| } | ||
| } | ||
| Ok(Self(vec)) | ||
| } | ||
| } | ||
| impl TryFrom<&str> for UniversalString { | ||
| type Error = Error; | ||
| /// Converts a `&str` to a [`UniversalString`]. | ||
| /// | ||
| /// Any character not in the [`UniversalString`] charset will be rejected. | ||
| /// See [`UniversalString`] documentation for more information. | ||
| /// | ||
| /// The result is allocated on the heap. | ||
| fn try_from(value: &str) -> Result<Self, Self::Error> { | ||
| let capacity = value.len().checked_mul(4).ok_or_else(|| { | ||
| Error::InvalidAsn1String(InvalidAsn1String::UniversalString(value.to_string())) | ||
| })?; | ||
| let mut bytes = Vec::with_capacity(capacity); | ||
| // A `char` is any ‘Unicode code point’ other than a surrogate code point. | ||
| // The code units for UTF-32 correspond exactly to Unicode code points. | ||
| // (https://www.unicode.org/reports/tr19/tr19-9.html#Introduction) | ||
| // So any `char` is a valid UTF-32, we just cast it to perform the convertion. | ||
| for char in value.chars().map(|char| char as u32) { | ||
| bytes.extend(char.to_be_bytes()) | ||
| } | ||
| UniversalString::from_utf32be(bytes) | ||
| } | ||
| } | ||
| impl TryFrom<String> for UniversalString { | ||
| type Error = Error; | ||
| /// Converts a [`String`][`std::string::String`] into a [`UniversalString`] | ||
| /// | ||
| /// Any character not in the [`UniversalString`] charset will be rejected. | ||
| /// See [`UniversalString`] documentation for more information. | ||
| /// | ||
| /// Parsing a `UniversalString` allocates memory since the UTF-8 to UTF-32 conversion requires a memory allocation. | ||
| fn try_from(value: String) -> Result<Self, Self::Error> { | ||
| value.as_str().try_into() | ||
| } | ||
| } | ||
| #[cfg(test)] | ||
| #[allow(clippy::unwrap_used)] | ||
| mod tests { | ||
| use crate::{BmpString, Ia5String, PrintableString, TeletexString, UniversalString}; | ||
| #[test] | ||
| fn printable_string() { | ||
| const EXAMPLE_UTF8: &str = "CertificateTemplate"; | ||
| let printable_string = PrintableString::try_from(EXAMPLE_UTF8).unwrap(); | ||
| assert_eq!(printable_string, EXAMPLE_UTF8); | ||
| assert!(PrintableString::try_from("@").is_err()); | ||
| assert!(PrintableString::try_from("*").is_err()); | ||
| } | ||
| #[test] | ||
| fn ia5_string() { | ||
| const EXAMPLE_UTF8: &str = "CertificateTemplate"; | ||
| let ia5_string = Ia5String::try_from(EXAMPLE_UTF8).unwrap(); | ||
| assert_eq!(ia5_string, EXAMPLE_UTF8); | ||
| assert!(Ia5String::try_from(String::from('\u{7F}')).is_ok()); | ||
| assert!(Ia5String::try_from(String::from('\u{8F}')).is_err()); | ||
| } | ||
| #[test] | ||
| fn teletext_string() { | ||
| const EXAMPLE_UTF8: &str = "CertificateTemplate"; | ||
| let teletext_string = TeletexString::try_from(EXAMPLE_UTF8).unwrap(); | ||
| assert_eq!(teletext_string, EXAMPLE_UTF8); | ||
| assert!(Ia5String::try_from(String::from('\u{7F}')).is_ok()); | ||
| assert!(Ia5String::try_from(String::from('\u{8F}')).is_err()); | ||
| } | ||
| #[test] | ||
| fn bmp_string() { | ||
| const EXPECTED_BYTES: &[u8] = &[ | ||
| 0x00, 0x43, 0x00, 0x65, 0x00, 0x72, 0x00, 0x74, 0x00, 0x69, 0x00, 0x66, 0x00, 0x69, | ||
| 0x00, 0x63, 0x00, 0x61, 0x00, 0x74, 0x00, 0x65, 0x00, 0x54, 0x00, 0x65, 0x00, 0x6d, | ||
| 0x00, 0x70, 0x00, 0x6c, 0x00, 0x61, 0x00, 0x74, 0x00, 0x65, | ||
| ]; | ||
| const EXAMPLE_UTF8: &str = "CertificateTemplate"; | ||
| let bmp_string = BmpString::try_from(EXAMPLE_UTF8).unwrap(); | ||
| assert_eq!(bmp_string.as_bytes(), EXPECTED_BYTES); | ||
| assert!(BmpString::try_from(String::from('\u{FFFE}')).is_ok()); | ||
| assert!(BmpString::try_from(String::from('\u{FFFF}')).is_err()); | ||
| } | ||
| #[test] | ||
| fn universal_string() { | ||
| const EXPECTED_BYTES: &[u8] = &[ | ||
| 0x00, 0x00, 0x00, 0x43, 0x00, 0x00, 0x00, 0x65, 0x00, 0x00, 0x00, 0x72, 0x00, 0x00, | ||
| 0x00, 0x74, 0x00, 0x00, 0x00, 0x69, 0x00, 0x00, 0x00, 0x66, 0x00, 0x00, 0x00, 0x69, | ||
| 0x00, 0x00, 0x00, 0x63, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00, 0x74, 0x00, 0x00, | ||
| 0x00, 0x65, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x00, 0x65, 0x00, 0x00, 0x00, 0x6d, | ||
| 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0x6c, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00, | ||
| 0x00, 0x74, 0x00, 0x00, 0x00, 0x65, | ||
| ]; | ||
| const EXAMPLE_UTF8: &str = "CertificateTemplate"; | ||
| let universal_string = UniversalString::try_from(EXAMPLE_UTF8).unwrap(); | ||
| assert_eq!(universal_string.as_bytes(), EXPECTED_BYTES); | ||
| } | ||
| } |
Sorry, the diff of this file is not supported yet