Skip to content

Use pki_types to improve the interoperability with the rustls ecosystem #223

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ resolver = "2"

[workspace.dependencies]
pem = "3.0.2"
pki-types = { package = "rustls-pki-types", version = "1.3.0" }
rand = "0.8"
ring = "0.17"
x509-parser = "0.16"
Expand Down
4 changes: 3 additions & 1 deletion rcgen/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ aws-lc-rs = { version = "1.6.0", optional = true }
yasna = { version = "0.5.2", features = ["time", "std"] }
ring = { workspace = true, optional = true }
pem = { workspace = true, optional = true }
pki-types = { workspace = true }
time = { version = "0.3.6", default-features = false }
x509-parser = { workspace = true, features = ["verify"], optional = true }
zeroize = { version = "1.2", optional = true }
Expand All @@ -48,7 +49,8 @@ features = ["x509-parser"]
[package.metadata.cargo_check_external_types]
allowed_external_types = [
"time::offset_date_time::OffsetDateTime",
"zeroize::Zeroize"
"zeroize::Zeroize",
"rustls_pki_types::*"
]

[dev-dependencies]
Expand Down
39 changes: 31 additions & 8 deletions rcgen/src/certificate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use std::str::FromStr;

#[cfg(feature = "pem")]
use pem::Pem;
use pki_types::{CertificateDer, CertificateSigningRequestDer};
use time::{Date, Month, OffsetDateTime, PrimitiveDateTime, Time};
use yasna::models::ObjectIdentifier;
use yasna::{DERWriter, Tag};
Expand All @@ -23,7 +24,7 @@ use crate::{
pub struct Certificate {
pub(crate) params: CertificateParams,
pub(crate) subject_public_key_info: Vec<u8>,
pub(crate) der: Vec<u8>,
pub(crate) der: CertificateDer<'static>,
}

impl Certificate {
Expand All @@ -39,13 +40,16 @@ impl Certificate {
.derive(&self.subject_public_key_info)
}
/// Get the certificate in DER encoded format.
pub fn der(&self) -> &[u8] {
///
/// As the return type implements `Deref<Target = [u8]>`, in can easily be saved
/// to a file like a byte slice.
pub fn der(&self) -> &CertificateDer<'static> {
&self.der
}
/// Get the certificate in PEM encoded format.
#[cfg(feature = "pem")]
pub fn pem(&self) -> String {
pem::encode_config(&Pem::new("CERTIFICATE", self.der()), ENCODE_CONFIG)
pem::encode_config(&Pem::new("CERTIFICATE", self.der().to_vec()), ENCODE_CONFIG)
}
/// Generate and serialize a certificate signing request (CSR) in binary DER format.
///
Expand All @@ -57,7 +61,13 @@ impl Certificate {
/// should not call `serialize_request_der` and then `serialize_request_pem`. This will
/// result in two different CSRs. Instead call only `serialize_request_pem` and base64
/// decode the inner content to get the DER encoded CSR using a library like `pem`.
pub fn serialize_request_der(&self, subject_key: &KeyPair) -> Result<Vec<u8>, Error> {
///
/// As the return type implements `Deref<Target = [u8]>`, in can easily be saved
/// to a file like a byte slice.
pub fn serialize_request_der(
&self,
subject_key: &KeyPair,
) -> Result<CertificateSigningRequestDer<'static>, Error> {
yasna::try_construct_der(|writer| {
writer.write_sequence(|writer| {
let cert_data = yasna::try_construct_der(|writer| {
Expand All @@ -74,6 +84,7 @@ impl Certificate {
Ok(())
})
})
.map(CertificateSigningRequestDer::from)
}
/// Generate and serialize a certificate signing request (CSR) in binary DER format.
///
Expand All @@ -88,7 +99,7 @@ impl Certificate {
#[cfg(feature = "pem")]
pub fn serialize_request_pem(&self, subject_key: &KeyPair) -> Result<String, Error> {
let contents = self.serialize_request_der(subject_key)?;
let p = Pem::new("CERTIFICATE REQUEST", contents);
let p = Pem::new("CERTIFICATE REQUEST", contents.to_vec());
Ok(pem::encode_config(&p, ENCODE_CONFIG))
}
}
Expand Down Expand Up @@ -218,7 +229,7 @@ impl CertificateParams {
#[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())
Self::from_ca_cert_der(&certificate.contents().into())
}

/// Parses an existing ca certificate from the DER format.
Expand All @@ -236,8 +247,14 @@ impl CertificateParams {
/// 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.
///
/// You can use [`rustls_pemfile::certs`] to get the `ca_cert` input. If
/// you have already a byte slice, just calling `into()` and taking a reference
/// will convert it to [`CertificateDer`].
///
/// [`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: &[u8]) -> Result<Self, Error> {
pub 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))?;

Expand Down Expand Up @@ -802,12 +819,17 @@ impl CertificateParams {
Ok(())
})
}

/// Generate and serialize a certificate signed by the given issuer.
///
/// As the return type implements `Deref<Target = [u8]>`, in can easily be saved
/// to a file like a byte slice.
pub(crate) fn serialize_der_with_signer<K: PublicKeyData>(
&self,
pub_key: &K,
issuer: &KeyPair,
issuer_name: &DistinguishedName,
) -> Result<Vec<u8>, Error> {
) -> Result<CertificateDer<'static>, Error> {
yasna::try_construct_der(|writer| {
writer.write_sequence(|writer| {
let tbs_cert_list_serialized = yasna::try_construct_der(|writer| {
Expand All @@ -826,6 +848,7 @@ impl CertificateParams {
Ok(())
})
})
.map(CertificateDer::from)
}
}

Expand Down
13 changes: 11 additions & 2 deletions rcgen/src/csr.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
#[cfg(feature = "x509-parser")]
use pki_types::CertificateSigningRequestDer;

use std::hash::Hash;

use crate::{Certificate, CertificateParams, Error, KeyPair, PublicKeyData, SignatureAlgorithm};
Expand Down Expand Up @@ -36,15 +39,21 @@ impl CertificateSigningRequestParams {
#[cfg(all(feature = "pem", feature = "x509-parser"))]
pub fn from_pem(pem_str: &str) -> Result<Self, Error> {
let csr = pem::parse(pem_str).or(Err(Error::CouldNotParseCertificationRequest))?;
Self::from_der(csr.contents())
Self::from_der(&csr.contents().into())
}

/// Parse a certificate signing request from DER-encoded bytes
///
/// Currently, this only supports the `Subject Alternative Name` extension.
/// On encountering other extensions, this function will return an error.
///
/// You can use [`rustls_pemfile::csr`] to get the `csr` input. If
/// you have already a byte slice, just calling `into()` and taking a reference
/// will convert it to [`CertificateSigningRequestDer`].
///
/// [`rustls_pemfile::csr`]: https://docs.rs/rustls-pemfile/latest/rustls_pemfile/fn.csr.html
#[cfg(feature = "x509-parser")]
pub fn from_der(csr: &[u8]) -> Result<Self, Error> {
pub fn from_der(csr: &CertificateSigningRequestDer<'_>) -> Result<Self, Error> {
use x509_parser::prelude::FromDer;
let csr = x509_parser::certification_request::X509CertificationRequest::from_der(csr)
.map_err(|_| Error::CouldNotParseCertificationRequest)?
Expand Down
75 changes: 59 additions & 16 deletions rcgen/src/key_pair.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#[cfg(feature = "pem")]
use pem::Pem;
#[cfg(feature = "crypto")]
use pki_types::PrivatePkcs8KeyDer;
use std::fmt;
use yasna::DERWriter;

Expand Down Expand Up @@ -158,8 +160,14 @@ impl KeyPair {
/// Parses the key pair from the DER format
///
/// Equivalent to using the [`TryFrom`] implementation.
///
/// You can use [`rustls_pemfile::private_key`] to get the `der` input. If
/// you have already a byte slice, just calling `into()` and taking a reference
/// will convert it to a [`PrivatePkcs8KeyDer`].
///
/// [`rustls_pemfile::private_key`]: https://docs.rs/rustls-pemfile/latest/rustls_pemfile/fn.private_key.html
#[cfg(feature = "crypto")]
pub fn from_der(der: &[u8]) -> Result<Self, Error> {
pub fn from_der(der: &PrivatePkcs8KeyDer<'_>) -> Result<Self, Error> {
der.try_into()
}

Expand All @@ -169,11 +177,15 @@ impl KeyPair {
}

/// Parses the key pair from the ASCII PEM format
///
/// The key must be a DER-encoded plaintext private key; as specified in PKCS #8/RFC 5958;
///
/// Appears as "PRIVATE KEY" in PEM files.
#[cfg(all(feature = "pem", feature = "crypto"))]
pub fn from_pem(pem_str: &str) -> Result<Self, Error> {
let private_key = pem::parse(pem_str)._err()?;
let private_key_der: &[_] = private_key.contents();
private_key_der.try_into()
Self::from_der(&private_key_der.into())
}

/// Obtains the key pair from a raw public key and a remote private key
Expand All @@ -188,6 +200,9 @@ impl KeyPair {
/// Obtains the key pair from a DER formatted key
/// using the specified [`SignatureAlgorithm`]
///
/// The key must be a DER-encoded plaintext private key; as specified in PKCS #8/RFC 5958;
///
/// Appears as "PRIVATE KEY" in PEM files
/// Same as [from_pem_and_sign_algo](Self::from_pem_and_sign_algo).
#[cfg(all(feature = "pem", feature = "crypto"))]
pub fn from_pem_and_sign_algo(
Expand All @@ -196,7 +211,7 @@ impl KeyPair {
) -> Result<Self, Error> {
let private_key = pem::parse(pem_str)._err()?;
let private_key_der: &[_] = private_key.contents();
Self::from_der_and_sign_algo(private_key_der, alg)
Self::from_der_and_sign_algo(&PrivatePkcs8KeyDer::from(private_key_der), alg)
}

/// Obtains the key pair from a DER formatted key
Expand All @@ -208,39 +223,45 @@ impl KeyPair {
/// key pair. However, sometimes multiple signature algorithms fit for the
/// same der key. In that instance, you can use this function to precisely
/// specify the `SignatureAlgorithm`.
///
/// You can use [`rustls_pemfile::private_key`] to get the `pkcs8` input. If
/// you have already a byte slice, just calling `into()` and taking a reference
/// will convert it to a [`PrivatePkcs8KeyDer`].
///
/// [`rustls_pemfile::private_key`]: https://docs.rs/rustls-pemfile/latest/rustls_pemfile/fn.private_key.html
#[cfg(feature = "crypto")]
pub fn from_der_and_sign_algo(
pkcs8: &[u8],
pkcs8: &PrivatePkcs8KeyDer<'_>,
alg: &'static SignatureAlgorithm,
) -> Result<Self, Error> {
let rng = &SystemRandom::new();
let pkcs8_vec = pkcs8.to_vec();
let serialized_der = pkcs8.secret_pkcs8_der().to_vec();

let kind = if alg == &PKCS_ED25519 {
KeyPairKind::Ed(Ed25519KeyPair::from_pkcs8_maybe_unchecked(pkcs8)._err()?)
KeyPairKind::Ed(Ed25519KeyPair::from_pkcs8_maybe_unchecked(&serialized_der)._err()?)
} else if alg == &PKCS_ECDSA_P256_SHA256 {
KeyPairKind::Ec(ecdsa_from_pkcs8(
&signature::ECDSA_P256_SHA256_ASN1_SIGNING,
pkcs8,
&serialized_der,
rng,
)?)
} else if alg == &PKCS_ECDSA_P384_SHA384 {
KeyPairKind::Ec(ecdsa_from_pkcs8(
&signature::ECDSA_P384_SHA384_ASN1_SIGNING,
pkcs8,
&serialized_der,
rng,
)?)
} else if alg == &PKCS_RSA_SHA256 {
let rsakp = RsaKeyPair::from_pkcs8(pkcs8)._err()?;
let rsakp = RsaKeyPair::from_pkcs8(&serialized_der)._err()?;
KeyPairKind::Rsa(rsakp, &signature::RSA_PKCS1_SHA256)
} else if alg == &PKCS_RSA_SHA384 {
let rsakp = RsaKeyPair::from_pkcs8(pkcs8)._err()?;
let rsakp = RsaKeyPair::from_pkcs8(&serialized_der)._err()?;
KeyPairKind::Rsa(rsakp, &signature::RSA_PKCS1_SHA384)
} else if alg == &PKCS_RSA_SHA512 {
let rsakp = RsaKeyPair::from_pkcs8(pkcs8)._err()?;
let rsakp = RsaKeyPair::from_pkcs8(&serialized_der)._err()?;
KeyPairKind::Rsa(rsakp, &signature::RSA_PKCS1_SHA512)
} else if alg == &PKCS_RSA_PSS_SHA256 {
let rsakp = RsaKeyPair::from_pkcs8(pkcs8)._err()?;
let rsakp = RsaKeyPair::from_pkcs8(&serialized_der)._err()?;
KeyPairKind::Rsa(rsakp, &signature::RSA_PSS_SHA256)
} else {
panic!("Unknown SignatureAlgorithm specified!");
Expand All @@ -249,14 +270,22 @@ impl KeyPair {
Ok(KeyPair {
kind,
alg,
serialized_der: pkcs8_vec,
serialized_der,
})
}

/// Parses the key pair from the DER format
///
/// You can use [`rustls_pemfile::private_key`] to get the `pkcs8` input. If
/// you have already a byte slice, just calling `into()` and taking a reference
/// will convert it to a [`PrivatePkcs8KeyDer`].
///
/// [`rustls_pemfile::private_key`]: https://docs.rs/rustls-pemfile/latest/rustls_pemfile/fn.private_key.html
#[cfg(feature = "crypto")]
pub(crate) fn from_raw(
pkcs8: &[u8],
pkcs8: &PrivatePkcs8KeyDer,
) -> Result<(KeyPairKind, &'static SignatureAlgorithm), Error> {
let pkcs8 = pkcs8.secret_pkcs8_der();
let rng = SystemRandom::new();
let (kind, alg) = if let Ok(edkp) = Ed25519KeyPair::from_pkcs8_maybe_unchecked(pkcs8) {
(KeyPairKind::Ed(edkp), &PKCS_ED25519)
Expand Down Expand Up @@ -399,7 +428,7 @@ impl TryFrom<&[u8]> for KeyPair {
type Error = Error;

fn try_from(pkcs8: &[u8]) -> Result<KeyPair, Error> {
let (kind, alg) = KeyPair::from_raw(pkcs8)?;
let (kind, alg) = KeyPair::from_raw(&pkcs8.into())?;
Ok(KeyPair {
kind,
alg,
Expand All @@ -413,7 +442,7 @@ impl TryFrom<Vec<u8>> for KeyPair {
type Error = Error;

fn try_from(pkcs8: Vec<u8>) -> Result<KeyPair, Error> {
let (kind, alg) = KeyPair::from_raw(pkcs8.as_slice())?;
let (kind, alg) = KeyPair::from_raw(&pkcs8.as_slice().into())?;
Ok(KeyPair {
kind,
alg,
Expand All @@ -422,6 +451,20 @@ impl TryFrom<Vec<u8>> for KeyPair {
}
}

#[cfg(feature = "crypto")]
impl TryFrom<&PrivatePkcs8KeyDer<'_>> for KeyPair {
type Error = Error;

fn try_from(pkcs8: &PrivatePkcs8KeyDer) -> Result<KeyPair, Error> {
let (kind, alg) = KeyPair::from_raw(pkcs8)?;
Ok(KeyPair {
kind,
alg,
serialized_der: pkcs8.secret_pkcs8_der().into(),
})
}
}

/// The key size used for RSA key generation
#[cfg(all(feature = "crypto", feature = "aws_lc_rs", not(feature = "ring")))]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
Expand Down
Loading