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.14.3
to
0.14.4
+1
-1
.cargo_vcs_info.json
{
"git": {
"sha1": "620e2195d8574bb4d4fd753a3c82fd5eca739b39"
"sha1": "9f7fbb653e505cf0f4a84d8eb25fc3827216a30f"
},
"path_in_vcs": "rcgen"
}

@@ -75,5 +75,5 @@ # This file is automatically @generated by Cargo.

name = "aws-lc-rs"
version = "1.13.1"
version = "1.13.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93fcc8f365936c834db5514fc45aee5b1202d677e6b40e48468aaaa8183ca8c7"
checksum = "5c953fe1ba023e6b7730c0d4b031d06f267f23a46167dcbd40316644b10a17ba"
dependencies = [

@@ -87,5 +87,5 @@ "aws-lc-fips-sys",

name = "aws-lc-sys"
version = "0.29.0"
version = "0.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61b1d86e7705efe1be1b569bab41d4fa1e14e220b60a160f78de2db687add079"
checksum = "dbfd150b5dbdb988bcc8fb1fe787eb6b7ee6180ca24da683b61ea5405f3d43ff"
dependencies = [

@@ -135,26 +135,2 @@ "bindgen",

[[package]]
name = "botan"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d4c7647d67c53194fa0740404c6c508880aef2bfe99a9868dbb4b86f090377"
dependencies = [
"botan-sys",
]
[[package]]
name = "botan-src"
version = "0.30701.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c4e1c7910f3b4712aed10e4259eca77e79ed84c1b023098c8eac596b993fc44"
[[package]]
name = "botan-sys"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04285fa0c094cc9961fe435b1b279183db9394844ad82ce483aa6196c0e6da38"
dependencies = [
"botan-src",
]
[[package]]
name = "cc"

@@ -558,6 +534,5 @@ version = "1.2.27"

name = "rcgen"
version = "0.14.3"
version = "0.14.4"
dependencies = [
"aws-lc-rs",
"botan",
"openssl",

@@ -567,3 +542,2 @@ "pem",

"rustls-pki-types",
"rustls-webpki",
"time",

@@ -656,13 +630,2 @@ "x509-parser",

[[package]]
name = "rustls-webpki"
version = "0.103.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4a72fe2bcf7a6ac6fd7d0b9e5cb68aeb7d4c0a0271730218b3e92d43b4eb435"
dependencies = [
"ring",
"rustls-pki-types",
"untrusted",
]
[[package]]
name = "serde"

@@ -977,5 +940,5 @@ version = "1.0.219"

name = "x509-parser"
version = "0.17.0"
version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4569f339c0c402346d4a75a9e39cf8dad310e287eef1ff56d4c68e5067f53460"
checksum = "eb3e137310115a65136898d2079f003ce33331a6c4b0d51f1531d1be082b6425"
dependencies = [

@@ -982,0 +945,0 @@ "asn1-rs",

@@ -16,3 +16,3 @@ # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO

name = "rcgen"
version = "0.14.3"
version = "0.14.4"
build = false

@@ -51,2 +51,6 @@ autolib = false

]
aws_lc_rs_unstable = [
"aws_lc_rs",
"aws-lc-rs/unstable",
]
crypto = []

@@ -93,24 +97,4 @@ default = [

[[test]]
name = "botan"
path = "tests/botan.rs"
[[test]]
name = "generic"
path = "tests/generic.rs"
[[test]]
name = "openssl"
path = "tests/openssl.rs"
[[test]]
name = "util"
path = "tests/util.rs"
[[test]]
name = "webpki"
path = "tests/webpki.rs"
[dependencies.aws-lc-rs]
version = "1.6.0"
version = "1.13.3"
optional = true

@@ -136,3 +120,3 @@ default-features = false

[dependencies.x509-parser]
version = "0.17"
version = "0.18"
features = ["verify"]

@@ -152,25 +136,3 @@ optional = true

[dev-dependencies.botan]
version = "0.11"
features = ["vendored"]
[dev-dependencies.pki-types]
version = "1"
package = "rustls-pki-types"
[dev-dependencies.ring]
version = "0.17"
[dev-dependencies.rustls-webpki]
version = "0.103"
features = [
"ring",
"std",
]
[dev-dependencies.x509-parser]
version = "0.17"
features = ["verify"]
[target."cfg(unix)".dev-dependencies.openssl]
version = "0.10"

@@ -30,2 +30,4 @@ #[cfg(feature = "crypto")]

use crate::{sign_algo::SignatureAlgorithm, Error};
#[cfg(all(feature = "aws_lc_rs_unstable", not(feature = "fips")))]
use aws_lc_rs::unstable::signature::PqdsaKeyPair;

@@ -40,2 +42,5 @@ /// A key pair variant

Ed(Ed25519KeyPair),
/// A Pqdsa key pair
#[cfg(all(feature = "aws_lc_rs_unstable", not(feature = "fips")))]
Pq(PqdsaKeyPair),
/// A RSA key pair

@@ -51,2 +56,4 @@ Rsa(RsaKeyPair, &'static dyn RsaEncoding),

Self::Ed(key_pair) => write!(f, "{key_pair:?}"),
#[cfg(all(feature = "aws_lc_rs_unstable", not(feature = "fips")))]
Self::Pq(key_pair) => write!(f, "{key_pair:?}"),
Self::Rsa(key_pair, _) => write!(f, "{key_pair:?}"),

@@ -58,8 +65,2 @@ }

/// A key pair used to sign certificates and CSRs
///
/// Note that ring, the underlying library to handle RSA keys
/// requires them to be in a special format, meaning that
/// `openssl genrsa` doesn't work. See ring's [documentation](ring::signature::RsaKeyPair::from_pkcs8)
/// for how to generate RSA keys in the wanted format
/// and conversion between the formats.
#[cfg(feature = "crypto")]

@@ -124,2 +125,13 @@ pub struct KeyPair {

},
#[cfg(all(feature = "aws_lc_rs_unstable", not(feature = "fips")))]
SignAlgo::PqDsa(sign_alg) => {
let key_pair = PqdsaKeyPair::generate(sign_alg)._err()?;
let key_pair_serialized = key_pair.to_pkcs8()._err()?.as_ref().to_vec();
Ok(KeyPair {
kind: KeyPairKind::Pq(key_pair),
alg,
serialized_der: key_pair_serialized,
})
},
#[cfg(feature = "aws_lc_rs")]

@@ -386,5 +398,8 @@ SignAlgo::Rsa(sign_alg) => Self::generate_rsa_inner(alg, sign_alg, KeySize::Rsa2048),

///
/// The key is in raw format, as how [`ring::signature::KeyPair::public_key`]
/// would output, and how [`ring::signature::UnparsedPublicKey::verify`]
/// The key is in raw format, as how [`KeyPair::public_key()`][public_key]
/// would output, and how [`UnparsedPublicKey::verify()`][verify]
/// would accept.
///
/// [public_key]: crate::ring_like::signature::KeyPair::public_key()
/// [verify]: crate::ring_like::signature::UnparsedPublicKey::verify()
pub fn public_key_raw(&self) -> &[u8] {

@@ -445,2 +460,8 @@ self.der_bytes()

KeyPairKind::Ed(kp) => kp.sign(msg).as_ref().to_owned(),
#[cfg(all(feature = "aws_lc_rs_unstable", not(feature = "fips")))]
KeyPairKind::Pq(kp) => {
let mut signature = vec![0; kp.algorithm().signature_len()];
kp.sign(msg, &mut signature)._err()?;
signature
},
KeyPairKind::Rsa(kp, padding_alg) => {

@@ -463,2 +484,4 @@ let system_random = SystemRandom::new();

KeyPairKind::Ed(kp) => kp.public_key().as_ref(),
#[cfg(all(feature = "aws_lc_rs_unstable", not(feature = "fips")))]
KeyPairKind::Pq(kp) => kp.public_key().as_ref(),
KeyPairKind::Rsa(kp, _) => kp.public_key().as_ref(),

@@ -465,0 +488,0 @@ }

@@ -28,2 +28,9 @@ /// pkcs-9-at-extensionRequest in [RFC 2985](https://www.rfc-editor.org/rfc/rfc2985#appendix-A)

#[cfg(all(feature = "aws_lc_rs_unstable", not(feature = "fips")))]
pub(crate) const ML_DSA_44: &[u64] = &[2, 16, 840, 1, 101, 3, 4, 3, 17];
#[cfg(all(feature = "aws_lc_rs_unstable", not(feature = "fips")))]
pub(crate) const ML_DSA_65: &[u64] = &[2, 16, 840, 1, 101, 3, 4, 3, 18];
#[cfg(all(feature = "aws_lc_rs_unstable", not(feature = "fips")))]
pub(crate) const ML_DSA_87: &[u64] = &[2, 16, 840, 1, 101, 3, 4, 3, 19];
/// rsaEncryption in [RFC 4055](https://www.rfc-editor.org/rfc/rfc4055#section-6)

@@ -30,0 +37,0 @@ pub(crate) const RSA_ENCRYPTION: &[u64] = &[1, 2, 840, 113549, 1, 1, 1];

@@ -11,2 +11,6 @@ use std::fmt;

use crate::Error;
#[cfg(all(feature = "aws_lc_rs_unstable", not(feature = "fips")))]
use aws_lc_rs::unstable::signature::{
PqdsaSigningAlgorithm, ML_DSA_44_SIGNING, ML_DSA_65_SIGNING, ML_DSA_87_SIGNING,
};

@@ -18,2 +22,4 @@ #[cfg(feature = "crypto")]

EdDsa(&'static EdDSAParameters),
#[cfg(all(feature = "aws_lc_rs_unstable", not(feature = "fips")))]
PqDsa(&'static PqdsaSigningAlgorithm),
Rsa(&'static dyn RsaEncoding),

@@ -214,2 +220,32 @@ }

};
/// ML-DSA-44 signing as per <https://www.ietf.org/archive/id/draft-ietf-lamps-dilithium-certificates-12.html#name-identifiers>.
#[cfg(all(feature = "aws_lc_rs_unstable", not(feature = "fips")))]
pub static PKCS_ML_DSA_44: SignatureAlgorithm = SignatureAlgorithm {
oids_sign_alg: &[ML_DSA_44],
#[cfg(all(feature = "crypto", feature = "aws_lc_rs_unstable"))]
sign_alg: SignAlgo::PqDsa(&ML_DSA_44_SIGNING),
oid_components: ML_DSA_44,
params: SignatureAlgorithmParams::None,
};
/// ML-DSA-44 signing as per <https://www.ietf.org/archive/id/draft-ietf-lamps-dilithium-certificates-12.html#name-identifiers>.
#[cfg(all(feature = "aws_lc_rs_unstable", not(feature = "fips")))]
pub static PKCS_ML_DSA_65: SignatureAlgorithm = SignatureAlgorithm {
oids_sign_alg: &[ML_DSA_65],
#[cfg(all(feature = "crypto", feature = "aws_lc_rs_unstable"))]
sign_alg: SignAlgo::PqDsa(&ML_DSA_65_SIGNING),
oid_components: ML_DSA_65,
params: SignatureAlgorithmParams::None,
};
/// ML-DSA-44 signing as per <https://www.ietf.org/archive/id/draft-ietf-lamps-dilithium-certificates-12.html#name-identifiers>.
#[cfg(all(feature = "aws_lc_rs_unstable", not(feature = "fips")))]
pub static PKCS_ML_DSA_87: SignatureAlgorithm = SignatureAlgorithm {
oids_sign_alg: &[ML_DSA_87],
#[cfg(all(feature = "crypto", feature = "aws_lc_rs_unstable"))]
sign_alg: SignAlgo::PqDsa(&ML_DSA_87_SIGNING),
oid_components: ML_DSA_87,
params: SignatureAlgorithmParams::None,
};
}

@@ -216,0 +252,0 @@ // Signature algorithm IDs as per https://tools.ietf.org/html/rfc4055

#![cfg(all(feature = "crypto", feature = "x509-parser"))]
use time::{Duration, OffsetDateTime};
use rcgen::{BasicConstraints, Certificate, CertificateParams, DnType, IsCa, Issuer};
use rcgen::{CertificateRevocationListParams, RevocationReason, RevokedCertParams};
use rcgen::{DnValue, KeyPair};
use rcgen::{KeyUsagePurpose, SerialNumber};
mod util;
fn default_params() -> (CertificateParams, KeyPair) {
let (mut params, key_pair) = util::default_params();
// Botan has a sanity check that enforces a maximum expiration date
params.not_after = rcgen::date_time_ymd(3016, 1, 1);
(params, key_pair)
}
fn check_cert(cert_der: &[u8], cert: &Certificate) {
println!("{}", cert.pem());
check_cert_ca(cert_der, cert, cert_der);
}
fn check_cert_ca(cert_der: &[u8], _cert: &Certificate, ca_der: &[u8]) {
println!(
"botan version: {}",
botan::Version::current().unwrap().string
);
let trust_anchor = botan::Certificate::load(ca_der).unwrap();
let end_entity_cert = botan::Certificate::load(cert_der).unwrap();
// Set time to Jan 10, 2004
const REFERENCE_TIME: Option<u64> = Some(0x40_00_00_00);
// Verify the certificate
end_entity_cert
.verify(
&[],
&[&trust_anchor],
None,
Some("crabs.crabs"),
REFERENCE_TIME,
)
.unwrap();
// TODO perform a full handshake
}
#[test]
fn test_botan() {
let (params, key_pair) = default_params();
let cert = params.self_signed(&key_pair).unwrap();
// Now verify the certificate.
check_cert(cert.der(), &cert);
}
#[test]
fn test_botan_256() {
let (params, _) = default_params();
let key_pair = KeyPair::generate_for(&rcgen::PKCS_ECDSA_P256_SHA256).unwrap();
let cert = params.self_signed(&key_pair).unwrap();
// Now verify the certificate.
check_cert(cert.der(), &cert);
}
#[test]
fn test_botan_384() {
let (params, _) = default_params();
let key_pair = KeyPair::generate_for(&rcgen::PKCS_ECDSA_P384_SHA384).unwrap();
let cert = params.self_signed(&key_pair).unwrap();
// Now verify the certificate.
check_cert(cert.der(), &cert);
}
#[test]
#[cfg(feature = "aws_lc_rs")]
fn test_botan_521() {
let (params, _) = default_params();
let key_pair = KeyPair::generate_for(&rcgen::PKCS_ECDSA_P521_SHA512).unwrap();
let cert = params.self_signed(&key_pair).unwrap();
// Now verify the certificate.
check_cert(cert.der(), &cert);
}
#[test]
fn test_botan_25519() {
let (params, _) = default_params();
let key_pair = KeyPair::generate_for(&rcgen::PKCS_ED25519).unwrap();
let cert = params.self_signed(&key_pair).unwrap();
// Now verify the certificate.
check_cert(cert.der(), &cert);
}
#[test]
fn test_botan_25519_v1_given() {
let (params, _) = default_params();
let key_pair = KeyPair::from_pem(util::ED25519_TEST_KEY_PAIR_PEM_V1).unwrap();
let cert = params.self_signed(&key_pair).unwrap();
// Now verify the certificate.
check_cert(cert.der(), &cert);
}
#[test]
fn test_botan_25519_v2_given() {
let (params, _) = default_params();
let key_pair = KeyPair::from_pem(util::ED25519_TEST_KEY_PAIR_PEM_V2).unwrap();
let cert = params.self_signed(&key_pair).unwrap();
// Now verify the certificate.
check_cert(cert.der(), &cert);
}
#[test]
fn test_botan_rsa_given() {
let (params, _) = default_params();
let key_pair = KeyPair::from_pem(util::RSA_TEST_KEY_PAIR_PEM).unwrap();
let cert = params.self_signed(&key_pair).unwrap();
// Now verify the certificate.
check_cert(cert.der(), &cert);
}
#[test]
fn test_botan_separate_ca() {
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();
let mut params = CertificateParams::new(vec!["crabs.crabs".to_string()]).unwrap();
params
.distinguished_name
.push(DnType::OrganizationName, "Crab widgits SE");
params
.distinguished_name
.push(DnType::CommonName, "Dev domain");
// Botan has a sanity check that enforces a maximum expiration date
params.not_after = rcgen::date_time_ymd(3016, 1, 1);
let key_pair = KeyPair::generate().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());
}
#[cfg(feature = "x509-parser")]
#[test]
fn test_botan_imported_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 ca_cert_der = ca_cert.der();
let ca = Issuer::from_ca_cert_der(ca_cert.der(), ca_key).unwrap();
let mut params = CertificateParams::new(vec!["crabs.crabs".to_string()]).unwrap();
params
.distinguished_name
.push(DnType::OrganizationName, "Crab widgits SE");
params
.distinguished_name
.push(DnType::CommonName, "Dev domain");
// Botan has a sanity check that enforces a maximum expiration date
params.not_after = rcgen::date_time_ymd(3016, 1, 1);
let key_pair = KeyPair::generate().unwrap();
let cert = params.signed_by(&key_pair, &ca).unwrap();
check_cert_ca(cert.der(), &cert, ca_cert_der);
}
#[cfg(feature = "x509-parser")]
#[test]
fn test_botan_imported_ca_with_printable_string() {
let (mut params, imported_ca_key) = default_params();
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(&imported_ca_key).unwrap();
let ca = Issuer::from_ca_cert_der(ca_cert.der(), imported_ca_key).unwrap();
let mut params = CertificateParams::new(vec!["crabs.crabs".to_string()]).unwrap();
params
.distinguished_name
.push(DnType::OrganizationName, "Crab widgits SE");
params
.distinguished_name
.push(DnType::CommonName, "Dev domain");
// Botan has a sanity check that enforces a maximum expiration date
params.not_after = rcgen::date_time_ymd(3016, 1, 1);
let key_pair = KeyPair::generate().unwrap();
let cert = params.signed_by(&key_pair, &ca).unwrap();
check_cert_ca(cert.der(), &cert, ca_cert.der());
}
#[test]
fn test_botan_crl_parse() {
// Create an issuer CA.
let alg = &rcgen::PKCS_ECDSA_P256_SHA256;
let (mut issuer, _) = util::default_params();
issuer.is_ca = IsCa::Ca(BasicConstraints::Unconstrained);
issuer.key_usages = vec![
KeyUsagePurpose::KeyCertSign,
KeyUsagePurpose::DigitalSignature,
KeyUsagePurpose::CrlSign,
];
let issuer_key = KeyPair::generate_for(alg).unwrap();
let ca = Issuer::new(issuer, issuer_key);
// Create an end entity cert issued by the issuer.
let (mut ee, _) = util::default_params();
ee.is_ca = IsCa::NoCa;
ee.serial_number = Some(SerialNumber::from(99999));
// Botan has a sanity check that enforces a maximum expiration date
ee.not_after = rcgen::date_time_ymd(3016, 1, 1);
let ee_key = KeyPair::generate_for(alg).unwrap();
let ee_cert = ee.signed_by(&ee_key, &ca).unwrap();
let botan_ee = botan::Certificate::load(ee_cert.der()).unwrap();
// Generate a CRL with the issuer that revokes the EE cert.
let now = OffsetDateTime::now_utc();
let crl = CertificateRevocationListParams {
this_update: now,
next_update: now + Duration::weeks(1),
crl_number: rcgen::SerialNumber::from(1234),
issuing_distribution_point: None,
revoked_certs: vec![RevokedCertParams {
serial_number: ee.serial_number.clone().unwrap(),
revocation_time: now,
reason_code: Some(RevocationReason::KeyCompromise),
invalidity_date: None,
}],
key_identifier_method: rcgen::KeyIdMethod::Sha256,
};
let crl = crl.signed_by(&ca).unwrap();
// We should be able to load the CRL in both serializations.
botan::CRL::load(crl.pem().unwrap().as_ref()).unwrap();
let crl = botan::CRL::load(crl.der()).unwrap();
// We should find the EE cert revoked.
assert!(crl.is_revoked(&botan_ee).unwrap());
}
#![cfg(feature = "crypto")]
mod util;
#[cfg(feature = "pem")]
mod test_key_params_mismatch {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
fn generate_hash<T: Hash>(subject: &T) -> u64 {
let mut hasher = DefaultHasher::new();
subject.hash(&mut hasher);
hasher.finish()
}
#[test]
fn test_key_params_mismatch() {
let available_key_params = [
&rcgen::PKCS_RSA_SHA256,
&rcgen::PKCS_ECDSA_P256_SHA256,
&rcgen::PKCS_ECDSA_P384_SHA384,
#[cfg(feature = "aws_lc_rs")]
&rcgen::PKCS_ECDSA_P521_SHA512,
&rcgen::PKCS_ED25519,
];
for (i, kalg_1) in available_key_params.iter().enumerate() {
for (j, kalg_2) in available_key_params.iter().enumerate() {
if i == j {
assert_eq!(*kalg_1, *kalg_2);
assert_eq!(generate_hash(*kalg_1), generate_hash(*kalg_2));
continue;
}
assert_ne!(*kalg_1, *kalg_2);
assert_ne!(generate_hash(*kalg_1), generate_hash(*kalg_2));
}
}
}
}
#[cfg(feature = "x509-parser")]
mod test_x509_custom_ext {
use crate::util;
use rcgen::CustomExtension;
use x509_parser::oid_registry::asn1_rs;
use x509_parser::prelude::{
FromDer, ParsedCriAttribute, X509Certificate, X509CertificationRequest,
};
#[test]
fn custom_ext() {
// Create an imaginary critical custom extension for testing.
let test_oid = asn1_rs::Oid::from(&[2, 5, 29, 999999]).unwrap();
let test_ext = yasna::construct_der(|writer| {
writer.write_utf8_string("🦀 greetz to ferris 🦀");
});
let mut custom_ext = CustomExtension::from_oid_content(
test_oid.iter().unwrap().collect::<Vec<u64>>().as_slice(),
test_ext.clone(),
);
custom_ext.set_criticality(true);
// Generate a certificate with the custom extension, parse it with x509-parser.
let (mut params, test_key) = util::default_params();
params.custom_extensions = vec![custom_ext];
// Ensure the custom exts. being omitted into a CSR doesn't require SAN ext being present.
// See https://github.com/rustls/rcgen/issues/122
params.subject_alt_names = Vec::default();
let test_cert = params.self_signed(&test_key).unwrap();
let (_, x509_test_cert) = X509Certificate::from_der(test_cert.der()).unwrap();
// We should be able to find the extension by OID, with expected criticality and value.
let favorite_drink_ext = x509_test_cert
.get_extension_unique(&test_oid)
.expect("invalid extensions")
.expect("missing custom extension");
assert!(favorite_drink_ext.critical);
assert_eq!(favorite_drink_ext.value, test_ext);
// Generate a CSR with the custom extension, parse it with x509-parser.
let test_cert_csr = params.serialize_request(&test_key).unwrap();
let (_, x509_csr) = X509CertificationRequest::from_der(test_cert_csr.der()).unwrap();
// We should find that the CSR contains requested extensions.
// Note: we can't use `x509_csr.requested_extensions()` here because it maps the raw extension
// request extensions to their parsed form, and of course x509-parser doesn't parse our custom extension.
let exts = x509_csr
.certification_request_info
.iter_attributes()
.find_map(|attr| {
if let ParsedCriAttribute::ExtensionRequest(requested) = &attr.parsed_attribute() {
Some(requested.extensions.iter().collect::<Vec<_>>())
} else {
None
}
})
.expect("missing requested extensions");
// We should find the custom extension with expected criticality and value.
let custom_ext = exts
.iter()
.find(|ext| ext.oid == test_oid)
.expect("missing requested custom extension");
assert!(custom_ext.critical);
assert_eq!(custom_ext.value, test_ext);
}
}
#[cfg(feature = "x509-parser")]
mod test_csr_custom_attributes {
use rcgen::{Attribute, CertificateParams, KeyPair};
use x509_parser::{
der_parser::Oid,
prelude::{FromDer, X509CertificationRequest},
};
/// Test serializing a CSR with custom attributes.
/// This test case uses `challengePassword` from [RFC 2985], a simple
/// ATTRIBUTE that contains a single UTF8String.
///
/// [RFC 2985]: <https://datatracker.ietf.org/doc/html/rfc2985>
#[test]
fn test_csr_custom_attributes() {
// OID for challengePassword
const CHALLENGE_PWD_OID: &[u64] = &[1, 2, 840, 113549, 1, 9, 7];
// Attribute values for challengePassword
let challenge_pwd_values = yasna::try_construct_der::<_, ()>(|writer| {
// Reminder: CSR attribute values are contained in a SET
writer.write_set(|writer| {
// Challenge passwords only have one value, a UTF8String
writer
.next()
.write_utf8_string("nobody uses challenge passwords anymore");
Ok(())
})
})
.unwrap();
// Challenge password attribute
let challenge_password_attribute = Attribute {
oid: CHALLENGE_PWD_OID,
values: challenge_pwd_values.clone(),
};
// Serialize a DER-encoded CSR
let params = CertificateParams::default();
let key_pair = KeyPair::generate().unwrap();
let csr = params
.serialize_request_with_attributes(&key_pair, vec![challenge_password_attribute])
.unwrap();
// Parse the CSR
let (_, x509_csr) = X509CertificationRequest::from_der(csr.der()).unwrap();
let parsed_attribute_value = x509_csr
.certification_request_info
.attributes_map()
.unwrap()
.get(&Oid::from(CHALLENGE_PWD_OID).unwrap())
.unwrap()
.value;
assert_eq!(parsed_attribute_value, challenge_pwd_values);
}
}
#[cfg(feature = "x509-parser")]
mod test_x509_parser_crl {
use crate::util;
use x509_parser::extensions::{DistributionPointName, ParsedExtension};
use x509_parser::num_bigint::BigUint;
use x509_parser::prelude::{FromDer, GeneralName, IssuingDistributionPoint, X509Certificate};
use x509_parser::revocation_list::CertificateRevocationList;
use x509_parser::x509::X509Version;
#[test]
fn parse_crl() {
// Create a CRL with one revoked cert, and an issuer to sign the CRL.
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());
let (_, x509_issuer) = X509Certificate::from_der(issuer.der()).unwrap();
// We should be able to parse the CRL with x509-parser without error.
let (_, x509_crl) =
CertificateRevocationList::from_der(crl.der()).expect("failed to parse CRL DER");
// The properties of the CRL should match expected.
assert_eq!(x509_crl.version().unwrap(), X509Version(1));
assert_eq!(x509_crl.issuer(), x509_issuer.subject());
assert_eq!(
x509_crl.last_update().to_datetime().unix_timestamp(),
crl_params.this_update.unix_timestamp()
);
assert_eq!(
x509_crl
.next_update()
.unwrap()
.to_datetime()
.unix_timestamp(),
crl_params.next_update.unix_timestamp()
);
let crl_number = BigUint::from_bytes_be(crl_params.crl_number.as_ref());
assert_eq!(x509_crl.crl_number().unwrap(), &crl_number);
// We should find the expected revoked certificate serial with the correct reason code.
let x509_revoked_cert = x509_crl
.iter_revoked_certificates()
.next()
.expect("failed to find revoked cert in CRL");
assert_eq!(x509_revoked_cert.user_certificate, revoked_cert_serial);
let (_, reason_code) = x509_revoked_cert.reason_code().unwrap();
assert_eq!(reason_code.0, revoked_cert.reason_code.unwrap() as u8);
// The issuing distribution point extension should be present and marked critical.
let issuing_dp_ext = x509_crl
.extensions()
.iter()
.find(|ext| {
ext.oid == x509_parser::oid_registry::OID_X509_EXT_ISSUER_DISTRIBUTION_POINT
})
.expect("failed to find issuing distribution point extension");
assert!(issuing_dp_ext.critical);
// The parsed issuing distribution point extension should match expected.
let ParsedExtension::IssuingDistributionPoint(idp) = issuing_dp_ext.parsed_extension()
else {
panic!("missing parsed CRL IDP ext");
};
assert_eq!(
idp,
&IssuingDistributionPoint {
only_contains_user_certs: true,
only_contains_ca_certs: false,
only_contains_attribute_certs: false,
indirect_crl: false,
only_some_reasons: None,
distribution_point: Some(DistributionPointName::FullName(vec![GeneralName::URI(
"http://example.com/crl",
)])),
}
);
// We should be able to verify the CRL signature with the issuer.
assert!(x509_crl.verify_signature(x509_issuer.public_key()).is_ok());
}
}
#[cfg(feature = "x509-parser")]
mod test_parse_crl_dps {
use crate::util;
use x509_parser::extensions::{DistributionPointName, ParsedExtension};
#[test]
fn parse_crl_dps() {
// Generate and parse a certificate that includes two CRL distribution points.
let der = util::cert_with_crl_dps();
let (_, parsed_cert) = x509_parser::parse_x509_certificate(&der).unwrap();
// We should find a CRL DP extension was parsed.
let crl_dps = parsed_cert
.get_extension_unique(&x509_parser::oid_registry::OID_X509_EXT_CRL_DISTRIBUTION_POINTS)
.expect("malformed CRL distribution points extension")
.expect("missing CRL distribution points extension");
// The extension should not be critical.
assert!(!crl_dps.critical);
// We should be able to parse the definition.
let crl_dps = match crl_dps.parsed_extension() {
ParsedExtension::CRLDistributionPoints(crl_dps) => crl_dps,
_ => panic!("unexpected parsed extension type"),
};
// There should be two DPs.
assert_eq!(crl_dps.points.len(), 2);
// Each distribution point should only include a distribution point name holding a sequence
// of general names.
let general_names = crl_dps
.points
.iter()
.flat_map(|dp| {
// We shouldn't find a cRLIssuer or onlySomeReasons field.
assert!(dp.crl_issuer.is_none());
assert!(dp.reasons.is_none());
match dp
.distribution_point
.as_ref()
.expect("missing distribution point name")
{
DistributionPointName::FullName(general_names) => general_names.iter(),
DistributionPointName::NameRelativeToCRLIssuer(_) => {
panic!("unexpected name relative to cRL issuer")
},
}
})
.collect::<Vec<_>>();
// All of the general names should be URIs.
let uris = general_names
.iter()
.map(|general_name| match general_name {
x509_parser::extensions::GeneralName::URI(uri) => *uri,
_ => panic!("unexpected general name type"),
})
.collect::<Vec<_>>();
// We should find the expected URIs.
assert_eq!(
uris,
&[
"http://example.com/crl.der",
"http://crls.example.com/1234",
"ldap://example.com/crl.der"
]
);
}
}
#[cfg(feature = "x509-parser")]
mod test_csr_extension_request {
use rcgen::{CertificateParams, ExtendedKeyUsagePurpose, KeyPair, KeyUsagePurpose};
use x509_parser::prelude::{FromDer, ParsedExtension, X509CertificationRequest};
#[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(_)
));
}
}
#[cfg(feature = "x509-parser")]
mod test_csr {
use rcgen::{
CertificateParams, CertificateSigningRequestParams, ExtendedKeyUsagePurpose, KeyPair,
KeyUsagePurpose,
};
#[test]
fn test_csr_roundtrip() {
// We should be able to serialize a CSR, and then parse the CSR.
let params = CertificateParams::default();
generate_and_test_parsed_csr(&params);
}
#[test]
fn test_csr_with_key_usages_roundtrip() {
let mut params = CertificateParams::default();
params.key_usages = vec![
KeyUsagePurpose::DigitalSignature,
KeyUsagePurpose::ContentCommitment,
KeyUsagePurpose::KeyEncipherment,
KeyUsagePurpose::DataEncipherment,
KeyUsagePurpose::KeyAgreement,
KeyUsagePurpose::KeyCertSign,
KeyUsagePurpose::CrlSign,
// It doesn't make sense to have both encipher and decipher only
// So we'll take this opportunity to test omitting a key usage
// KeyUsagePurpose::EncipherOnly,
KeyUsagePurpose::DecipherOnly,
];
generate_and_test_parsed_csr(&params);
}
#[test]
fn test_csr_with_extended_key_usages_roundtrip() {
let mut params = CertificateParams::default();
params.extended_key_usages = vec![
ExtendedKeyUsagePurpose::ServerAuth,
ExtendedKeyUsagePurpose::ClientAuth,
];
generate_and_test_parsed_csr(&params);
}
#[test]
fn test_csr_with_key_usgaes_and_extended_key_usages_roundtrip() {
let mut params = CertificateParams::default();
params.key_usages = vec![KeyUsagePurpose::DigitalSignature];
params.extended_key_usages = vec![ExtendedKeyUsagePurpose::ClientAuth];
generate_and_test_parsed_csr(&params);
}
fn generate_and_test_parsed_csr(params: &CertificateParams) {
// Generate a key pair for the CSR
let key_pair = KeyPair::generate().unwrap();
// Serialize the CSR into DER from the given parameters
let csr = params.serialize_request(&key_pair).unwrap();
// Parse the CSR we just serialized
let csrp = CertificateSigningRequestParams::from_der(csr.der()).unwrap();
// Ensure algorithms match.
assert_eq!(key_pair.algorithm(), csrp.public_key.algorithm());
// Assert that our parsed parameters match our initial parameters
assert_eq!(*params, csrp.params);
}
}
#[cfg(feature = "x509-parser")]
mod test_subject_alternative_name_criticality {
use x509_parser::certificate::X509Certificate;
use x509_parser::extensions::X509Extension;
use x509_parser::{oid_registry, parse_x509_certificate};
use crate::util::default_params;
#[test]
fn with_subject_sans_not_critical() {
let (params, keypair) = default_params();
assert!(
!params
.distinguished_name
.iter()
.collect::<Vec<_>>()
.is_empty(),
"non-empty subject required for test"
);
let cert = params.self_signed(&keypair).unwrap();
let cert = cert.der();
let (_, parsed) = parse_x509_certificate(cert).unwrap();
assert!(
!san_ext(&parsed).critical,
"with subject, SAN ext should not be critical"
);
}
#[test]
fn without_subject_sans_critical() {
let (mut params, keypair) = default_params();
params.distinguished_name = Default::default();
let cert = params.self_signed(&keypair).unwrap();
let cert = cert.der();
let (_, parsed) = parse_x509_certificate(cert).unwrap();
assert!(
san_ext(&parsed).critical,
"without subject, SAN ext should be critical"
);
}
fn san_ext<'cert>(cert: &'cert X509Certificate) -> &'cert X509Extension<'cert> {
cert.extensions()
.iter()
.find(|ext| ext.oid == oid_registry::OID_X509_EXT_SUBJECT_ALT_NAME)
.expect("missing SAN extension")
}
}
#![cfg(all(unix, feature = "pem"))]
use std::cell::RefCell;
use std::io::{Error, ErrorKind, Read, Result as ioResult, Write};
use std::rc::Rc;
use openssl::asn1::{Asn1Integer, Asn1Time};
use openssl::bn::BigNum;
use openssl::pkey::PKey;
use openssl::ssl::{HandshakeError, SslAcceptor, SslConnector, SslMethod};
use openssl::stack::Stack;
use openssl::x509::store::{X509Store, X509StoreBuilder};
use openssl::x509::{CrlStatus, X509Crl, X509Req, X509StoreContext, X509};
use rcgen::{
BasicConstraints, Certificate, CertificateParams, DnType, DnValue, GeneralSubtree, IsCa,
Issuer, KeyPair, NameConstraints,
};
mod util;
fn verify_cert_basic(cert: &Certificate) {
let cert_pem = cert.pem();
println!("{cert_pem}");
let x509 = X509::from_pem(cert_pem.as_bytes()).unwrap();
let mut builder = X509StoreBuilder::new().unwrap();
builder.add_cert(x509.clone()).unwrap();
let store: X509Store = builder.build();
let mut ctx = X509StoreContext::new().unwrap();
let mut stack = Stack::new().unwrap();
stack.push(x509.clone()).unwrap();
ctx.init(&store, &x509, stack.as_ref(), |ctx| {
ctx.verify_cert().unwrap();
Ok(())
})
.unwrap();
}
// TODO implement Debug manually instead of
// deriving it
#[derive(Clone, Debug)]
struct PipeInner([Vec<u8>; 2]);
#[derive(Clone, Debug)]
struct PipeEnd {
read_pos: usize,
/// Which end of the pipe
end_idx: usize,
inner: Rc<RefCell<PipeInner>>,
}
fn create_pipe() -> (PipeEnd, PipeEnd) {
let pipe_inner = PipeInner([Vec::new(), Vec::new()]);
let inner = Rc::new(RefCell::new(pipe_inner));
(
PipeEnd {
read_pos: 0,
end_idx: 0,
inner: inner.clone(),
},
PipeEnd {
read_pos: 0,
end_idx: 1,
inner,
},
)
}
impl Write for PipeEnd {
fn write(&mut self, buf: &[u8]) -> ioResult<usize> {
self.inner.borrow_mut().0[self.end_idx].extend_from_slice(buf);
Ok(buf.len())
}
fn flush(&mut self) -> ioResult<()> {
Ok(())
}
}
impl Read for PipeEnd {
fn read(&mut self, mut buf: &mut [u8]) -> ioResult<usize> {
let inner = self.inner.borrow_mut();
let r_sl = &inner.0[1 - self.end_idx][self.read_pos..];
if r_sl.is_empty() {
return Err(Error::new(ErrorKind::WouldBlock, "oh no!"));
}
let r = buf.len().min(r_sl.len());
std::io::copy(&mut &r_sl[..r], &mut buf)?;
self.read_pos += r;
Ok(r)
}
}
fn verify_cert(cert: &Certificate, key_pair: &KeyPair) {
verify_cert_basic(cert);
let key = key_pair.serialize_der();
verify_cert_ca(&cert.pem(), &key, &cert.pem());
}
fn verify_cert_ca(cert_pem: &str, key: &[u8], ca_cert_pem: &str) {
println!("{cert_pem}");
println!("{ca_cert_pem}");
let x509 = X509::from_pem(cert_pem.as_bytes()).unwrap();
let ca_x509 = X509::from_pem(ca_cert_pem.as_bytes()).unwrap();
let mut builder = X509StoreBuilder::new().unwrap();
builder.add_cert(ca_x509).unwrap();
let store: X509Store = builder.build();
let srv = SslMethod::tls_server();
let mut ssl_srv_ctx = SslAcceptor::mozilla_modern(srv).unwrap();
//let key = cert.serialize_private_key_der();
let pkey = PKey::private_key_from_der(key).unwrap();
ssl_srv_ctx.set_private_key(&pkey).unwrap();
ssl_srv_ctx.set_certificate(&x509).unwrap();
let cln = SslMethod::tls_client();
let mut ssl_cln_ctx = SslConnector::builder(cln).unwrap();
ssl_cln_ctx.set_cert_store(store);
let ssl_srv_ctx = ssl_srv_ctx.build();
let ssl_cln_ctx = ssl_cln_ctx.build();
let (pipe_end_1, pipe_end_2) = create_pipe();
let (mut ssl_srv_stream, mut ssl_cln_stream) = {
let mut srv_res = ssl_srv_ctx.accept(pipe_end_1);
let mut cln_res = ssl_cln_ctx.connect("crabs.crabs", pipe_end_2);
let mut ready = 0u8;
let mut iter_budget = 100;
loop {
match cln_res {
Ok(_) => ready |= 2,
Err(HandshakeError::WouldBlock(mh)) => cln_res = mh.handshake(),
Err(e) => panic!("Error: {e:?}"),
}
match srv_res {
Ok(_) => ready |= 1,
Err(HandshakeError::WouldBlock(mh)) => srv_res = mh.handshake(),
Err(e) => panic!("Error: {e:?}"),
}
if ready == 3 {
break (cln_res.unwrap(), srv_res.unwrap());
}
if iter_budget == 0 {
panic!("iter budget exhausted");
}
iter_budget -= 1;
}
};
const HELLO_FROM_SRV: &[u8] = b"hello from server";
const HELLO_FROM_CLN: &[u8] = b"hello from client";
ssl_srv_stream.ssl_write(HELLO_FROM_SRV).unwrap();
ssl_cln_stream.ssl_write(HELLO_FROM_CLN).unwrap();
// TODO read the data we just wrote from the streams
}
fn verify_csr(params: &CertificateParams, key_pair: &KeyPair) {
let csr = params
.serialize_request(key_pair)
.and_then(|csr| csr.pem())
.unwrap();
println!("{csr}");
let key = key_pair.serialize_der();
let pkey = PKey::private_key_from_der(&key).unwrap();
let req = X509Req::from_pem(csr.as_bytes()).unwrap();
req.verify(&pkey).unwrap();
}
#[test]
fn test_openssl() {
let (params, key_pair) = util::default_params();
let cert = params.self_signed(&key_pair).unwrap();
verify_cert(&cert, &key_pair);
}
#[test]
fn test_request() {
let (params, key_pair) = util::default_params();
verify_csr(&params, &key_pair);
}
#[test]
fn test_openssl_256() {
let (params, _) = util::default_params();
let key_pair = KeyPair::generate_for(&rcgen::PKCS_ECDSA_P256_SHA256).unwrap();
let cert = params.self_signed(&key_pair).unwrap();
// Now verify the certificate.
verify_cert(&cert, &key_pair);
verify_csr(&params, &key_pair);
}
#[test]
fn test_openssl_384() {
let (params, _) = util::default_params();
let key_pair = KeyPair::generate_for(&rcgen::PKCS_ECDSA_P384_SHA384).unwrap();
let cert = params.self_signed(&key_pair).unwrap();
// Now verify the certificate.
verify_cert(&cert, &key_pair);
verify_csr(&params, &key_pair);
}
#[test]
#[cfg(feature = "aws_lc_rs")]
fn test_openssl_521() {
let (params, _) = util::default_params();
let key_pair = KeyPair::generate_for(&rcgen::PKCS_ECDSA_P521_SHA512).unwrap();
let cert = params.self_signed(&key_pair).unwrap();
// Now verify the certificate.
verify_cert(&cert, &key_pair);
verify_csr(&params, &key_pair);
}
#[test]
fn test_openssl_25519() {
let (params, _) = util::default_params();
let key_pair = KeyPair::generate_for(&rcgen::PKCS_ED25519).unwrap();
let cert = params.self_signed(&key_pair).unwrap();
// Now verify the certificate.
// TODO openssl doesn't support v2 keys (yet)
// https://github.com/est31/rcgen/issues/11
// https://github.com/openssl/openssl/issues/10468
verify_cert_basic(&cert);
//verify_csr(&cert);
}
#[test]
fn test_openssl_25519_v1_given() {
let (params, _) = util::default_params();
let key_pair = rcgen::KeyPair::from_pem(util::ED25519_TEST_KEY_PAIR_PEM_V1).unwrap();
let cert = params.self_signed(&key_pair).unwrap();
// Now verify the certificate as well as CSR,
// but only on OpenSSL >= 1.1.1
// On prior versions, only do basic verification
#[allow(clippy::unusual_byte_groupings)]
if openssl::version::number() >= 0x1_01_01_00_f {
verify_cert(&cert, &key_pair);
verify_csr(&params, &key_pair);
} else {
verify_cert_basic(&cert);
}
}
#[test]
fn test_openssl_25519_v2_given() {
let (params, _) = util::default_params();
let key_pair = rcgen::KeyPair::from_pem(util::ED25519_TEST_KEY_PAIR_PEM_V2).unwrap();
let cert = params.self_signed(&key_pair).unwrap();
// Now verify the certificate.
// TODO openssl doesn't support v2 keys (yet)
// https://github.com/est31/rcgen/issues/11
// https://github.com/openssl/openssl/issues/10468
verify_cert_basic(&cert);
//verify_csr(&cert);
}
#[test]
fn test_openssl_rsa_given() {
let (params, _) = util::default_params();
let key_pair = KeyPair::from_pem(util::RSA_TEST_KEY_PAIR_PEM).unwrap();
let cert = params.self_signed(&key_pair).unwrap();
// Now verify the certificate.
verify_cert(&cert, &key_pair);
verify_csr(&params, &key_pair);
}
#[test]
fn test_openssl_rsa_combinations_given() {
let alg_list = [
&rcgen::PKCS_RSA_SHA256,
&rcgen::PKCS_RSA_SHA384,
&rcgen::PKCS_RSA_SHA512,
//&rcgen::PKCS_RSA_PSS_SHA256,
];
for (i, alg) in alg_list.iter().enumerate() {
let (params, _) = util::default_params();
let key_pair =
KeyPair::from_pkcs8_pem_and_sign_algo(util::RSA_TEST_KEY_PAIR_PEM, alg).unwrap();
let cert = params.self_signed(&key_pair).unwrap();
// Now verify the certificate.
if i >= 4 {
verify_cert(&cert, &key_pair);
verify_csr(&params, &key_pair);
} else {
// The PSS key types are not fully supported.
// An attempt to use them gives a handshake error.
verify_cert_basic(&cert);
}
}
}
#[test]
fn test_openssl_separate_ca() {
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);
let mut params = CertificateParams::new(vec!["crabs.crabs".to_string()]).unwrap();
params
.distinguished_name
.push(DnType::OrganizationName, "Crab widgits SE");
params
.distinguished_name
.push(DnType::CommonName, "Dev domain");
let cert_key = KeyPair::generate().unwrap();
let cert = params.signed_by(&cert_key, &ca).unwrap();
let key = cert_key.serialize_der();
verify_cert_ca(&cert.pem(), &key, &ca_cert_pem);
}
#[test]
fn test_openssl_separate_ca_with_printable_string() {
let (mut ca_params, ca_key) = util::default_params();
ca_params.distinguished_name.push(
DnType::CountryName,
DnValue::PrintableString("US".try_into().unwrap()),
);
ca_params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained);
let ca_cert = ca_params.self_signed(&ca_key).unwrap();
let mut params = CertificateParams::new(vec!["crabs.crabs".to_string()]).unwrap();
params
.distinguished_name
.push(DnType::OrganizationName, "Crab widgits SE");
params
.distinguished_name
.push(DnType::CommonName, "Dev domain");
let cert_key = KeyPair::generate().unwrap();
let ca = Issuer::new(ca_params, ca_key);
let cert = params.signed_by(&cert_key, &ca).unwrap();
let key = cert_key.serialize_der();
verify_cert_ca(&cert.pem(), &key, &ca_cert.pem());
}
#[test]
fn test_openssl_separate_ca_with_other_signing_alg() {
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 = ca_params.self_signed(&ca_key).unwrap();
let ca = Issuer::new(ca_params, ca_key);
let mut params = CertificateParams::new(vec!["crabs.crabs".to_string()]).unwrap();
params
.distinguished_name
.push(DnType::OrganizationName, "Crab widgits SE");
params
.distinguished_name
.push(DnType::CommonName, "Dev domain");
let cert_key = KeyPair::generate_for(&rcgen::PKCS_ECDSA_P384_SHA384).unwrap();
let cert = params.signed_by(&cert_key, &ca).unwrap();
let key = cert_key.serialize_der();
verify_cert_ca(&cert.pem(), &key, &ca_cert.pem());
}
#[test]
fn test_openssl_separate_ca_name_constraints() {
let (mut ca_params, ca_key) = util::default_params();
ca_params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained);
println!("openssl version: {:x}", openssl::version::number());
ca_params.name_constraints = Some(NameConstraints {
permitted_subtrees: vec![GeneralSubtree::DnsName("crabs.crabs".to_string())],
//permitted_subtrees : vec![GeneralSubtree::DnsName("".to_string())],
//permitted_subtrees : Vec::new(),
//excluded_subtrees : vec![GeneralSubtree::DnsName(".v".to_string())],
excluded_subtrees: Vec::new(),
});
let ca_cert = ca_params.self_signed(&ca_key).unwrap();
let ca = Issuer::new(ca_params, ca_key);
let mut params = CertificateParams::new(vec!["crabs.crabs".to_string()]).unwrap();
params
.distinguished_name
.push(DnType::OrganizationName, "Crab widgits SE");
params
.distinguished_name
.push(DnType::CommonName, "Dev domain");
let cert_key = KeyPair::generate().unwrap();
let cert = params.signed_by(&cert_key, &ca).unwrap();
let key = cert_key.serialize_der();
verify_cert_ca(&cert.pem(), &key, &ca_cert.pem());
}
#[test]
fn test_openssl_crl_parse() {
// Create a CRL with one revoked cert, and an issuer to sign the CRL.
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;
// Serialize the CRL signed by the issuer in both PEM and DER.
let crl_pem = crl.pem().unwrap();
// We should be able to parse the PEM form without error.
assert!(X509Crl::from_pem(crl_pem.as_bytes()).is_ok());
// We should also be able to parse the DER form without error.
let openssl_crl = X509Crl::from_der(crl.der()).expect("failed to parse CRL DER");
// The properties of the CRL should match expected.
let openssl_issuer = X509::from_der(issuer.der()).unwrap();
// Asn1Time::from_unix takes i64 or i32 (depending on CPU architecture)
#[allow(clippy::useless_conversion)]
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();
assert!(openssl_crl.next_update().unwrap().eq(&expected_next_update));
assert!(matches!(
openssl_crl
.issuer_name()
.try_cmp(openssl_issuer.issuer_name())
.unwrap(),
core::cmp::Ordering::Equal
));
// We should find the revoked certificate is revoked.
let openssl_serial = BigNum::from_slice(revoked_cert_serial.as_ref()).unwrap();
let openssl_serial = Asn1Integer::from_bn(&openssl_serial).unwrap();
let openssl_crl_status = openssl_crl.get_by_serial(&openssl_serial);
assert!(matches!(openssl_crl_status, CrlStatus::Revoked(_)));
// We should be able to verify the CRL signature with the issuer's public key.
let issuer_pkey = openssl_issuer.public_key().unwrap();
assert!(openssl_crl
.verify(&issuer_pkey)
.expect("failed to verify CRL signature"));
}
#[test]
fn test_openssl_crl_dps_parse() {
// Generate and parse a certificate that includes two CRL distribution points.
let der = util::cert_with_crl_dps();
let cert = X509::from_der(&der).expect("failed to parse cert DER");
// We should find the CRL DPs extension.
let dps = cert
.crl_distribution_points()
.expect("missing crl distribution points extension");
assert!(!dps.is_empty());
// We should find two distribution points, each with a distribution point name containing
// a full name sequence of general names.
let general_names = dps
.iter()
.flat_map(|dp| {
dp.distpoint()
.expect("distribution point missing distribution point name")
.fullname()
.expect("distribution point name missing general names")
.iter()
})
.collect::<Vec<_>>();
// Each general name should be a URI name.
let uris = general_names
.iter()
.map(|general_name| {
general_name
.uri()
.expect("general name is not a directory name")
})
.collect::<Vec<_>>();
// We should find the expected URIs.
assert_eq!(
uris,
&[
"http://example.com/crl.der",
"http://crls.example.com/1234",
"ldap://example.com/crl.der"
]
);
}
#[test]
#[cfg(all(feature = "crypto", feature = "aws_lc_rs"))]
fn test_openssl_pkcs1_and_sec1_keys() {
use openssl::ec::{EcGroup, EcKey};
use openssl::nid::Nid;
use openssl::pkey::PKey;
use openssl::rsa::Rsa;
use pki_types::PrivateKeyDer;
let rsa = Rsa::generate(2048).unwrap();
let rsa = PKey::from_rsa(rsa).unwrap();
let pkcs1_rsa_key_der = PrivateKeyDer::try_from(rsa.private_key_to_der().unwrap()).unwrap();
KeyPair::try_from(&pkcs1_rsa_key_der).unwrap();
let pkcs8_rsa_key_der = PrivateKeyDer::try_from(rsa.private_key_to_pkcs8().unwrap()).unwrap();
KeyPair::try_from(&pkcs8_rsa_key_der).unwrap();
let group = EcGroup::from_curve_name(Nid::SECP521R1).unwrap();
let ec_key = EcKey::generate(&group).unwrap();
let ec_key = PKey::from_ec_key(ec_key).unwrap();
let sec1_ec_key_der = PrivateKeyDer::try_from(ec_key.private_key_to_der().unwrap()).unwrap();
KeyPair::try_from(&sec1_ec_key_der).unwrap();
let pkcs8_ec_key_der = PrivateKeyDer::try_from(ec_key.private_key_to_pkcs8().unwrap()).unwrap();
KeyPair::try_from(&pkcs8_ec_key_der).unwrap();
}
#![cfg(feature = "crypto")]
use time::{Duration, OffsetDateTime};
use rcgen::{BasicConstraints, Certificate, CertificateParams, Issuer, KeyPair};
use rcgen::{
CertificateRevocationList, CrlDistributionPoint, CrlIssuingDistributionPoint, CrlScope,
};
use rcgen::{CertificateRevocationListParams, DnType, IsCa, KeyIdMethod};
use rcgen::{KeyUsagePurpose, RevocationReason, RevokedCertParams, SerialNumber};
// Generated by adding `println!("{}", cert.serialize_private_key_pem());`
// to the test_webpki_25519 test and panicing explicitly.
// This is a "v2" key containing the public key as well as the
// private one.
#[allow(unused)]
pub const ED25519_TEST_KEY_PAIR_PEM_V2: &str = r#"
-----BEGIN PRIVATE KEY-----
MFMCAQEwBQYDK2VwBCIEIC2pHJYjFHhK8V7mj6BnHWUVMS4CRolUlDdRXKCtguDu
oSMDIQDrvH/x8Nx9untsuc6ET+ce3w7PSuLY8BLWcHdXDGvkQA==
-----END PRIVATE KEY-----
"#;
// Generated with `openssl genpkey -algorithm ED25519`
// A "v1" key as it doesn't contain the public key (which can be
// derived from the private one)
#[allow(unused)]
pub const ED25519_TEST_KEY_PAIR_PEM_V1: &str = r#"
-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VwBCIEIDSat0MacDt2fokpnzuBaXvAQR6RJGS9rgIYOU2mZKld
-----END PRIVATE KEY-----
"#;
/*
Generated by: openssl genpkey -algorithm RSA \
-pkeyopt rsa_keygen_bits:2048 \
-pkeyopt rsa_keygen_pubexp:65537 | \
openssl pkcs8 -topk8 -nocrypt -outform pem
*/
#[allow(dead_code)] // Used in some but not all test compilation units.
#[cfg(feature = "pem")]
pub const RSA_TEST_KEY_PAIR_PEM: &str = r#"
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDYjmgyV3/LSizJ
XrYrATZrrPr2Edo8yiOgBLFmi4sgeGdQ5n6nhjTGfBEIP2Ia6z+hbiGOMncabEBc
zkdME+JFYVCSkS7r4ivMOzp2egxLgcPKcerBoXI8DUbHhIR9z89lHiPHDJv3+d0A
c1b9bz9b8OAeZWiQmFvmjpbc2DfhQ2OFx2MwFZCYF196rrXOc6/SR2esZVRrkW22
RBKFTgz6GIA5A/5VWKIISSqEB1gOcMz2iq5987I28+Ez4rcLZ2lB7cZ7TbNxkAwt
0fPL+EuyP7XOzbIj4/kSAlU5xfwNERa3BEuOFro4i5EmSDj+lR5xdRpFnx0j5zOo
zUL2lHG9AgMBAAECggEARpV8DtSIOcmOeYAeXjwB8eyqy+Obv26fV/vPmr3m9glo
m2zVYWMT9pHft1F5d46v6b0MwN1gBsO74sP1Zy2f9b83VN5vbcEFR4cSkiVLtpyw
JV8mBkDKDBrDtCpUSPGgBrRhMvLAL35Ic2oks2w8OYp0clPZVi/i3G4jbA4pgIkt
yB6k79Uhzz2nfZ0VpPORGNsBOl5UK1LkmIhTJ6S0LsLj7XSet9YHR0k0F0/NOSzz
+jMUzfjOPm8M+b3wk9yAQP7qT9Iy3MHbGAad4gNXGu1LqeDRkfmM5pnoG0ASP3+B
IvX2l0ZLeCtg+GRLlGvUVI1HSQHCsuiC6/g2bq7JAQKBgQD3/Eb58VjpdwJYPrg/
srfnC9sKSf5C0Q8YSmkfvOmeD6Vqe0EXRuMyhwTkkVdz04yPiB2j0fXdeB9h16Ic
9HWb/UNGWNpV7Ul1MSHbeu32Xor+5IkqCGgSoMznlt9QPR4PxfIOgO8cVL1HgNAZ
JnBDzhTG0FfY75hqpCDmFGAZwQKBgQDfjhk5aM0yGLYgZfw/K9BrwjctQBWdrps2
4TtkG7Kuj0hsimCdrqJQ5JN8aUM41zDUr3Px1uN5gUAZ3dE9DoGsgj15ZwgVkAMM
E54bfzOqkbh+mRpptIxL4HmHB45vgvz0YljeRoOEQvPF/OSGLti7VIkD4898PFKl
cU+P9m5+/QKBgDi8XTi+AQuZEM5Duz/Hkc+opLqb5zI+RmfWTmrWe9SP29aa0G+U
5lIfFf19SzbSxavpBm7+kHPVEcj+3rYlL+s6bHPhzEIwgcfwL8DZRSxCwSZD/yXA
up7Yb0jk+b6P3RravOCYmxwuPwfm7rVyV+kLczFxZUfauVJcrrI1Iy+BAoGBAJjG
MEDGeSxaLOS5LYgyNg3ePPzkhaEruRDpHUBNmW+npZPfgSVhObXUb2IfQXwvu0Qt
3yuPcgcQKDFFIH/8UOwGWWKE4cZyk1KGeY9K/5D6Yr3JfX5tj08vSX3Y0SMtvhZ4
u0izoZ8abiOIrtdwXlau76/D2ICLbON5Kykz/NE1AoGAId2+pO9p8jBt9l+5jZo7
Rw/mb5icMaG2hqAzs37gUPbpSwQFOmGhQmNM+WvYEvUUuiTxI3AOeEK8Mj+BVB4+
uE3X/fWK/JR9iOzH9OM31Nua8/EJzr7BmUpXeRr4dAtVimeQ+5HY6IgRsFGPKKwv
YPTHy8SWRA2sMII3ArhHJ8A=
-----END PRIVATE KEY-----
"#;
pub fn default_params() -> (CertificateParams, KeyPair) {
let mut params =
CertificateParams::new(vec!["crabs.crabs".to_string(), "localhost".to_string()]).unwrap();
params
.distinguished_name
.push(DnType::OrganizationName, "Crab widgits SE");
params
.distinguished_name
.push(DnType::CommonName, "Master CA");
let key_pair = KeyPair::generate().unwrap();
(params, key_pair)
}
#[allow(unused)] // Used by openssl + x509-parser features.
pub fn test_crl() -> (
CertificateRevocationListParams,
CertificateRevocationList,
Certificate,
) {
let (mut issuer, key_pair) = default_params();
issuer.is_ca = IsCa::Ca(BasicConstraints::Unconstrained);
issuer.key_usages = vec![
KeyUsagePurpose::KeyCertSign,
KeyUsagePurpose::DigitalSignature,
KeyUsagePurpose::CrlSign,
];
let issuer_cert = issuer.self_signed(&key_pair).unwrap();
let ca = Issuer::new(issuer, key_pair);
let now = OffsetDateTime::now_utc();
let next_week = now + Duration::weeks(1);
let revoked_cert = RevokedCertParams {
serial_number: SerialNumber::from_slice(&[0x00, 0xC0, 0xFF, 0xEE]),
revocation_time: now,
reason_code: Some(RevocationReason::KeyCompromise),
invalidity_date: None,
};
let params = CertificateRevocationListParams {
this_update: now,
next_update: next_week,
crl_number: SerialNumber::from(1234),
issuing_distribution_point: Some(CrlIssuingDistributionPoint {
distribution_point: CrlDistributionPoint {
uris: vec!["http://example.com/crl".to_string()],
},
scope: Some(CrlScope::UserCertsOnly),
}),
revoked_certs: vec![revoked_cert],
key_identifier_method: KeyIdMethod::Sha256,
};
let crl = params.signed_by(&ca).unwrap();
(params, crl, issuer_cert)
}
#[allow(unused)] // Used by openssl + x509-parser features.
pub fn cert_with_crl_dps() -> Vec<u8> {
let (mut params, key_pair) = default_params();
params.crl_distribution_points = vec![
CrlDistributionPoint {
uris: vec![
"http://example.com/crl.der".to_string(),
"http://crls.example.com/1234".to_string(),
],
},
CrlDistributionPoint {
uris: vec!["ldap://example.com/crl.der".to_string()],
},
];
params.self_signed(&key_pair).unwrap().der().to_vec()
}
#![cfg(feature = "crypto")]
use std::time::Duration as StdDuration;
use pki_types::{CertificateDer, ServerName, SignatureVerificationAlgorithm, UnixTime};
use ring::rand::SystemRandom;
use ring::signature::{self, EcdsaKeyPair, EcdsaSigningAlgorithm, Ed25519KeyPair, KeyPair as _};
#[cfg(feature = "pem")]
use ring::signature::{RsaEncoding, RsaKeyPair};
use time::{Duration, OffsetDateTime};
use webpki::{
anchor_from_trusted_cert, BorrowedCertRevocationList, CertRevocationList, EndEntityCert,
KeyUsage, RevocationOptionsBuilder,
};
use rcgen::{
BasicConstraints, Certificate, CertificateParams, DnType, Error, IsCa, Issuer, KeyPair,
PublicKeyData, SigningKey,
};
use rcgen::{CertificateRevocationListParams, RevocationReason, RevokedCertParams};
#[cfg(feature = "x509-parser")]
use rcgen::{CertificateSigningRequestParams, DnValue};
use rcgen::{ExtendedKeyUsagePurpose, KeyUsagePurpose, SerialNumber};
mod util;
fn sign_msg_ecdsa(key_pair: &KeyPair, msg: &[u8], alg: &'static EcdsaSigningAlgorithm) -> Vec<u8> {
let pk_der = key_pair.serialize_der();
let key_pair =
EcdsaKeyPair::from_pkcs8(alg, &pk_der, &ring::rand::SystemRandom::new()).unwrap();
let system_random = SystemRandom::new();
let signature = key_pair.sign(&system_random, msg).unwrap();
signature.as_ref().to_vec()
}
fn sign_msg_ed25519(key_pair: &KeyPair, msg: &[u8]) -> Vec<u8> {
let pk_der = key_pair.serialize_der();
let key_pair = Ed25519KeyPair::from_pkcs8_maybe_unchecked(&pk_der).unwrap();
let signature = key_pair.sign(msg);
signature.as_ref().to_vec()
}
#[cfg(feature = "pem")]
fn sign_msg_rsa(key_pair: &KeyPair, msg: &[u8], encoding: &'static dyn RsaEncoding) -> Vec<u8> {
let pk_der = key_pair.serialize_der();
let key_pair = RsaKeyPair::from_pkcs8(&pk_der).unwrap();
let system_random = SystemRandom::new();
let mut signature = vec![0; key_pair.public().modulus_len()];
key_pair
.sign(encoding, &system_random, msg, &mut signature)
.unwrap();
signature
}
fn check_cert<'a, 'b, S: SigningKey + 'a>(
cert_der: &CertificateDer<'_>,
cert: &'a Certificate,
cert_key: &'a S,
alg: &dyn SignatureVerificationAlgorithm,
sign_fn: impl FnOnce(&'a S, &'b [u8]) -> Vec<u8>,
) {
#[cfg(feature = "pem")]
{
println!("{}", cert.pem());
}
check_cert_ca(cert_der, cert_key, cert_der, alg, alg, sign_fn);
}
fn check_cert_ca<'a, 'b, S: SigningKey + 'a>(
cert_der: &CertificateDer<'_>,
cert_key: &'a S,
ca_der: &CertificateDer<'_>,
cert_alg: &dyn SignatureVerificationAlgorithm,
ca_alg: &dyn SignatureVerificationAlgorithm,
sign_fn: impl FnOnce(&'a S, &'b [u8]) -> Vec<u8>,
) {
let trust_anchor = anchor_from_trusted_cert(ca_der).unwrap();
let trust_anchor_list = &[trust_anchor];
let end_entity_cert = EndEntityCert::try_from(cert_der).unwrap();
// Set time to Jan 10, 2004
let time = UnixTime::since_unix_epoch(StdDuration::from_secs(0x40_00_00_00));
// (1/3) Check whether the cert is valid
end_entity_cert
.verify_for_usage(
&[cert_alg, ca_alg],
&trust_anchor_list[..],
&[],
time,
KeyUsage::server_auth(),
None,
None,
)
.expect("valid TLS server cert");
// (2/3) Check that the cert is valid for the given DNS name
let dns_name = ServerName::try_from("crabs.crabs").unwrap();
end_entity_cert
.verify_is_valid_for_subject_name(&dns_name)
.expect("valid for DNS name");
// (3/3) Check that a message signed by the cert is valid.
let msg = b"Hello, World! This message is signed.";
let signature = sign_fn(cert_key, msg);
end_entity_cert
.verify_signature(cert_alg, msg, &signature)
.expect("signature is valid");
}
#[test]
fn test_webpki() {
let (params, key_pair) = util::default_params();
let cert = params.self_signed(&key_pair).unwrap();
// Now verify the certificate.
let sign_fn =
|key_pair, msg| sign_msg_ecdsa(key_pair, msg, &signature::ECDSA_P256_SHA256_ASN1_SIGNING);
check_cert(
cert.der(),
&cert,
&key_pair,
webpki::ring::ECDSA_P256_SHA256,
sign_fn,
);
}
#[test]
fn test_webpki_256() {
let (params, _) = util::default_params();
let key_pair = KeyPair::generate_for(&rcgen::PKCS_ECDSA_P256_SHA256).unwrap();
let cert = params.self_signed(&key_pair).unwrap();
// Now verify the certificate.
let sign_fn = |cert, msg| sign_msg_ecdsa(cert, msg, &signature::ECDSA_P256_SHA256_ASN1_SIGNING);
check_cert(
cert.der(),
&cert,
&key_pair,
webpki::ring::ECDSA_P256_SHA256,
sign_fn,
);
}
#[test]
fn test_webpki_384() {
let (params, _) = util::default_params();
let key_pair = KeyPair::generate_for(&rcgen::PKCS_ECDSA_P384_SHA384).unwrap();
let cert = params.self_signed(&key_pair).unwrap();
// Now verify the certificate.
let sign_fn = |cert, msg| sign_msg_ecdsa(cert, msg, &signature::ECDSA_P384_SHA384_ASN1_SIGNING);
check_cert(
cert.der(),
&cert,
&key_pair,
webpki::ring::ECDSA_P384_SHA384,
sign_fn,
);
}
#[test]
fn test_webpki_25519() {
let (params, _) = util::default_params();
let key_pair = KeyPair::generate_for(&rcgen::PKCS_ED25519).unwrap();
let cert = params.self_signed(&key_pair).unwrap();
// Now verify the certificate.
check_cert(
cert.der(),
&cert,
&key_pair,
webpki::ring::ED25519,
sign_msg_ed25519,
);
}
#[cfg(feature = "pem")]
#[test]
fn test_webpki_25519_v1_given() {
let (params, _) = util::default_params();
let key_pair = rcgen::KeyPair::from_pem(util::ED25519_TEST_KEY_PAIR_PEM_V1).unwrap();
let cert = params.self_signed(&key_pair).unwrap();
// Now verify the certificate.
check_cert(
cert.der(),
&cert,
&key_pair,
webpki::ring::ED25519,
sign_msg_ed25519,
);
}
#[cfg(feature = "pem")]
#[test]
fn test_webpki_25519_v2_given() {
let (params, _) = util::default_params();
let key_pair = rcgen::KeyPair::from_pem(util::ED25519_TEST_KEY_PAIR_PEM_V2).unwrap();
let cert = params.self_signed(&key_pair).unwrap();
// Now verify the certificate.
check_cert(
cert.der(),
&cert,
&key_pair,
webpki::ring::ED25519,
sign_msg_ed25519,
);
}
#[cfg(feature = "pem")]
#[test]
fn test_webpki_rsa_given() {
let (params, _) = util::default_params();
let key_pair = rcgen::KeyPair::from_pem(util::RSA_TEST_KEY_PAIR_PEM).unwrap();
let cert = params.self_signed(&key_pair).unwrap();
// Now verify the certificate.
check_cert(
cert.der(),
&cert,
&key_pair,
webpki::ring::RSA_PKCS1_2048_8192_SHA256,
|msg, cert| sign_msg_rsa(msg, cert, &signature::RSA_PKCS1_SHA256),
);
}
#[cfg(feature = "pem")]
#[test]
fn test_webpki_rsa_combinations_given() {
let configs: &[(_, _, &'static dyn signature::RsaEncoding)] = &[
(
&rcgen::PKCS_RSA_SHA256,
webpki::ring::RSA_PKCS1_2048_8192_SHA256,
&signature::RSA_PKCS1_SHA256,
),
(
&rcgen::PKCS_RSA_SHA384,
webpki::ring::RSA_PKCS1_2048_8192_SHA384,
&signature::RSA_PKCS1_SHA384,
),
(
&rcgen::PKCS_RSA_SHA512,
webpki::ring::RSA_PKCS1_2048_8192_SHA512,
&signature::RSA_PKCS1_SHA512,
),
//(&rcgen::PKCS_RSA_PSS_SHA256, &webpki::RSA_PSS_2048_8192_SHA256_LEGACY_KEY, &signature::RSA_PSS_SHA256),
];
for c in configs {
let (params, _) = util::default_params();
let key_pair =
rcgen::KeyPair::from_pkcs8_pem_and_sign_algo(util::RSA_TEST_KEY_PAIR_PEM, c.0).unwrap();
let cert = params.self_signed(&key_pair).unwrap();
// Now verify the certificate.
check_cert(cert.der(), &cert, &key_pair, c.1, |msg, cert| {
sign_msg_rsa(msg, cert, c.2)
});
}
}
#[test]
fn test_webpki_separate_ca() {
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 mut params = CertificateParams::new(vec!["crabs.crabs".to_string()]).unwrap();
params
.distinguished_name
.push(DnType::OrganizationName, "Crab widgits SE");
params
.distinguished_name
.push(DnType::CommonName, "Dev domain");
let key_pair = KeyPair::generate().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);
check_cert_ca(
cert.der(),
&key_pair,
ca_cert.der(),
webpki::ring::ECDSA_P256_SHA256,
webpki::ring::ECDSA_P256_SHA256,
sign_fn,
);
}
#[test]
fn test_webpki_separate_ca_with_other_signing_alg() {
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 = ca_params.self_signed(&ca_key).unwrap();
let mut params = CertificateParams::new(vec!["crabs.crabs".to_string()]).unwrap();
params
.distinguished_name
.push(DnType::OrganizationName, "Crab widgits SE");
params
.distinguished_name
.push(DnType::CommonName, "Dev domain");
let key_pair = KeyPair::generate_for(&rcgen::PKCS_ED25519).unwrap();
let ca = Issuer::new(ca_params, ca_key);
let cert = params.signed_by(&key_pair, &ca).unwrap();
check_cert_ca(
cert.der(),
&key_pair,
ca_cert.der(),
webpki::ring::ED25519,
webpki::ring::ECDSA_P256_SHA256,
sign_msg_ed25519,
);
}
#[test]
fn from_remote() {
struct Remote(EcdsaKeyPair);
impl SigningKey for Remote {
fn sign(&self, msg: &[u8]) -> Result<Vec<u8>, rcgen::Error> {
let system_random = SystemRandom::new();
self.0
.sign(&system_random, msg)
.map(|s| s.as_ref().to_owned())
.map_err(|_| Error::RingUnspecified)
}
}
impl PublicKeyData for Remote {
fn der_bytes(&self) -> &[u8] {
self.0.public_key().as_ref()
}
fn algorithm(&self) -> &'static rcgen::SignatureAlgorithm {
&rcgen::PKCS_ECDSA_P256_SHA256
}
}
let rng = ring::rand::SystemRandom::new();
let key_pair = KeyPair::generate_for(&rcgen::PKCS_ECDSA_P256_SHA256).unwrap();
let remote = EcdsaKeyPair::from_pkcs8(
&signature::ECDSA_P256_SHA256_ASN1_SIGNING,
&key_pair.serialize_der(),
&rng,
)
.unwrap();
let key_pair = EcdsaKeyPair::from_pkcs8(
&signature::ECDSA_P256_SHA256_ASN1_SIGNING,
&key_pair.serialize_der(),
&rng,
)
.unwrap();
let remote = Remote(remote);
let (params, _) = util::default_params();
let cert = params.self_signed(&remote).unwrap();
// Now verify the certificate.
let sign_fn = move |_, msg| {
let system_random = SystemRandom::new();
let signature = key_pair.sign(&system_random, msg).unwrap();
signature.as_ref().to_vec()
};
check_cert(
cert.der(),
&cert,
&remote,
webpki::ring::ECDSA_P256_SHA256,
sign_fn,
);
}
/*
// TODO https://github.com/briansmith/webpki/issues/134
// TODO https://github.com/briansmith/webpki/issues/135
#[test]
fn test_webpki_separate_ca_name_constraints() {
let mut params = util::default_params();
params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained);
params.name_constraints = Some(NameConstraints {
// TODO also add a test with non-empty permitted_subtrees that
// doesn't contain a DirectoryName entry. This isn't possible
// currently due to a limitation of webpki.
permitted_subtrees : vec![GeneralSubtree::DnsName("dev".to_string()), GeneralSubtree::DirectoryName(rcgen::DistinguishedName::new())],
//permitted_subtrees : vec![GeneralSubtree::DnsName("dev".to_string())],
//permitted_subtrees : Vec::new(),
//excluded_subtrees : vec![GeneralSubtree::DnsName("v".to_string())],
excluded_subtrees : Vec::new(),
});
let ca_cert = Certificate::from_params(params).unwrap();
println!("{}", ca_cert.serialize_pem().unwrap());
let ca_der = ca_cert.serialize_der().unwrap();
let mut params = CertificateParams::new(vec!["crabs.dev".to_string()]);
params.distinguished_name = rcgen::DistinguishedName::new();
//params.distinguished_name.push(DnType::OrganizationName, "Crab widgits SE");
//params.distinguished_name.push(DnType::CommonName, "Dev domain");
let cert = Certificate::from_params(params).unwrap();
let cert_der = cert.serialize_der_with_signer(&ca_cert).unwrap();
println!("{}", cert.serialize_pem_with_signer(&ca_cert).unwrap());
let sign_fn = |cert, msg| sign_msg_ecdsa(cert, msg,
&signature::ECDSA_P256_SHA256_ASN1_SIGNING);
check_cert_ca(&cert_der, &cert, &ca_der,
&webpki::ECDSA_P256_SHA256, &webpki::ECDSA_P256_SHA256, sign_fn);
}
*/
#[cfg(feature = "x509-parser")]
#[test]
fn test_webpki_imported_ca() {
let (mut params, ca_key) = util::default_params();
params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained);
params.key_usages.push(KeyUsagePurpose::KeyCertSign);
let ca_cert = params.self_signed(&ca_key).unwrap();
let ca = Issuer::from_ca_cert_der(ca_cert.der(), ca_key).unwrap();
assert_eq!(ca.key_usages(), &[KeyUsagePurpose::KeyCertSign]);
let mut params = CertificateParams::new(vec!["crabs.crabs".to_string()]).unwrap();
params
.distinguished_name
.push(DnType::OrganizationName, "Crab widgits SE");
params
.distinguished_name
.push(DnType::CommonName, "Dev domain");
let cert_key = KeyPair::generate().unwrap();
let cert = params.signed_by(&cert_key, &ca).unwrap();
let sign_fn = |cert, msg| sign_msg_ecdsa(cert, msg, &signature::ECDSA_P256_SHA256_ASN1_SIGNING);
check_cert_ca(
cert.der(),
&cert_key,
ca_cert.der(),
webpki::ring::ECDSA_P256_SHA256,
webpki::ring::ECDSA_P256_SHA256,
sign_fn,
);
}
#[cfg(feature = "x509-parser")]
#[test]
fn test_webpki_imported_ca_with_printable_string() {
let (mut params, ca_key) = util::default_params();
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();
let ca = Issuer::from_ca_cert_der(ca_cert.der(), ca_key).unwrap();
let mut params = CertificateParams::new(vec!["crabs.crabs".to_string()]).unwrap();
params
.distinguished_name
.push(DnType::OrganizationName, "Crab widgits SE");
params
.distinguished_name
.push(DnType::CommonName, "Dev domain");
let cert_key = KeyPair::generate().unwrap();
let cert = params.signed_by(&cert_key, &ca).unwrap();
let sign_fn = |cert, msg| sign_msg_ecdsa(cert, msg, &signature::ECDSA_P256_SHA256_ASN1_SIGNING);
check_cert_ca(
cert.der(),
&cert_key,
ca_cert.der(),
webpki::ring::ECDSA_P256_SHA256,
webpki::ring::ECDSA_P256_SHA256,
sign_fn,
);
}
#[cfg(feature = "x509-parser")]
#[test]
fn test_certificate_from_csr() {
let mut params = CertificateParams::new(vec!["crabs.crabs".to_string()]).unwrap();
params
.distinguished_name
.push(DnType::OrganizationName, "Crab widgits SE");
params
.distinguished_name
.push(DnType::CommonName, "Dev domain");
let eku_test = vec![
ExtendedKeyUsagePurpose::Any,
ExtendedKeyUsagePurpose::ClientAuth,
ExtendedKeyUsagePurpose::CodeSigning,
ExtendedKeyUsagePurpose::EmailProtection,
ExtendedKeyUsagePurpose::OcspSigning,
ExtendedKeyUsagePurpose::ServerAuth,
ExtendedKeyUsagePurpose::TimeStamping,
];
for eku in &eku_test {
params.insert_extended_key_usage(eku.clone());
}
let cert_key = KeyPair::generate().unwrap();
let csr = params.serialize_request(&cert_key).unwrap();
let csr = CertificateSigningRequestParams::from_der(csr.der()).unwrap();
let ekus_contained = &csr.params.extended_key_usages;
for eku in &eku_test {
assert!(ekus_contained.contains(eku));
}
let (mut ca_params, ca_key) = util::default_params();
ca_params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained);
for eku in &eku_test {
ca_params.insert_extended_key_usage(eku.clone());
}
let ekus_contained = &ca_params.extended_key_usages;
for eku in &eku_test {
assert!(ekus_contained.contains(eku));
}
let ca_cert = ca_params.self_signed(&ca_key).unwrap();
let ekus_contained = &ca_params.extended_key_usages;
for eku in &eku_test {
assert!(ekus_contained.contains(eku));
}
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 = &csr.params.extended_key_usages;
for eku in &eku_test {
assert!(ekus_contained.contains(eku));
}
for eku in &eku_test {
assert!(ekus.contains(eku));
}
let sign_fn =
|key_pair, msg| sign_msg_ecdsa(key_pair, msg, &signature::ECDSA_P256_SHA256_ASN1_SIGNING);
check_cert_ca(
cert.der(),
&cert_key,
ca_cert.der(),
webpki::ring::ECDSA_P256_SHA256,
webpki::ring::ECDSA_P256_SHA256,
sign_fn,
);
}
#[test]
fn test_webpki_serial_number() {
let (mut params, key_pair) = util::default_params();
params.serial_number = Some(vec![0, 1, 2].into());
let cert = params.self_signed(&key_pair).unwrap();
// Now verify the certificate.
let sign_fn = |cert, msg| sign_msg_ecdsa(cert, msg, &signature::ECDSA_P256_SHA256_ASN1_SIGNING);
check_cert(
cert.der(),
&cert,
&key_pair,
webpki::ring::ECDSA_P256_SHA256,
sign_fn,
);
}
#[test]
fn test_webpki_crl_parse() {
// Create a CRL with one revoked cert, and an issuer to sign the CRL.
let (crl_params, crl, _) = util::test_crl();
let revoked_cert = crl_params.revoked_certs.first().unwrap();
// We should be able to parse the CRL DER without error.
let webpki_crl = CertRevocationList::from(
BorrowedCertRevocationList::from_der(crl.der()).expect("failed to parse CRL DER"),
);
// We should be able to find the revoked cert with the expected properties.
let webpki_revoked_cert = webpki_crl
.find_serial(revoked_cert.serial_number.as_ref())
.expect("failed to parse revoked certs in CRL")
.expect("failed to find expected revoked cert in CRL");
assert_eq!(
webpki_revoked_cert.serial_number,
revoked_cert.serial_number.as_ref()
);
assert_eq!(
webpki_revoked_cert.reason_code.unwrap() as u64,
revoked_cert.reason_code.unwrap() as u64
);
assert_eq!(
webpki_revoked_cert.revocation_date,
UnixTime::since_unix_epoch(StdDuration::from_secs(
revoked_cert.revocation_time.unix_timestamp() as u64
))
);
}
#[test]
fn test_webpki_crl_revoke() {
// Create an issuer CA.
let alg = &rcgen::PKCS_ECDSA_P256_SHA256;
let (mut issuer, _) = util::default_params();
issuer.is_ca = IsCa::Ca(BasicConstraints::Unconstrained);
issuer.key_usages = vec![
KeyUsagePurpose::KeyCertSign,
KeyUsagePurpose::DigitalSignature,
KeyUsagePurpose::CrlSign,
];
let issuer_key = KeyPair::generate_for(alg).unwrap();
let issuer_cert = issuer.self_signed(&issuer_key).unwrap();
// Create an end entity cert issued by the issuer.
let (mut ee, _) = util::default_params();
ee.is_ca = IsCa::NoCa;
ee.extended_key_usages = vec![ExtendedKeyUsagePurpose::ClientAuth];
ee.serial_number = Some(SerialNumber::from(99999));
let ee_key = KeyPair::generate_for(alg).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_cert.der()).unwrap();
let trust_anchor_list = &[trust_anchor];
let end_entity_cert = EndEntityCert::try_from(ee_cert.der()).unwrap();
let unix_time = 0x40_00_00_00;
let time = UnixTime::since_unix_epoch(StdDuration::from_secs(unix_time));
// The end entity cert should validate with the issuer without error.
end_entity_cert
.verify_for_usage(
&[webpki::ring::ECDSA_P256_SHA256],
&trust_anchor_list[..],
&[],
time,
KeyUsage::client_auth(),
None,
None,
)
.expect("failed to validate ee cert with issuer");
// Generate a CRL with the issuer that revokes the EE cert.
let now = OffsetDateTime::from_unix_timestamp(unix_time as i64).unwrap();
let crl = CertificateRevocationListParams {
this_update: now,
next_update: now + Duration::weeks(1),
crl_number: rcgen::SerialNumber::from(1234),
issuing_distribution_point: None,
revoked_certs: vec![RevokedCertParams {
serial_number: ee.serial_number.clone().unwrap(),
revocation_time: now,
reason_code: Some(RevocationReason::KeyCompromise),
invalidity_date: None,
}],
key_identifier_method: rcgen::KeyIdMethod::Sha256,
}
.signed_by(&issuer)
.unwrap();
let crl = CertRevocationList::from(BorrowedCertRevocationList::from_der(crl.der()).unwrap());
// The end entity cert should **not** validate when we provide a CRL that revokes the EE cert.
let result = end_entity_cert.verify_for_usage(
&[webpki::ring::ECDSA_P256_SHA256],
&trust_anchor_list[..],
&[],
time,
KeyUsage::client_auth(),
Some(RevocationOptionsBuilder::new(&[&crl]).unwrap().build()),
None,
);
assert!(matches!(result, Err(webpki::Error::CertRevoked)));
}
#[test]
fn test_webpki_cert_crl_dps() {
let der = util::cert_with_crl_dps();
let cert = CertificateDer::from(der);
webpki::EndEntityCert::try_from(&cert).expect("failed to parse cert with CRL DPs ext");
// Webpki doesn't expose the parsed CRL distribution extension, so we can't interrogate that
// it matches the expected form. See `openssl.rs` for more extensive coverage.
}

Sorry, the diff of this file is not supported yet