Skip to content
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
4 changes: 2 additions & 2 deletions Cargo.lock

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

4 changes: 2 additions & 2 deletions x509-cert/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,12 @@ spki = { version = "0.7.1", features = ["alloc"] }
# optional dependencies
arbitrary = { version = "1.3", features = ["derive"], optional = true }
sha1 = { version = "0.10.0", optional = true }
signature = { version = "2.1.0", optional = true }
signature = { version = "2.1.0", features = ["rand_core"], optional = true }

[dev-dependencies]
hex-literal = "0.4"
rand = "0.8.5"
rsa = { version = "0.9.0-pre.1", features = ["sha2"] }
rsa = { version = "0.9.0-pre.2", features = ["sha2"] }
ecdsa = { version = "0.16.4", features = ["digest", "pem"] }
p256 = "0.13.0"
rstest = "0.17"
Expand Down
48 changes: 35 additions & 13 deletions x509-cert/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
use alloc::vec;
use core::fmt;
use der::{asn1::BitString, referenced::OwnedToRef, Encode};
use signature::{Keypair, SignatureEncoding, Signer};
use signature::{rand_core::CryptoRngCore, Keypair, RandomizedSigner, SignatureEncoding, Signer};
use spki::{
DynSignatureAlgorithmIdentifier, EncodePublicKey, SubjectPublicKeyInfoOwned,
SubjectPublicKeyInfoRef,
Expand Down Expand Up @@ -32,7 +32,7 @@ pub enum Error {
/// Public key errors propagated from the [`spki::Error`] type.
PublicKey(spki::Error),

/// Signing error propagated for the [`signature::Signer`] type.
/// Signing error propagated for the [`signature::Error`] type.
Signature(signature::Error),
}

Expand Down Expand Up @@ -244,17 +244,14 @@ where
S::VerifyingKey: EncodePublicKey,
{
/// Creates a new certificate builder
pub fn new<Signature>(
pub fn new(
profile: Profile,
serial_number: SerialNumber,
mut validity: Validity,
subject: Name,
subject_public_key_info: SubjectPublicKeyInfoOwned,
signer: &'s S,
) -> Result<Self>
where
S: Signer<Signature>,
{
) -> Result<Self> {
let verifying_key = signer.verifying_key();
let signer_pub = verifying_key
.to_public_key_der()?
Expand Down Expand Up @@ -313,19 +310,24 @@ where
Ok(())
}

/// Run the certificate through the signer and build the end certificate.
pub fn build<Signature>(mut self) -> Result<Certificate>
where
S: Signer<Signature>,
Signature: SignatureEncoding,
{
fn finalize(&mut self) {
if self.tbs.extensions.is_none() {
if self.tbs.issuer_unique_id.is_some() || self.tbs.subject_unique_id.is_some() {
self.tbs.version = Version::V2;
} else {
self.tbs.version = Version::V1;
}
}
}

/// Run the certificate through the signer and build the end certificate.
pub fn build<Signature>(mut self) -> Result<Certificate>
where
S: Signer<Signature>,
Signature: SignatureEncoding,
{
self.finalize();

let signature = self.signer.try_sign(&self.tbs.to_der()?)?;
let signature = BitString::from_bytes(signature.to_bytes().as_ref())?;

Expand All @@ -337,4 +339,24 @@ where

Ok(cert)
}

/// Run the certificate through the signer and build the end certificate.
pub fn build_with_rng<Signature>(mut self, rng: &mut impl CryptoRngCore) -> Result<Certificate>
where
S: RandomizedSigner<Signature>,
Signature: SignatureEncoding,
{
self.finalize();

let signature = self.signer.try_sign_with_rng(rng, &self.tbs.to_der()?)?;
let signature = BitString::from_bytes(signature.to_bytes().as_ref())?;

let cert = Certificate {
tbs_certificate: self.tbs.clone(),
signature_algorithm: self.tbs.signature,
signature,
};

Ok(cert)
}
}
83 changes: 64 additions & 19 deletions x509-cert/tests/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,15 +62,9 @@ fn sub_ca_certificate() {
SubjectPublicKeyInfoOwned::try_from(RSA_2048_DER_EXAMPLE).expect("get rsa pub key");

let signer = ecdsa_signer();
let builder = CertificateBuilder::new::<ecdsa::Signature<NistP256>>(
profile,
serial_number,
validity,
subject,
pub_key,
&signer,
)
.expect("Create certificate");
let builder =
CertificateBuilder::new(profile, serial_number, validity, subject, pub_key, &signer)
.expect("Create certificate");

let certificate = builder.build::<ecdsa::Signature<NistP256>>().unwrap();

Expand Down Expand Up @@ -106,15 +100,9 @@ fn leaf_certificate() {
SubjectPublicKeyInfoOwned::try_from(RSA_2048_DER_EXAMPLE).expect("get rsa pub key");

let signer = ecdsa_signer();
let builder = CertificateBuilder::new::<ecdsa::Signature<NistP256>>(
profile,
serial_number,
validity,
subject,
pub_key,
&signer,
)
.expect("Create certificate");
let builder =
CertificateBuilder::new(profile, serial_number, validity, subject, pub_key, &signer)
.expect("Create certificate");

let certificate = builder.build::<ecdsa::Signature<NistP256>>().unwrap();

Expand All @@ -141,11 +129,68 @@ fn leaf_certificate() {
zlint::check_certificate(pem.as_bytes(), ignored);
}

#[test]
fn pss_certificate() {
let serial_number = SerialNumber::from(42u32);
let validity = Validity::from_now(Duration::new(5, 0)).unwrap();

let issuer =
Name::from_str("CN=World domination corporation,O=World domination Inc,C=US").unwrap();
let profile = Profile::Leaf {
issuer,
enable_key_agreement: false,
enable_key_encipherment: false,
};

let subject = Name::from_str("CN=service.domination.world").unwrap();
let pub_key =
SubjectPublicKeyInfoOwned::try_from(RSA_2048_DER_EXAMPLE).expect("get rsa pub key");

let signer = rsa_pss_signer();
let builder =
CertificateBuilder::new(profile, serial_number, validity, subject, pub_key, &signer)
.expect("Create certificate");

let certificate = builder
.build_with_rng::<rsa::pss::Signature>(&mut rand::thread_rng())
.unwrap();

let pem = certificate.to_pem(LineEnding::LF).expect("generate pem");
println!("{}", openssl::check_certificate(pem.as_bytes()));

// TODO(baloo): not too sure we should tackle those in this API.
let ignored = &[
"e_sub_cert_aia_missing",
"e_sub_cert_crl_distribution_points_missing",
"w_sub_cert_aia_does_not_contain_issuing_ca_url",
// Missing policies
"e_sub_cert_certificate_policies_missing",
"e_sub_cert_cert_policy_empty",
// Needs to be added by the end-user
"e_sub_cert_aia_does_not_contain_ocsp_url",
// SAN needs to include DNS name (if used)
"e_ext_san_missing",
"e_subject_common_name_not_exactly_from_san",
// Extended key usage needs to be added by end-user and is use-case dependent
"e_sub_cert_eku_missing",
// zlint warns on RSAPSS signature algorithms
"e_signature_algorithm_not_supported",
];

zlint::check_certificate(pem.as_bytes(), ignored);
}

const RSA_2048_PRIV_DER_EXAMPLE: &[u8] = include_bytes!("examples/rsa2048-priv.der");

fn rsa_signer() -> SigningKey<Sha256> {
let private_key = rsa::RsaPrivateKey::from_pkcs1_der(RSA_2048_PRIV_DER_EXAMPLE).unwrap();
let signing_key = SigningKey::<Sha256>::new_with_prefix(private_key);
let signing_key = SigningKey::<Sha256>::new(private_key);
signing_key
}

fn rsa_pss_signer() -> rsa::pss::SigningKey<Sha256> {
let private_key = rsa::RsaPrivateKey::from_pkcs1_der(RSA_2048_PRIV_DER_EXAMPLE).unwrap();
let signing_key = rsa::pss::SigningKey::<Sha256>::new(private_key);
signing_key
}

Expand Down