1use der::{Decode, Encode};
2use std::collections::BTreeMap;
3use x509_cert::crl::CertificateList;
4use x509_cert::ext::pkix::CrlReason;
5
6use crate::error::{Result, SignError};
7use crate::source::{
8 CaIdentity, CertificateStatus, Epoch, RevocationSource, StatusChange, StatusSnapshot,
9};
10
11pub struct CrlSource {
12 crl: CertificateList,
13 issuer: Option<x509_cert::Certificate>,
14}
15
16impl CrlSource {
17 pub fn from_der(data: Vec<u8>) -> Result<Self> {
18 let crl = CertificateList::from_der(&data)
19 .map_err(|e| SignError::CrlParse(format!("failed to parse CRL DER: {e}")))?;
20 Ok(CrlSource { crl, issuer: None })
21 }
22
23 pub fn with_issuer_certificate(mut self, der: &[u8]) -> Result<Self> {
25 self.issuer = Some(
26 crate::rotation::parse_certificate(der)
27 .map_err(|e| SignError::Verify(format!("CRL issuer certificate: {e}")))?,
28 );
29 Ok(self)
30 }
31
32 fn authenticate(&self, ca: &CaIdentity) -> Result<()> {
33 use x509_cert::ext::pkix::KeyUsage;
34 let cert = self.issuer.as_ref().ok_or_else(|| {
35 SignError::Verify("CRL requires an authorized issuer certificate".into())
36 })?;
37 let tbs = &cert.tbs_certificate;
38 let crl = &self.crl;
39 let now = crate::source::unix_now()?;
40 if tbs.subject.to_der()? != ca.issuer_name_der
41 || tbs.subject_public_key_info.subject_public_key.raw_bytes() != ca.issuer_key_bytes
42 || crl.tbs_cert_list.issuer != tbs.subject
43 || tbs.subject.0.is_empty()
44 {
45 return Err(SignError::Verify(
46 "CRL issuer does not match authorized CA".into(),
47 ));
48 }
49 if now < time_to_epoch(tbs.validity.not_before)
50 || now >= time_to_epoch(tbs.validity.not_after)
51 {
52 return Err(SignError::Verify(
53 "CRL issuer certificate is outside validity".into(),
54 ));
55 }
56 if let Some(exts) = &tbs.extensions {
57 for ext in exts {
58 if ext.extn_id == KeyUsage::OID
59 && !KeyUsage::from_der(ext.extn_value.as_bytes())?.crl_sign()
60 {
61 return Err(SignError::Verify(
62 "issuer key usage forbids CRL signing".into(),
63 ));
64 }
65 }
66 }
67 if crl.signature_algorithm != crl.tbs_cert_list.signature {
68 return Err(SignError::Verify(
69 "CRL signature algorithms disagree".into(),
70 ));
71 }
72 if let Some(exts) = &crl.tbs_cert_list.crl_extensions {
73 let mut seen = std::collections::BTreeSet::new();
74 for ext in exts {
75 let oid = ext.extn_id.to_string();
76 if oid == "2.5.29.20" {
77 let _ = x509_cert::ext::pkix::CrlNumber::from_der(ext.extn_value.as_bytes())?;
78 }
79 if oid == "2.5.29.35" {
80 use x509_cert::ext::pkix::{AuthorityKeyIdentifier, SubjectKeyIdentifier};
81 let aki = AuthorityKeyIdentifier::from_der(ext.extn_value.as_bytes())?;
82 if let Some(serial) = aki.authority_cert_serial_number {
83 if serial != tbs.serial_number {
84 return Err(SignError::Verify("CRL authority serial mismatch".into()));
85 }
86 }
87 if let Some(names) = aki.authority_cert_issuer {
88 if !names.iter().any(|n| matches!(n, x509_cert::ext::pkix::name::GeneralName::DirectoryName(name) if name == &tbs.issuer)) {
89 return Err(SignError::Verify("CRL authority certificate issuer mismatch".into()));
90 }
91 }
92 if let Some(id) = aki.key_identifier {
93 if let Some(ski) = tbs.extensions.as_ref().and_then(|es| {
94 es.iter().find(|e| e.extn_id == SubjectKeyIdentifier::OID)
95 }) {
96 if SubjectKeyIdentifier::from_der(ski.extn_value.as_bytes())?.0 != id {
97 return Err(SignError::Verify(
98 "CRL authority key identifier mismatch".into(),
99 ));
100 }
101 }
102 }
103 }
104 if !seen.insert(oid.clone())
105 || matches!(oid.as_str(), "2.5.29.27" | "2.5.29.28")
106 || (ext.critical && !matches!(oid.as_str(), "2.5.29.20" | "2.5.29.35"))
107 {
108 return Err(SignError::Verify(format!(
109 "unsupported or duplicate CRL extension {oid}"
110 )));
111 }
112 }
113 }
114 let bytes = crl.tbs_cert_list.to_der()?;
115 let signature = crl
116 .signature
117 .as_bytes()
118 .ok_or_else(|| SignError::Verify("invalid CRL signature bit string".into()))?;
119 let spki = &tbs.subject_public_key_info;
120 match crl.signature_algorithm.oid.to_string().as_str() {
121 "1.2.840.10045.4.3.2" => {
122 if crl.signature_algorithm.parameters.is_some() {
123 return Err(SignError::Verify(
124 "ECDSA signature parameters must be absent".into(),
125 ));
126 }
127 use p256::pkcs8::DecodePublicKey;
128 use signature::Verifier;
129 let key = p256::ecdsa::VerifyingKey::from_public_key_der(&spki.to_der()?)
130 .map_err(|e| SignError::Verify(format!("CRL P-256 key: {e}")))?;
131 let sig = p256::ecdsa::Signature::from_der(signature)
132 .map_err(|e| SignError::Verify(format!("CRL signature: {e}")))?;
133 key.verify(&bytes, &sig)
134 .map_err(|_| SignError::Verify("CRL signature verification failed".into()))
135 }
136 oid @ ("1.2.840.113549.1.1.11" | "1.2.840.113549.1.1.12" | "1.2.840.113549.1.1.13") => {
137 use aws_lc_rs::signature as aws;
138 if spki.algorithm.oid.to_string() != "1.2.840.113549.1.1.1" {
139 return Err(SignError::Verify(
140 "CRL signature requires RSA public key".into(),
141 ));
142 }
143 for parameters in [
144 &spki.algorithm.parameters,
145 &crl.signature_algorithm.parameters,
146 ]
147 .into_iter()
148 .flatten()
149 {
150 if parameters.to_der()? != [5, 0] {
151 return Err(SignError::Verify("invalid RSA algorithm parameters".into()));
152 }
153 }
154 let algorithm = match oid {
155 "1.2.840.113549.1.1.11" => &aws::RSA_PKCS1_2048_8192_SHA256,
156 "1.2.840.113549.1.1.12" => &aws::RSA_PKCS1_2048_8192_SHA384,
157 _ => &aws::RSA_PKCS1_2048_8192_SHA512,
158 };
159 let key = spki
160 .subject_public_key
161 .as_bytes()
162 .ok_or_else(|| SignError::Verify("invalid RSA key bit string".into()))?;
163 aws::UnparsedPublicKey::new(algorithm, key).verify(&bytes, signature)
164 .map_err(|_| SignError::Verify("CRL RSA signature verification failed (supported keys: 2048..8192 bits)".into()))
165 }
166
167 oid @ ("2.16.840.1.101.3.4.3.17"
168 | "2.16.840.1.101.3.4.3.18"
169 | "2.16.840.1.101.3.4.3.19") => {
170 if spki.algorithm.oid.to_string() != oid
171 || spki.algorithm.parameters.is_some()
172 || crl.signature_algorithm.parameters.is_some()
173 {
174 return Err(SignError::Verify(
175 "ML-DSA CRL algorithm/key mismatch".into(),
176 ));
177 }
178 let key = spki
179 .subject_public_key
180 .as_bytes()
181 .ok_or_else(|| SignError::Verify("invalid public key bits".into()))?;
182 match oid {
183 "2.16.840.1.101.3.4.3.17" => {
184 verify_ml_crl::<ml_dsa::MlDsa44>(&bytes, signature, key)
185 }
186 "2.16.840.1.101.3.4.3.18" => {
187 verify_ml_crl::<ml_dsa::MlDsa65>(&bytes, signature, key)
188 }
189 _ => verify_ml_crl::<ml_dsa::MlDsa87>(&bytes, signature, key),
190 }
191 }
192 other => Err(SignError::Verify(format!(
193 "unsupported CRL signature algorithm: {other}; supported: ECDSA P-256/SHA-256, RSA PKCS1v1.5/SHA-256/384/512, ML-DSA"
194 ))),
195 }
196 }
197
198 pub fn from_pem(pem_text: &str) -> Result<Self> {
199 let der_data = pem_to_der(pem_text)?;
200 Self::from_der(der_data)
201 }
202}
203
204impl RevocationSource for CrlSource {
205 fn snapshot(&self, ca: &CaIdentity) -> Result<StatusSnapshot> {
206 self.authenticate(ca)?;
207 let crl = &self.crl;
208 let mut entries = BTreeMap::new();
209
210 if let Some(revoked_certs) = &crl.tbs_cert_list.revoked_certificates {
211 for rc in revoked_certs {
212 if let Some(exts) = &rc.crl_entry_extensions {
213 for ext in exts {
214 if ext.extn_id.to_string() == "2.5.29.29"
215 || (ext.critical && ext.extn_id != CrlReason::OID)
216 {
217 return Err(SignError::Verify(
218 "unsupported CRL entry extension".into(),
219 ));
220 }
221 if ext.extn_id == CrlReason::OID {
222 let reason = CrlReason::from_der(ext.extn_value.as_bytes())?;
223 if reason == CrlReason::RemoveFromCRL {
224 return Err(SignError::Verify(
225 "removeFromCRL requires unsupported delta semantics".into(),
226 ));
227 }
228 }
229 }
230 }
231 let serial = rc.serial_number.as_bytes().to_vec();
232
233 let reason = rc.crl_entry_extensions.as_ref().and_then(|exts| {
234 exts.iter()
235 .find(|ext| ext.extn_id == CrlReason::OID)
236 .and_then(|ext| CrlReason::from_der(ext.extn_value.as_bytes()).ok())
237 });
238
239 let revocation_time = time_to_epoch(rc.revocation_date);
240
241 entries.insert(
242 serial,
243 CertificateStatus::Revoked {
244 revocation_time,
245 reason,
246 },
247 );
248 }
249 }
250
251 let this_update = time_to_epoch(crl.tbs_cert_list.this_update);
252 let next_update = crl.tbs_cert_list.next_update.map(time_to_epoch);
253
254 let snapshot = StatusSnapshot {
255 entries,
256 this_update,
257 next_update,
258 not_after: std::collections::BTreeMap::new(),
261 };
262 snapshot.validate_at(crate::source::unix_now()?)?;
263 Ok(snapshot)
264 }
265
266 fn changes_since(&self, ca: &CaIdentity, _since: Epoch) -> Result<Vec<StatusChange>> {
267 let snapshot = self.snapshot(ca)?;
268 Ok(snapshot
269 .entries
270 .into_iter()
271 .map(|(serial, status)| StatusChange {
272 serial,
273 status,
274 timestamp: snapshot.this_update,
275 })
276 .collect())
277 }
278
279 fn supports_streaming(&self) -> bool {
280 false
281 }
282}
283
284fn verify_ml_crl<P: ml_dsa::MlDsaParams>(data: &[u8], sig: &[u8], key: &[u8]) -> Result<()> {
285 use ml_dsa::Verifier;
286 let encoded = ml_dsa::EncodedVerifyingKey::<P>::try_from(key)
287 .map_err(|_| SignError::Verify("invalid ML-DSA key".into()))?;
288 let key = ml_dsa::VerifyingKey::<P>::decode(&encoded);
289 let sig = ml_dsa::Signature::<P>::try_from(sig)
290 .map_err(|_| SignError::Verify("invalid ML-DSA signature".into()))?;
291 key.verify(data, &sig)
292 .map_err(|_| SignError::Verify("CRL signature verification failed".into()))
293}
294
295fn time_to_epoch(time: x509_cert::time::Time) -> u64 {
296 let dt = match time {
297 x509_cert::time::Time::UtcTime(t) => t.to_date_time(),
298 x509_cert::time::Time::GeneralTime(t) => t.to_date_time(),
299 };
300 datetime_to_epoch(dt)
301}
302
303fn datetime_to_epoch(dt: der::DateTime) -> u64 {
304 crate::generate::datetime_to_epoch(dt)
305}
306
307fn pem_to_der(pem: &str) -> Result<Vec<u8>> {
308 use base64::Engine;
309 let mut collecting = false;
310 let mut found_end = false;
311 let mut b64 = String::new();
312 for line in pem.lines() {
313 if line.starts_with("-----BEGIN") {
314 collecting = true;
315 continue;
316 }
317 if line.starts_with("-----END") {
318 found_end = true;
319 break;
320 }
321 if collecting {
322 b64.push_str(line.trim());
323 }
324 }
325 if !collecting {
326 return Err(SignError::CrlParse("PEM has no BEGIN marker".into()));
327 }
328 if !found_end {
329 return Err(SignError::CrlParse(
330 "PEM is truncated: found BEGIN but no END marker".into(),
331 ));
332 }
333 base64::engine::general_purpose::STANDARD
334 .decode(&b64)
335 .map_err(|e| SignError::CrlParse(format!("PEM base64 decode: {e}")))
336}
337
338use const_oid::AssociatedOid;
339
340#[cfg(test)]
341mod tests {
342 use super::*;
343
344 fn build_test_crl_der() -> Vec<u8> {
345 use der::Encode;
346 use der::asn1::BitString;
347 use der::asn1::UtcTime;
348 use spki::AlgorithmIdentifierOwned;
349 use x509_cert::crl::{CertificateList, RevokedCert, TbsCertList};
350 use x509_cert::name::RdnSequence;
351 use x509_cert::time::Time;
352
353 let now_dt = der::DateTime::new(2025, 1, 15, 12, 0, 0).unwrap();
354 let next_dt = der::DateTime::new(2025, 1, 16, 12, 0, 0).unwrap();
355 let revoke_dt = der::DateTime::new(2025, 1, 10, 8, 0, 0).unwrap();
356
357 let this_update = Time::UtcTime(UtcTime::from_date_time(now_dt).unwrap());
358 let next_update = Time::UtcTime(UtcTime::from_date_time(next_dt).unwrap());
359 let revoke_time = Time::UtcTime(UtcTime::from_date_time(revoke_dt).unwrap());
360
361 let serial_42 = x509_cert::serial_number::SerialNumber::new(&[42u8]).unwrap();
362 let serial_100 = x509_cert::serial_number::SerialNumber::new(&[100u8]).unwrap();
363
364 let revoked_certs = vec![
365 RevokedCert {
366 serial_number: serial_42,
367 revocation_date: revoke_time,
368 crl_entry_extensions: None,
369 },
370 RevokedCert {
371 serial_number: serial_100,
372 revocation_date: revoke_time,
373 crl_entry_extensions: None,
374 },
375 ];
376
377 let sha256_with_ecdsa = const_oid::ObjectIdentifier::new_unwrap("1.2.840.10045.4.3.2");
378
379 let tbs = TbsCertList {
380 version: x509_cert::Version::V2,
381 signature: AlgorithmIdentifierOwned {
382 oid: sha256_with_ecdsa,
383 parameters: None,
384 },
385 issuer: RdnSequence::default(),
386 this_update,
387 next_update: Some(next_update),
388 revoked_certificates: Some(revoked_certs),
389 crl_extensions: None,
390 };
391
392 let crl = CertificateList {
393 tbs_cert_list: tbs,
394 signature_algorithm: AlgorithmIdentifierOwned {
395 oid: sha256_with_ecdsa,
396 parameters: None,
397 },
398 signature: BitString::from_bytes(&[0u8; 64]).unwrap(),
399 };
400
401 crl.to_der().expect("CRL encoding failed")
402 }
403
404 #[test]
405 fn parse_crl_extracts_revoked_serials() {
406 let crl_der = build_test_crl_der();
407 let source = CrlSource::from_der(crl_der).unwrap();
408
409 let ca = CaIdentity {
410 label: "test".into(),
411 issuer_name_der: vec![],
412 issuer_key_bytes: vec![],
413 };
414
415 assert!(source.snapshot(&ca).is_err());
416 }
417 fn authenticated_fixture() -> (CrlSource, CaIdentity) {
418 use signature::Signer;
419 let key = crate::demo_ecdsa_p256_key();
420 let cert_der = crate::generate_seal_cert(&key).unwrap();
421 let cert = x509_cert::Certificate::from_der(&cert_der).unwrap();
422 let ca = CaIdentity {
423 label: "fixture".into(),
424 issuer_name_der: cert.tbs_certificate.subject.to_der().unwrap(),
425 issuer_key_bytes: cert
426 .tbs_certificate
427 .subject_public_key_info
428 .subject_public_key
429 .raw_bytes()
430 .to_vec(),
431 };
432 let mut crl = CertificateList::from_der(&build_test_crl_der()).unwrap();
433 crl.tbs_cert_list.issuer = cert.tbs_certificate.subject;
434 let now = crate::source::unix_now().unwrap();
435 let time = |n| {
436 x509_cert::time::Time::GeneralTime(
437 der::asn1::GeneralizedTime::from_unix_duration(std::time::Duration::from_secs(n))
438 .unwrap(),
439 )
440 };
441 crl.tbs_cert_list.this_update = time(now - 1);
442 crl.tbs_cert_list.next_update = Some(time(now + 3600));
443 let sig: p256::ecdsa::DerSignature = key.sign(&crl.tbs_cert_list.to_der().unwrap());
444 crl.signature = der::asn1::BitString::from_bytes(sig.as_bytes()).unwrap();
445 (
446 CrlSource::from_der(crl.to_der().unwrap())
447 .unwrap()
448 .with_issuer_certificate(&cert_der)
449 .unwrap(),
450 ca,
451 )
452 }
453
454 #[test]
455 fn authenticated_crl_accepts_only_matching_issuer_and_signature() {
456 let (mut source, ca) = authenticated_fixture();
457 assert_eq!(source.snapshot(&ca).unwrap().entries.len(), 2);
458 let mut wrong = ca.clone();
459 wrong.issuer_key_bytes[0] ^= 1;
460 assert!(source.snapshot(&wrong).is_err());
461 source
462 .crl
463 .tbs_cert_list
464 .revoked_certificates
465 .as_mut()
466 .unwrap()
467 .clear();
468 assert!(source.snapshot(&ca).is_err());
469 }
470
471 #[test]
472 fn authenticated_crl_rejects_delta_semantics() {
473 let (mut source, ca) = authenticated_fixture();
474 source.crl.tbs_cert_list.crl_extensions = Some(vec![x509_cert::ext::Extension {
475 extn_id: const_oid::ObjectIdentifier::new_unwrap("2.5.29.27"),
476 critical: true,
477 extn_value: der::asn1::OctetString::new(vec![2, 1, 1]).unwrap(),
478 }]);
479 assert!(
480 source
481 .snapshot(&ca)
482 .unwrap_err()
483 .to_string()
484 .contains("unsupported")
485 );
486 }
487 #[test]
488 fn rsa_crls_verify_sha256_sha384_sha512_and_reject_tampering() {
489 use aws_lc_rs::signature::{self as aws, KeyPair};
490 let key = aws::RsaKeyPair::generate(aws_lc_rs::rsa::KeySize::Rsa2048).unwrap();
491 let (template, _) = authenticated_fixture();
492 for oid in [
493 "1.2.840.113549.1.1.11",
494 "1.2.840.113549.1.1.12",
495 "1.2.840.113549.1.1.13",
496 ] {
497 let mut cert = template.issuer.clone().unwrap();
498 cert.tbs_certificate.subject_public_key_info = spki::SubjectPublicKeyInfoOwned {
499 algorithm: spki::AlgorithmIdentifierOwned {
500 oid: "1.2.840.113549.1.1.1".parse().unwrap(),
501 parameters: None,
502 },
503 subject_public_key: der::asn1::BitString::from_bytes(key.public_key().as_ref())
504 .unwrap(),
505 };
506 let ca = CaIdentity {
507 label: "rsa".into(),
508 issuer_name_der: cert.tbs_certificate.subject.to_der().unwrap(),
509 issuer_key_bytes: cert
510 .tbs_certificate
511 .subject_public_key_info
512 .subject_public_key
513 .raw_bytes()
514 .to_vec(),
515 };
516 let mut crl = template.crl.clone();
517 crl.signature_algorithm.oid = oid.parse().unwrap();
518 crl.tbs_cert_list.signature = crl.signature_algorithm.clone();
519 let tbs = crl.tbs_cert_list.to_der().unwrap();
520 let algorithm = match oid {
521 "1.2.840.113549.1.1.11" => &aws::RSA_PKCS1_SHA256,
522 "1.2.840.113549.1.1.12" => &aws::RSA_PKCS1_SHA384,
523 _ => &aws::RSA_PKCS1_SHA512,
524 };
525 let mut signature = vec![0; key.public_modulus_len()];
526 key.sign(
527 algorithm,
528 &aws_lc_rs::rand::SystemRandom::new(),
529 &tbs,
530 &mut signature,
531 )
532 .unwrap();
533 crl.signature = der::asn1::BitString::from_bytes(&signature).unwrap();
534 let mut source = CrlSource {
535 crl,
536 issuer: Some(cert),
537 };
538 assert!(source.snapshot(&ca).is_ok(), "{oid}");
539 source.crl.tbs_cert_list.revoked_certificates = None;
540 assert!(source.snapshot(&ca).is_err());
541 }
542 }
543 #[test]
544 fn issuer_certificate_accepts_pem_and_der_identically() {
545 use der::EncodePem;
546 let (source, ca) = authenticated_fixture();
547 let pem = source
548 .issuer
549 .as_ref()
550 .unwrap()
551 .to_pem(der::pem::LineEnding::LF)
552 .unwrap();
553 let from_pem = CrlSource::from_der(source.crl.to_der().unwrap())
554 .unwrap()
555 .with_issuer_certificate(pem.as_bytes())
556 .unwrap();
557 assert_eq!(
558 source.snapshot(&ca).unwrap().entries.len(),
559 from_pem.snapshot(&ca).unwrap().entries.len()
560 );
561 }
562}