rcgen/
crl.rs

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