x509/lib.rs
1//! *Pure-Rust X.509 certificate serialization*
2//!
3//! `x509` is a crate providing serialization APIs for X.509 v3 ([RFC 5280]) certificates,
4//! implemented using the `cookie-factory` combinatorial serializer framework.
5//!
6//! [RFC 5280]: https://tools.ietf.org/html/rfc5280
7
8use cookie_factory::{GenResult, WriteContext};
9use std::io::Write;
10
11pub mod der;
12
13/// A trait for objects which represent ASN.1 `AlgorithmIdentifier`s.
14pub trait AlgorithmIdentifier {
15 type AlgorithmOid: der::Oid;
16
17 /// Returns the object identifier for this `AlgorithmIdentifier`.
18 fn algorithm(&self) -> Self::AlgorithmOid;
19
20 /// Writes the parameters for this `AlgorithmIdentifier`, if any.
21 fn parameters<W: Write>(&self, w: WriteContext<W>) -> GenResult<W>;
22}
23
24/// A trait for objects which represent ASN.1 `SubjectPublicKeyInfo`s.
25pub trait SubjectPublicKeyInfo {
26 type AlgorithmId: AlgorithmIdentifier;
27 type SubjectPublicKey: AsRef<[u8]>;
28
29 /// Returns the [`AlgorithmIdentifier`] for this public key.
30 fn algorithm_id(&self) -> Self::AlgorithmId;
31
32 /// Returns the encoded public key.
33 fn public_key(&self) -> Self::SubjectPublicKey;
34}
35
36#[derive(Clone)]
37enum RdnType {
38 Country,
39 Organization,
40 OrganizationalUnit,
41 CommonName,
42}
43
44/// An X.509 RelativeDistinguishedName.
45#[derive(Clone)]
46pub struct RelativeDistinguishedName<'a> {
47 typ: RdnType,
48 value: &'a str,
49}
50
51impl<'a> RelativeDistinguishedName<'a> {
52 /// Constructs a Country RDN.
53 ///
54 /// # Panics
55 /// Panics if `value.len() > 64`.
56 pub fn country(value: &'a str) -> Self {
57 assert!(value.len() <= 64);
58
59 RelativeDistinguishedName {
60 typ: RdnType::Country,
61 value,
62 }
63 }
64
65 /// Constructs an Organization RDN.
66 ///
67 /// # Panics
68 /// Panics if `value.len() > 64`.
69 pub fn organization(value: &'a str) -> Self {
70 assert!(value.len() <= 64);
71
72 RelativeDistinguishedName {
73 typ: RdnType::Organization,
74 value,
75 }
76 }
77
78 /// Constructs an OrganizationalUnit RDN.
79 ///
80 /// # Panics
81 /// Panics if `value.len() > 64`.
82 pub fn organizational_unit(value: &'a str) -> Self {
83 assert!(value.len() <= 64);
84
85 RelativeDistinguishedName {
86 typ: RdnType::OrganizationalUnit,
87 value,
88 }
89 }
90
91 /// Constructs a CommonName RDN.
92 ///
93 /// # Panics
94 /// Panics if `value.len() > 64`.
95 pub fn common_name(value: &'a str) -> Self {
96 assert!(value.len() <= 64);
97
98 RelativeDistinguishedName {
99 typ: RdnType::CommonName,
100 value,
101 }
102 }
103}
104
105/// A certificate extension.
106pub struct Extension<'a, O: der::Oid + 'a> {
107 /// An OID that specifies the format and definitions of the extension.
108 oid: O,
109 /// Whether the information in the extension is important.
110 ///
111 /// ```text
112 /// Each extension in a certificate may be designated as critical or non-critical. A
113 /// certificate using system MUST reject the certificate if it encounters a critical
114 /// extension it does not recognize; however, a non-critical extension may be ignored
115 /// if it is not recognized.
116 /// ```
117 critical: bool,
118 /// The DER encoding of an ASN.1 value corresponding to the extension type identified
119 /// by `oid`.
120 value: &'a [u8],
121}
122
123impl<'a, O: der::Oid + 'a> Extension<'a, O> {
124 /// Constructs an extension.
125 ///
126 /// If this extension is not recognized by a certificate-using system, it will be
127 /// ignored.
128 ///
129 /// `oid` is an OID that specifies the format and definitions of the extension.
130 ///
131 /// `value` is the DER encoding of an ASN.1 value corresponding to the extension type
132 /// identified by `oid`.
133 pub fn regular(oid: O, value: &'a [u8]) -> Self {
134 Extension {
135 oid,
136 critical: false,
137 value,
138 }
139 }
140
141 /// Constructs a critical extension.
142 ///
143 /// If this extension is not recognized by a certificate-using system, the certificate
144 /// will be rejected.
145 ///
146 /// `oid` is an OID that specifies the format and definitions of the extension.
147 ///
148 /// `value` is the DER encoding of an ASN.1 value corresponding to the extension type
149 /// identified by `oid`.
150 pub fn critical(oid: O, value: &'a [u8]) -> Self {
151 Extension {
152 oid,
153 critical: true,
154 value,
155 }
156 }
157}
158
159/// X.509 serialization APIs.
160pub mod write {
161 use chrono::{DateTime, Datelike, TimeZone, Utc};
162 use cookie_factory::{
163 combinator::{cond, slice},
164 multi::all,
165 sequence::pair,
166 SerializeFn, WriteContext,
167 };
168 use std::io::Write;
169
170 use crate::{Extension, RdnType, RelativeDistinguishedName};
171
172 use super::{
173 der::{write::*, Oid},
174 AlgorithmIdentifier, SubjectPublicKeyInfo,
175 };
176
177 /// X.509 versions that we care about.
178 #[derive(Clone, Copy)]
179 enum Version {
180 V3,
181 }
182
183 impl From<Version> for usize {
184 fn from(version: Version) -> usize {
185 match version {
186 Version::V3 => 2,
187 }
188 }
189 }
190
191 /// Object identifiers used internally by X.509.
192 enum InternalOid {
193 Country,
194 Organization,
195 OrganizationalUnit,
196 CommonName,
197 }
198
199 impl AsRef<[u64]> for InternalOid {
200 fn as_ref(&self) -> &[u64] {
201 match self {
202 InternalOid::Country => &[2, 5, 4, 6],
203 InternalOid::Organization => &[2, 5, 4, 10],
204 InternalOid::OrganizationalUnit => &[2, 5, 4, 11],
205 InternalOid::CommonName => &[2, 5, 4, 3],
206 }
207 }
208 }
209
210 impl Oid for InternalOid {}
211
212 impl<'a> RelativeDistinguishedName<'a> {
213 fn oid(&self) -> InternalOid {
214 match self.typ {
215 RdnType::Country => InternalOid::Country,
216 RdnType::CommonName => InternalOid::CommonName,
217 RdnType::Organization => InternalOid::Organization,
218 RdnType::OrganizationalUnit => InternalOid::OrganizationalUnit,
219 }
220 }
221 }
222
223 /// From [RFC 5280](https://tools.ietf.org/html/rfc5280#section-4.1):
224 /// ```text
225 /// TBSCertificate ::= SEQUENCE {
226 /// version [0] EXPLICIT Version DEFAULT v1,
227 /// ...
228 /// }
229 ///
230 /// Version ::= INTEGER { v1(0), v2(1), v3(2) }
231 /// ```
232 fn version<W: Write>(version: Version) -> impl SerializeFn<W> {
233 // TODO: Omit version if V1, once x509-parser correctly handles this.
234 der_explicit(0, der_integer_usize(version.into()))
235 }
236
237 /// From [RFC 5280](https://tools.ietf.org/html/rfc5280#section-4.1.1.2):
238 /// ```text
239 /// AlgorithmIdentifier ::= SEQUENCE {
240 /// algorithm OBJECT IDENTIFIER,
241 /// parameters ANY DEFINED BY algorithm OPTIONAL }
242 /// ```
243 pub fn algorithm_identifier<'a, W: Write + 'a, Alg: AlgorithmIdentifier>(
244 algorithm_id: &'a Alg,
245 ) -> impl SerializeFn<W> + 'a {
246 der_sequence((
247 der_oid(algorithm_id.algorithm()),
248 move |w: WriteContext<Vec<u8>>| algorithm_id.parameters(w),
249 ))
250 }
251
252 /// Encodes an X.509 RelativeDistinguishedName.
253 ///
254 /// From [RFC 5280 section 4.1.2.4](https://tools.ietf.org/html/rfc5280#section-4.1.2.4):
255 /// ```text
256 /// RelativeDistinguishedName ::=
257 /// SET SIZE (1..MAX) OF AttributeTypeAndValue
258 ///
259 /// AttributeTypeAndValue ::= SEQUENCE {
260 /// type AttributeType,
261 /// value AttributeValue }
262 ///
263 /// AttributeType ::= OBJECT IDENTIFIER
264 ///
265 /// AttributeValue ::= ANY -- DEFINED BY AttributeType
266 /// ```
267 ///
268 /// From [RFC 5280 appendix A.1](https://tools.ietf.org/html/rfc5280#appendix-A.1):
269 /// ```text
270 /// X520CommonName ::= CHOICE {
271 /// teletexString TeletexString (SIZE (1..ub-common-name)),
272 /// printableString PrintableString (SIZE (1..ub-common-name)),
273 /// universalString UniversalString (SIZE (1..ub-common-name)),
274 /// utf8String UTF8String (SIZE (1..ub-common-name)),
275 /// bmpString BMPString (SIZE (1..ub-common-name)) }
276 ///
277 /// ub-common-name INTEGER ::= 64
278 /// ```
279 fn relative_distinguished_name<'a, W: Write + 'a>(
280 rdn: &'a RelativeDistinguishedName<'a>,
281 ) -> impl SerializeFn<W> + 'a {
282 der_set((der_sequence((
283 der_oid(rdn.oid()),
284 der_utf8_string(&rdn.value),
285 )),))
286 }
287
288 /// Encodes an X.509 Name.
289 ///
290 /// From [RFC 5280 section 4.1.2.4](https://tools.ietf.org/html/rfc5280#section-4.1.2.4):
291 /// ```text
292 /// Name ::= CHOICE { -- only one possibility for now --
293 /// rdnSequence RDNSequence }
294 ///
295 /// RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
296 /// ```
297 fn name<'a, W: Write + 'a>(
298 name: &'a [RelativeDistinguishedName<'a>],
299 ) -> impl SerializeFn<W> + 'a {
300 der_sequence((all(name.iter().map(relative_distinguished_name)),))
301 }
302
303 /// From [RFC 5280](https://tools.ietf.org/html/rfc5280#section-4.1):
304 /// ```text
305 /// Time ::= CHOICE {
306 /// utcTime UTCTime,
307 /// generalTime GeneralizedTime }
308 ///
309 /// CAs conforming to this profile MUST always encode certificate
310 /// validity dates through the year 2049 as UTCTime; certificate validity
311 /// dates in 2050 or later MUST be encoded as GeneralizedTime.
312 /// ```
313 fn time<W: Write>(t: DateTime<Utc>) -> impl SerializeFn<W> {
314 pair(
315 cond(t.year() < 2050, der_utc_time(t)),
316 cond(t.year() >= 2050, der_generalized_time(t)),
317 )
318 }
319
320 /// From [RFC 5280](https://tools.ietf.org/html/rfc5280#section-4.1):
321 /// ```text
322 /// Validity ::= SEQUENCE {
323 /// notBefore Time,
324 /// notAfter Time }
325 ///
326 /// To indicate that a certificate has no well-defined expiration date,
327 /// the notAfter SHOULD be assigned the GeneralizedTime value of
328 /// 99991231235959Z.
329 /// ```
330 fn validity<W: Write>(
331 not_before: DateTime<Utc>,
332 not_after: Option<DateTime<Utc>>,
333 ) -> impl SerializeFn<W> {
334 der_sequence((
335 time(not_before),
336 time(not_after.unwrap_or_else(|| Utc.ymd(9999, 12, 31).and_hms(23, 59, 59))),
337 ))
338 }
339
340 /// Encodes a `PublicKeyInfo` as an ASN.1 `SubjectPublicKeyInfo` using DER.
341 ///
342 /// From [RFC 5280](https://tools.ietf.org/html/rfc5280#section-4.1):
343 /// ```text
344 /// SubjectPublicKeyInfo ::= SEQUENCE {
345 /// algorithm AlgorithmIdentifier,
346 /// subjectPublicKey BIT STRING }
347 /// ```
348 fn subject_public_key_info<'a, W: Write + 'a, PKI: SubjectPublicKeyInfo>(
349 subject_pki: &'a PKI,
350 ) -> impl SerializeFn<W> + 'a {
351 move |w: WriteContext<W>| {
352 der_sequence((
353 algorithm_identifier(&subject_pki.algorithm_id()),
354 der_bit_string(subject_pki.public_key().as_ref()),
355 ))(w)
356 }
357 }
358
359 /// From [RFC 5280](https://tools.ietf.org/html/rfc5280#section-4.1):
360 /// ```text
361 /// Extension ::= SEQUENCE {
362 /// extnID OBJECT IDENTIFIER,
363 /// critical BOOLEAN DEFAULT FALSE,
364 /// extnValue OCTET STRING
365 /// -- contains the DER encoding of an ASN.1 value
366 /// -- corresponding to the extension type identified
367 /// -- by extnID
368 /// }
369 /// ```
370 fn extension<'a, W: Write + 'a, O: Oid + 'a>(
371 extension: &'a Extension<'a, O>,
372 ) -> impl SerializeFn<W> + 'a {
373 der_sequence((
374 der_oid(&extension.oid),
375 der_default(der_boolean, extension.critical, false),
376 der_octet_string(extension.value),
377 ))
378 }
379
380 /// From [RFC 5280](https://tools.ietf.org/html/rfc5280#section-4.1):
381 /// ```text
382 /// TBSCertificate ::= SEQUENCE {
383 /// ...
384 /// extensions [3] EXPLICIT Extensions OPTIONAL
385 /// -- If present, version MUST be v3
386 /// }
387 ///
388 /// Extensions ::= SEQUENCE SIZE (1..MAX) OF Extension
389 /// ```
390 fn extensions<'a, W: Write + 'a, O: Oid + 'a>(
391 exts: &'a [Extension<'a, O>],
392 ) -> impl SerializeFn<W> + 'a {
393 cond(
394 !exts.is_empty(),
395 der_explicit(3, der_sequence((all(exts.iter().map(extension)),))),
396 )
397 }
398
399 /// Encodes a version 1 X.509 `TBSCertificate` using DER.
400 ///
401 /// `extensions` is optional; if empty, no extensions section will be serialized. Due
402 /// to the need for an `O: Oid` type parameter, users who do not have any extensions
403 /// should use the following workaround:
404 ///
405 /// ```ignore
406 /// let exts: &[Extension<'_, &[u64]>] = &[];
407 /// x509::write::tbs_certificate(
408 /// serial_number,
409 /// signature,
410 /// issuer,
411 /// not_before,
412 /// not_after,
413 /// subject,
414 /// subject_pki,
415 /// exts,
416 /// );
417 /// ```
418 ///
419 /// From [RFC 5280](https://tools.ietf.org/html/rfc5280#section-4.1):
420 /// ```text
421 /// TBSCertificate ::= SEQUENCE {
422 /// version [0] EXPLICIT Version DEFAULT v1,
423 /// serialNumber CertificateSerialNumber,
424 /// signature AlgorithmIdentifier,
425 /// issuer Name,
426 /// validity Validity,
427 /// subject Name,
428 /// subjectPublicKeyInfo SubjectPublicKeyInfo,
429 /// issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL,
430 /// -- If present, version MUST be v2 or v3
431 /// subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL,
432 /// -- If present, version MUST be v2 or v3
433 /// extensions [3] EXPLICIT Extensions OPTIONAL
434 /// -- If present, version MUST be v3
435 /// }
436 ///
437 /// CertificateSerialNumber ::= INTEGER
438 ///
439 /// Certificate users MUST be able to handle serialNumber values up to 20 octets.
440 /// Conforming CAs MUST NOT use serialNumber values longer than 20 octets.
441 /// ```
442 ///
443 /// # Panics
444 ///
445 /// Panics if:
446 /// - `serial_number.len() > 20`
447 pub fn tbs_certificate<'a, W: Write + 'a, Alg, PKI, O: Oid + 'a>(
448 serial_number: &'a [u8],
449 signature: &'a Alg,
450 issuer: &'a [RelativeDistinguishedName<'a>],
451 not_before: DateTime<Utc>,
452 not_after: Option<DateTime<Utc>>,
453 subject: &'a [RelativeDistinguishedName<'a>],
454 subject_pki: &'a PKI,
455 exts: &'a [Extension<'a, O>],
456 ) -> impl SerializeFn<W> + 'a
457 where
458 Alg: AlgorithmIdentifier,
459 PKI: SubjectPublicKeyInfo,
460 {
461 assert!(serial_number.len() <= 20);
462
463 der_sequence((
464 version(Version::V3),
465 der_integer(serial_number),
466 algorithm_identifier(signature),
467 name(issuer),
468 validity(not_before, not_after),
469 name(subject),
470 subject_public_key_info(subject_pki),
471 extensions(exts),
472 ))
473 }
474
475 /// Encodes an X.509 certificate using DER.
476 ///
477 /// From [RFC 5280](https://tools.ietf.org/html/rfc5280#section-4.1):
478 /// ```text
479 /// Certificate ::= SEQUENCE {
480 /// tbsCertificate TBSCertificate,
481 /// signatureAlgorithm AlgorithmIdentifier,
482 /// signatureValue BIT STRING }
483 /// ```
484 ///
485 /// Use [`tbs_certificate`] to serialize the certificate itself, then sign it and call
486 /// this function with the serialized `TBSCertificate` and signature.
487 pub fn certificate<'a, W: Write + 'a, Alg: AlgorithmIdentifier>(
488 cert: &'a [u8],
489 signature_algorithm: &'a Alg,
490 signature: &'a [u8],
491 ) -> impl SerializeFn<W> + 'a {
492 der_sequence((
493 slice(cert),
494 algorithm_identifier(signature_algorithm),
495 der_bit_string(signature),
496 ))
497 }
498}
499
500#[cfg(test)]
501mod tests {
502 use chrono::Utc;
503
504 use crate::{
505 write, AlgorithmIdentifier, Extension, RelativeDistinguishedName, SubjectPublicKeyInfo,
506 };
507
508 struct MockAlgorithmId;
509
510 impl AlgorithmIdentifier for MockAlgorithmId {
511 type AlgorithmOid = &'static [u64];
512
513 fn algorithm(&self) -> Self::AlgorithmOid {
514 &[1, 1, 1, 1]
515 }
516
517 fn parameters<W: std::io::Write>(
518 &self,
519 w: cookie_factory::WriteContext<W>,
520 ) -> cookie_factory::GenResult<W> {
521 Ok(w)
522 }
523 }
524
525 struct MockPublicKeyInfo;
526
527 impl SubjectPublicKeyInfo for MockPublicKeyInfo {
528 type AlgorithmId = MockAlgorithmId;
529 type SubjectPublicKey = Vec<u8>;
530
531 fn algorithm_id(&self) -> Self::AlgorithmId {
532 MockAlgorithmId
533 }
534
535 fn public_key(&self) -> Self::SubjectPublicKey {
536 vec![]
537 }
538 }
539
540 #[test]
541 fn names() {
542 const COUNTRY: &str = "NZ";
543 const ORGANIZATION: &str = "ACME";
544 const ORGANIZATIONAL_UNIT: &str = "Road Runner";
545 const COMMON_NAME: &str = "Test-in-a-Box";
546
547 let subject = &[
548 RelativeDistinguishedName::country(COUNTRY),
549 RelativeDistinguishedName::organization(ORGANIZATION),
550 RelativeDistinguishedName::organizational_unit(ORGANIZATIONAL_UNIT),
551 RelativeDistinguishedName::common_name(COMMON_NAME),
552 ];
553 let exts: &[Extension<'_, &[u64]>] = &[];
554
555 let mut tbs_cert = vec![];
556 cookie_factory::gen(
557 write::tbs_certificate(
558 &[],
559 &MockAlgorithmId,
560 &[],
561 Utc::now(),
562 None,
563 subject,
564 &MockPublicKeyInfo,
565 exts,
566 ),
567 &mut tbs_cert,
568 )
569 .unwrap();
570
571 let mut data = vec![];
572 cookie_factory::gen(
573 write::certificate(&tbs_cert, &MockAlgorithmId, &[]),
574 &mut data,
575 )
576 .unwrap();
577
578 let (_, cert) = x509_parser::parse_x509_certificate(&data).unwrap();
579
580 assert_eq!(
581 cert.subject()
582 .iter_country()
583 .map(|c| c.as_str())
584 .collect::<Result<Vec<_>, _>>(),
585 Ok(vec![COUNTRY])
586 );
587 assert_eq!(
588 cert.subject()
589 .iter_organization()
590 .map(|c| c.as_str())
591 .collect::<Result<Vec<_>, _>>(),
592 Ok(vec![ORGANIZATION])
593 );
594 assert_eq!(
595 cert.subject()
596 .iter_organizational_unit()
597 .map(|c| c.as_str())
598 .collect::<Result<Vec<_>, _>>(),
599 Ok(vec![ORGANIZATIONAL_UNIT])
600 );
601 assert_eq!(
602 cert.subject()
603 .iter_common_name()
604 .map(|c| c.as_str())
605 .collect::<Result<Vec<_>, _>>(),
606 Ok(vec![COMMON_NAME])
607 );
608 }
609
610 #[test]
611 fn extensions() {
612 let signature = MockAlgorithmId;
613 let not_before = Utc::now();
614 let subject_pki = MockPublicKeyInfo;
615 let exts = &[
616 Extension::regular(&[1u64, 2, 3, 4][..], &[1, 2, 3]),
617 Extension::critical(&[1u64, 4, 5, 6][..], &[7, 7, 7]),
618 ];
619
620 let mut tbs_cert = vec![];
621 cookie_factory::gen(
622 write::tbs_certificate(
623 &[],
624 &signature,
625 &[],
626 not_before,
627 None,
628 &[],
629 &subject_pki,
630 exts,
631 ),
632 &mut tbs_cert,
633 )
634 .unwrap();
635
636 let mut data = vec![];
637 cookie_factory::gen(
638 write::certificate(&tbs_cert, &MockAlgorithmId, &[]),
639 &mut data,
640 )
641 .unwrap();
642
643 let (_, cert) = x509_parser::parse_x509_certificate(&data).unwrap();
644
645 assert_eq!(
646 cert.validity().not_before.timestamp(),
647 not_before.timestamp()
648 );
649
650 for ext in exts {
651 let oid = x509_parser::der_parser::oid::Oid::from(ext.oid).unwrap();
652 if let Some(extension) = cert.extensions().get(&oid) {
653 assert_eq!(extension.critical, ext.critical);
654 assert_eq!(extension.value, ext.value);
655 } else {
656 panic!();
657 }
658 }
659 }
660}