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(rcgen_docsrs, feature(doc_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
44pub use certificate::{
45 date_time_ymd, Attribute, BasicConstraints, Certificate, CertificateParams, CidrSubnet,
46 CustomExtension, DnType, ExtendedKeyUsagePurpose, GeneralSubtree, IsCa, NameConstraints,
47};
48pub use crl::{
49 CertificateRevocationList, CertificateRevocationListParams, CrlDistributionPoint,
50 CrlIssuingDistributionPoint, CrlScope, RevocationReason, RevokedCertParams,
51};
52pub use csr::{CertificateSigningRequest, CertificateSigningRequestParams, PublicKey};
53pub use error::{Error, InvalidAsn1String};
54#[cfg(feature = "crypto")]
55pub use key_pair::KeyPair;
56#[cfg(all(feature = "crypto", feature = "aws_lc_rs"))]
57pub use key_pair::RsaKeySize;
58pub use key_pair::{PublicKeyData, SigningKey, SubjectPublicKeyInfo};
59#[cfg(feature = "pem")]
60use pem::Pem;
61use pki_types::CertificateDer;
62#[cfg(feature = "crypto")]
63use ring_like::digest;
64pub use sign_algo::algo::*;
65pub use sign_algo::SignatureAlgorithm;
66use time::{OffsetDateTime, Time};
67use yasna::models::{GeneralizedTime, ObjectIdentifier, UTCTime};
68use yasna::tags::{TAG_BMPSTRING, TAG_TELETEXSTRING, TAG_UNIVERSALSTRING};
69use yasna::{DERWriter, Tag};
70
71use crate::string::{BmpString, Ia5String, PrintableString, TeletexString, UniversalString};
72
73mod certificate;
74mod crl;
75mod csr;
76mod error;
77mod key_pair;
78mod oid;
79mod ring_like;
80mod sign_algo;
81pub mod string;
82
83#[deprecated(
85 note = "Renamed to `Error`. We recommend to refer to it by fully-qualifying the crate: `rcgen::Error`."
86)]
87pub type RcgenError = Error;
88
89#[derive(PartialEq, Eq)]
91pub struct CertifiedKey<S: SigningKey> {
92 pub cert: Certificate,
94 pub signing_key: S,
96}
97
98#[cfg(feature = "crypto")]
107#[cfg_attr(
108 feature = "pem",
109 doc = r##"
110## Example
111
112```
113use rcgen::{generate_simple_self_signed, CertifiedKey};
114# fn main () {
115// Generate a certificate that's valid for "localhost" and "hello.world.example"
116let subject_alt_names = vec!["hello.world.example".to_string(),
117 "localhost".to_string()];
118
119let CertifiedKey { cert, signing_key } = generate_simple_self_signed(subject_alt_names).unwrap();
120
121// The certificate is now valid for localhost and the domain "hello.world.example"
122println!("{}", cert.pem());
123println!("{}", signing_key.serialize_pem());
124# }
125```
126"##
127)]
128pub fn generate_simple_self_signed(
129 subject_alt_names: impl Into<Vec<String>>,
130) -> Result<CertifiedKey<KeyPair>, Error> {
131 let signing_key = KeyPair::generate()?;
132 let cert = CertificateParams::new(subject_alt_names)?.self_signed(&signing_key)?;
133 Ok(CertifiedKey { cert, signing_key })
134}
135
136#[derive(Debug)]
138pub struct CertifiedIssuer<'a, S> {
139 certificate: Certificate,
140 issuer: Issuer<'a, S>,
141}
142
143impl<'a, S: SigningKey> CertifiedIssuer<'a, S> {
144 pub fn self_signed(params: CertificateParams, signing_key: S) -> Result<Self, Error> {
146 Ok(Self {
147 certificate: params.self_signed(&signing_key)?,
148 issuer: Issuer::new(params, signing_key),
149 })
150 }
151
152 pub fn signed_by(
154 params: CertificateParams,
155 signing_key: S,
156 issuer: &Issuer<'_, impl SigningKey>,
157 ) -> Result<Self, Error> {
158 Ok(Self {
159 certificate: params.signed_by(&signing_key, issuer)?,
160 issuer: Issuer::new(params, signing_key),
161 })
162 }
163
164 #[cfg(feature = "pem")]
166 pub fn pem(&self) -> String {
167 pem::encode_config(&Pem::new("CERTIFICATE", self.der().to_vec()), ENCODE_CONFIG)
168 }
169
170 pub fn der(&self) -> &CertificateDer<'static> {
174 self.certificate.der()
175 }
176}
177
178impl<'a, S> Deref for CertifiedIssuer<'a, S> {
179 type Target = Issuer<'a, S>;
180
181 fn deref(&self) -> &Self::Target {
182 &self.issuer
183 }
184}
185
186impl<'a, S> AsRef<Certificate> for CertifiedIssuer<'a, S> {
187 fn as_ref(&self) -> &Certificate {
188 &self.certificate
189 }
190}
191
192pub struct Issuer<'a, S> {
197 distinguished_name: Cow<'a, DistinguishedName>,
198 key_identifier_method: Cow<'a, KeyIdMethod>,
199 key_usages: Cow<'a, [KeyUsagePurpose]>,
200 signing_key: S,
201}
202
203impl<'a, S: SigningKey> Issuer<'a, S> {
204 pub fn new(params: CertificateParams, signing_key: S) -> Self {
206 Self {
207 distinguished_name: Cow::Owned(params.distinguished_name),
208 key_identifier_method: Cow::Owned(params.key_identifier_method),
209 key_usages: Cow::Owned(params.key_usages),
210 signing_key,
211 }
212 }
213
214 pub fn from_params(params: &'a CertificateParams, signing_key: S) -> Self {
219 Self {
220 distinguished_name: Cow::Borrowed(¶ms.distinguished_name),
221 key_identifier_method: Cow::Borrowed(¶ms.key_identifier_method),
222 key_usages: Cow::Borrowed(¶ms.key_usages),
223 signing_key,
224 }
225 }
226
227 #[cfg(all(feature = "pem", feature = "x509-parser"))]
231 pub fn from_ca_cert_pem(pem_str: &str, signing_key: S) -> Result<Self, Error> {
232 let certificate = pem::parse(pem_str).map_err(|_| Error::CouldNotParseCertificate)?;
233 Self::from_ca_cert_der(&certificate.contents().into(), signing_key)
234 }
235
236 #[cfg(feature = "x509-parser")]
245 pub fn from_ca_cert_der(ca_cert: &CertificateDer<'_>, signing_key: S) -> Result<Self, Error> {
246 let (_remainder, x509) = x509_parser::parse_x509_certificate(ca_cert)
247 .map_err(|_| Error::CouldNotParseCertificate)?;
248
249 Ok(Self {
250 key_usages: Cow::Owned(KeyUsagePurpose::from_x509(&x509)?),
251 key_identifier_method: Cow::Owned(KeyIdMethod::from_x509(&x509)?),
252 distinguished_name: Cow::Owned(DistinguishedName::from_name(
253 &x509.tbs_certificate.subject,
254 )?),
255 signing_key,
256 })
257 }
258
259 pub fn key_usages(&self) -> &[KeyUsagePurpose] {
261 &self.key_usages
262 }
263
264 pub fn key(&self) -> &S {
266 &self.signing_key
267 }
268}
269
270impl<'a, S> fmt::Debug for Issuer<'a, S> {
271 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
273 let Issuer {
275 distinguished_name,
276 key_identifier_method,
277 key_usages,
278 signing_key: _,
279 } = self;
280
281 f.debug_struct("Issuer")
282 .field("distinguished_name", distinguished_name)
283 .field("key_identifier_method", key_identifier_method)
284 .field("key_usages", key_usages)
285 .field("signing_key", &"[elided]")
286 .finish()
287 }
288}
289
290#[cfg(feature = "pem")]
296const ENCODE_CONFIG: pem::EncodeConfig = {
297 let line_ending = match cfg!(target_family = "windows") {
298 true => pem::LineEnding::CRLF,
299 false => pem::LineEnding::LF,
300 };
301 pem::EncodeConfig::new().set_line_ending(line_ending)
302};
303
304#[derive(Debug, PartialEq, Eq, Hash, Clone)]
305#[allow(missing_docs)]
306#[non_exhaustive]
307pub enum SanType {
309 Rfc822Name(Ia5String),
311 DnsName(Ia5String),
312 URI(Ia5String),
313 IpAddress(IpAddr),
314 OtherName((Vec<u64>, OtherNameValue)),
315}
316
317impl SanType {
318 #[cfg(all(test, feature = "x509-parser"))]
319 fn from_x509(x509: &x509_parser::certificate::X509Certificate<'_>) -> Result<Vec<Self>, Error> {
320 let sans = x509
321 .subject_alternative_name()
322 .map_err(|_| Error::CouldNotParseCertificate)?
323 .map(|ext| &ext.value.general_names);
324
325 let Some(sans) = sans else {
326 return Ok(Vec::new());
327 };
328
329 let mut subject_alt_names = Vec::with_capacity(sans.len());
330 for san in sans {
331 subject_alt_names.push(Self::try_from_general(san)?);
332 }
333 Ok(subject_alt_names)
334 }
335}
336
337#[derive(Debug, PartialEq, Eq, Hash, Clone)]
345#[non_exhaustive]
346pub enum OtherNameValue {
347 Utf8String(String),
349}
350
351impl OtherNameValue {
352 fn write_der(&self, writer: DERWriter) {
353 writer.write_tagged(Tag::context(0), |writer| match self {
354 OtherNameValue::Utf8String(s) => writer.write_utf8_string(s),
355 });
356 }
357}
358
359impl<T> From<T> for OtherNameValue
360where
361 T: Into<String>,
362{
363 fn from(t: T) -> Self {
364 OtherNameValue::Utf8String(t.into())
365 }
366}
367
368#[cfg(feature = "x509-parser")]
369fn ip_addr_from_octets(octets: &[u8]) -> Result<IpAddr, Error> {
370 if let Ok(ipv6_octets) = <&[u8; 16]>::try_from(octets) {
371 Ok(Ipv6Addr::from(*ipv6_octets).into())
372 } else if let Ok(ipv4_octets) = <&[u8; 4]>::try_from(octets) {
373 Ok(Ipv4Addr::from(*ipv4_octets).into())
374 } else {
375 Err(Error::InvalidIpAddressOctetLength(octets.len()))
376 }
377}
378
379impl SanType {
380 #[cfg(feature = "x509-parser")]
381 fn try_from_general(name: &x509_parser::extensions::GeneralName<'_>) -> Result<Self, Error> {
382 use x509_parser::der_parser::asn1_rs::{self, FromDer, Tag, TaggedExplicit};
383 Ok(match name {
384 x509_parser::extensions::GeneralName::RFC822Name(name) => {
385 SanType::Rfc822Name((*name).try_into()?)
386 },
387 x509_parser::extensions::GeneralName::DNSName(name) => {
388 SanType::DnsName((*name).try_into()?)
389 },
390 x509_parser::extensions::GeneralName::URI(name) => SanType::URI((*name).try_into()?),
391 x509_parser::extensions::GeneralName::IPAddress(octets) => {
392 SanType::IpAddress(ip_addr_from_octets(octets)?)
393 },
394 x509_parser::extensions::GeneralName::OtherName(oid, value) => {
395 let oid = oid.iter().ok_or(Error::CouldNotParseCertificate)?;
396 let (_, other_name) = TaggedExplicit::<asn1_rs::Any, _, 0>::from_der(value)
398 .map_err(|_| Error::CouldNotParseCertificate)?;
399 let other_name = other_name.into_inner();
400
401 let other_name_value = match other_name.tag() {
402 Tag::Utf8String => OtherNameValue::Utf8String(
403 std::str::from_utf8(other_name.data)
404 .map_err(|_| Error::CouldNotParseCertificate)?
405 .to_owned(),
406 ),
407 _ => return Err(Error::CouldNotParseCertificate),
408 };
409 SanType::OtherName((oid.collect(), other_name_value))
410 },
411 _ => return Err(Error::InvalidNameType),
412 })
413 }
414
415 fn tag(&self) -> u64 {
416 const TAG_OTHER_NAME: u64 = 0;
419 const TAG_RFC822_NAME: u64 = 1;
420 const TAG_DNS_NAME: u64 = 2;
421 const TAG_URI: u64 = 6;
422 const TAG_IP_ADDRESS: u64 = 7;
423
424 match self {
425 SanType::Rfc822Name(_name) => TAG_RFC822_NAME,
426 SanType::DnsName(_name) => TAG_DNS_NAME,
427 SanType::URI(_name) => TAG_URI,
428 SanType::IpAddress(_addr) => TAG_IP_ADDRESS,
429 Self::OtherName(_oid) => TAG_OTHER_NAME,
430 }
431 }
432}
433
434#[derive(Debug, PartialEq, Eq, Hash, Clone)]
436#[non_exhaustive]
437pub enum DnValue {
438 BmpString(BmpString),
440 Ia5String(Ia5String),
442 PrintableString(PrintableString),
444 TeletexString(TeletexString),
446 UniversalString(UniversalString),
448 Utf8String(String),
450}
451
452impl<T> From<T> for DnValue
453where
454 T: Into<String>,
455{
456 fn from(t: T) -> Self {
457 DnValue::Utf8String(t.into())
458 }
459}
460
461#[derive(Debug, Default, PartialEq, Eq, Clone)]
462pub struct DistinguishedName {
473 entries: HashMap<DnType, DnValue>,
474 order: Vec<DnType>,
475}
476
477impl DistinguishedName {
478 pub fn new() -> Self {
480 Self::default()
481 }
482 pub fn get(&self, ty: &DnType) -> Option<&DnValue> {
484 self.entries.get(ty)
485 }
486 pub fn remove(&mut self, ty: DnType) -> bool {
492 let removed = self.entries.remove(&ty).is_some();
493 if removed {
494 self.order.retain(|ty_o| &ty != ty_o);
495 }
496 removed
497 }
498 pub fn push(&mut self, ty: DnType, s: impl Into<DnValue>) {
509 if !self.entries.contains_key(&ty) {
510 self.order.push(ty.clone());
511 }
512 self.entries.insert(ty, s.into());
513 }
514 pub fn iter(&self) -> DistinguishedNameIterator<'_> {
516 DistinguishedNameIterator {
517 distinguished_name: self,
518 iter: self.order.iter(),
519 }
520 }
521
522 #[cfg(feature = "x509-parser")]
523 fn from_name(name: &x509_parser::x509::X509Name) -> Result<Self, Error> {
524 use x509_parser::der_parser::asn1_rs::Tag;
525
526 let mut dn = DistinguishedName::new();
527 for rdn in name.iter() {
528 let mut rdn_iter = rdn.iter();
529 let dn_opt = rdn_iter.next();
530 let attr = if let Some(dn) = dn_opt {
531 if rdn_iter.next().is_some() {
532 return Err(Error::CouldNotParseCertificate);
534 } else {
535 dn
536 }
537 } else {
538 panic!("x509-parser distinguished name set is empty");
539 };
540
541 let attr_type_oid = attr
542 .attr_type()
543 .iter()
544 .ok_or(Error::CouldNotParseCertificate)?;
545 let dn_type = DnType::from_oid(&attr_type_oid.collect::<Vec<_>>());
546 let data = attr.attr_value().data;
547 let try_str =
548 |data| std::str::from_utf8(data).map_err(|_| Error::CouldNotParseCertificate);
549 let dn_value = match attr.attr_value().header.tag() {
550 Tag::BmpString => DnValue::BmpString(BmpString::from_utf16be(data.to_vec())?),
551 Tag::Ia5String => DnValue::Ia5String(try_str(data)?.try_into()?),
552 Tag::PrintableString => DnValue::PrintableString(try_str(data)?.try_into()?),
553 Tag::T61String => DnValue::TeletexString(try_str(data)?.try_into()?),
554 Tag::UniversalString => {
555 DnValue::UniversalString(UniversalString::from_utf32be(data.to_vec())?)
556 },
557 Tag::Utf8String => DnValue::Utf8String(try_str(data)?.to_owned()),
558 _ => return Err(Error::CouldNotParseCertificate),
559 };
560
561 dn.push(dn_type, dn_value);
562 }
563 Ok(dn)
564 }
565}
566
567#[derive(Clone, Debug)]
571pub struct DistinguishedNameIterator<'a> {
572 distinguished_name: &'a DistinguishedName,
573 iter: std::slice::Iter<'a, DnType>,
574}
575
576impl<'a> Iterator for DistinguishedNameIterator<'a> {
577 type Item = (&'a DnType, &'a DnValue);
578
579 fn next(&mut self) -> Option<Self::Item> {
580 self.iter
581 .next()
582 .and_then(|ty| self.distinguished_name.entries.get(ty).map(|v| (ty, v)))
583 }
584}
585
586#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
588pub enum KeyUsagePurpose {
589 DigitalSignature,
591 ContentCommitment,
593 KeyEncipherment,
595 DataEncipherment,
597 KeyAgreement,
599 KeyCertSign,
601 CrlSign,
603 EncipherOnly,
605 DecipherOnly,
607}
608
609impl KeyUsagePurpose {
610 #[cfg(feature = "x509-parser")]
611 fn from_x509(x509: &x509_parser::certificate::X509Certificate<'_>) -> Result<Vec<Self>, Error> {
612 let key_usage = x509
613 .key_usage()
614 .map_err(|_| Error::CouldNotParseCertificate)?
615 .map(|ext| ext.value);
616 let flags = key_usage.map_or(0u16, |k| k.flags).reverse_bits();
618 Ok(Self::from_u16(flags))
619 }
620
621 fn to_u16(self) -> u16 {
624 const FLAG: u16 = 0b1000_0000_0000_0000;
625 FLAG >> match self {
626 KeyUsagePurpose::DigitalSignature => 0,
627 KeyUsagePurpose::ContentCommitment => 1,
628 KeyUsagePurpose::KeyEncipherment => 2,
629 KeyUsagePurpose::DataEncipherment => 3,
630 KeyUsagePurpose::KeyAgreement => 4,
631 KeyUsagePurpose::KeyCertSign => 5,
632 KeyUsagePurpose::CrlSign => 6,
633 KeyUsagePurpose::EncipherOnly => 7,
634 KeyUsagePurpose::DecipherOnly => 8,
635 }
636 }
637
638 #[cfg(feature = "x509-parser")]
641 fn from_u16(value: u16) -> Vec<Self> {
642 [
643 KeyUsagePurpose::DigitalSignature,
644 KeyUsagePurpose::ContentCommitment,
645 KeyUsagePurpose::KeyEncipherment,
646 KeyUsagePurpose::DataEncipherment,
647 KeyUsagePurpose::KeyAgreement,
648 KeyUsagePurpose::KeyCertSign,
649 KeyUsagePurpose::CrlSign,
650 KeyUsagePurpose::EncipherOnly,
651 KeyUsagePurpose::DecipherOnly,
652 ]
653 .iter()
654 .filter_map(|key_usage| {
655 let present = key_usage.to_u16() & value != 0;
656 present.then_some(*key_usage)
657 })
658 .collect()
659 }
660}
661
662#[derive(Debug, PartialEq, Eq, Hash, Clone)]
675#[non_exhaustive]
676pub enum KeyIdMethod {
677 #[cfg(feature = "crypto")]
679 Sha256,
680 #[cfg(feature = "crypto")]
682 Sha384,
683 #[cfg(feature = "crypto")]
685 Sha512,
686 PreSpecified(Vec<u8>),
688}
689
690impl KeyIdMethod {
691 #[cfg(feature = "x509-parser")]
692 fn from_x509(x509: &x509_parser::certificate::X509Certificate<'_>) -> Result<Self, Error> {
693 let key_identifier_method =
694 x509.iter_extensions()
695 .find_map(|ext| match ext.parsed_extension() {
696 x509_parser::extensions::ParsedExtension::SubjectKeyIdentifier(key_id) => {
697 Some(KeyIdMethod::PreSpecified(key_id.0.into()))
698 },
699 _ => None,
700 });
701
702 Ok(match key_identifier_method {
703 Some(method) => method,
704 None => {
705 #[cfg(not(feature = "crypto"))]
706 return Err(Error::UnsupportedSignatureAlgorithm);
707 #[cfg(feature = "crypto")]
708 KeyIdMethod::Sha256
709 },
710 })
711 }
712
713 #[allow(unused_variables)]
721 pub(crate) fn derive(&self, subject_public_key_info: impl AsRef<[u8]>) -> Vec<u8> {
722 #[cfg_attr(not(feature = "crypto"), expect(clippy::let_unit_value))]
723 let digest_method = match &self {
724 #[cfg(feature = "crypto")]
725 Self::Sha256 => &digest::SHA256,
726 #[cfg(feature = "crypto")]
727 Self::Sha384 => &digest::SHA384,
728 #[cfg(feature = "crypto")]
729 Self::Sha512 => &digest::SHA512,
730 Self::PreSpecified(b) => {
731 return b.to_vec();
732 },
733 };
734 #[cfg(feature = "crypto")]
735 {
736 let digest = digest::digest(digest_method, subject_public_key_info.as_ref());
737 digest.as_ref()[0..20].to_vec()
738 }
739 }
740}
741
742fn dt_strip_nanos(dt: OffsetDateTime) -> OffsetDateTime {
743 let time =
751 Time::from_hms(dt.hour(), dt.minute(), dt.second()).expect("invalid or out-of-range time");
752 dt.replace_time(time)
753}
754
755fn dt_to_generalized(dt: OffsetDateTime) -> GeneralizedTime {
756 let date_time = dt_strip_nanos(dt);
757 GeneralizedTime::from_datetime(date_time)
758}
759
760fn write_dt_utc_or_generalized(writer: DERWriter, dt: OffsetDateTime) {
761 if (1950..2050).contains(&dt.year()) {
768 let date_time = dt_strip_nanos(dt);
769 let ut = UTCTime::from_datetime(date_time);
770 writer.write_utctime(&ut);
771 } else {
772 let gt = dt_to_generalized(dt);
773 writer.write_generalized_time(>);
774 }
775}
776
777fn write_distinguished_name(writer: DERWriter, dn: &DistinguishedName) {
778 writer.write_sequence(|writer| {
779 for (ty, content) in dn.iter() {
780 writer.next().write_set(|writer| {
781 writer.next().write_sequence(|writer| {
782 writer.next().write_oid(&ty.to_oid());
783 match content {
784 DnValue::BmpString(s) => writer
785 .next()
786 .write_tagged_implicit(TAG_BMPSTRING, |writer| {
787 writer.write_bytes(s.as_bytes())
788 }),
789
790 DnValue::Ia5String(s) => writer.next().write_ia5_string(s.as_str()),
791
792 DnValue::PrintableString(s) => {
793 writer.next().write_printable_string(s.as_str())
794 },
795 DnValue::TeletexString(s) => writer
796 .next()
797 .write_tagged_implicit(TAG_TELETEXSTRING, |writer| {
798 writer.write_bytes(s.as_bytes())
799 }),
800 DnValue::UniversalString(s) => writer
801 .next()
802 .write_tagged_implicit(TAG_UNIVERSALSTRING, |writer| {
803 writer.write_bytes(s.as_bytes())
804 }),
805 DnValue::Utf8String(s) => writer.next().write_utf8_string(s),
806 }
807 });
808 });
809 }
810 });
811}
812
813fn write_x509_extension(
815 writer: DERWriter,
816 extension_oid: &[u64],
817 is_critical: bool,
818 value_serializer: impl FnOnce(DERWriter),
819) {
820 writer.write_sequence(|writer| {
831 let oid = ObjectIdentifier::from_slice(extension_oid);
832 writer.next().write_oid(&oid);
833 if is_critical {
834 writer.next().write_bool(true);
835 }
836 let bytes = yasna::construct_der(value_serializer);
837 writer.next().write_bytes(&bytes);
838 })
839}
840
841fn write_x509_authority_key_identifier(writer: DERWriter, aki: Vec<u8>) {
843 write_x509_extension(writer, oid::AUTHORITY_KEY_IDENTIFIER, false, |writer| {
854 writer.write_sequence(|writer| {
855 writer
856 .next()
857 .write_tagged_implicit(Tag::context(0), |writer| writer.write_bytes(&aki))
858 });
859 });
860}
861
862#[cfg(feature = "zeroize")]
863impl zeroize::Zeroize for KeyPair {
864 fn zeroize(&mut self) {
865 self.serialized_der.zeroize();
866 }
867}
868
869#[derive(Debug, PartialEq, Eq, Hash, Clone)]
871pub struct SerialNumber {
872 inner: Vec<u8>,
873}
874
875#[allow(clippy::len_without_is_empty)]
876impl SerialNumber {
877 pub fn from_slice(bytes: &[u8]) -> SerialNumber {
879 let inner = bytes.to_vec();
880 SerialNumber { inner }
881 }
882
883 pub fn to_bytes(&self) -> Vec<u8> {
885 self.inner.clone()
886 }
887
888 pub fn len(&self) -> usize {
890 self.inner.len()
891 }
892}
893
894impl fmt::Display for SerialNumber {
895 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
896 let hex: Vec<_> = self.inner.iter().map(|b| format!("{b:02x}")).collect();
897 write!(f, "{}", hex.join(":"))
898 }
899}
900
901impl From<Vec<u8>> for SerialNumber {
902 fn from(inner: Vec<u8>) -> SerialNumber {
903 SerialNumber { inner }
904 }
905}
906
907impl From<u64> for SerialNumber {
908 fn from(u: u64) -> SerialNumber {
909 let inner = u.to_be_bytes().into();
910 SerialNumber { inner }
911 }
912}
913
914impl AsRef<[u8]> for SerialNumber {
915 fn as_ref(&self) -> &[u8] {
916 &self.inner
917 }
918}
919
920#[cfg(test)]
921mod tests {
922 use std::panic::catch_unwind;
923
924 use time::{Date, Month, PrimitiveDateTime};
925
926 use super::*;
927
928 fn times() -> [OffsetDateTime; 2] {
929 let dt_nanos = {
930 let date = Date::from_calendar_date(2020, Month::December, 3).unwrap();
931 let time = Time::from_hms_nano(0, 0, 1, 444).unwrap();
932 PrimitiveDateTime::new(date, time).assume_utc()
933 };
934 let dt_zero = {
935 let date = Date::from_calendar_date(2020, Month::December, 3).unwrap();
936 let time = Time::from_hms_nano(0, 0, 1, 0).unwrap();
937 PrimitiveDateTime::new(date, time).assume_utc()
938 };
939 [dt_nanos, dt_zero]
941 }
942
943 #[test]
944 fn test_dt_utc_strip_nanos() {
945 let times = times();
946
947 let res = catch_unwind(|| UTCTime::from_datetime(times[0]));
949 assert!(res.is_err());
950
951 for dt in times {
953 let date_time = dt_strip_nanos(dt);
954 assert_eq!(date_time.time().nanosecond(), 0);
955 let _ut = UTCTime::from_datetime(date_time);
956 }
957 }
958
959 #[test]
960 fn test_dt_to_generalized() {
961 let times = times();
962
963 for dt in times {
964 let _gt = dt_to_generalized(dt);
965 }
966 }
967
968 #[test]
969 fn signature_algos_different() {
970 for (i, alg_i) in SignatureAlgorithm::iter().enumerate() {
974 for (j, alg_j) in SignatureAlgorithm::iter().enumerate() {
975 assert_eq!(
976 alg_i == alg_j,
977 i == j,
978 "Algorithm relationship mismatch for algorithm index pair {i} and {j}"
979 );
980 }
981 }
982 }
983
984 #[cfg(feature = "x509-parser")]
985 mod test_ip_address_from_octets {
986 use std::net::IpAddr;
987
988 use super::super::{ip_addr_from_octets, Error};
989
990 #[test]
991 fn ipv4() {
992 let octets = [10, 20, 30, 40];
993
994 let actual = ip_addr_from_octets(&octets).unwrap();
995
996 assert_eq!(IpAddr::from(octets), actual)
997 }
998
999 #[test]
1000 fn ipv6() {
1001 let octets = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
1002
1003 let actual = ip_addr_from_octets(&octets).unwrap();
1004
1005 assert_eq!(IpAddr::from(octets), actual)
1006 }
1007
1008 #[test]
1009 fn mismatch() {
1010 let incorrect = Vec::from_iter(0..10);
1011 let actual = ip_addr_from_octets(&incorrect).unwrap_err();
1012
1013 assert_eq!(Error::InvalidIpAddressOctetLength(10), actual);
1014 }
1015
1016 #[test]
1017 fn none() {
1018 let actual = ip_addr_from_octets(&[]).unwrap_err();
1019
1020 assert_eq!(Error::InvalidIpAddressOctetLength(0), actual);
1021 }
1022
1023 #[test]
1024 fn too_many() {
1025 let incorrect = Vec::from_iter(0..20);
1026 let actual = ip_addr_from_octets(&incorrect).unwrap_err();
1027
1028 assert_eq!(Error::InvalidIpAddressOctetLength(20), actual);
1029 }
1030 }
1031
1032 #[cfg(feature = "x509-parser")]
1033 mod test_san_type_from_general_name {
1034 use std::net::IpAddr;
1035
1036 use x509_parser::extensions::GeneralName;
1037
1038 use crate::SanType;
1039
1040 #[test]
1041 fn with_ipv4() {
1042 let octets = [1, 2, 3, 4];
1043 let value = GeneralName::IPAddress(&octets);
1044 let actual = SanType::try_from_general(&value).unwrap();
1045
1046 assert_eq!(SanType::IpAddress(IpAddr::from(octets)), actual);
1047 }
1048 }
1049}