/rust/registry/src/index.crates.io-1949cf8c6b5b557f/rcgen-0.14.7/src/lib.rs
Line | Count | Source |
1 | | /*! |
2 | | Rust X.509 certificate generation utility |
3 | | |
4 | | This crate provides a way to generate self signed X.509 certificates. |
5 | | |
6 | | The most simple way of using this crate is by calling the |
7 | | [`generate_simple_self_signed`] function. |
8 | | For more customization abilities, construct a [`CertificateParams`] and |
9 | | a key pair to call [`CertificateParams::signed_by()`] or [`CertificateParams::self_signed()`]. |
10 | | */ |
11 | | #![cfg_attr( |
12 | | feature = "pem", |
13 | | doc = r##" |
14 | | ## Example |
15 | | |
16 | | ``` |
17 | | use rcgen::{generate_simple_self_signed, CertifiedKey}; |
18 | | # fn main () { |
19 | | // Generate a certificate that's valid for "localhost" and "hello.world.example" |
20 | | let subject_alt_names = vec!["hello.world.example".to_string(), |
21 | | "localhost".to_string()]; |
22 | | |
23 | | let CertifiedKey { cert, signing_key } = generate_simple_self_signed(subject_alt_names).unwrap(); |
24 | | println!("{}", cert.pem()); |
25 | | println!("{}", 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 | | |
35 | | use std::borrow::Cow; |
36 | | use std::collections::HashMap; |
37 | | use std::fmt; |
38 | | use std::hash::Hash; |
39 | | use std::net::IpAddr; |
40 | | #[cfg(feature = "x509-parser")] |
41 | | use std::net::{Ipv4Addr, Ipv6Addr}; |
42 | | use std::ops::Deref; |
43 | | |
44 | | pub use certificate::{ |
45 | | date_time_ymd, Attribute, BasicConstraints, Certificate, CertificateParams, CidrSubnet, |
46 | | CustomExtension, DnType, ExtendedKeyUsagePurpose, GeneralSubtree, IsCa, NameConstraints, |
47 | | }; |
48 | | pub use crl::{ |
49 | | CertificateRevocationList, CertificateRevocationListParams, CrlDistributionPoint, |
50 | | CrlIssuingDistributionPoint, CrlScope, RevocationReason, RevokedCertParams, |
51 | | }; |
52 | | pub use csr::{CertificateSigningRequest, CertificateSigningRequestParams, PublicKey}; |
53 | | pub use error::{Error, InvalidAsn1String}; |
54 | | #[cfg(feature = "crypto")] |
55 | | pub use key_pair::KeyPair; |
56 | | #[cfg(all(feature = "crypto", feature = "aws_lc_rs"))] |
57 | | pub use key_pair::RsaKeySize; |
58 | | pub use key_pair::{PublicKeyData, SigningKey, SubjectPublicKeyInfo}; |
59 | | #[cfg(feature = "pem")] |
60 | | use pem::Pem; |
61 | | use pki_types::CertificateDer; |
62 | | #[cfg(feature = "crypto")] |
63 | | use ring_like::digest; |
64 | | pub use sign_algo::algo::*; |
65 | | pub use sign_algo::SignatureAlgorithm; |
66 | | use time::{OffsetDateTime, Time}; |
67 | | use yasna::models::{GeneralizedTime, ObjectIdentifier, UTCTime}; |
68 | | use yasna::tags::{TAG_BMPSTRING, TAG_TELETEXSTRING, TAG_UNIVERSALSTRING}; |
69 | | use yasna::{DERWriter, Tag}; |
70 | | |
71 | | use crate::string::{BmpString, Ia5String, PrintableString, TeletexString, UniversalString}; |
72 | | |
73 | | mod certificate; |
74 | | mod crl; |
75 | | mod csr; |
76 | | mod error; |
77 | | mod key_pair; |
78 | | mod oid; |
79 | | mod ring_like; |
80 | | mod sign_algo; |
81 | | pub mod string; |
82 | | |
83 | | /// Type-alias for the old name of [`Error`]. |
84 | | #[deprecated( |
85 | | note = "Renamed to `Error`. We recommend to refer to it by fully-qualifying the crate: `rcgen::Error`." |
86 | | )] |
87 | | pub type RcgenError = Error; |
88 | | |
89 | | /// An issued certificate, together with the subject keypair. |
90 | | #[derive(PartialEq, Eq)] |
91 | | pub struct CertifiedKey<S: SigningKey> { |
92 | | /// An issued certificate. |
93 | | pub cert: Certificate, |
94 | | /// The certificate's subject signing key. |
95 | | pub signing_key: S, |
96 | | } |
97 | | |
98 | | /** |
99 | | KISS function to generate a self signed certificate |
100 | | |
101 | | Given a set of domain names you want your certificate to be valid for, |
102 | | this function fills in the other generation parameters with |
103 | | reasonable defaults and generates a self signed certificate |
104 | | and key pair as output. |
105 | | */ |
106 | | #[cfg(feature = "crypto")] |
107 | | #[cfg_attr( |
108 | | feature = "pem", |
109 | | doc = r##" |
110 | | ## Example |
111 | | |
112 | | ``` |
113 | | use rcgen::{generate_simple_self_signed, CertifiedKey}; |
114 | | # fn main () { |
115 | | // Generate a certificate that's valid for "localhost" and "hello.world.example" |
116 | | let subject_alt_names = vec!["hello.world.example".to_string(), |
117 | | "localhost".to_string()]; |
118 | | |
119 | | let 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" |
122 | | println!("{}", cert.pem()); |
123 | | println!("{}", signing_key.serialize_pem()); |
124 | | # } |
125 | | ``` |
126 | | "## |
127 | | )] |
128 | 0 | pub fn generate_simple_self_signed( |
129 | 0 | subject_alt_names: impl Into<Vec<String>>, |
130 | 0 | ) -> Result<CertifiedKey<KeyPair>, Error> { |
131 | 0 | let signing_key = KeyPair::generate()?; |
132 | 0 | let cert = CertificateParams::new(subject_alt_names)?.self_signed(&signing_key)?; |
133 | 0 | Ok(CertifiedKey { cert, signing_key }) |
134 | 0 | } |
135 | | |
136 | | /// An [`Issuer`] wrapper that also contains the issuer's [`Certificate`]. |
137 | | #[derive(Debug)] |
138 | | pub struct CertifiedIssuer<'a, S> { |
139 | | certificate: Certificate, |
140 | | issuer: Issuer<'a, S>, |
141 | | } |
142 | | |
143 | | impl<'a, S: SigningKey> CertifiedIssuer<'a, S> { |
144 | | /// Create a new issuer from the given parameters and key, with a self-signed certificate. |
145 | 0 | pub fn self_signed(params: CertificateParams, signing_key: S) -> Result<Self, Error> { |
146 | | Ok(Self { |
147 | 0 | certificate: params.self_signed(&signing_key)?, |
148 | 0 | issuer: Issuer::new(params, signing_key), |
149 | | }) |
150 | 0 | } |
151 | | |
152 | | /// Create a new issuer from the given parameters and key, signed by the given `issuer`. |
153 | 0 | pub fn signed_by( |
154 | 0 | params: CertificateParams, |
155 | 0 | signing_key: S, |
156 | 0 | issuer: &Issuer<'_, impl SigningKey>, |
157 | 0 | ) -> Result<Self, Error> { |
158 | | Ok(Self { |
159 | 0 | certificate: params.signed_by(&signing_key, issuer)?, |
160 | 0 | issuer: Issuer::new(params, signing_key), |
161 | | }) |
162 | 0 | } |
163 | | |
164 | | /// Get the certificate in PEM encoded format. |
165 | | #[cfg(feature = "pem")] |
166 | 0 | pub fn pem(&self) -> String { |
167 | 0 | pem::encode_config(&Pem::new("CERTIFICATE", self.der().to_vec()), ENCODE_CONFIG) |
168 | 0 | } |
169 | | |
170 | | /// Get the certificate in DER encoded format. |
171 | | /// |
172 | | /// See also [`Certificate::der()`] |
173 | 0 | pub fn der(&self) -> &CertificateDer<'static> { |
174 | 0 | self.certificate.der() |
175 | 0 | } |
176 | | } |
177 | | |
178 | | impl<'a, S> Deref for CertifiedIssuer<'a, S> { |
179 | | type Target = Issuer<'a, S>; |
180 | | |
181 | 0 | fn deref(&self) -> &Self::Target { |
182 | 0 | &self.issuer |
183 | 0 | } |
184 | | } |
185 | | |
186 | | impl<'a, S> AsRef<Certificate> for CertifiedIssuer<'a, S> { |
187 | 0 | fn as_ref(&self) -> &Certificate { |
188 | 0 | &self.certificate |
189 | 0 | } |
190 | | } |
191 | | |
192 | | /// An issuer that can sign certificates. |
193 | | /// |
194 | | /// Encapsulates the distinguished name, key identifier method, key usages and signing key |
195 | | /// of the issuing certificate. |
196 | | pub 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 | | |
203 | | impl<'a, S: SigningKey> Issuer<'a, S> { |
204 | | /// Create a new issuer from the given parameters and signing key. |
205 | 0 | pub fn new(params: CertificateParams, signing_key: S) -> Self { |
206 | 0 | Self { |
207 | 0 | distinguished_name: Cow::Owned(params.distinguished_name), |
208 | 0 | key_identifier_method: Cow::Owned(params.key_identifier_method), |
209 | 0 | key_usages: Cow::Owned(params.key_usages), |
210 | 0 | signing_key, |
211 | 0 | } |
212 | 0 | } |
213 | | |
214 | | /// Create a new issuer from the given parameters and signing key references. |
215 | | /// |
216 | | /// Use [`Issuer::new`] instead if you want to obtain an [`Issuer`] that owns |
217 | | /// its parameters. |
218 | 0 | pub fn from_params(params: &'a CertificateParams, signing_key: S) -> Self { |
219 | 0 | Self { |
220 | 0 | distinguished_name: Cow::Borrowed(¶ms.distinguished_name), |
221 | 0 | key_identifier_method: Cow::Borrowed(¶ms.key_identifier_method), |
222 | 0 | key_usages: Cow::Borrowed(¶ms.key_usages), |
223 | 0 | signing_key, |
224 | 0 | } |
225 | 0 | } |
226 | | |
227 | | /// Parses an existing CA certificate from the ASCII PEM format. |
228 | | /// |
229 | | /// See [`from_ca_cert_der`](Self::from_ca_cert_der) for more details. |
230 | | #[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 | | /// Parses an existing CA certificate from the DER format. |
237 | | /// |
238 | | /// This function assumes the provided certificate is a CA. It will not check |
239 | | /// for the presence of the `BasicConstraints` extension, or perform any other |
240 | | /// validation. |
241 | | /// |
242 | | /// If you already have a byte slice containing DER, it can trivially be converted into |
243 | | /// [`CertificateDer`] using the [`Into`] trait. |
244 | | #[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 | | /// Allowed key usages for this issuer. |
260 | 0 | pub fn key_usages(&self) -> &[KeyUsagePurpose] { |
261 | 0 | &self.key_usages |
262 | 0 | } |
263 | | |
264 | | /// Yield a reference to the signing key. |
265 | 0 | pub fn key(&self) -> &S { |
266 | 0 | &self.signing_key |
267 | 0 | } |
268 | | } |
269 | | |
270 | | impl<'a, S> fmt::Debug for Issuer<'a, S> { |
271 | | /// Formats the issuer information without revealing the key pair. |
272 | 0 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
273 | | // The key pair is omitted from the debug output as it contains secret information. |
274 | | let Issuer { |
275 | 0 | distinguished_name, |
276 | 0 | key_identifier_method, |
277 | 0 | key_usages, |
278 | | signing_key: _, |
279 | 0 | } = self; |
280 | | |
281 | 0 | f.debug_struct("Issuer") |
282 | 0 | .field("distinguished_name", distinguished_name) |
283 | 0 | .field("key_identifier_method", key_identifier_method) |
284 | 0 | .field("key_usages", key_usages) |
285 | 0 | .field("signing_key", &"[elided]") |
286 | 0 | .finish() |
287 | 0 | } |
288 | | } |
289 | | |
290 | | // https://tools.ietf.org/html/rfc5280#section-4.1.1 |
291 | | |
292 | | // Example certs usable as reference: |
293 | | // Uses ECDSA: https://crt.sh/?asn1=607203242 |
294 | | |
295 | | #[cfg(feature = "pem")] |
296 | | const 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] |
307 | | /// The type of subject alt name |
308 | | pub enum SanType { |
309 | | /// Also known as E-Mail address |
310 | | Rfc822Name(Ia5String), |
311 | | DnsName(Ia5String), |
312 | | URI(Ia5String), |
313 | | IpAddress(IpAddr), |
314 | | OtherName((Vec<u64>, OtherNameValue)), |
315 | | } |
316 | | |
317 | | impl 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 | | /// An `OtherName` value, defined in [RFC 5280ยง4.1.2.4]. |
338 | | /// |
339 | | /// While the standard specifies this could be any ASN.1 type rcgen limits |
340 | | /// the value to a UTF-8 encoded string as this will cover the most common |
341 | | /// use cases, for instance smart card user principal names (UPN). |
342 | | /// |
343 | | /// [RFC 5280ยง4.1.2.4]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.4 |
344 | | #[derive(Debug, PartialEq, Eq, Hash, Clone)] |
345 | | #[non_exhaustive] |
346 | | pub enum OtherNameValue { |
347 | | /// A string encoded using UTF-8 |
348 | | Utf8String(String), |
349 | | } |
350 | | |
351 | | impl OtherNameValue { |
352 | 0 | fn write_der(&self, writer: DERWriter) { |
353 | 0 | writer.write_tagged(Tag::context(0), |writer| match self { |
354 | 0 | OtherNameValue::Utf8String(s) => writer.write_utf8_string(s), |
355 | 0 | }); |
356 | 0 | } |
357 | | } |
358 | | |
359 | | impl<T> From<T> for OtherNameValue |
360 | | where |
361 | | T: Into<String>, |
362 | | { |
363 | 0 | fn from(t: T) -> Self { |
364 | 0 | OtherNameValue::Utf8String(t.into()) |
365 | 0 | } |
366 | | } |
367 | | |
368 | | #[cfg(feature = "x509-parser")] |
369 | | fn 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 | | |
379 | | impl 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 | | // We first remove the explicit tag ([0] EXPLICIT) |
397 | | 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 | 0 | fn tag(&self) -> u64 { |
416 | | // Defined in the GeneralName list in |
417 | | // https://tools.ietf.org/html/rfc5280#page-38 |
418 | | 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 | 0 | match self { |
425 | 0 | SanType::Rfc822Name(_name) => TAG_RFC822_NAME, |
426 | 0 | SanType::DnsName(_name) => TAG_DNS_NAME, |
427 | 0 | SanType::URI(_name) => TAG_URI, |
428 | 0 | SanType::IpAddress(_addr) => TAG_IP_ADDRESS, |
429 | 0 | Self::OtherName(_oid) => TAG_OTHER_NAME, |
430 | | } |
431 | 0 | } |
432 | | } |
433 | | |
434 | | /// A distinguished name entry |
435 | | #[derive(Debug, PartialEq, Eq, Hash, Clone)] |
436 | | #[non_exhaustive] |
437 | | pub enum DnValue { |
438 | | /// A string encoded using UCS-2 |
439 | | BmpString(BmpString), |
440 | | /// An ASCII string. |
441 | | Ia5String(Ia5String), |
442 | | /// An ASCII string containing only A-Z, a-z, 0-9, '()+,-./:=? and `<SPACE>` |
443 | | PrintableString(PrintableString), |
444 | | /// A string of characters from the T.61 character set |
445 | | TeletexString(TeletexString), |
446 | | /// A string encoded using UTF-32 |
447 | | UniversalString(UniversalString), |
448 | | /// A string encoded using UTF-8 |
449 | | Utf8String(String), |
450 | | } |
451 | | |
452 | | impl<T> From<T> for DnValue |
453 | | where |
454 | | T: Into<String>, |
455 | | { |
456 | 0 | fn from(t: T) -> Self { |
457 | 0 | DnValue::Utf8String(t.into()) |
458 | 0 | } |
459 | | } |
460 | | |
461 | | #[derive(Debug, Default, PartialEq, Eq, Clone)] |
462 | | /** |
463 | | Distinguished name used e.g. for the issuer and subject fields of a certificate |
464 | | |
465 | | A distinguished name is a set of (attribute type, attribute value) tuples. |
466 | | |
467 | | This datastructure keeps them ordered by insertion order. |
468 | | |
469 | | See also the RFC 5280 sections on the [issuer](https://tools.ietf.org/html/rfc5280#section-4.1.2.4) |
470 | | and [subject](https://tools.ietf.org/html/rfc5280#section-4.1.2.6) fields. |
471 | | */ |
472 | | pub struct DistinguishedName { |
473 | | entries: HashMap<DnType, DnValue>, |
474 | | order: Vec<DnType>, |
475 | | } |
476 | | |
477 | | impl DistinguishedName { |
478 | | /// Creates a new, empty distinguished name |
479 | 0 | pub fn new() -> Self { |
480 | 0 | Self::default() |
481 | 0 | } |
482 | | /// Obtains the attribute value for the given attribute type |
483 | 0 | pub fn get(&self, ty: &DnType) -> Option<&DnValue> { |
484 | 0 | self.entries.get(ty) |
485 | 0 | } |
486 | | /// Removes the attribute with the specified DnType |
487 | | /// |
488 | | /// Returns true when an actual removal happened, false |
489 | | /// when no attribute with the specified DnType was |
490 | | /// found. |
491 | 0 | pub fn remove(&mut self, ty: DnType) -> bool { |
492 | 0 | let removed = self.entries.remove(&ty).is_some(); |
493 | 0 | if removed { |
494 | 0 | self.order.retain(|ty_o| &ty != ty_o); |
495 | 0 | } |
496 | 0 | removed |
497 | 0 | } |
498 | | /// Inserts or updates an attribute that consists of type and name |
499 | | /// |
500 | | /// ``` |
501 | | /// # use rcgen::{DistinguishedName, DnType, DnValue}; |
502 | | /// let mut dn = DistinguishedName::new(); |
503 | | /// dn.push(DnType::OrganizationName, "Crab widgits SE"); |
504 | | /// dn.push(DnType::CommonName, DnValue::PrintableString("Master Cert".try_into().unwrap())); |
505 | | /// assert_eq!(dn.get(&DnType::OrganizationName), Some(&DnValue::Utf8String("Crab widgits SE".to_string()))); |
506 | | /// assert_eq!(dn.get(&DnType::CommonName), Some(&DnValue::PrintableString("Master Cert".try_into().unwrap()))); |
507 | | /// ``` |
508 | 0 | pub fn push(&mut self, ty: DnType, s: impl Into<DnValue>) { |
509 | 0 | if !self.entries.contains_key(&ty) { |
510 | 0 | self.order.push(ty.clone()); |
511 | 0 | } |
512 | 0 | self.entries.insert(ty, s.into()); |
513 | 0 | } |
514 | | /// Iterate over the entries |
515 | 0 | pub fn iter(&self) -> DistinguishedNameIterator<'_> { |
516 | 0 | DistinguishedNameIterator { |
517 | 0 | distinguished_name: self, |
518 | 0 | iter: self.order.iter(), |
519 | 0 | } |
520 | 0 | } |
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 | | // no support for distinguished names with more than one attribute |
533 | | 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 | | /** |
568 | | Iterator over [`DistinguishedName`] entries |
569 | | */ |
570 | | #[derive(Clone, Debug)] |
571 | | pub struct DistinguishedNameIterator<'a> { |
572 | | distinguished_name: &'a DistinguishedName, |
573 | | iter: std::slice::Iter<'a, DnType>, |
574 | | } |
575 | | |
576 | | impl<'a> Iterator for DistinguishedNameIterator<'a> { |
577 | | type Item = (&'a DnType, &'a DnValue); |
578 | | |
579 | 0 | fn next(&mut self) -> Option<Self::Item> { |
580 | 0 | self.iter |
581 | 0 | .next() |
582 | 0 | .and_then(|ty| self.distinguished_name.entries.get(ty).map(|v| (ty, v))) |
583 | 0 | } |
584 | | } |
585 | | |
586 | | /// One of the purposes contained in the [key usage](https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.3) extension |
587 | | #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] |
588 | | pub enum KeyUsagePurpose { |
589 | | /// digitalSignature |
590 | | DigitalSignature, |
591 | | /// contentCommitment / nonRepudiation |
592 | | ContentCommitment, |
593 | | /// keyEncipherment |
594 | | KeyEncipherment, |
595 | | /// dataEncipherment |
596 | | DataEncipherment, |
597 | | /// keyAgreement |
598 | | KeyAgreement, |
599 | | /// keyCertSign |
600 | | KeyCertSign, |
601 | | /// cRLSign |
602 | | CrlSign, |
603 | | /// encipherOnly |
604 | | EncipherOnly, |
605 | | /// decipherOnly |
606 | | DecipherOnly, |
607 | | } |
608 | | |
609 | | impl 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 | | // This x509 parser stores flags in reversed bit BIT STRING order |
617 | | let flags = key_usage.map_or(0u16, |k| k.flags).reverse_bits(); |
618 | | Ok(Self::from_u16(flags)) |
619 | | } |
620 | | |
621 | | /// Encode a key usage as the value of a BIT STRING as defined by RFC 5280. |
622 | | /// [`u16`] is sufficient to encode the largest possible key usage value (two bytes). |
623 | 0 | fn to_u16(self) -> u16 { |
624 | | const FLAG: u16 = 0b1000_0000_0000_0000; |
625 | 0 | FLAG >> match self { |
626 | 0 | KeyUsagePurpose::DigitalSignature => 0, |
627 | 0 | KeyUsagePurpose::ContentCommitment => 1, |
628 | 0 | KeyUsagePurpose::KeyEncipherment => 2, |
629 | 0 | KeyUsagePurpose::DataEncipherment => 3, |
630 | 0 | KeyUsagePurpose::KeyAgreement => 4, |
631 | 0 | KeyUsagePurpose::KeyCertSign => 5, |
632 | 0 | KeyUsagePurpose::CrlSign => 6, |
633 | 0 | KeyUsagePurpose::EncipherOnly => 7, |
634 | 0 | KeyUsagePurpose::DecipherOnly => 8, |
635 | | } |
636 | 0 | } |
637 | | |
638 | | /// Parse a collection of key usages from a [`u16`] representing the value |
639 | | /// of a KeyUsage BIT STRING as defined by RFC 5280. |
640 | | #[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 | | /// Method to generate key identifiers from public keys. |
663 | | /// |
664 | | /// Key identifiers should be derived from the public key data. [RFC 7093] defines |
665 | | /// three methods to do so using a choice of SHA256 (method 1), SHA384 (method 2), or SHA512 |
666 | | /// (method 3). In each case the first 160 bits of the hash are used as the key identifier |
667 | | /// to match the output length that would be produced were SHA1 used (a legacy option defined |
668 | | /// in RFC 5280). |
669 | | /// |
670 | | /// In addition to the RFC 7093 mechanisms, rcgen supports using a pre-specified key identifier. |
671 | | /// This can be helpful when working with an existing `Certificate`. |
672 | | /// |
673 | | /// [RFC 7093]: https://www.rfc-editor.org/rfc/rfc7093 |
674 | | #[derive(Debug, PartialEq, Eq, Hash, Clone)] |
675 | | #[non_exhaustive] |
676 | | pub enum KeyIdMethod { |
677 | | /// RFC 7093 method 1 - a truncated SHA256 digest. |
678 | | #[cfg(feature = "crypto")] |
679 | | Sha256, |
680 | | /// RFC 7093 method 2 - a truncated SHA384 digest. |
681 | | #[cfg(feature = "crypto")] |
682 | | Sha384, |
683 | | /// RFC 7093 method 3 - a truncated SHA512 digest. |
684 | | #[cfg(feature = "crypto")] |
685 | | Sha512, |
686 | | /// Pre-specified identifier. The exact given value is used as the key identifier. |
687 | | PreSpecified(Vec<u8>), |
688 | | } |
689 | | |
690 | | impl 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 | | /// Derive a key identifier for the provided subject public key info using the key ID method. |
714 | | /// |
715 | | /// Typically this is a truncated hash over the raw subject public key info, but may |
716 | | /// be a pre-specified value. |
717 | | /// |
718 | | /// This key identifier is used in the SubjectKeyIdentifier and AuthorityKeyIdentifier |
719 | | /// X.509v3 extensions. |
720 | | #[allow(unused_variables)] |
721 | 0 | 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 | 0 | let digest_method = match &self { |
724 | | #[cfg(feature = "crypto")] |
725 | 0 | Self::Sha256 => &digest::SHA256, |
726 | | #[cfg(feature = "crypto")] |
727 | 0 | Self::Sha384 => &digest::SHA384, |
728 | | #[cfg(feature = "crypto")] |
729 | 0 | Self::Sha512 => &digest::SHA512, |
730 | 0 | Self::PreSpecified(b) => { |
731 | 0 | return b.to_vec(); |
732 | | }, |
733 | | }; |
734 | | #[cfg(feature = "crypto")] |
735 | | { |
736 | 0 | let digest = digest::digest(digest_method, subject_public_key_info.as_ref()); |
737 | 0 | digest.as_ref()[0..20].to_vec() |
738 | | } |
739 | 0 | } |
740 | | } |
741 | | |
742 | 0 | fn dt_strip_nanos(dt: OffsetDateTime) -> OffsetDateTime { |
743 | | // Set nanoseconds to zero |
744 | | // This is needed because the GeneralizedTime serializer would otherwise |
745 | | // output fractional values which RFC 5280 explicitly forbode [1]. |
746 | | // UTCTime cannot express fractional seconds or leap seconds |
747 | | // therefore, it needs to be stripped of nanoseconds fully. |
748 | | // [1]: https://tools.ietf.org/html/rfc5280#section-4.1.2.5.2 |
749 | | // TODO: handle leap seconds if dt becomes leap second aware |
750 | 0 | let time = |
751 | 0 | Time::from_hms(dt.hour(), dt.minute(), dt.second()).expect("invalid or out-of-range time"); |
752 | 0 | dt.replace_time(time) |
753 | 0 | } |
754 | | |
755 | 0 | fn dt_to_generalized(dt: OffsetDateTime) -> GeneralizedTime { |
756 | 0 | let date_time = dt_strip_nanos(dt); |
757 | 0 | GeneralizedTime::from_datetime(date_time) |
758 | 0 | } |
759 | | |
760 | 0 | fn write_dt_utc_or_generalized(writer: DERWriter, dt: OffsetDateTime) { |
761 | | // RFC 5280 requires CAs to write certificate validity dates |
762 | | // below 2050 as UTCTime, and anything starting from 2050 |
763 | | // as GeneralizedTime [1]. The RFC doesn't say anything |
764 | | // about dates before 1950, but as UTCTime can't represent |
765 | | // them, we have to use GeneralizedTime if we want to or not. |
766 | | // [1]: https://tools.ietf.org/html/rfc5280#section-4.1.2.5 |
767 | 0 | if (1950..2050).contains(&dt.year()) { |
768 | 0 | let date_time = dt_strip_nanos(dt); |
769 | 0 | let ut = UTCTime::from_datetime(date_time); |
770 | 0 | writer.write_utctime(&ut); |
771 | 0 | } else { |
772 | 0 | let gt = dt_to_generalized(dt); |
773 | 0 | writer.write_generalized_time(>); |
774 | 0 | } |
775 | 0 | } |
776 | | |
777 | 0 | fn write_distinguished_name(writer: DERWriter, dn: &DistinguishedName) { |
778 | 0 | writer.write_sequence(|writer| { |
779 | 0 | for (ty, content) in dn.iter() { |
780 | 0 | writer.next().write_set(|writer| { |
781 | 0 | writer.next().write_sequence(|writer| { |
782 | 0 | writer.next().write_oid(&ty.to_oid()); |
783 | 0 | match content { |
784 | 0 | DnValue::BmpString(s) => writer |
785 | 0 | .next() |
786 | 0 | .write_tagged_implicit(TAG_BMPSTRING, |writer| { |
787 | 0 | writer.write_bytes(s.as_bytes()) |
788 | 0 | }), |
789 | | |
790 | 0 | DnValue::Ia5String(s) => writer.next().write_ia5_string(s.as_str()), |
791 | | |
792 | 0 | DnValue::PrintableString(s) => { |
793 | 0 | writer.next().write_printable_string(s.as_str()) |
794 | | }, |
795 | 0 | DnValue::TeletexString(s) => writer |
796 | 0 | .next() |
797 | 0 | .write_tagged_implicit(TAG_TELETEXSTRING, |writer| { |
798 | 0 | writer.write_bytes(s.as_bytes()) |
799 | 0 | }), |
800 | 0 | DnValue::UniversalString(s) => writer |
801 | 0 | .next() |
802 | 0 | .write_tagged_implicit(TAG_UNIVERSALSTRING, |writer| { |
803 | 0 | writer.write_bytes(s.as_bytes()) |
804 | 0 | }), |
805 | 0 | DnValue::Utf8String(s) => writer.next().write_utf8_string(s), |
806 | | } |
807 | 0 | }); |
808 | 0 | }); |
809 | | } |
810 | 0 | }); |
811 | 0 | } |
812 | | |
813 | | /// Serializes an X.509v3 extension according to RFC 5280 |
814 | 0 | fn write_x509_extension( |
815 | 0 | writer: DERWriter, |
816 | 0 | extension_oid: &[u64], |
817 | 0 | is_critical: bool, |
818 | 0 | value_serializer: impl FnOnce(DERWriter), |
819 | 0 | ) { |
820 | | // Extension specification: |
821 | | // Extension ::= SEQUENCE { |
822 | | // extnID OBJECT IDENTIFIER, |
823 | | // critical BOOLEAN DEFAULT FALSE, |
824 | | // extnValue OCTET STRING |
825 | | // -- contains the DER encoding of an ASN.1 value |
826 | | // -- corresponding to the extension type identified |
827 | | // -- by extnID |
828 | | // } |
829 | | |
830 | 0 | writer.write_sequence(|writer| { |
831 | 0 | let oid = ObjectIdentifier::from_slice(extension_oid); |
832 | 0 | writer.next().write_oid(&oid); |
833 | 0 | if is_critical { |
834 | 0 | writer.next().write_bool(true); |
835 | 0 | } |
836 | 0 | let bytes = yasna::construct_der(value_serializer); |
837 | 0 | writer.next().write_bytes(&bytes); |
838 | 0 | }) Unexecuted instantiation: rcgen::write_x509_extension::<<rcgen::certificate::CertificateParams>::write_extension_request_attribute::{closure#0}::{closure#0}::{closure#0}::{closure#0}>::{closure#0}Unexecuted instantiation: rcgen::write_x509_extension::<<rcgen::crl::RevokedCertParams>::write_der::{closure#0}::{closure#0}::{closure#0}>::{closure#0}Unexecuted instantiation: rcgen::write_x509_extension::<<rcgen::crl::RevokedCertParams>::write_der::{closure#0}::{closure#0}::{closure#1}>::{closure#0}Unexecuted instantiation: rcgen::write_x509_extension::<rcgen::write_x509_authority_key_identifier::{closure#0}>::{closure#0}Unexecuted instantiation: rcgen::write_x509_extension::<<rcgen::certificate::CertificateParams>::write_key_usage::{closure#0}>::{closure#0}Unexecuted instantiation: rcgen::write_x509_extension::<<rcgen::certificate::CertificateParams>::write_subject_alt_names::{closure#0}>::{closure#0}Unexecuted instantiation: rcgen::write_x509_extension::<<rcgen::certificate::CertificateParams>::write_extended_key_usage::{closure#0}>::{closure#0} |
839 | 0 | } Unexecuted instantiation: rcgen::write_x509_extension::<<rcgen::certificate::CertificateParams>::write_extension_request_attribute::{closure#0}::{closure#0}::{closure#0}::{closure#0}>Unexecuted instantiation: rcgen::write_x509_extension::<<rcgen::crl::RevokedCertParams>::write_der::{closure#0}::{closure#0}::{closure#0}>Unexecuted instantiation: rcgen::write_x509_extension::<<rcgen::crl::RevokedCertParams>::write_der::{closure#0}::{closure#0}::{closure#1}>Unexecuted instantiation: rcgen::write_x509_extension::<rcgen::write_x509_authority_key_identifier::{closure#0}>Unexecuted instantiation: rcgen::write_x509_extension::<<rcgen::certificate::CertificateParams>::write_key_usage::{closure#0}>Unexecuted instantiation: rcgen::write_x509_extension::<<rcgen::certificate::CertificateParams>::write_subject_alt_names::{closure#0}>Unexecuted instantiation: rcgen::write_x509_extension::<<rcgen::certificate::CertificateParams>::write_extended_key_usage::{closure#0}> |
840 | | |
841 | | /// Serializes an X.509v3 authority key identifier extension according to RFC 5280. |
842 | 0 | fn write_x509_authority_key_identifier(writer: DERWriter, aki: Vec<u8>) { |
843 | | // Write Authority Key Identifier |
844 | | // RFC 5280 states: |
845 | | // 'The keyIdentifier field of the authorityKeyIdentifier extension MUST |
846 | | // be included in all certificates generated by conforming CAs to |
847 | | // facilitate certification path construction. There is one exception; |
848 | | // where a CA distributes its public key in the form of a "self-signed" |
849 | | // certificate, the authority key identifier MAY be omitted.' |
850 | | // In addition, for CRLs: |
851 | | // 'Conforming CRL issuers MUST use the key identifier method, and MUST |
852 | | // include this extension in all CRLs issued.' |
853 | 0 | write_x509_extension(writer, oid::AUTHORITY_KEY_IDENTIFIER, false, |writer| { |
854 | 0 | writer.write_sequence(|writer| { |
855 | 0 | writer |
856 | 0 | .next() |
857 | 0 | .write_tagged_implicit(Tag::context(0), |writer| writer.write_bytes(&aki)) |
858 | 0 | }); |
859 | 0 | }); |
860 | 0 | } |
861 | | |
862 | | #[cfg(feature = "zeroize")] |
863 | | impl zeroize::Zeroize for KeyPair { |
864 | | fn zeroize(&mut self) { |
865 | | self.serialized_der.zeroize(); |
866 | | } |
867 | | } |
868 | | |
869 | | /// A certificate serial number. |
870 | | #[derive(Debug, PartialEq, Eq, Hash, Clone)] |
871 | | pub struct SerialNumber { |
872 | | inner: Vec<u8>, |
873 | | } |
874 | | |
875 | | #[allow(clippy::len_without_is_empty)] |
876 | | impl SerialNumber { |
877 | | /// Create a serial number from the given byte slice. |
878 | 0 | pub fn from_slice(bytes: &[u8]) -> SerialNumber { |
879 | 0 | let inner = bytes.to_vec(); |
880 | 0 | SerialNumber { inner } |
881 | 0 | } |
882 | | |
883 | | /// Return the byte representation of the serial number. |
884 | 0 | pub fn to_bytes(&self) -> Vec<u8> { |
885 | 0 | self.inner.clone() |
886 | 0 | } |
887 | | |
888 | | /// Return the length of the serial number in bytes. |
889 | 0 | pub fn len(&self) -> usize { |
890 | 0 | self.inner.len() |
891 | 0 | } |
892 | | } |
893 | | |
894 | | impl fmt::Display for SerialNumber { |
895 | 0 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { |
896 | 0 | let hex: Vec<_> = self.inner.iter().map(|b| format!("{b:02x}")).collect(); |
897 | 0 | write!(f, "{}", hex.join(":")) |
898 | 0 | } |
899 | | } |
900 | | |
901 | | impl From<Vec<u8>> for SerialNumber { |
902 | 0 | fn from(inner: Vec<u8>) -> SerialNumber { |
903 | 0 | SerialNumber { inner } |
904 | 0 | } |
905 | | } |
906 | | |
907 | | impl From<u64> for SerialNumber { |
908 | 0 | fn from(u: u64) -> SerialNumber { |
909 | 0 | let inner = u.to_be_bytes().into(); |
910 | 0 | SerialNumber { inner } |
911 | 0 | } |
912 | | } |
913 | | |
914 | | impl AsRef<[u8]> for SerialNumber { |
915 | 0 | fn as_ref(&self) -> &[u8] { |
916 | 0 | &self.inner |
917 | 0 | } |
918 | | } |
919 | | |
920 | | #[cfg(test)] |
921 | | mod 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 | | // TODO: include leap seconds if time becomes leap second aware |
940 | | [dt_nanos, dt_zero] |
941 | | } |
942 | | |
943 | | #[test] |
944 | | fn test_dt_utc_strip_nanos() { |
945 | | let times = times(); |
946 | | |
947 | | // No stripping - OffsetDateTime with nanos |
948 | | let res = catch_unwind(|| UTCTime::from_datetime(times[0])); |
949 | | assert!(res.is_err()); |
950 | | |
951 | | // Stripping |
952 | | 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 | | // TODO unify this with test_key_params_mismatch. |
971 | | // Note that that test doesn't have a full list of signature |
972 | | // algorithms, as it has no access to the iter function. |
973 | | 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 | | } |