You're Invited:Meet the Socket Team at RSAC and BSidesSF 2026, March 23–26.RSVP
Socket
Book a DemoSign in
Socket

rcgen

Package Overview
Dependencies
Maintainers
1
Versions
50
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

rcgen - cargo Package Compare versions

Comparing version
0.13.3
to
0.14.0
+665
src/string.rs
//! 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);
}
}
+1
-1
{
"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"

@@ -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 @@

@@ -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();

@@ -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.

@@ -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(_)
));
}
}
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(())

@@ -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(&params.distinguished_name),
key_identifier_method: Cow::Borrowed(&params.key_identifier_method),
key_usages: Cow::Borrowed(&params.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 @@ }

@@ -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 @@

@@ -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),

@@ -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.

@@ -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};

@@ -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(&params, &key_pair);
}

@@ -208,3 +206,3 @@

verify_cert(&cert, &key_pair);
verify_csr(&cert, &key_pair);
verify_csr(&params, &key_pair);
}

@@ -220,3 +218,3 @@

verify_cert(&cert, &key_pair);
verify_csr(&cert, &key_pair);
verify_csr(&params, &key_pair);
}

@@ -233,3 +231,3 @@

verify_cert(&cert, &key_pair);
verify_csr(&cert, &key_pair);
verify_csr(&params, &key_pair);
}

@@ -263,3 +261,3 @@

verify_cert(&cert, &key_pair);
verify_csr(&cert, &key_pair);
verify_csr(&params, &key_pair);
} else {

@@ -292,3 +290,3 @@ verify_cert_basic(&cert);

verify_cert(&cert, &key_pair);
verify_csr(&cert, &key_pair);
verify_csr(&params, &key_pair);
}

@@ -313,3 +311,3 @@

verify_cert(&cert, &key_pair);
verify_csr(&cert, &key_pair);
verify_csr(&params, &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!(

@@ -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 @@

@@ -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 = &params.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