Skip to content

Commit 2353f2e

Browse files
committed
x509-cert: add a CRL builder
1 parent 3db966f commit 2353f2e

File tree

4 files changed

+361
-5
lines changed

4 files changed

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

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/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)