Skip to content

Commit e7e4b54

Browse files
authored
x509-cert: add a CRL builder (#1759)
* x509-cert: Create a `CrlNumber` from native types * x509-cert: add a CRL builder
1 parent 305318b commit e7e4b54

File tree

5 files changed

+377
-5
lines changed

5 files changed

+377
-5
lines changed

x509-cert/src/builder.rs

Lines changed: 122 additions & 2 deletions
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;
@@ -564,3 +568,119 @@ where
564568
<T as Builder>::finalize(self, signer)
565569
}
566570
}
571+
572+
/// X.509 CRL builder
573+
pub struct CrlBuilder {
574+
tbs: TbsCertList,
575+
}
576+
577+
impl CrlBuilder {
578+
/// Create a `CrlBuilder` with the given issuer and the given monotonic [`CrlNumber`]
579+
#[cfg(feature = "std")]
580+
pub fn new(issuer: &Certificate, crl_number: CrlNumber) -> der::Result<Self> {
581+
let this_update = Time::now()?;
582+
Self::new_with_this_update(issuer, crl_number, this_update)
583+
}
584+
585+
/// Create a `CrlBuilder` with the given issuer, a given monotonic [`CrlNumber`], and valid
586+
/// from the given `this_update` start validity date.
587+
pub fn new_with_this_update(
588+
issuer: &Certificate,
589+
crl_number: CrlNumber,
590+
this_update: Time,
591+
) -> der::Result<Self> {
592+
// Replaced later when the finalize is called
593+
let signature_alg = AlgorithmIdentifier {
594+
oid: NULL_OID,
595+
parameters: None,
596+
};
597+
598+
let issuer_name = issuer.tbs_certificate.subject().clone();
599+
600+
let mut crl_extensions = Extensions::new();
601+
crl_extensions.push(crl_number.to_extension(&issuer_name, &crl_extensions)?);
602+
let aki = match issuer
603+
.tbs_certificate
604+
.get_extension::<AuthorityKeyIdentifier>()?
605+
{
606+
Some((_, aki)) => aki,
607+
None => {
608+
let ski = SubjectKeyIdentifier::try_from(
609+
issuer
610+
.tbs_certificate
611+
.subject_public_key_info()
612+
.owned_to_ref(),
613+
)?;
614+
AuthorityKeyIdentifier {
615+
// KeyIdentifier must be the same as subjectKeyIdentifier
616+
key_identifier: Some(ski.0.clone()),
617+
// other fields must not be present.
618+
..Default::default()
619+
}
620+
}
621+
};
622+
crl_extensions.push(aki.to_extension(&issuer_name, &crl_extensions)?);
623+
624+
let tbs = TbsCertList {
625+
version: Version::V2,
626+
signature: signature_alg,
627+
issuer: issuer_name,
628+
this_update,
629+
next_update: None,
630+
revoked_certificates: None,
631+
crl_extensions: Some(crl_extensions),
632+
};
633+
634+
Ok(Self { tbs })
635+
}
636+
637+
/// Make the CRL valid until the given `next_update`
638+
pub fn with_next_update(mut self, next_update: Option<Time>) -> Self {
639+
self.tbs.next_update = next_update;
640+
self
641+
}
642+
643+
/// Add certificates to the revocation list
644+
pub fn with_certificates<I>(mut self, revoked: I) -> Self
645+
where
646+
I: Iterator<Item = RevokedCert>,
647+
{
648+
let certificates = self
649+
.tbs
650+
.revoked_certificates
651+
.get_or_insert_with(vec::Vec::new);
652+
653+
let mut revoked: vec::Vec<RevokedCert> = revoked.collect();
654+
certificates.append(&mut revoked);
655+
656+
self
657+
}
658+
}
659+
660+
impl Builder for CrlBuilder {
661+
type Output = CertificateList;
662+
663+
fn finalize<S>(&mut self, cert_signer: &S) -> Result<vec::Vec<u8>>
664+
where
665+
S: Keypair + DynSignatureAlgorithmIdentifier,
666+
S::VerifyingKey: EncodePublicKey,
667+
{
668+
self.tbs.signature = cert_signer.signature_algorithm_identifier()?;
669+
670+
self.tbs.to_der().map_err(Error::from)
671+
}
672+
673+
fn assemble<S>(self, signature: BitString, _signer: &S) -> Result<Self::Output>
674+
where
675+
S: Keypair + DynSignatureAlgorithmIdentifier,
676+
S::VerifyingKey: EncodePublicKey,
677+
{
678+
let signature_algorithm = self.tbs.signature.clone();
679+
680+
Ok(CertificateList {
681+
tbs_cert_list: self.tbs,
682+
signature_algorithm,
683+
signature,
684+
})
685+
}
686+
}

