rcgen/
crl.rs

1#[cfg(feature = "pem")]
2use pem::Pem;
3use pki_types::CertificateRevocationListDer;
4use time::OffsetDateTime;
5use yasna::DERWriter;
6use yasna::Tag;
7
8#[cfg(feature = "pem")]
9use crate::ENCODE_CONFIG;
10use crate::{
11	oid, write_distinguished_name, write_dt_utc_or_generalized,
12	write_x509_authority_key_identifier, write_x509_extension, Certificate, Error, Issuer,
13	KeyIdMethod, KeyPair, KeyUsagePurpose, SerialNumber,
14};
15
16/// A certificate revocation list (CRL)
17///
18/// ## Example
19///
20/// ```
21/// extern crate rcgen;
22/// use rcgen::*;
23///
24/// #[cfg(not(feature = "crypto"))]
25/// struct MyKeyPair { public_key: Vec<u8> }
26/// #[cfg(not(feature = "crypto"))]
27/// impl RemoteKeyPair for MyKeyPair {
28///   fn public_key(&self) -> &[u8] { &self.public_key }
29///   fn sign(&self, _: &[u8]) -> Result<Vec<u8>, rcgen::Error> { Ok(vec![]) }
30///   fn algorithm(&self) -> &'static SignatureAlgorithm { &PKCS_ED25519 }
31/// }
32/// # fn main () {
33/// // Generate a CRL issuer.
34/// let mut issuer_params = CertificateParams::new(vec!["crl.issuer.example.com".to_string()]).unwrap();
35/// issuer_params.serial_number = Some(SerialNumber::from(9999));
36/// issuer_params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained);
37/// issuer_params.key_usages = vec![KeyUsagePurpose::KeyCertSign, KeyUsagePurpose::DigitalSignature, KeyUsagePurpose::CrlSign];
38/// #[cfg(feature = "crypto")]
39/// let key_pair = KeyPair::generate().unwrap();
40/// #[cfg(not(feature = "crypto"))]
41/// let remote_key_pair = MyKeyPair { public_key: vec![] };
42/// #[cfg(not(feature = "crypto"))]
43/// let key_pair = KeyPair::from_remote(Box::new(remote_key_pair)).unwrap();
44/// let issuer = issuer_params.self_signed(&key_pair).unwrap();
45/// // Describe a revoked certificate.
46/// let revoked_cert = RevokedCertParams{
47///   serial_number: SerialNumber::from(9999),
48///   revocation_time: date_time_ymd(2024, 06, 17),
49///   reason_code: Some(RevocationReason::KeyCompromise),
50///   invalidity_date: None,
51/// };
52/// // Create a CRL signed by the issuer, revoking revoked_cert.
53/// let crl = CertificateRevocationListParams{
54///   this_update: date_time_ymd(2023, 06, 17),
55///   next_update: date_time_ymd(2024, 06, 17),
56///   crl_number: SerialNumber::from(1234),
57///   issuing_distribution_point: None,
58///   revoked_certs: vec![revoked_cert],
59///   #[cfg(feature = "crypto")]
60///   key_identifier_method: KeyIdMethod::Sha256,
61///   #[cfg(not(feature = "crypto"))]
62///   key_identifier_method: KeyIdMethod::PreSpecified(vec![]),
63/// }.signed_by(&issuer, &key_pair).unwrap();
64///# }
65pub struct CertificateRevocationList {
66	params: CertificateRevocationListParams,
67	der: CertificateRevocationListDer<'static>,
68}
69
70impl CertificateRevocationList {
71	/// Returns the certificate revocation list (CRL) parameters.
72	pub fn params(&self) -> &CertificateRevocationListParams {
73		&self.params
74	}
75
76	/// Get the CRL in PEM encoded format.
77	#[cfg(feature = "pem")]
78	pub fn pem(&self) -> Result<String, Error> {
79		let p = Pem::new("X509 CRL", &*self.der);
80		Ok(pem::encode_config(&p, ENCODE_CONFIG))
81	}
82
83	/// Get the CRL in DER encoded format.
84	///
85	/// [`CertificateRevocationListDer`] implements `Deref<Target = [u8]>` and `AsRef<[u8]>`,
86	/// so you can easily extract the DER bytes from the return value.
87	pub fn der(&self) -> &CertificateRevocationListDer<'static> {
88		&self.der
89	}
90}
91
92impl From<CertificateRevocationList> for CertificateRevocationListDer<'static> {
93	fn from(crl: CertificateRevocationList) -> Self {
94		crl.der
95	}
96}
97
98/// A certificate revocation list (CRL) distribution point, to be included in a certificate's
99/// [distribution points extension](https://www.rfc-editor.org/rfc/rfc5280#section-4.2.1.13) or
100/// a CRL's [issuing distribution point extension](https://datatracker.ietf.org/doc/html/rfc5280#section-5.2.5)
101#[derive(Debug, PartialEq, Eq, Clone)]
102pub struct CrlDistributionPoint {
103	/// One or more URI distribution point names, indicating a place the current CRL can
104	/// be retrieved. When present, SHOULD include at least one LDAP or HTTP URI.
105	pub uris: Vec<String>,
106}
107
108impl CrlDistributionPoint {
109	pub(crate) fn write_der(&self, writer: DERWriter) {
110		// DistributionPoint SEQUENCE
111		writer.write_sequence(|writer| {
112			write_distribution_point_name_uris(writer.next(), &self.uris);
113		});
114	}
115}
116
117fn write_distribution_point_name_uris<'a>(
118	writer: DERWriter,
119	uris: impl IntoIterator<Item = &'a String>,
120) {
121	// distributionPoint DistributionPointName
122	writer.write_tagged_implicit(Tag::context(0), |writer| {
123		writer.write_sequence(|writer| {
124			// fullName GeneralNames
125			writer
126				.next()
127				.write_tagged_implicit(Tag::context(0), |writer| {
128					// GeneralNames
129					writer.write_sequence(|writer| {
130						for uri in uris.into_iter() {
131							// uniformResourceIdentifier [6] IA5String,
132							writer
133								.next()
134								.write_tagged_implicit(Tag::context(6), |writer| {
135									writer.write_ia5_string(uri)
136								});
137						}
138					})
139				});
140		});
141	});
142}
143
144/// Identifies the reason a certificate was revoked.
145/// See [RFC 5280 §5.3.1][1]
146///
147/// [1]: <https://www.rfc-editor.org/rfc/rfc5280#section-5.3.1>
148#[derive(Debug, Clone, Copy, Eq, PartialEq)]
149#[allow(missing_docs)] // Not much to add above the code name.
150pub enum RevocationReason {
151	Unspecified = 0,
152	KeyCompromise = 1,
153	CaCompromise = 2,
154	AffiliationChanged = 3,
155	Superseded = 4,
156	CessationOfOperation = 5,
157	CertificateHold = 6,
158	// 7 is not defined.
159	RemoveFromCrl = 8,
160	PrivilegeWithdrawn = 9,
161	AaCompromise = 10,
162}
163
164/// Parameters used for certificate revocation list (CRL) generation
165pub struct CertificateRevocationListParams {
166	/// Issue date of the CRL.
167	pub this_update: OffsetDateTime,
168	/// The date by which the next CRL will be issued.
169	pub next_update: OffsetDateTime,
170	/// A monotonically increasing sequence number for a given CRL scope and issuer.
171	pub crl_number: SerialNumber,
172	/// An optional CRL extension identifying the CRL distribution point and scope for a
173	/// particular CRL as described in RFC 5280 Section 5.2.5[^1].
174	///
175	/// [^1]: <https://datatracker.ietf.org/doc/html/rfc5280#section-5.2.5>
176	pub issuing_distribution_point: Option<CrlIssuingDistributionPoint>,
177	/// A list of zero or more parameters describing revoked certificates included in the CRL.
178	pub revoked_certs: Vec<RevokedCertParams>,
179	/// Method to generate key identifiers from public keys
180	///
181	/// Defaults to SHA-256.
182	pub key_identifier_method: KeyIdMethod,
183}
184
185impl CertificateRevocationListParams {
186	/// Serializes the certificate revocation list (CRL).
187	///
188	/// Including a signature from the issuing certificate authority's key.
189	pub fn signed_by(
190		self,
191		issuer: &Certificate,
192		issuer_key: &KeyPair,
193	) -> Result<CertificateRevocationList, Error> {
194		if self.next_update.le(&self.this_update) {
195			return Err(Error::InvalidCrlNextUpdate);
196		}
197
198		let issuer = Issuer {
199			distinguished_name: &issuer.params.distinguished_name,
200			key_identifier_method: &issuer.params.key_identifier_method,
201			key_usages: &issuer.params.key_usages,
202			key_pair: issuer_key,
203		};
204
205		if !issuer.key_usages.is_empty() && !issuer.key_usages.contains(&KeyUsagePurpose::CrlSign) {
206			return Err(Error::IssuerNotCrlSigner);
207		}
208
209		Ok(CertificateRevocationList {
210			der: self.serialize_der(issuer)?.into(),
211			params: self,
212		})
213	}
214
215	fn serialize_der(&self, issuer: Issuer) -> Result<Vec<u8>, Error> {
216		issuer.key_pair.sign_der(|writer| {
217			// Write CRL version.
218			// RFC 5280 §5.1.2.1:
219			//   This optional field describes the version of the encoded CRL.  When
220			//   extensions are used, as required by this profile, this field MUST be
221			//   present and MUST specify version 2 (the integer value is 1).
222			// RFC 5280 §5.2:
223			//   Conforming CRL issuers are REQUIRED to include the authority key
224			//   identifier (Section 5.2.1) and the CRL number (Section 5.2.3)
225			//   extensions in all CRLs issued.
226			writer.next().write_u8(1);
227
228			// Write algorithm identifier.
229			// RFC 5280 §5.1.2.2:
230			//   This field MUST contain the same algorithm identifier as the
231			//   signatureAlgorithm field in the sequence CertificateList
232			issuer.key_pair.alg.write_alg_ident(writer.next());
233
234			// Write issuer.
235			// RFC 5280 §5.1.2.3:
236			//   The issuer field MUST contain a non-empty X.500 distinguished name (DN).
237			write_distinguished_name(writer.next(), &issuer.distinguished_name);
238
239			// Write thisUpdate date.
240			// RFC 5280 §5.1.2.4:
241			//    This field indicates the issue date of this CRL.  thisUpdate may be
242			//    encoded as UTCTime or GeneralizedTime.
243			write_dt_utc_or_generalized(writer.next(), self.this_update);
244
245			// Write nextUpdate date.
246			// While OPTIONAL in the ASN.1 module, RFC 5280 §5.1.2.5 says:
247			//   Conforming CRL issuers MUST include the nextUpdate field in all CRLs.
248			write_dt_utc_or_generalized(writer.next(), self.next_update);
249
250			// Write revokedCertificates.
251			// RFC 5280 §5.1.2.6:
252			//   When there are no revoked certificates, the revoked certificates list
253			//   MUST be absent
254			if !self.revoked_certs.is_empty() {
255				writer.next().write_sequence(|writer| {
256					for revoked_cert in &self.revoked_certs {
257						revoked_cert.write_der(writer.next());
258					}
259				});
260			}
261
262			// Write crlExtensions.
263			// RFC 5280 §5.1.2.7:
264			//   This field may only appear if the version is 2 (Section 5.1.2.1).  If
265			//   present, this field is a sequence of one or more CRL extensions.
266			// RFC 5280 §5.2:
267			//   Conforming CRL issuers are REQUIRED to include the authority key
268			//   identifier (Section 5.2.1) and the CRL number (Section 5.2.3)
269			//   extensions in all CRLs issued.
270			writer.next().write_tagged(Tag::context(0), |writer| {
271				writer.write_sequence(|writer| {
272					// Write authority key identifier.
273					write_x509_authority_key_identifier(
274						writer.next(),
275						self.key_identifier_method
276							.derive(issuer.key_pair.public_key_der()),
277					);
278
279					// Write CRL number.
280					write_x509_extension(writer.next(), oid::CRL_NUMBER, false, |writer| {
281						writer.write_bigint_bytes(self.crl_number.as_ref(), true);
282					});
283
284					// Write issuing distribution point (if present).
285					if let Some(issuing_distribution_point) = &self.issuing_distribution_point {
286						write_x509_extension(
287							writer.next(),
288							oid::CRL_ISSUING_DISTRIBUTION_POINT,
289							true,
290							|writer| {
291								issuing_distribution_point.write_der(writer);
292							},
293						);
294					}
295				});
296			});
297
298			Ok(())
299		})
300	}
301}
302
303/// A certificate revocation list (CRL) issuing distribution point, to be included in a CRL's
304/// [issuing distribution point extension](https://datatracker.ietf.org/doc/html/rfc5280#section-5.2.5).
305pub struct CrlIssuingDistributionPoint {
306	/// The CRL's distribution point, containing a sequence of URIs the CRL can be retrieved from.
307	pub distribution_point: CrlDistributionPoint,
308	/// An optional description of the CRL's scope. If omitted, the CRL may contain
309	/// both user certs and CA certs.
310	pub scope: Option<CrlScope>,
311}
312
313impl CrlIssuingDistributionPoint {
314	fn write_der(&self, writer: DERWriter) {
315		// IssuingDistributionPoint SEQUENCE
316		writer.write_sequence(|writer| {
317			// distributionPoint [0] DistributionPointName OPTIONAL
318			write_distribution_point_name_uris(writer.next(), &self.distribution_point.uris);
319
320			// -- at most one of onlyContainsUserCerts, onlyContainsCACerts,
321			// -- and onlyContainsAttributeCerts may be set to TRUE.
322			if let Some(scope) = self.scope {
323				let tag = match scope {
324					// onlyContainsUserCerts [1] BOOLEAN DEFAULT FALSE,
325					CrlScope::UserCertsOnly => Tag::context(1),
326					// onlyContainsCACerts [2] BOOLEAN DEFAULT FALSE,
327					CrlScope::CaCertsOnly => Tag::context(2),
328				};
329				writer.next().write_tagged_implicit(tag, |writer| {
330					writer.write_bool(true);
331				});
332			}
333		});
334	}
335}
336
337/// Describes the scope of a CRL for an issuing distribution point extension.
338#[derive(Debug, Clone, Copy, Eq, PartialEq)]
339pub enum CrlScope {
340	/// The CRL contains only end-entity user certificates.
341	UserCertsOnly,
342	/// The CRL contains only CA certificates.
343	CaCertsOnly,
344}
345
346/// Parameters used for describing a revoked certificate included in a [`CertificateRevocationList`].
347pub struct RevokedCertParams {
348	/// Serial number identifying the revoked certificate.
349	pub serial_number: SerialNumber,
350	/// The date at which the CA processed the revocation.
351	pub revocation_time: OffsetDateTime,
352	/// An optional reason code identifying why the certificate was revoked.
353	pub reason_code: Option<RevocationReason>,
354	/// An optional field describing the date on which it was known or suspected that the
355	/// private key was compromised or the certificate otherwise became invalid. This date
356	/// may be earlier than the [`RevokedCertParams::revocation_time`].
357	pub invalidity_date: Option<OffsetDateTime>,
358}
359
360impl RevokedCertParams {
361	fn write_der(&self, writer: DERWriter) {
362		writer.write_sequence(|writer| {
363			// Write serial number.
364			// RFC 5280 §4.1.2.2:
365			//    Certificate users MUST be able to handle serialNumber values up to 20 octets.
366			//    Conforming CAs MUST NOT use serialNumber values longer than 20 octets.
367			//
368			//    Note: Non-conforming CAs may issue certificates with serial numbers
369			//    that are negative or zero.  Certificate users SHOULD be prepared to
370			//    gracefully handle such certificates.
371			writer
372				.next()
373				.write_bigint_bytes(self.serial_number.as_ref(), true);
374
375			// Write revocation date.
376			write_dt_utc_or_generalized(writer.next(), self.revocation_time);
377
378			// Write extensions if applicable.
379			// RFC 5280 §5.3:
380			//   Support for the CRL entry extensions defined in this specification is
381			//   optional for conforming CRL issuers and applications.  However, CRL
382			//   issuers SHOULD include reason codes (Section 5.3.1) and invalidity
383			//   dates (Section 5.3.2) whenever this information is available.
384			let has_reason_code =
385				matches!(self.reason_code, Some(reason) if reason != RevocationReason::Unspecified);
386			let has_invalidity_date = self.invalidity_date.is_some();
387			if has_reason_code || has_invalidity_date {
388				writer.next().write_sequence(|writer| {
389					// Write reason code if present.
390					if let Some(reason_code) = self.reason_code {
391						write_x509_extension(writer.next(), oid::CRL_REASONS, false, |writer| {
392							writer.write_enum(reason_code as i64);
393						});
394					}
395
396					// Write invalidity date if present.
397					if let Some(invalidity_date) = self.invalidity_date {
398						write_x509_extension(
399							writer.next(),
400							oid::CRL_INVALIDITY_DATE,
401							false,
402							|writer| {
403								write_dt_utc_or_generalized(writer, invalidity_date);
404							},
405						)
406					}
407				});
408			}
409		})
410	}
411}