1#![cfg_attr(
12 feature = "pem",
13 doc = r##"
14## Example
15
16```
17use rcgen::{generate_simple_self_signed, CertifiedKey};
18# fn main () {
19// Generate a certificate that's valid for "localhost" and "hello.world.example"
20let subject_alt_names = vec!["hello.world.example".to_string(),
21 "localhost".to_string()];
22
23let CertifiedKey { cert, signing_key } = generate_simple_self_signed(subject_alt_names).unwrap();
24println!("{}", cert.pem());
25println!("{}", signing_key.serialize_pem());
26# }
27```"##
28)]
29#![forbid(unsafe_code)]
30#![forbid(non_ascii_idents)]
31#![deny(missing_docs)]
32#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
33#![warn(unreachable_pub)]
34
35use std::borrow::Cow;
36use std::collections::HashMap;
37use std::fmt;
38use std::hash::Hash;
39use std::net::IpAddr;
40#[cfg(feature = "x509-parser")]
41use std::net::{Ipv4Addr, Ipv6Addr};
42use std::ops::Deref;
43
44#[cfg(feature = "pem")]
45use pem::Pem;
46use pki_types::CertificateDer;
47use time::{OffsetDateTime, Time};
48use yasna::models::ObjectIdentifier;
49use yasna::models::{GeneralizedTime, UTCTime};
50use yasna::tags::{TAG_BMPSTRING, TAG_TELETEXSTRING, TAG_UNIVERSALSTRING};
51use yasna::DERWriter;
52use yasna::Tag;
53
54use crate::string::{BmpString, Ia5String, PrintableString, TeletexString, UniversalString};
55
56pub use certificate::{
57 date_time_ymd, Attribute, BasicConstraints, Certificate, CertificateParams, CidrSubnet,
58 CustomExtension, DnType, ExtendedKeyUsagePurpose, GeneralSubtree, IsCa, NameConstraints,
59};
60pub use crl::{
61 CertificateRevocationList, CertificateRevocationListParams, CrlDistributionPoint,
62 CrlIssuingDistributionPoint, CrlScope, RevocationReason, RevokedCertParams,
63};
64pub use csr::{CertificateSigningRequest, CertificateSigningRequestParams, PublicKey};
65pub use error::{Error, InvalidAsn1String};
66#[cfg(feature = "crypto")]
67pub use key_pair::KeyPair;
68pub use key_pair::PublicKeyData;
69#[cfg(all(feature = "crypto", feature = "aws_lc_rs"))]
70pub use key_pair::RsaKeySize;
71pub use key_pair::{SigningKey, SubjectPublicKeyInfo};
72#[cfg(feature = "crypto")]
73use ring_like::digest;
74pub use sign_algo::algo::*;
75pub use sign_algo::SignatureAlgorithm;
76
77mod certificate;
78mod crl;
79mod csr;
80mod error;
81mod key_pair;
82mod oid;
83mod ring_like;
84mod sign_algo;
85pub mod string;
86
87#[deprecated(
89 note = "Renamed to `Error`. We recommend to refer to it by fully-qualifying the crate: `rcgen::Error`."
90)]
91pub type RcgenError = Error;
92
93#[derive(PartialEq, Eq)]
95pub struct CertifiedKey<S: SigningKey> {
96 pub cert: Certificate,
98 pub signing_key: S,
100}
101
102#[cfg(feature = "crypto")]
111#[cfg_attr(
112 feature = "pem",
113 doc = r##"
114## Example
115
116```
117use rcgen::{generate_simple_self_signed, CertifiedKey};
118# fn main () {
119// Generate a certificate that's valid for "localhost" and "hello.world.example"
120let subject_alt_names = vec!["hello.world.example".to_string(),
121 "localhost".to_string()];
122
123let CertifiedKey { cert, signing_key } = generate_simple_self_signed(subject_alt_names).unwrap();
124
125// The certificate is now valid for localhost and the domain "hello.world.example"
126println!("{}", cert.pem());
127println!("{}", signing_key.serialize_pem());
128# }
129```
130"##
131)]
132pub fn generate_simple_self_signed(
133 subject_alt_names: impl Into<Vec<String>>,
134) -> Result<CertifiedKey<KeyPair>, Error> {
135 let signing_key = KeyPair::generate()?;
136 let cert = CertificateParams::new(subject_alt_names)?.self_signed(&signing_key)?;
137 Ok(CertifiedKey { cert, signing_key })
138}
139
140#[derive(Debug)]
142pub struct CertifiedIssuer<'a, S> {
143 certificate: Certificate,
144 issuer: Issuer<'a, S>,
145}
146
147impl<'a, S: SigningKey> CertifiedIssuer<'a, S> {
148 pub fn self_signed(params: CertificateParams, signing_key: S) -> Result<Self, Error> {
150 Ok(Self {
151 certificate: params.self_signed(&signing_key)?,
152 issuer: Issuer::new(params, signing_key),
153 })
154 }
155
156 pub fn signed_by(
158 params: CertificateParams,
159 signing_key: S,
160 issuer: &Issuer<'_, impl SigningKey>,
161 ) -> Result<Self, Error> {
162 Ok(Self {
163 certificate: params.signed_by(&signing_key, issuer)?,
164 issuer: Issuer::new(params, signing_key),
165 })
166 }
167
168 #[cfg(feature = "pem")]
170 pub fn pem(&self) -> String {
171 pem::encode_config(&Pem::new("CERTIFICATE", self.der().to_vec()), ENCODE_CONFIG)
172 }
173
174 pub fn der(&self) -> &CertificateDer<'static> {
178 self.certificate.der()
179 }
180}
181
182impl<'a, S> Deref for CertifiedIssuer<'a, S> {
183 type Target = Issuer<'a, S>;
184
185 fn deref(&self) -> &Self::Target {
186 &self.issuer
187 }
188}
189
190impl<'a, S> AsRef<Certificate> for CertifiedIssuer<'a, S> {
191 fn as_ref(&self) -> &Certificate {
192 &self.certificate
193 }
194}
195
196pub struct Issuer<'a, S> {
201 distinguished_name: Cow<'a, DistinguishedName>,
202 key_identifier_method: Cow<'a, KeyIdMethod>,
203 key_usages: Cow<'a, [KeyUsagePurpose]>,
204 signing_key: MaybeOwned<'a, S>,
205}
206
207impl<'a, S: SigningKey> Issuer<'a, S> {
208 pub fn new(params: CertificateParams, signing_key: S) -> Self {
210 Self {
211 distinguished_name: Cow::Owned(params.distinguished_name),
212 key_identifier_method: Cow::Owned(params.key_identifier_method),
213 key_usages: Cow::Owned(params.key_usages),
214 signing_key: MaybeOwned::Owned(signing_key),
215 }
216 }
217
218 pub fn from_params(params: &'a CertificateParams, signing_key: &'a S) -> Self {
223 Self {
224 distinguished_name: Cow::Borrowed(¶ms.distinguished_name),
225 key_identifier_method: Cow::Borrowed(¶ms.key_identifier_method),
226 key_usages: Cow::Borrowed(¶ms.key_usages),
227 signing_key: MaybeOwned::Borrowed(signing_key),
228 }
229 }
230
231 #[cfg(all(feature = "pem", feature = "x509-parser"))]
235 pub fn from_ca_cert_pem(pem_str: &str, signing_key: S) -> Result<Self, Error> {
236 let certificate = pem::parse(pem_str).map_err(|_| Error::CouldNotParseCertificate)?;
237 Self::from_ca_cert_der(&certificate.contents().into(), signing_key)
238 }
239
240 #[cfg(feature = "x509-parser")]
249 pub fn from_ca_cert_der(ca_cert: &CertificateDer<'_>, signing_key: S) -> Result<Self, Error> {
250 let (_remainder, x509) = x509_parser::parse_x509_certificate(ca_cert)
251 .map_err(|_| Error::CouldNotParseCertificate)?;
252
253 Ok(Self {
254 key_usages: Cow::Owned(KeyUsagePurpose::from_x509(&x509)?),
255 key_identifier_method: Cow::Owned(KeyIdMethod::from_x509(&x509)?),
256 distinguished_name: Cow::Owned(DistinguishedName::from_name(
257 &x509.tbs_certificate.subject,
258 )?),
259 signing_key: MaybeOwned::Owned(signing_key),
260 })
261 }
262
263 pub fn key_usages(&self) -> &[KeyUsagePurpose] {
265 &self.key_usages
266 }
267
268 pub fn key(&self) -> &S {
270 &self.signing_key
271 }
272}
273
274impl<'a, S> fmt::Debug for Issuer<'a, S> {
275 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
277 let Issuer {
279 distinguished_name,
280 key_identifier_method,
281 key_usages,
282 signing_key: _,
283 } = self;
284
285 f.debug_struct("Issuer")
286 .field("distinguished_name", distinguished_name)
287 .field("key_identifier_method", key_identifier_method)
288 .field("key_usages", key_usages)
289 .field("signing_key", &"[elided]")
290 .finish()
291 }
292}
293
294enum MaybeOwned<'a, T> {
295 Owned(T),
296 Borrowed(&'a T),
297}
298
299impl<T> Deref for MaybeOwned<'_, T> {
300 type Target = T;
301
302 fn deref(&self) -> &Self::Target {
303 match self {
304 MaybeOwned::Owned(t) => t,
305 MaybeOwned::Borrowed(t) => t,
306 }
307 }
308}
309
310#[cfg(feature = "pem")]
316const ENCODE_CONFIG: pem::EncodeConfig = {
317 let line_ending = match cfg!(target_family = "windows") {
318 true => pem::LineEnding::CRLF,
319 false => pem::LineEnding::LF,
320 };
321 pem::EncodeConfig::new().set_line_ending(line_ending)
322};
323
324#[derive(Debug, PartialEq, Eq, Hash, Clone)]
325#[allow(missing_docs)]
326#[non_exhaustive]
327pub enum SanType {
329 Rfc822Name(Ia5String),
331 DnsName(Ia5String),
332 URI(Ia5String),
333 IpAddress(IpAddr),
334 OtherName((Vec<u64>, OtherNameValue)),
335}
336
337impl SanType {
338 #[cfg(all(test, feature = "x509-parser"))]
339 fn from_x509(x509: &x509_parser::certificate::X509Certificate<'_>) -> Result<Vec<Self>, Error> {
340 let sans = x509
341 .subject_alternative_name()
342 .map_err(|_| Error::CouldNotParseCertificate)?
343 .map(|ext| &ext.value.general_names);
344
345 let Some(sans) = sans else {
346 return Ok(Vec::new());
347 };
348
349 let mut subject_alt_names = Vec::with_capacity(sans.len());
350 for san in sans {
351 subject_alt_names.push(Self::try_from_general(san)?);
352 }
353 Ok(subject_alt_names)
354 }
355}
356
357#[derive(Debug, PartialEq, Eq, Hash, Clone)]
365#[non_exhaustive]
366pub enum OtherNameValue {
367 Utf8String(String),
369}
370
371impl OtherNameValue {
372 fn write_der(&self, writer: DERWriter) {
373 writer.write_tagged(Tag::context(0), |writer| match self {
374 OtherNameValue::Utf8String(s) => writer.write_utf8_string(s),
375 });
376 }
377}
378
379impl<T> From<T> for OtherNameValue
380where
381 T: Into<String>,
382{
383 fn from(t: T) -> Self {
384 OtherNameValue::Utf8String(t.into())
385 }
386}
387
388#[cfg(feature = "x509-parser")]
389fn ip_addr_from_octets(octets: &[u8]) -> Result<IpAddr, Error> {
390 if let Ok(ipv6_octets) = <&[u8; 16]>::try_from(octets) {
391 Ok(Ipv6Addr::from(*ipv6_octets).into())
392 } else if let Ok(ipv4_octets) = <&[u8; 4]>::try_from(octets) {
393 Ok(Ipv4Addr::from(*ipv4_octets).into())
394 } else {
395 Err(Error::InvalidIpAddressOctetLength(octets.len()))
396 }
397}
398
399impl SanType {
400 #[cfg(feature = "x509-parser")]
401 fn try_from_general(name: &x509_parser::extensions::GeneralName<'_>) -> Result<Self, Error> {
402 use x509_parser::der_parser::asn1_rs::{self, FromDer, Tag, TaggedExplicit};
403 Ok(match name {
404 x509_parser::extensions::GeneralName::RFC822Name(name) => {
405 SanType::Rfc822Name((*name).try_into()?)
406 },
407 x509_parser::extensions::GeneralName::DNSName(name) => {
408 SanType::DnsName((*name).try_into()?)
409 },
410 x509_parser::extensions::GeneralName::URI(name) => SanType::URI((*name).try_into()?),
411 x509_parser::extensions::GeneralName::IPAddress(octets) => {
412 SanType::IpAddress(ip_addr_from_octets(octets)?)
413 },
414 x509_parser::extensions::GeneralName::OtherName(oid, value) => {
415 let oid = oid.iter().ok_or(Error::CouldNotParseCertificate)?;
416 let (_, other_name) = TaggedExplicit::<asn1_rs::Any, _, 0>::from_der(value)
418 .map_err(|_| Error::CouldNotParseCertificate)?;
419 let other_name = other_name.into_inner();
420
421 let other_name_value = match other_name.tag() {
422 Tag::Utf8String => OtherNameValue::Utf8String(
423 std::str::from_utf8(other_name.data)
424 .map_err(|_| Error::CouldNotParseCertificate)?
425 .to_owned(),
426 ),
427 _ => return Err(Error::CouldNotParseCertificate),
428 };
429 SanType::OtherName((oid.collect(), other_name_value))
430 },
431 _ => return Err(Error::InvalidNameType),
432 })
433 }
434
435 fn tag(&self) -> u64 {
436 const TAG_OTHER_NAME: u64 = 0;
439 const TAG_RFC822_NAME: u64 = 1;
440 const TAG_DNS_NAME: u64 = 2;
441 const TAG_URI: u64 = 6;
442 const TAG_IP_ADDRESS: u64 = 7;
443
444 match self {
445 SanType::Rfc822Name(_name) => TAG_RFC822_NAME,
446 SanType::DnsName(_name) => TAG_DNS_NAME,
447 SanType::URI(_name) => TAG_URI,
448 SanType::IpAddress(_addr) => TAG_IP_ADDRESS,
449 Self::OtherName(_oid) => TAG_OTHER_NAME,
450 }
451 }
452}
453
454#[derive(Debug, PartialEq, Eq, Hash, Clone)]
456#[non_exhaustive]
457pub enum DnValue {
458 BmpString(BmpString),
460 Ia5String(Ia5String),
462 PrintableString(PrintableString),
464 TeletexString(TeletexString),
466 UniversalString(UniversalString),
468 Utf8String(String),
470}
471
472impl<T> From<T> for DnValue
473where
474 T: Into<String>,
475{
476 fn from(t: T) -> Self {
477 DnValue::Utf8String(t.into())
478 }
479}
480
481#[derive(Debug, Default, PartialEq, Eq, Clone)]
482pub struct DistinguishedName {
493 entries: HashMap<DnType, DnValue>,
494 order: Vec<DnType>,
495}
496
497impl DistinguishedName {
498 pub fn new() -> Self {
500 Self::default()
501 }
502 pub fn get(&self, ty: &DnType) -> Option<&DnValue> {
504 self.entries.get(ty)
505 }
506 pub fn remove(&mut self, ty: DnType) -> bool {
512 let removed = self.entries.remove(&ty).is_some();
513 if removed {
514 self.order.retain(|ty_o| &ty != ty_o);
515 }
516 removed
517 }
518 pub fn push(&mut self, ty: DnType, s: impl Into<DnValue>) {
529 if !self.entries.contains_key(&ty) {
530 self.order.push(ty.clone());
531 }
532 self.entries.insert(ty, s.into());
533 }
534 pub fn iter(&self) -> DistinguishedNameIterator<'_> {
536 DistinguishedNameIterator {
537 distinguished_name: self,
538 iter: self.order.iter(),
539 }
540 }
541
542 #[cfg(feature = "x509-parser")]
543 fn from_name(name: &x509_parser::x509::X509Name) -> Result<Self, Error> {
544 use x509_parser::der_parser::asn1_rs::Tag;
545
546 let mut dn = DistinguishedName::new();
547 for rdn in name.iter() {
548 let mut rdn_iter = rdn.iter();
549 let dn_opt = rdn_iter.next();
550 let attr = if let Some(dn) = dn_opt {
551 if rdn_iter.next().is_some() {
552 return Err(Error::CouldNotParseCertificate);
554 } else {
555 dn
556 }
557 } else {
558 panic!("x509-parser distinguished name set is empty");
559 };
560
561 let attr_type_oid = attr
562 .attr_type()
563 .iter()
564 .ok_or(Error::CouldNotParseCertificate)?;
565 let dn_type = DnType::from_oid(&attr_type_oid.collect::<Vec<_>>());
566 let data = attr.attr_value().data;
567 let try_str =
568 |data| std::str::from_utf8(data).map_err(|_| Error::CouldNotParseCertificate);
569 let dn_value = match attr.attr_value().header.tag() {
570 Tag::BmpString => DnValue::BmpString(BmpString::from_utf16be(data.to_vec())?),
571 Tag::Ia5String => DnValue::Ia5String(try_str(data)?.try_into()?),
572 Tag::PrintableString => DnValue::PrintableString(try_str(data)?.try_into()?),
573 Tag::T61String => DnValue::TeletexString(try_str(data)?.try_into()?),
574 Tag::UniversalString => {
575 DnValue::UniversalString(UniversalString::from_utf32be(data.to_vec())?)
576 },
577 Tag::Utf8String => DnValue::Utf8String(try_str(data)?.to_owned()),
578 _ => return Err(Error::CouldNotParseCertificate),
579 };
580
581 dn.push(dn_type, dn_value);
582 }
583 Ok(dn)
584 }
585}
586
587#[derive(Clone, Debug)]
591pub struct DistinguishedNameIterator<'a> {
592 distinguished_name: &'a DistinguishedName,
593 iter: std::slice::Iter<'a, DnType>,
594}
595
596impl<'a> Iterator for DistinguishedNameIterator<'a> {
597 type Item = (&'a DnType, &'a DnValue);
598
599 fn next(&mut self) -> Option<Self::Item> {
600 self.iter
601 .next()
602 .and_then(|ty| self.distinguished_name.entries.get(ty).map(|v| (ty, v)))
603 }
604}
605
606#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
608pub enum KeyUsagePurpose {
609 DigitalSignature,
611 ContentCommitment,
613 KeyEncipherment,
615 DataEncipherment,
617 KeyAgreement,
619 KeyCertSign,
621 CrlSign,
623 EncipherOnly,
625 DecipherOnly,
627}
628
629impl KeyUsagePurpose {
630 #[cfg(feature = "x509-parser")]
631 fn from_x509(x509: &x509_parser::certificate::X509Certificate<'_>) -> Result<Vec<Self>, Error> {
632 let key_usage = x509
633 .key_usage()
634 .map_err(|_| Error::CouldNotParseCertificate)?
635 .map(|ext| ext.value);
636 let flags = key_usage.map_or(0u16, |k| k.flags).reverse_bits();
638 Ok(Self::from_u16(flags))
639 }
640
641 fn to_u16(self) -> u16 {
644 const FLAG: u16 = 0b1000_0000_0000_0000;
645 FLAG >> match self {
646 KeyUsagePurpose::DigitalSignature => 0,
647 KeyUsagePurpose::ContentCommitment => 1,
648 KeyUsagePurpose::KeyEncipherment => 2,
649 KeyUsagePurpose::DataEncipherment => 3,
650 KeyUsagePurpose::KeyAgreement => 4,
651 KeyUsagePurpose::KeyCertSign => 5,
652 KeyUsagePurpose::CrlSign => 6,
653 KeyUsagePurpose::EncipherOnly => 7,
654 KeyUsagePurpose::DecipherOnly => 8,
655 }
656 }
657
658 #[cfg(feature = "x509-parser")]
661 fn from_u16(value: u16) -> Vec<Self> {
662 [
663 KeyUsagePurpose::DigitalSignature,
664 KeyUsagePurpose::ContentCommitment,
665 KeyUsagePurpose::KeyEncipherment,
666 KeyUsagePurpose::DataEncipherment,
667 KeyUsagePurpose::KeyAgreement,
668 KeyUsagePurpose::KeyCertSign,
669 KeyUsagePurpose::CrlSign,
670 KeyUsagePurpose::EncipherOnly,
671 KeyUsagePurpose::DecipherOnly,
672 ]
673 .iter()
674 .filter_map(|key_usage| {
675 let present = key_usage.to_u16() & value != 0;
676 present.then_some(*key_usage)
677 })
678 .collect()
679 }
680}
681
682#[derive(Debug, PartialEq, Eq, Hash, Clone)]
695#[non_exhaustive]
696pub enum KeyIdMethod {
697 #[cfg(feature = "crypto")]
699 Sha256,
700 #[cfg(feature = "crypto")]
702 Sha384,
703 #[cfg(feature = "crypto")]
705 Sha512,
706 PreSpecified(Vec<u8>),
708}
709
710impl KeyIdMethod {
711 #[cfg(feature = "x509-parser")]
712 fn from_x509(x509: &x509_parser::certificate::X509Certificate<'_>) -> Result<Self, Error> {
713 let key_identifier_method =
714 x509.iter_extensions()
715 .find_map(|ext| match ext.parsed_extension() {
716 x509_parser::extensions::ParsedExtension::SubjectKeyIdentifier(key_id) => {
717 Some(KeyIdMethod::PreSpecified(key_id.0.into()))
718 },
719 _ => None,
720 });
721
722 Ok(match key_identifier_method {
723 Some(method) => method,
724 None => {
725 #[cfg(not(feature = "crypto"))]
726 return Err(Error::UnsupportedSignatureAlgorithm);
727 #[cfg(feature = "crypto")]
728 KeyIdMethod::Sha256
729 },
730 })
731 }
732
733 #[allow(unused_variables)]
741 pub(crate) fn derive(&self, subject_public_key_info: impl AsRef<[u8]>) -> Vec<u8> {
742 #[cfg_attr(not(feature = "crypto"), expect(clippy::let_unit_value))]
743 let digest_method = match &self {
744 #[cfg(feature = "crypto")]
745 Self::Sha256 => &digest::SHA256,
746 #[cfg(feature = "crypto")]
747 Self::Sha384 => &digest::SHA384,
748 #[cfg(feature = "crypto")]
749 Self::Sha512 => &digest::SHA512,
750 Self::PreSpecified(b) => {
751 return b.to_vec();
752 },
753 };
754 #[cfg(feature = "crypto")]
755 {
756 let digest = digest::digest(digest_method, subject_public_key_info.as_ref());
757 digest.as_ref()[0..20].to_vec()
758 }
759 }
760}
761
762fn dt_strip_nanos(dt: OffsetDateTime) -> OffsetDateTime {
763 let time =
771 Time::from_hms(dt.hour(), dt.minute(), dt.second()).expect("invalid or out-of-range time");
772 dt.replace_time(time)
773}
774
775fn dt_to_generalized(dt: OffsetDateTime) -> GeneralizedTime {
776 let date_time = dt_strip_nanos(dt);
777 GeneralizedTime::from_datetime(date_time)
778}
779
780fn write_dt_utc_or_generalized(writer: DERWriter, dt: OffsetDateTime) {
781 if (1950..2050).contains(&dt.year()) {
788 let date_time = dt_strip_nanos(dt);
789 let ut = UTCTime::from_datetime(date_time);
790 writer.write_utctime(&ut);
791 } else {
792 let gt = dt_to_generalized(dt);
793 writer.write_generalized_time(>);
794 }
795}
796
797fn write_distinguished_name(writer: DERWriter, dn: &DistinguishedName) {
798 writer.write_sequence(|writer| {
799 for (ty, content) in dn.iter() {
800 writer.next().write_set(|writer| {
801 writer.next().write_sequence(|writer| {
802 writer.next().write_oid(&ty.to_oid());
803 match content {
804 DnValue::BmpString(s) => writer
805 .next()
806 .write_tagged_implicit(TAG_BMPSTRING, |writer| {
807 writer.write_bytes(s.as_bytes())
808 }),
809
810 DnValue::Ia5String(s) => writer.next().write_ia5_string(s.as_str()),
811
812 DnValue::PrintableString(s) => {
813 writer.next().write_printable_string(s.as_str())
814 },
815 DnValue::TeletexString(s) => writer
816 .next()
817 .write_tagged_implicit(TAG_TELETEXSTRING, |writer| {
818 writer.write_bytes(s.as_bytes())
819 }),
820 DnValue::UniversalString(s) => writer
821 .next()
822 .write_tagged_implicit(TAG_UNIVERSALSTRING, |writer| {
823 writer.write_bytes(s.as_bytes())
824 }),
825 DnValue::Utf8String(s) => writer.next().write_utf8_string(s),
826 }
827 });
828 });
829 }
830 });
831}
832
833fn write_x509_extension(
835 writer: DERWriter,
836 extension_oid: &[u64],
837 is_critical: bool,
838 value_serializer: impl FnOnce(DERWriter),
839) {
840 writer.write_sequence(|writer| {
851 let oid = ObjectIdentifier::from_slice(extension_oid);
852 writer.next().write_oid(&oid);
853 if is_critical {
854 writer.next().write_bool(true);
855 }
856 let bytes = yasna::construct_der(value_serializer);
857 writer.next().write_bytes(&bytes);
858 })
859}
860
861fn write_x509_authority_key_identifier(writer: DERWriter, aki: Vec<u8>) {
863 write_x509_extension(writer, oid::AUTHORITY_KEY_IDENTIFIER, false, |writer| {
874 writer.write_sequence(|writer| {
875 writer
876 .next()
877 .write_tagged_implicit(Tag::context(0), |writer| writer.write_bytes(&aki))
878 });
879 });
880}
881
882#[cfg(feature = "zeroize")]
883impl zeroize::Zeroize for KeyPair {
884 fn zeroize(&mut self) {
885 self.serialized_der.zeroize();
886 }
887}
888
889#[derive(Debug, PartialEq, Eq, Hash, Clone)]
891pub struct SerialNumber {
892 inner: Vec<u8>,
893}
894
895#[allow(clippy::len_without_is_empty)]
896impl SerialNumber {
897 pub fn from_slice(bytes: &[u8]) -> SerialNumber {
899 let inner = bytes.to_vec();
900 SerialNumber { inner }
901 }
902
903 pub fn to_bytes(&self) -> Vec<u8> {
905 self.inner.clone()
906 }
907
908 pub fn len(&self) -> usize {
910 self.inner.len()
911 }
912}
913
914impl fmt::Display for SerialNumber {
915 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
916 let hex: Vec<_> = self.inner.iter().map(|b| format!("{b:02x}")).collect();
917 write!(f, "{}", hex.join(":"))
918 }
919}
920
921impl From<Vec<u8>> for SerialNumber {
922 fn from(inner: Vec<u8>) -> SerialNumber {
923 SerialNumber { inner }
924 }
925}
926
927impl From<u64> for SerialNumber {
928 fn from(u: u64) -> SerialNumber {
929 let inner = u.to_be_bytes().into();
930 SerialNumber { inner }
931 }
932}
933
934impl AsRef<[u8]> for SerialNumber {
935 fn as_ref(&self) -> &[u8] {
936 &self.inner
937 }
938}
939
940#[cfg(test)]
941mod tests {
942 use std::panic::catch_unwind;
943
944 use time::{Date, Month, PrimitiveDateTime};
945
946 use super::*;
947
948 fn times() -> [OffsetDateTime; 2] {
949 let dt_nanos = {
950 let date = Date::from_calendar_date(2020, Month::December, 3).unwrap();
951 let time = Time::from_hms_nano(0, 0, 1, 444).unwrap();
952 PrimitiveDateTime::new(date, time).assume_utc()
953 };
954 let dt_zero = {
955 let date = Date::from_calendar_date(2020, Month::December, 3).unwrap();
956 let time = Time::from_hms_nano(0, 0, 1, 0).unwrap();
957 PrimitiveDateTime::new(date, time).assume_utc()
958 };
959 [dt_nanos, dt_zero]
961 }
962
963 #[test]
964 fn test_dt_utc_strip_nanos() {
965 let times = times();
966
967 let res = catch_unwind(|| UTCTime::from_datetime(times[0]));
969 assert!(res.is_err());
970
971 for dt in times {
973 let date_time = dt_strip_nanos(dt);
974 assert_eq!(date_time.time().nanosecond(), 0);
975 let _ut = UTCTime::from_datetime(date_time);
976 }
977 }
978
979 #[test]
980 fn test_dt_to_generalized() {
981 let times = times();
982
983 for dt in times {
984 let _gt = dt_to_generalized(dt);
985 }
986 }
987
988 #[test]
989 fn signature_algos_different() {
990 for (i, alg_i) in SignatureAlgorithm::iter().enumerate() {
994 for (j, alg_j) in SignatureAlgorithm::iter().enumerate() {
995 assert_eq!(
996 alg_i == alg_j,
997 i == j,
998 "Algorithm relationship mismatch for algorithm index pair {i} and {j}"
999 );
1000 }
1001 }
1002 }
1003
1004 #[cfg(feature = "x509-parser")]
1005 mod test_ip_address_from_octets {
1006 use super::super::ip_addr_from_octets;
1007 use super::super::Error;
1008 use std::net::IpAddr;
1009
1010 #[test]
1011 fn ipv4() {
1012 let octets = [10, 20, 30, 40];
1013
1014 let actual = ip_addr_from_octets(&octets).unwrap();
1015
1016 assert_eq!(IpAddr::from(octets), actual)
1017 }
1018
1019 #[test]
1020 fn ipv6() {
1021 let octets = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
1022
1023 let actual = ip_addr_from_octets(&octets).unwrap();
1024
1025 assert_eq!(IpAddr::from(octets), actual)
1026 }
1027
1028 #[test]
1029 fn mismatch() {
1030 let incorrect = Vec::from_iter(0..10);
1031 let actual = ip_addr_from_octets(&incorrect).unwrap_err();
1032
1033 assert_eq!(Error::InvalidIpAddressOctetLength(10), actual);
1034 }
1035
1036 #[test]
1037 fn none() {
1038 let actual = ip_addr_from_octets(&[]).unwrap_err();
1039
1040 assert_eq!(Error::InvalidIpAddressOctetLength(0), actual);
1041 }
1042
1043 #[test]
1044 fn too_many() {
1045 let incorrect = Vec::from_iter(0..20);
1046 let actual = ip_addr_from_octets(&incorrect).unwrap_err();
1047
1048 assert_eq!(Error::InvalidIpAddressOctetLength(20), actual);
1049 }
1050 }
1051
1052 #[cfg(feature = "x509-parser")]
1053 mod test_san_type_from_general_name {
1054 use crate::SanType;
1055 use std::net::IpAddr;
1056 use x509_parser::extensions::GeneralName;
1057
1058 #[test]
1059 fn with_ipv4() {
1060 let octets = [1, 2, 3, 4];
1061 let value = GeneralName::IPAddress(&octets);
1062 let actual = SanType::try_from_general(&value).unwrap();
1063
1064 assert_eq!(SanType::IpAddress(IpAddr::from(octets)), actual);
1065 }
1066 }
1067}