Skip to content

Commit de5d087

Browse files
committed
x509-cert: add a CRL builder
1 parent 261cdec commit de5d087

File tree

4 files changed

+201
-2
lines changed

4 files changed

+201
-2
lines changed

x509-cert/src/builder.rs

+122-2
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,13 @@ use spki::{
1313
use crate::{
1414
AlgorithmIdentifier, SubjectPublicKeyInfo,
1515
certificate::{Certificate, TbsCertificate, Version},
16-
ext::{AsExtension, Extensions},
16+
crl::{CertificateList, RevokedCert, TbsCertList},
17+
ext::{
18+
AsExtension, Extensions,
19+
pkix::{AuthorityKeyIdentifier, CrlNumber, SubjectKeyIdentifier},
20+
},
1721
serial_number::SerialNumber,
18-
time::Validity,
22+
time::{Time, Validity},
1923
};
2024

2125
pub mod profile;
@@ -410,3 +414,119 @@ where
410414
<T as Builder>::finalize(self, signer)
411415
}
412416
}
417+
418+
/// X.509 CRL builder
419+
pub struct CrlBuilder {
420+
tbs: TbsCertList,
421+
}
422+
423+
impl CrlBuilder {
424+
/// Create a `CrlBuilder` with the given issuer and the given monotonic [`CrlNumber`]
425+
#[cfg(feature = "std")]
426+
pub fn new(issuer: &Certificate, crl_number: CrlNumber) -> der::Result<Self> {
427+
let this_update = Time::now()?;
428+
Self::new_with_this_update(issuer, crl_number, this_update)
429+
}
430+
431+
/// Create a `CrlBuilder` with the given issuer, a given monotonic [`CrlNumber`], and valid
432+
/// from the given `this_update` start validity date.
433+
pub fn new_with_this_update(
434+
issuer: &Certificate,
435+
crl_number: CrlNumber,
436+
this_update: Time,
437+
) -> der::Result<Self> {
438+
// Replaced later when the finalize is called
439+
let signature_alg = AlgorithmIdentifier {
440+
oid: NULL_OID,
441+
parameters: None,
442+
};
443+
444+
let issuer_name = issuer.tbs_certificate.subject().clone();
445+
446+
let mut crl_extensions = Extensions::new();
447+
crl_extensions.push(crl_number.to_extension(&issuer_name, &crl_extensions)?);
448+
let aki = match issuer
449+
.tbs_certificate
450+
.get_extension::<AuthorityKeyIdentifier>()?
451+
{
452+
Some((_, aki)) => aki,
453+
None => {
454+
let ski = SubjectKeyIdentifier::try_from(
455+
issuer
456+
.tbs_certificate
457+
.subject_public_key_info()
458+
.owned_to_ref(),
459+
)?;
460+
AuthorityKeyIdentifier {
461+
// KeyIdentifier must be the same as subjectKeyIdentifier
462+
key_identifier: Some(ski.0.clone()),
463+
// other fields must not be present.
464+
..Default::default()
465+
}
466+
}
467+
};
468+
crl_extensions.push(aki.to_extension(&issuer_name, &crl_extensions)?);
469+
470+
let tbs = TbsCertList {
471+
version: Version::V2,
472+
signature: signature_alg,
473+
issuer: issuer_name,
474+
this_update,
475+
next_update: None,
476+
revoked_certificates: None,
477+
crl_extensions: Some(crl_extensions),
478+
};
479+
480+
Ok(Self { tbs })
481+
}
482+
483+
/// Make the CRL valid until the given `next_update`
484+
pub fn with_next_update(mut self, next_update: Option<Time>) -> Self {
485+
self.tbs.next_update = next_update;
486+
self
487+
}
488+
489+
/// Add certificates to the revocation list
490+
pub fn with_certificates<I>(mut self, revoked: I) -> Self
491+
where
492+
I: Iterator<Item = RevokedCert>,
493+
{
494+
let certificates = self
495+
.tbs
496+
.revoked_certificates
497+
.get_or_insert_with(vec::Vec::new);
498+
499+
let mut revoked: vec::Vec<RevokedCert> = revoked.collect();
500+
certificates.append(&mut revoked);
501+
502+
self
503+
}
504+
}
505+
506+
impl Builder for CrlBuilder {
507+
type Output = CertificateList;
508+
509+
fn finalize<S>(&mut self, cert_signer: &S) -> Result<vec::Vec<u8>>
510+
where
511+
S: Keypair + DynSignatureAlgorithmIdentifier,
512+
S::VerifyingKey: EncodePublicKey,
513+
{
514+
self.tbs.signature = cert_signer.signature_algorithm_identifier()?;
515+
516+
self.tbs.to_der().map_err(Error::from)
517+
}
518+
519+
fn assemble<S>(self, signature: BitString, _signer: &S) -> Result<Self::Output>
520+
where
521+
S: Keypair + DynSignatureAlgorithmIdentifier,
522+
S::VerifyingKey: EncodePublicKey,
523+
{
524+
let signature_algorithm = self.tbs.signature.clone();
525+
526+
Ok(CertificateList {
527+
tbs_cert_list: self.tbs,
528+
signature_algorithm,
529+
signature,
530+
})
531+
}
532+
}