x509-cert/src/crl.rs

Lines changed: 8 additions & 0 deletions
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/src/ext/pkix/crl.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,22 @@ impl AssociatedOid for CrlNumber {
3030
impl_newtype!(CrlNumber, Uint);
3131
impl_extension!(CrlNumber, critical = false);
3232

33+
macro_rules! impl_from_traits {
34+
($($uint:ty),+) => {
35+
$(
36+
impl TryFrom<$uint> for CrlNumber {
37+
type Error = der::Error;
38+
39+
fn try_from(value: $uint) -> der::Result<Self> {
40+
Uint::try_from(value).map(Self)
41+
}
42+
}
43+
)+
44+
}
45+
}
46+
47+
impl_from_traits!(u8, u16, u32, u64, u128);
48+
3349
/// BaseCRLNumber as defined in [RFC 5280 Section 5.2.4].
3450
///
3551
/// ```text

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

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::{
2-
fs::File,
2+
fs::{self, File},
33
io::{Read, Write},
4-
process::{Command, Stdio},
4+
process::{Command, ExitStatus, Stdio},
55
};
66
use tempfile::tempdir;
77

@@ -21,7 +21,7 @@ fn check_openssl_output(command_and_args: &[&str], pem: &[u8]) -> String {
2121
.stderr(Stdio::inherit())
2222
.stdout(Stdio::piped())
2323
.spawn()
24-
.expect("zlint failed");
24+
.expect("openssl failed");
2525
let mut stdout = child.stdout.take().unwrap();
2626
let exit_status = child.wait().expect("get openssl x509 status");
2727

@@ -38,6 +38,53 @@ 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
}
48+
49+
pub fn verify(trust_anchor: &[u8], leaf: &[u8], crl: &[u8]) -> (ExitStatus, String, String) {
50+
let tmp_dir = tempdir().expect("create tempdir");
51+
let trust_anchor_path = tmp_dir.path().join("trust_anchor.pem");
52+
let leaf_path = tmp_dir.path().join("leaf.pem");
53+
let crl_path = tmp_dir.path().join("crl.pem");
54+
55+
fs::write(&trust_anchor_path, trust_anchor).expect("Write trust anchor");
56+
fs::write(&leaf_path, leaf).expect("Write leaf");
57+
fs::write(&crl_path, crl).expect("Write crl");
58+
59+
let mut child = Command::new("openssl")
60+
.arg("verify")
61+
.arg("-crl_check")
62+
.arg("-CRLfile")
63+
.arg(&crl_path)
64+
.arg("-trusted")
65+
.arg(&trust_anchor_path)
66+
.arg("--")
67+
.arg(&leaf_path)
68+
.stderr(Stdio::piped())
69+
.stdout(Stdio::piped())
70+
.spawn()
71+
.expect("openssl failed");
72+
let mut stdout = child.stdout.take().unwrap();
73+
let mut stderr = child.stderr.take().unwrap();
74+
75+
let mut output_buf = Vec::new();
76+
stdout
77+
.read_to_end(&mut output_buf)
78+
.expect("read openssl output");
79+
let mut stderr_buf = Vec::new();
80+
stderr
81+
.read_to_end(&mut stderr_buf)
82+
.expect("read openssl output");
83+
let exit_status = child.wait().expect("get openssl verify status");
84+
85+
(
86+
exit_status,
87+
String::from_utf8(output_buf.clone()).unwrap(),
88+
String::from_utf8(stderr_buf.clone()).unwrap(),
89+
)
90+
}

0 commit comments

Comments
 (0)