diff --git a/x501/src/time.rs b/x501/src/time.rs index 57857cbe2..a83d15093 100644 --- a/x501/src/time.rs +++ b/x501/src/time.rs @@ -41,7 +41,7 @@ impl Time { } } - /// Get Time as DateTime + /// Get [`Time`] as [`DateTime`] pub fn to_date_time(&self) -> DateTime { match self { Time::UtcTime(t) => t.to_date_time(), diff --git a/x509/Cargo.toml b/x509/Cargo.toml index f87d03c88..2d5624d76 100644 --- a/x509/Cargo.toml +++ b/x509/Cargo.toml @@ -23,7 +23,9 @@ x501 = { version = "=0.1.0-pre.0", path = "../x501" } hex-literal = "0.3" [features] +alloc = ["der/alloc"] std = ["der/std"] +pem = ["alloc", "der/pem"] [package.metadata.docs.rs] all-features = true diff --git a/x509/src/certificate.rs b/x509/src/certificate.rs index 5fb269557..756d35bd4 100644 --- a/x509/src/certificate.rs +++ b/x509/src/certificate.rs @@ -1,14 +1,17 @@ //! Certificate [`Certificate`] and TBSCertificate [`TBSCertificate`] as defined in RFC 5280 use der::asn1::{BitString, ContextSpecific, ObjectIdentifier, UIntBytes}; -use der::{Sequence, TagMode, TagNumber}; +use der::{ + DecodeValue, Decoder, EncodeValue, Encoder, Error, FixedTag, Header, Length, Sequence, Tag, + TagMode, TagNumber, +}; use spki::{AlgorithmIdentifier, SubjectPublicKeyInfo}; use x501::name::Name; use x501::time::Validity; /// returns false in support of integer DEFAULT fields set to 0 -pub fn default_zero_u8() -> u8 { - 0 +fn default_v1() -> Version { + Version::V1 } /// returns false in support of boolean DEFAULT fields @@ -21,9 +24,65 @@ pub fn default_zero() -> u32 { 0 } -/// only support v3 certificates +/// only supporting v3 certificates, but all versions are listed here /// Version ::= INTEGER { v1(0), v2(1), v3(2) } -pub const X509_CERT_VERSION: u8 = 2; + +/// Version identifier for X.509 certificates. In practice, only v3 is used. +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd)] +pub enum Version { + /// Denotes X.509 v1 + V1 = 0, + + /// Denotes X.509 v2 - added issuerUniqueID and subjectUniqueID (both of which have been deprecated) + V2 = 1, + + /// Denotes X.509 v3 - add extensions + V3 = 2, +} + +impl Default for Version { + fn default() -> Self { + Version::V3 + } +} + +impl DecodeValue<'_> for Version { + fn decode_value(decoder: &mut Decoder<'_>, header: Header) -> der::Result { + Version::try_from(u8::decode_value(decoder, header)?).map_err(|_| Self::TAG.value_error()) + } +} + +impl EncodeValue for Version { + fn value_len(&self) -> der::Result { + Ok(Length::ONE) + } + + fn encode_value(&self, encoder: &mut Encoder<'_>) -> der::Result<()> { + u8::from(*self).encode_value(encoder) + } +} + +impl FixedTag for Version { + const TAG: Tag = Tag::Integer; +} + +impl From for u8 { + fn from(version: Version) -> Self { + version as u8 + } +} + +impl TryFrom for Version { + type Error = Error; + fn try_from(byte: u8) -> Result { + match byte { + 0 => Ok(Version::V1), + 1 => Ok(Version::V2), + 2 => Ok(Version::V3), + _ => Err(Self::TAG.value_error()), + } + } +} /// X.509 `TBSCertificate` as defined in [RFC 5280 Section 4.1.2.5] /// @@ -53,8 +112,8 @@ pub const X509_CERT_VERSION: u8 = 2; #[derive(Clone, Eq, PartialEq)] pub struct TBSCertificate<'a> { /// version [0] Version DEFAULT v1, - //#[asn1(context_specific = "0", default = "default_zero_u8")] - pub version: u8, + //#[asn1(context_specific = "0", default = "default_v1")] + pub version: Version, /// serialNumber CertificateSerialNumber, pub serial_number: UIntBytes<'a>, /// signature AlgorithmIdentifier{SIGNATURE-ALGORITHM, {SignatureAlgorithms}}, @@ -83,22 +142,33 @@ impl<'a> ::der::Decodable<'a> for TBSCertificate<'a> { let version = ::der::asn1::ContextSpecific::decode_explicit(decoder, ::der::TagNumber::N0)? .map(|cs| cs.value) - .unwrap_or_else(default_zero_u8); + .unwrap_or_else(default_v1); let serial_number = decoder.decode()?; let signature = decoder.decode()?; let issuer = decoder.decode()?; let validity = decoder.decode()?; let subject = decoder.decode()?; let subject_public_key_info = decoder.decode()?; - let issuer_unique_id = + + let issuer_unique_id = if Version::V2 <= version { ::der::asn1::ContextSpecific::decode_implicit(decoder, ::der::TagNumber::N1)? - .map(|cs| cs.value); - let subject_unique_id = + .map(|cs| cs.value) + } else { + None + }; + let subject_unique_id = if Version::V2 <= version { ::der::asn1::ContextSpecific::decode_implicit(decoder, ::der::TagNumber::N2)? - .map(|cs| cs.value); - let extensions = + .map(|cs| cs.value) + } else { + None + }; + let extensions = if Version::V3 <= version { ::der::asn1::ContextSpecific::decode_explicit(decoder, ::der::TagNumber::N3)? - .map(|cs| cs.value); + .map(|cs| cs.value) + } else { + None + }; + Ok(Self { version, serial_number, @@ -121,27 +191,49 @@ impl<'a> ::der::Sequence<'a> for TBSCertificate<'a> { where F: FnOnce(&[&dyn der::Encodable]) -> ::der::Result, { + let version = &ContextSpecific { + tag_number: VERSION_TAG, + tag_mode: TagMode::Explicit, + value: self.version, + }; + + let issuer_unique_id = if Version::V2 <= self.version { + &self.issuer_unique_id + } else { + &None + }; + let subject_unique_id = if Version::V2 <= self.version { + &self.subject_unique_id + } else { + &None + }; + let extensions = if Version::V3 <= self.version { + self.extensions.as_ref().map(|exts| ContextSpecific { + tag_number: EXTENSIONS_TAG, + tag_mode: TagMode::Explicit, + value: exts.clone(), + }) + } else { + None + }; + #[allow(unused_imports)] use core::convert::TryFrom; f(&[ - &ContextSpecific { - tag_number: VERSION_TAG, - tag_mode: TagMode::Explicit, - value: self.version, - }, + &::der::asn1::OptionalRef(if self.version == Version::V1 { + None + } else { + Some(version) + }), &self.serial_number, &self.signature, &self.issuer, &self.validity, &self.subject, &self.subject_public_key_info, - &self.issuer_unique_id, - &self.subject_unique_id, - &self.extensions.as_ref().map(|exts| ContextSpecific { - tag_number: EXTENSIONS_TAG, - tag_mode: TagMode::Explicit, - value: exts.clone(), - }), + issuer_unique_id, + subject_unique_id, + &extensions, ]) } } diff --git a/x509/src/certificate_document.rs b/x509/src/certificate_document.rs new file mode 100644 index 000000000..15ce62096 --- /dev/null +++ b/x509/src/certificate_document.rs @@ -0,0 +1,97 @@ +//! CertificateDocument implementation + +use crate::Certificate; +use der::{Error, Result}; + +use alloc::vec::Vec; +use core::fmt; +use der::{Decodable, Document}; + +#[cfg(feature = "pem")] +use {core::str::FromStr, der::pem}; + +/// Certificate document. +/// +/// This type provides storage for [`Certificate`] encoded as ASN.1 +/// DER with the invariant that the contained-document is "well-formed", i.e. +/// it will parse successfully according to this crate's parsing rules. +#[derive(Clone)] +#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] +pub struct CertificateDocument(Vec); + +impl<'a> TryFrom<&'a [u8]> for Certificate<'a> { + type Error = Error; + + fn try_from(bytes: &'a [u8]) -> Result { + Self::from_der(bytes) + } +} + +impl<'a> Document<'a> for CertificateDocument { + type Message = Certificate<'a>; + const SENSITIVE: bool = false; +} + +impl AsRef<[u8]> for CertificateDocument { + fn as_ref(&self) -> &[u8] { + self.0.as_ref() + } +} + +impl TryFrom<&[u8]> for CertificateDocument { + type Error = Error; + + fn try_from(bytes: &[u8]) -> Result { + Self::from_der(bytes) + } +} + +impl TryFrom> for CertificateDocument { + type Error = Error; + + fn try_from(cert: Certificate<'_>) -> Result { + Self::try_from(&cert) + } +} + +impl TryFrom<&Certificate<'_>> for CertificateDocument { + type Error = Error; + + fn try_from(cert: &Certificate<'_>) -> Result { + Self::from_msg(cert) + } +} + +impl TryFrom> for CertificateDocument { + type Error = der::Error; + + fn try_from(bytes: Vec) -> der::Result { + // Ensure document is well-formed + Certificate::from_der(bytes.as_slice())?; + Ok(Self(bytes)) + } +} + +impl fmt::Debug for CertificateDocument { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.debug_tuple("CertificateDocument") + .field(&self.decode()) + .finish() + } +} + +#[cfg(feature = "pem")] +#[cfg_attr(docsrs, doc(cfg(feature = "pem")))] +impl FromStr for CertificateDocument { + type Err = Error; + + fn from_str(s: &str) -> Result { + Self::from_pem(s) + } +} + +#[cfg(feature = "pem")] +#[cfg_attr(docsrs, doc(cfg(feature = "pem")))] +impl pem::PemLabel for CertificateDocument { + const TYPE_LABEL: &'static str = "CERTIFICATE"; +} diff --git a/x509/src/lib.rs b/x509/src/lib.rs index aadf4848b..6035c27ac 100644 --- a/x509/src/lib.rs +++ b/x509/src/lib.rs @@ -14,9 +14,10 @@ extern crate alloc; #[cfg(feature = "std")] extern crate std; -mod certificate; +pub mod certificate; +pub mod certificate_document; pub mod extensions_utils; -mod general_name; +pub mod general_name; pub mod pkix_extensions; pub mod pkix_oids; pub mod trust_anchor_format; diff --git a/x509/src/pkix_oids.rs b/x509/src/pkix_oids.rs index 14b7d0a4d..fb7012d6c 100644 --- a/x509/src/pkix_oids.rs +++ b/x509/src/pkix_oids.rs @@ -1,5 +1,6 @@ //! Object identifier values from PKIX1Implicit and PKIX1Explicit ASN.1 modules use crate::ObjectIdentifier; +use alloc::string::{String, ToString}; /// OID for CPS qualifier: 1.3.6.1.5.5.7.2.1 pub const PKIX_QT_CPS: ObjectIdentifier = ObjectIdentifier::new("1.3.6.1.5.5.7.2.1"); @@ -148,7 +149,7 @@ pub const PKIX_CE_CERTIFICATEISSUER: ObjectIdentifier = ObjectIdentifier::new("2 /// OID for holdInstructionCode extension: 2.5.29.23 pub const PKIX_CE_HOLDINSTRUCTIONCODE: ObjectIdentifier = ObjectIdentifier::new("2.5.29.23"); -/// OID forholdinstruction-callissuer attribute: 2.2.840.10040.2.2 +/// OID for holdinstruction-callissuer attribute: 2.2.840.10040.2.2 pub const PKIX_HI_HOLDINSTRUCTION_CALLISSUER: ObjectIdentifier = ObjectIdentifier::new("2.2.840.10040.2.2"); @@ -177,7 +178,7 @@ pub const PKIX_CE_AUTHORITY_KEY_IDENTIFIER: ObjectIdentifier = ObjectIdentifier: /// OID for policyConstraints extension: 2.5.29.36. See [`PolicyConstraints`](struct.PolicyConstraints.html). pub const PKIX_CE_POLICY_CONSTRAINTS: ObjectIdentifier = ObjectIdentifier::new("2.5.29.36"); -/// OID for policyConstraints extension: 2.5.29.46. See [`PolicyConstraints`](type.FreshestCRL.html). +/// OID for freshestCrl extension: 2.5.29.46. See [`FreshestCrl`](type.FreshestCRL.html). pub const PKIX_CE_FRESHEST_CRL: ObjectIdentifier = ObjectIdentifier::new("2.5.29.46"); /// OID for inhibitAnyPolicy extension: 2.5.29.54. See [`InhibitAnyPolicy`](type.InhibitAnyPolicy.html). @@ -188,3 +189,278 @@ pub const PKIX_OCSP_NOCHECK: ObjectIdentifier = ObjectIdentifier::new("1.3.6.1.5 /// OID for PIV NACI extension: 2.16.840.1.101.3.6.9.1. See [`PivNaciIndicator`](type.PivNaciIndicator.html). pub const PIV_NACI_INDICATOR: ObjectIdentifier = ObjectIdentifier::new("2.16.840.1.101.3.6.9.1"); + +// ------------------------------------------------------------------------------------------------- +// OIDs from PKIXAlgs-2009 +// ------------------------------------------------------------------------------------------------- + +/// rsaEncryption OBJECT IDENTIFIER ::= { +/// iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) +/// pkcs-1(1) 1 } +pub const PKIXALG_RSA_ENCRYPTION: ObjectIdentifier = ObjectIdentifier::new("1.2.840.113549.1.1.1"); + +/// id-ecPublicKey OBJECT IDENTIFIER ::= { +/// iso(1) member-body(2) us(840) ansi-X9-62(10045) keyType(2) 1 } +pub const PKIXALG_EC_PUBLIC_KEY: ObjectIdentifier = ObjectIdentifier::new("1.2.840.10045.2.1"); + +/// id-ecDH OBJECT IDENTIFIER ::= { +/// iso(1) identified-organization(3) certicom(132) schemes(1) +/// ecdh(12) } +pub const PKIXALG_DH: ObjectIdentifier = ObjectIdentifier::new("1.3.132.1.12"); + +/// secp192r1 OBJECT IDENTIFIER ::= { +/// iso(1) member-body(2) us(840) ansi-X9-62(10045) curves(3) +/// prime(1) 1 } +pub const PKIXALG_SECP192R1: ObjectIdentifier = ObjectIdentifier::new("1.2.840.10045.3.1.1"); + +/// sect163k1 OBJECT IDENTIFIER ::= { +/// iso(1) identified-organization(3) certicom(132) curve(0) 1 } +pub const PKIXALG_SECP163K1: ObjectIdentifier = ObjectIdentifier::new("1.3.132.0.1"); + +/// sect163r2 OBJECT IDENTIFIER ::= { +/// iso(1) identified-organization(3) certicom(132) curve(0) 15 } +pub const PKIXALG_SECP163R2: ObjectIdentifier = ObjectIdentifier::new("1.3.132.0.15"); + +/// secp224r1 OBJECT IDENTIFIER ::= { +/// iso(1) identified-organization(3) certicom(132) curve(0) 33 } +pub const PKIXALG_SECP224R1: ObjectIdentifier = ObjectIdentifier::new("1.3.132.0.33"); + +/// sect233k1 OBJECT IDENTIFIER ::= { +/// iso(1) identified-organization(3) certicom(132) curve(0) 26 } +pub const PKIXALG_SECP233K1: ObjectIdentifier = ObjectIdentifier::new("1.3.132.0.26"); + +/// sect233r1 OBJECT IDENTIFIER ::= { +/// iso(1) identified-organization(3) certicom(132) curve(0) 27 } +pub const PKIXALG_SECP233R1: ObjectIdentifier = ObjectIdentifier::new("1.3.132.0.27"); + +/// secp256r1 OBJECT IDENTIFIER ::= { +/// iso(1) member-body(2) us(840) ansi-X9-62(10045) curves(3) +/// prime(1) 7 } +pub const PKIXALG_SECP256R1: ObjectIdentifier = ObjectIdentifier::new("1.2.840.10045.3.1.7"); + +/// sect283k1 OBJECT IDENTIFIER ::= { +/// iso(1) identified-organization(3) certicom(132) curve(0) 16 } +pub const PKIXALG_SECP283K1: ObjectIdentifier = ObjectIdentifier::new("1.3.132.0.16"); + +/// sect283r1 OBJECT IDENTIFIER ::= { +/// iso(1) identified-organization(3) certicom(132) curve(0) 17 } +pub const PKIXALG_SECP283R1: ObjectIdentifier = ObjectIdentifier::new("1.3.132.0.17"); + +/// secp384r1 OBJECT IDENTIFIER ::= { +/// iso(1) identified-organization(3) certicom(132) curve(0) 34 } +pub const PKIXALG_SECP384R1: ObjectIdentifier = ObjectIdentifier::new("1.3.132.0.34"); + +/// sect409k1 OBJECT IDENTIFIER ::= { +/// iso(1) identified-organization(3) certicom(132) curve(0) 36 } +pub const PKIXALG_SECP409K1: ObjectIdentifier = ObjectIdentifier::new("1.3.132.0.36"); + +/// sect409r1 OBJECT IDENTIFIER ::= { +/// iso(1) identified-organization(3) certicom(132) curve(0) 37 } +pub const PKIXALG_SECP409R1: ObjectIdentifier = ObjectIdentifier::new("1.3.132.0.37"); + +/// secp521r1 OBJECT IDENTIFIER ::= { +/// iso(1) identified-organization(3) certicom(132) curve(0) 35 } +pub const PKIXALG_SECP521R1: ObjectIdentifier = ObjectIdentifier::new("1.3.132.0.35"); + +/// sect571k1 OBJECT IDENTIFIER ::= { +/// iso(1) identified-organization(3) certicom(132) curve(0) 38 } +pub const PKIXALG_SECP571K1: ObjectIdentifier = ObjectIdentifier::new("1.3.132.0.38"); + +/// sect571r1 OBJECT IDENTIFIER ::= { +/// iso(1) identified-organization(3) certicom(132) curve(0) 39 } +pub const PKIXALG_SECP571R1: ObjectIdentifier = ObjectIdentifier::new("1.3.132.0.39"); + +/// ecdsa-with-SHA224 OBJECT IDENTIFIER ::= { +/// iso(1) member-body(2) us(840) ansi-X9-62(10045) signatures(4) +/// ecdsa-with-SHA2(3) 1 } +pub const PKIXALG_ECDSA_WITH_SHA224: ObjectIdentifier = + ObjectIdentifier::new("1.2.840.10045.4.3.1"); + +/// ecdsa-with-SHA256 OBJECT IDENTIFIER ::= { +/// iso(1) member-body(2) us(840) ansi-X9-62(10045) signatures(4) +/// ecdsa-with-SHA2(3) 2 } +pub const PKIXALG_ECDSA_WITH_SHA256: ObjectIdentifier = + ObjectIdentifier::new("1.2.840.10045.4.3.2"); + +/// ecdsa-with-SHA384 OBJECT IDENTIFIER ::= { +/// iso(1) member-body(2) us(840) ansi-X9-62(10045) signatures(4) +/// ecdsa-with-SHA2(3) 3 } +pub const PKIXALG_ECDSA_WITH_SHA384: ObjectIdentifier = + ObjectIdentifier::new("1.2.840.10045.4.3.3"); + +/// ecdsa-with-SHA512 OBJECT IDENTIFIER ::= { +/// iso(1) member-body(2) us(840) ansi-X9-62(10045) signatures(4) +/// ecdsa-with-SHA2(3) 4 } +pub const PKIXALG_ECDSA_WITH_SHA512: ObjectIdentifier = + ObjectIdentifier::new("1.2.840.10045.4.3.4"); + +// ------------------------------------------------------------------------------------------------- +// OIDs from PKIX1-PSS-OAEP-Algorithms-2009 +// ------------------------------------------------------------------------------------------------- +// pkcs-1 OBJECT IDENTIFIER ::= +// { iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) 1 } + +/// sha224WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 14 } +pub const PKIXALG_SHA224_WITH_RSA_ENCRYPTION: ObjectIdentifier = + ObjectIdentifier::new("1.2.840.113549.1.1.14"); + +/// sha256WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 11 } +pub const PKIXALG_SHA256_WITH_RSA_ENCRYPTION: ObjectIdentifier = + ObjectIdentifier::new("1.2.840.113549.1.1.11"); + +/// sha384WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 12 } +pub const PKIXALG_SHA384_WITH_RSA_ENCRYPTION: ObjectIdentifier = + ObjectIdentifier::new("1.2.840.113549.1.1.12"); + +/// sha512WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 13 } +pub const PKIXALG_SHA512_WITH_RSA_ENCRYPTION: ObjectIdentifier = + ObjectIdentifier::new("1.2.840.113549.1.1.13"); + +/// id-RSAES-OAEP OBJECT IDENTIFIER ::= { pkcs-1 7 } +pub const PKIXALG_RSAES_OAEP: ObjectIdentifier = ObjectIdentifier::new("1.2.840.113549.1.1.7"); + +/// id-pSpecified OBJECT IDENTIFIER ::= { pkcs-1 9 } +pub const PKIXALG_PSPECIFIED: ObjectIdentifier = ObjectIdentifier::new("1.2.840.113549.1.1.9"); + +/// id-mgf1 OBJECT IDENTIFIER ::= { pkcs-1 8 } +pub const PKIXALG_MGF1: ObjectIdentifier = ObjectIdentifier::new("1.2.840.113549.1.1.8"); + +/// id-RSASSA-PSS OBJECT IDENTIFIER ::= { pkcs-1 10 } +pub const PKIXALG_RSASSA_PSS: ObjectIdentifier = ObjectIdentifier::new("1.2.840.113549.1.1.10"); + +/// sha-1 OBJECT IDENTIFIER ::= { iso(1) identified-organization(3) +/// oiw(14) secsig(3) algorithm(2) 26 } +pub const PKIXALG_SHA1: ObjectIdentifier = ObjectIdentifier::new("1.3.14.3.2.26"); + +/// id-sha224 OBJECT IDENTIFIER ::= +/// { joint-iso-itu-t(2) country(16) us(840) organization(1) gov(101) +/// csor(3) algorithms(4) hashalgs(2) 4 } +pub const PKIXALG_SHA224: ObjectIdentifier = ObjectIdentifier::new("2.16.840.1.101.3.4.2.4"); + +/// id-sha256 OBJECT IDENTIFIER ::= +/// { joint-iso-itu-t(2) country(16) us(840) organization(1) gov(101) +/// csor(3) algorithms(4) hashalgs(2) 1 } +pub const PKIXALG_SHA256: ObjectIdentifier = ObjectIdentifier::new("2.16.840.1.101.3.4.2.1"); + +/// id-sha384 OBJECT IDENTIFIER ::= +/// { joint-iso-itu-t(2) country(16) us(840) organization(1) gov(101) +/// csor(3) algorithms(4) hashalgs(2) 2 } +pub const PKIXALG_SHA384: ObjectIdentifier = ObjectIdentifier::new("2.16.840.1.101.3.4.2.2"); + +/// id-sha512 OBJECT IDENTIFIER ::= +/// { joint-iso-itu-t(2) country(16) us(840) organization(1) gov(101) +/// csor(3) algorithms(4) hashalgs(2) 3 } +pub const PKIXALG_SHA512: ObjectIdentifier = ObjectIdentifier::new("2.16.840.1.101.3.4.2.3"); + +/// oid_to_string takes an ObjectIdentifier and returns a String containing a friendly name or a +/// dot notation representation of the OID, i.e., 1.2.3.4, if no friendly name is found. +pub fn oid_to_string(oid: &ObjectIdentifier) -> String { + let s = oid_to_str(oid); + if s.is_empty() { + return oid.to_string(); + } + s.to_string() +} + +/// oid_to_str takes an ObjectIdentifier and returns a str containing a friendly name or an empty +/// string if no friendly name is found. +pub fn oid_to_str(oid: &ObjectIdentifier) -> &'static str { + match *oid { + PKIX_QT_CPS => "qtCps", + PKIX_QT_UNOTICE => "qtUnotice", + PKIX_AD_OCSP => "adOcsp", + PKIX_AD_CA_ISSUERS => "adCaIssuers", + PKIX_AD_TIME_STAMPING => "adTimeStamping", + PKIX_AD_CA_REPOSITORY => "adCaRepository", + PKIX_AT_NAME => "name", + PKIX_AT_SURNAME => "sn", + PKIX_AT_GIVENNAME => "givenName", + PKIX_AT_INITIALS => "initials", + PKIX_AT_GENERATION_QUALIFIER => "generationQualifier", + PKIX_AT_COMMON_NAME => "cn", + PKIX_AT_LOCALITY_NAME => "l", + PKIX_AT_STATEORPROVINCENAME => "st", + PKIX_AT_STREET => "street", + PKIX_AT_ORGANIZATIONNAME => "ou", + PKIX_AT_ORGANIZATIONALUNITNAME => "sn", + PKIX_AT_TITLE => "title", + PKIX_AT_DNQUALIFIER => "dnQualifier", + PKIX_AT_COUNTRYNAME => "c", + PKIX_AT_SERIALNUMBER => "serialNumber", + PKIX_AT_PSEUDONYM => "pseudonym", + PKIX_DOMAINCOMPONENT => "dc", + PKIX_EMAILADDRESS => "emailAddress", + PKIX_CE_ANYPOLICY => "anyPolicy", + PKIX_CE_EXTKEYUSAGE => "extKeyUsage", + PKIX_CE_ANYEXTENDEDKEYUSAGE => "anyExtendedKeyUsage", + PKIX_KP_SERVERAUTH => "serverAuth", + PKIX_KP_CLIENTAUTH => "clientAuth", + PKIX_KP_CODESIGNING => "codeSigning", + PKIX_KP_EMAILPROTECTION => "emailProtection", + PKIX_KP_TIMESTAMPING => "timeStamping", + PKIX_KP_OCSPSIGNING => "OCSPSigning", + PKIX_PE_AUTHORITYINFOACCESS => "authorityInfoAccess", + PKIX_PE_SUBJECTINFOACCESS => "subjectInfoAccess", + PKIX_CE_SUBJECT_DIRECTORY_ATTRIBUTES => "subjectDirectoryAttributes", + PKIX_CE_SUBJECT_KEY_IDENTIFIER => "subjectKeyIdentifier", + PKIX_CE_KEY_USAGE => "keyUsage", + PKIX_CE_PRIVATE_KEY_USAGE_PERIOD => "privateKeyUsagePeriod", + PKIX_CE_SUBJECT_ALT_NAME => "subjectAltName", + PKIX_CE_ISSUER_ALT_NAME => "issuerAltName", + PKIX_CE_BASIC_CONSTRAINTS => "basicConstraints", + PKIX_CE_CRLNUMBER => "cRLNumber", + PKIX_CE_CRLREASONS => "cRLReasons", + PKIX_CE_ISSUINGDISTRIBUTIONPOINT => "issuingDistributionPoint", + PKIX_CE_DELTACRLINDICATOR => "deltaCRLIndicator", + PKIX_CE_CERTIFICATEISSUER => "certificateIssuer", + PKIX_CE_HOLDINSTRUCTIONCODE => "holdInstructionCode", + PKIX_HI_HOLDINSTRUCTION_CALLISSUER => "holdinstruction-callissuer", + PKIX_HI_HOLDINSTRUCTION_REJECT => "holdinstruction-reject", + PKIX_CE_INVALIDITYDATE => "invalidityDate", + PKIX_CE_NAME_CONSTRAINTS => "nameConstraints", + PKIX_CE_CRL_DISTRIBUTION_POINTS => "cRLDistributionPoints", + PKIX_CE_CERTIFICATE_POLICIES => "certificatePolicies", + PKIX_CE_POLICY_MAPPINGS => "policyMappings", + PKIX_CE_AUTHORITY_KEY_IDENTIFIER => "authorityKeyIdentifier", + PKIX_CE_POLICY_CONSTRAINTS => "policyConstraints", + PKIX_CE_FRESHEST_CRL => "freshestCrl", + PKIX_CE_INHIBIT_ANY_POLICY => "inhibitAnyPolicy", + PKIX_OCSP_NOCHECK => "ocspNoCheck", + PIV_NACI_INDICATOR => "pivNaciIdendicator", + PKIXALG_RSA_ENCRYPTION => "rsaEncryption", + PKIXALG_EC_PUBLIC_KEY => "ecPublicKey", + PKIXALG_DH => "ecDH", + PKIXALG_SECP192R1 => "secp192r1", + PKIXALG_SECP163K1 => "sect163k1", + PKIXALG_SECP163R2 => "sect163r2", + PKIXALG_SECP224R1 => "secp224r1", + PKIXALG_SECP233K1 => "sect233k1", + PKIXALG_SECP233R1 => "sect233r1", + PKIXALG_SECP256R1 => "secp256r1", + PKIXALG_SECP283K1 => "sect283k1", + PKIXALG_SECP283R1 => "sect283r1", + PKIXALG_SECP384R1 => "secp384r1", + PKIXALG_SECP409K1 => "sect409k1", + PKIXALG_SECP409R1 => "sect409r1", + PKIXALG_SECP521R1 => "secp521r1", + PKIXALG_SECP571K1 => "sect571k1", + PKIXALG_SECP571R1 => "sect571r1", + PKIXALG_ECDSA_WITH_SHA224 => "ecdsa-with-SHA224", + PKIXALG_ECDSA_WITH_SHA256 => "ecdsa-with-SHA256", + PKIXALG_ECDSA_WITH_SHA384 => "ecdsa-with-SHA384", + PKIXALG_ECDSA_WITH_SHA512 => "ecdsa-with-SHA512", + PKIXALG_SHA224_WITH_RSA_ENCRYPTION => "sha224WithRSAEncryption", + PKIXALG_SHA256_WITH_RSA_ENCRYPTION => "sha256WithRSAEncryption", + PKIXALG_SHA384_WITH_RSA_ENCRYPTION => "sha384WithRSAEncryption", + PKIXALG_SHA512_WITH_RSA_ENCRYPTION => "sha512WithRSAEncryption", + PKIXALG_RSAES_OAEP => "RSAES-OAEP", + PKIXALG_PSPECIFIED => "pSpecified", + PKIXALG_MGF1 => "MFG1", + PKIXALG_RSASSA_PSS => "sha1", + PKIXALG_SHA224 => "sha224", + PKIXALG_SHA256 => "sha256", + PKIXALG_SHA384 => "sha384", + PKIXALG_SHA512 => "sha512", + _ => "", + } +} diff --git a/x509/tests/certificate.rs b/x509/tests/certificate.rs index 774a15186..b27de6382 100644 --- a/x509/tests/certificate.rs +++ b/x509/tests/certificate.rs @@ -1,6 +1,6 @@ //! Certificate tests use der::asn1::{BitString, UIntBytes}; -use der::{Decodable, Decoder, Encodable, Tag, Tagged}; +use der::{Decodable, Decoder, Encodable, ErrorKind, Tag, Tagged}; use hex_literal::hex; use x509::Certificate; use x509::*; @@ -67,7 +67,9 @@ pub struct DeferDecodeTBSCertificate<'a> { /// Defer decoded field pub extensions: &'a [u8], } - +pub fn default_zero_u8() -> u8 { + 0 +} impl<'a> Decodable<'a> for DeferDecodeTBSCertificate<'a> { fn decode(decoder: &mut Decoder<'a>) -> der::Result> { decoder.sequence(|decoder| { @@ -100,6 +102,25 @@ impl<'a> Decodable<'a> for DeferDecodeTBSCertificate<'a> { } } +#[test] +fn unusual_x509_versions() { + let der_encoded_cert = include_bytes!("examples/v1_cert.der"); + let defer_cert = DeferDecodeCertificate::from_der(der_encoded_cert).unwrap(); + + let parsed_tbs = TBSCertificate::from_der(defer_cert.tbs_certificate).unwrap(); + let reencoded_tbs = parsed_tbs.to_vec().unwrap(); + assert_eq!(defer_cert.tbs_certificate, reencoded_tbs); + + let der_encoded_cert = include_bytes!("examples/invalid_version.der"); + let defer_cert = DeferDecodeCertificate::from_der(der_encoded_cert).unwrap(); + + let r = TBSCertificate::from_der(defer_cert.tbs_certificate); + assert!(r.is_err()); + if let Err(e) = r { + assert_eq!(e.kind(), ErrorKind::Value { tag: Tag::Integer }); + } +} + #[test] fn reencode_cert() { let der_encoded_cert = @@ -194,7 +215,7 @@ fn decode_cert() { let result = Certificate::from_der(der_encoded_cert); let cert: Certificate = result.unwrap(); - assert_eq!(cert.tbs_certificate.version, 2); + assert_eq!(cert.tbs_certificate.version, Version::V3); let target_serial: [u8; 16] = [ 0x7F, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x49, 0xCF, 0x70, 0x66, 0x4D, 0x00, 0x00, 0x00, 0x02, diff --git a/x509/tests/certificate_document.rs b/x509/tests/certificate_document.rs new file mode 100644 index 000000000..33ea5f45e --- /dev/null +++ b/x509/tests/certificate_document.rs @@ -0,0 +1,131 @@ +//! Certificate document tests +use crate::certificate_document::CertificateDocument; +use der::Document; + +#[cfg(all(feature = "pem", any(feature = "alloc", feature = "std")))] +use der::Encodable; + +use x509::*; + +#[cfg(feature = "std")] +use std::path::Path; + +#[cfg(feature = "pem")] +use der::pem::LineEnding; + +/// `Certificate` encoded as ASN.1 DER +const CERT_DER_EXAMPLE: &[u8] = include_bytes!("examples/amazon.der"); + +/// `Certificate` encoded as PEM +#[cfg(all(feature = "pem"))] +const CERT_PEM_EXAMPLE: &str = include_str!("examples/amazon.pem"); + +#[test] +#[cfg(all(feature = "pem", feature = "std"))] +fn decode_cert_pem_file() { + let doc: CertificateDocument = + CertificateDocument::read_pem_file(Path::new("tests/examples/amazon.pem")).unwrap(); + assert_eq!(doc.as_ref(), CERT_DER_EXAMPLE); +} + +#[test] +#[cfg(all(feature = "std", feature = "alloc"))] +fn decode_cert_der_file() { + use crate::certificate_document::CertificateDocument; + let doc: CertificateDocument = + CertificateDocument::read_der_file(Path::new("tests/examples/amazon.der")).unwrap(); + assert_eq!(doc.as_ref(), CERT_DER_EXAMPLE); +} + +#[test] +#[cfg(all(feature = "pem", any(feature = "alloc", feature = "std")))] +fn decode_cert_pem() { + let doc: CertificateDocument = CERT_PEM_EXAMPLE.parse().unwrap(); + assert_eq!(doc.as_ref(), CERT_DER_EXAMPLE); + + // Ensure `CertificateDocument` parses successfully + let cert = Certificate::try_from(CERT_DER_EXAMPLE).unwrap(); + assert_eq!(doc.decode(), cert); + assert_eq!(doc.to_pem(LineEnding::default()).unwrap(), CERT_PEM_EXAMPLE); + + let doc: CertificateDocument = CertificateDocument::from_pem(CERT_PEM_EXAMPLE).unwrap(); + assert_eq!(doc.as_ref(), CERT_DER_EXAMPLE); + + // Ensure `CertificateDocument` parses successfully + let cert = Certificate::try_from(CERT_DER_EXAMPLE).unwrap(); + assert_eq!(doc.decode(), cert); + assert_eq!(doc.to_pem(LineEnding::default()).unwrap(), CERT_PEM_EXAMPLE); +} + +#[test] +fn decode_cert_der() { + let doc: CertificateDocument = CertificateDocument::from_der(CERT_DER_EXAMPLE).unwrap(); + assert_eq!(doc.as_ref(), CERT_DER_EXAMPLE); + + // Ensure `CertificateDocument` parses successfully + let cert = Certificate::try_from(CERT_DER_EXAMPLE).unwrap(); + assert_eq!(doc.decode(), cert); +} + +#[test] +#[cfg(all(feature = "pem", any(feature = "alloc", feature = "std")))] +fn encode_cert_der() { + let pk = Certificate::try_from(CERT_DER_EXAMPLE).unwrap(); + let pk_encoded = pk.to_vec().unwrap(); + assert_eq!(CERT_DER_EXAMPLE, pk_encoded.as_slice()); +} + +#[test] +#[cfg(feature = "std")] +fn write_cert_der() { + let doc: CertificateDocument = CertificateDocument::from_der(CERT_DER_EXAMPLE).unwrap(); + assert_eq!(doc.as_ref(), CERT_DER_EXAMPLE); + assert_eq!(doc.to_der().as_ref(), CERT_DER_EXAMPLE); + + let r = doc.write_der_file(Path::new("tests/examples/amazon.der.regen")); + if r.is_err() { + panic!("Failed to write file") + } + + let doc: CertificateDocument = + CertificateDocument::read_der_file(Path::new("tests/examples/amazon.der.regen")).unwrap(); + assert_eq!(doc.as_ref(), CERT_DER_EXAMPLE); + assert_eq!(doc.to_der().as_ref(), CERT_DER_EXAMPLE); + let r = std::fs::remove_file("tests/examples/amazon.der.regen"); + if r.is_err() {} +} + +#[test] +#[cfg(all(feature = "pem", any(feature = "alloc", feature = "std")))] +fn encode_cert_pem() { + let pk = Certificate::try_from(CERT_DER_EXAMPLE).unwrap(); + let pk_encoded = CertificateDocument::try_from(pk) + .unwrap() + .to_pem(Default::default()) + .unwrap(); + + assert_eq!(CERT_PEM_EXAMPLE, pk_encoded); +} + +#[test] +#[cfg(all(feature = "std", feature = "pem"))] +fn write_cert_pem() { + let doc: CertificateDocument = CertificateDocument::from_der(CERT_DER_EXAMPLE).unwrap(); + assert_eq!(doc.as_ref(), CERT_DER_EXAMPLE); + assert_eq!(doc.to_der().as_ref(), CERT_DER_EXAMPLE); + + let r = doc.write_pem_file( + Path::new("tests/examples/amazon.pem.regen"), + LineEnding::default(), + ); + if r.is_err() { + panic!("Failed to write file") + } + + let doc: CertificateDocument = + CertificateDocument::read_pem_file(Path::new("tests/examples/amazon.pem.regen")).unwrap(); + assert_eq!(doc.as_ref(), CERT_DER_EXAMPLE); + assert_eq!(doc.to_der().as_ref(), CERT_DER_EXAMPLE); + let r = std::fs::remove_file("tests/examples/amazon.pem.regen"); + if r.is_err() {} +} diff --git a/x509/tests/examples/amazon.der b/x509/tests/examples/amazon.der new file mode 100644 index 000000000..cc30bd69b Binary files /dev/null and b/x509/tests/examples/amazon.der differ diff --git a/x509/tests/examples/amazon.pem b/x509/tests/examples/amazon.pem new file mode 100644 index 000000000..8b41dea1f --- /dev/null +++ b/x509/tests/examples/amazon.pem @@ -0,0 +1,49 @@ +-----BEGIN CERTIFICATE----- +MIIIwTCCB6mgAwIBAgIQDkI5q4Xi5qJ8Usbem5B42TANBgkqhkiG9w0BAQsFADBE +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMR4wHAYDVQQDExVE +aWdpQ2VydCBHbG9iYWwgQ0EgRzIwHhcNMjExMDA2MDAwMDAwWhcNMjIwOTE5MjM1 +OTU5WjAYMRYwFAYDVQQDDA0qLnBlZy5hMnouY29tMIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEAv+/uM8Nke+pDU6lWoZALXfrNwH6/B3+8FEfNewD6mN8u +ntzCMH8fPU5Gb1/odQWS7GiBPowoU6smFj4F0kD3Qh4OUpAVbcYS2ad5nVBnwmh8 +Tm/U3DO34FZgxtjz3qxBVKr1ryYO3+1/H2xBuit8W1TSd/s+2joupzAAQq0zn8B3 +6kpi14dYIaZgBw0WuQHaybTlC0cko9x2t43RXxNZgRxqYyh+I+MK19ZOAZgCPAoo +JXyQiUIohTdOw8gnDY5gI4zhHyzvuhifSBeII1WA6MfGdiKV+K0R9LLr0oHYV3F7 +yCQoDN29ZVgXj4UZu64IxIQnuHRN6X2otR3qgUjAoQIDAQABo4IF2TCCBdUwHwYD +VR0jBBgwFoAUJG4rLdBqklFRJWkBqppHponnQCAwHQYDVR0OBBYEFJVFFD46QB6V +FvCCrEVzglhtm/B0MIICowYDVR0RBIICmjCCApaCDGFtYXpvbi5jby51a4ITdWVk +YXRhLmFtYXpvbi5jby51a4IQd3d3LmFtYXpvbi5jby51a4IXb3JpZ2luLXd3dy5h +bWF6b24uY28udWuCDSoucGVnLmEyei5jb22CCmFtYXpvbi5jb22CCGFtem4uY29t +ghF1ZWRhdGEuYW1hem9uLmNvbYINdXMuYW1hem9uLmNvbYIOd3d3LmFtYXpvbi5j +b22CDHd3dy5hbXpuLmNvbYIUY29ycG9yYXRlLmFtYXpvbi5jb22CEWJ1eWJveC5h +bWF6b24uY29tghFpcGhvbmUuYW1hem9uLmNvbYINeXAuYW1hem9uLmNvbYIPaG9t +ZS5hbWF6b24uY29tghVvcmlnaW4td3d3LmFtYXpvbi5jb22CFm9yaWdpbjItd3d3 +LmFtYXpvbi5jb22CIWJ1Y2tleWUtcmV0YWlsLXdlYnNpdGUuYW1hem9uLmNvbYIS +aHVkZGxlcy5hbWF6b24uY29tgglhbWF6b24uZGWCDXd3dy5hbWF6b24uZGWCFG9y +aWdpbi13d3cuYW1hem9uLmRlggxhbWF6b24uY28uanCCCWFtYXpvbi5qcIINd3d3 +LmFtYXpvbi5qcIIQd3d3LmFtYXpvbi5jby5qcIIXb3JpZ2luLXd3dy5hbWF6b24u +Y28uanCCECouYWEucGVnLmEyei5jb22CECouYWIucGVnLmEyei5jb22CECouYWMu +cGVnLmEyei5jb22CGG9yaWdpbi13d3cuYW1hem9uLmNvbS5hdYIRd3d3LmFtYXpv +bi5jb20uYXWCECouYnoucGVnLmEyei5jb22CDWFtYXpvbi5jb20uYXWCGG9yaWdp +bjItd3d3LmFtYXpvbi5jby5qcDAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYI +KwYBBQUHAwEGCCsGAQUFBwMCMHcGA1UdHwRwMG4wNaAzoDGGL2h0dHA6Ly9jcmwz +LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbENBRzIuY3JsMDWgM6Axhi9odHRw +Oi8vY3JsNC5kaWdpY2VydC5jb20vRGlnaUNlcnRHbG9iYWxDQUcyLmNybDA+BgNV +HSAENzA1MDMGBmeBDAECATApMCcGCCsGAQUFBwIBFhtodHRwOi8vd3d3LmRpZ2lj +ZXJ0LmNvbS9DUFMwdAYIKwYBBQUHAQEEaDBmMCQGCCsGAQUFBzABhhhodHRwOi8v +b2NzcC5kaWdpY2VydC5jb20wPgYIKwYBBQUHMAKGMmh0dHA6Ly9jYWNlcnRzLmRp +Z2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbENBRzIuY3J0MAwGA1UdEwEB/wQCMAAw +ggF+BgorBgEEAdZ5AgQCBIIBbgSCAWoBaAB2ACl5vvCeOTkh8FZzn2Old+W+V32c +YAr4+U1dJlwlXceEAAABfFOKWLsAAAQDAEcwRQIhAOIjvEg/ozDhjhiV0fbaYc83 +oPb2I08md/bU7nhfQufgAiAyA6CaJgpUYYQjchPqiS3DzHIIQL0FIkohEcluqPJV +oAB3AFGjsPX9AXmcVm24N3iPDKR6zBsny/eeiEKaDf7UiwXlAAABfFOKWOQAAAQD +AEgwRgIhAJA7QR62SZNgoU0SCfXaPUriW4FrPlVUZbSLl7s+q3W4AiEAg/iCIUET +pZEw8IAvQGC4ggNwRgm97pma3Ug8FhJp0KgAdQBByMqx3yJGShDGoToJQodeTjGL +GwPr60vHaPCQYpYG9gAAAXxTilifAAAEAwBGMEQCIFcuV+EsdfqUxhSV4+pSF5I/ +EVBVin/kOpaTBcSTlHccAiA7IdRwyN09v9bnXKJ2XsMDGfe9RHwWwVoXA/NJMx5A +3DANBgkqhkiG9w0BAQsFAAOCAQEAyLJluG6AFZ5fVgxdS574SZd7dInEumnQUQmt ++D/vbUb4NfiO4aRdPGkGotGHptFW9wxYjvYlSmtWbns5v+0CmpiXadXLNPWZtNjJ +gFR3WTLbHzBtD+C8yL1AQ/L2kOk9PVpq50leD7+dZimurjX3k7CbxaItjHnKAq3V +klINM2LLV//ZqnQxBlHeUiTfnUjKR/0FRDM4zr247HX0BH5CUl3wT5/e6mBqkXkK +Vvl+zzVfQjkeL40KjJJjQ4+N0gsPS8+rBPGHEVXFGPtlbI6pyS3Wo2rTkaIDwd+i +7ckVQviFKPooe4SVfp+kX4xVu9BEI4hlVa2y7qc/mMecBpXT+Q== +-----END CERTIFICATE----- diff --git a/x509/tests/examples/invalid_version.der b/x509/tests/examples/invalid_version.der new file mode 100644 index 000000000..020ae8459 Binary files /dev/null and b/x509/tests/examples/invalid_version.der differ diff --git a/x509/tests/examples/v1_cert.der b/x509/tests/examples/v1_cert.der new file mode 100644 index 000000000..6541c4ed3 Binary files /dev/null and b/x509/tests/examples/v1_cert.der differ diff --git a/x509/tests/pkix_extensions.rs b/x509/tests/pkix_extensions.rs index 262147468..f953e8ea2 100644 --- a/x509/tests/pkix_extensions.rs +++ b/x509/tests/pkix_extensions.rs @@ -613,7 +613,7 @@ fn decode_cert() { let result = Certificate::from_der(der_encoded_cert); let cert: Certificate = result.unwrap(); - assert_eq!(cert.tbs_certificate.version, 2); + assert_eq!(cert.tbs_certificate.version, Version::V3); let target_serial: [u8; 1] = [2]; assert_eq!( cert.tbs_certificate.serial_number,