x509-cert/src/crl.rs

+8
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ use alloc::vec::Vec;
1414
use der::asn1::BitString;
1515
use der::{Sequence, ValueOrd};
1616

17+
#[cfg(feature = "pem")]
18+
use der::pem::PemLabel;
19+
1720
/// `CertificateList` as defined in [RFC 5280 Section 5.1].
1821
///
1922
/// ```text
@@ -33,6 +36,11 @@ pub struct CertificateList<P: Profile = Rfc5280> {
3336
pub signature: BitString,
3437
}
3538

39+
#[cfg(feature = "pem")]
40+
impl<P: Profile> PemLabel for CertificateList<P> {
41+
const PEM_LABEL: &'static str = "X509 CRL";
42+
}
43+
3644
/// Implicit intermediate structure from the ASN.1 definition of `TBSCertList`.
3745
///
3846
/// This type is used for the `revoked_certificates` field of `TbsCertList`.

x509-cert/test-support/src/openssl.rs

+4
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ pub fn check_certificate(pem: &[u8]) -> String {
3838
check_openssl_output(&["x509"], pem)
3939
}
4040

41+
pub fn check_crl(pem: &[u8]) -> String {
42+
check_openssl_output(&["crl"], pem)
43+
}
44+
4145
pub fn check_request(pem: &[u8]) -> String {
4246
check_openssl_output(&["req", "-verify"], pem)
4347
}

x509-cert/tests/builder_crl.rs

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
#![cfg(all(feature = "builder", feature = "pem"))]
2+
3+
use std::{str::FromStr, time::Duration};
4+
5+
use der::{EncodePem, pem::LineEnding};
6+
use p256::{NistP256, ecdsa::DerSignature, pkcs8::DecodePrivateKey};
7+
use rand::rng;
8+
use x509_cert::{
9+
SubjectPublicKeyInfo,
10+
builder::{Builder, CertificateBuilder, CrlBuilder, profile},
11+
crl::RevokedCert,
12+
ext::pkix::CrlNumber,
13+
name::Name,
14+
serial_number::SerialNumber,
15+
time::{Time, Validity},
16+
};
17+
use x509_cert_test_support::openssl;
18+
19+
const PKCS8_PUBLIC_KEY_DER: &[u8] = include_bytes!("examples/p256-pub.der");
20+
const PKCS8_PRIVATE_KEY_DER: &[u8] = include_bytes!("examples/p256-priv.der");
21+
22+
fn ecdsa_signer() -> ecdsa::SigningKey<NistP256> {
23+
let secret_key = p256::SecretKey::from_pkcs8_der(PKCS8_PRIVATE_KEY_DER).unwrap();
24+
ecdsa::SigningKey::from(secret_key)
25+
}
26+
27+
#[test]
28+
fn crl_signer() {
29+
let mut rng = rng();
30+
let serial_number = SerialNumber::generate(&mut rng);
31+
let validity = Validity::from_now(Duration::new(5, 0)).unwrap();
32+
let subject =
33+
Name::from_str("CN=World domination corporation,O=World domination Inc,C=US").unwrap();
34+
let profile = profile::cabf::Root::new(false, subject).expect("create root profile");
35+
let pub_key = SubjectPublicKeyInfo::try_from(PKCS8_PUBLIC_KEY_DER).expect("get ecdsa pub key");
36+
37+
let signer = ecdsa_signer();
38+
let builder = CertificateBuilder::new(profile, serial_number, validity, pub_key)
39+
.expect("Create certificate");
40+
41+
let ca_certificate = builder.build::<_, DerSignature>(&signer).unwrap();
42+
43+
let crl_number = CrlNumber::try_from(42u128).unwrap();
44+
45+
let builder = CrlBuilder::new(&ca_certificate, crl_number)
46+
.unwrap()
47+
.with_certificates(
48+
vec![
49+
RevokedCert {
50+
serial_number: SerialNumber::generate(&mut rng),
51+
revocation_date: Time::now().unwrap(),
52+
crl_entry_extensions: None,
53+
},
54+
RevokedCert {
55+
serial_number: SerialNumber::generate(&mut rng),
56+
revocation_date: Time::now().unwrap(),
57+
crl_entry_extensions: None,
58+
},
59+
]
60+
.into_iter(),
61+
);
62+
63+
let crl = builder.build::<_, DerSignature>(&signer).unwrap();
64+
65+
let pem = crl.to_pem(LineEnding::LF).expect("generate pem");
66+
println!("{}", openssl::check_crl(pem.as_bytes()));
67+
}

0 commit comments

Comments
 (0)