diff --git a/x509-cert/Cargo.toml b/x509-cert/Cargo.toml index 3ba1503e2..499b35410 100644 --- a/x509-cert/Cargo.toml +++ b/x509-cert/Cargo.toml @@ -16,7 +16,7 @@ rust-version = "1.65" [dependencies] const-oid = { version = "0.9.2", features = ["db"] } # TODO: path = "../const-oid" -der = { version = "0.7.3", features = ["alloc", "derive", "flagset", "oid"] } +der = { version = "0.7.4", features = ["alloc", "derive", "flagset", "oid"] } spki = { version = "0.7.1", features = ["alloc"] } # optional dependencies diff --git a/x509-cert/src/builder.rs b/x509-cert/src/builder.rs index 399e7ee25..16579d70e 100644 --- a/x509-cert/src/builder.rs +++ b/x509-cert/src/builder.rs @@ -280,6 +280,8 @@ where // https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.8 issuer_unique_id: None, subject_unique_id: None, + + _profile: Default::default(), }; let extensions = profile.build_extensions( diff --git a/x509-cert/src/certificate.rs b/x509-cert/src/certificate.rs index 083c52fe8..0d4581a28 100644 --- a/x509-cert/src/certificate.rs +++ b/x509-cert/src/certificate.rs @@ -3,14 +3,49 @@ use crate::{name::Name, serial_number::SerialNumber, time::Validity}; use alloc::vec::Vec; use const_oid::AssociatedOid; -use core::cmp::Ordering; +use core::{cmp::Ordering, fmt::Debug, marker::PhantomData}; use der::asn1::BitString; -use der::{Decode, Enumerated, Error, ErrorKind, Sequence, ValueOrd}; +use der::{Decode, Enumerated, Error, ErrorKind, Sequence, Tag, ValueOrd}; use spki::{AlgorithmIdentifierOwned, SubjectPublicKeyInfoOwned}; #[cfg(feature = "pem")] use der::pem::PemLabel; +/// [`Profile`] allows the consumer of this crate to customize the behavior when parsing +/// certificates. +/// By default, parsing will be made in a rfc5280-compliant manner. +pub trait Profile: PartialEq + Debug + Eq + Clone { + /// Checks to run when parsing serial numbers + fn check_serial_number(serial: &SerialNumber) -> der::Result<()> { + // See the note in `SerialNumber::new`: we permit lengths of 21 bytes here, + // since some X.509 implementations interpret the limit of 20 bytes to refer + // to the pre-encoded value. + if serial.inner.len() > SerialNumber::::MAX_DECODE_LEN { + Err(Tag::Integer.value_error()) + } else { + Ok(()) + } + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +/// Parse certificates with rfc5280-compliant checks +pub struct Rfc5280; + +impl Profile for Rfc5280 {} + +#[cfg(feature = "hazmat")] +#[derive(Debug, PartialEq, Eq, Clone)] +/// Parse raw x509 certificate and disable all the checks +pub struct Raw; + +#[cfg(feature = "hazmat")] +impl Profile for Raw { + fn check_serial_number(_serial: &SerialNumber) -> der::Result<()> { + Ok(()) + } +} + /// Certificate `Version` as defined in [RFC 5280 Section 4.1]. /// /// ```text @@ -45,6 +80,9 @@ impl Default for Version { } } +/// X.509 `TbsCertificate` as defined in [RFC 5280 Section 4.1] +pub type TbsCertificate = TbsCertificateInner; + /// X.509 `TbsCertificate` as defined in [RFC 5280 Section 4.1] /// /// ASN.1 structure containing the names of the subject and issuer, a public @@ -73,7 +111,7 @@ impl Default for Version { #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, Eq, PartialEq, Sequence, ValueOrd)] #[allow(missing_docs)] -pub struct TbsCertificate { +pub struct TbsCertificateInner { /// The certificate version /// /// Note that this value defaults to Version 1 per the RFC. However, @@ -83,7 +121,7 @@ pub struct TbsCertificate { #[asn1(context_specific = "0", default = "Default::default")] pub version: Version, - pub serial_number: SerialNumber, + pub serial_number: SerialNumber

, pub signature: AlgorithmIdentifierOwned, pub issuer: Name, pub validity: Validity, @@ -98,9 +136,11 @@ pub struct TbsCertificate { #[asn1(context_specific = "3", tag_mode = "EXPLICIT", optional = "true")] pub extensions: Option, + + pub(crate) _profile: PhantomData

, } -impl TbsCertificate { +impl TbsCertificateInner

{ /// Decodes a single extension /// /// Returns an error if multiple of these extensions is present. Returns @@ -132,6 +172,11 @@ impl TbsCertificate { } } +/// X.509 certificates are defined in [RFC 5280 Section 4.1]. +/// +/// [RFC 5280 Section 4.1]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.1 +pub type Certificate = CertificateInner; + /// X.509 certificates are defined in [RFC 5280 Section 4.1]. /// /// ```text @@ -146,14 +191,14 @@ impl TbsCertificate { #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, Eq, PartialEq, Sequence, ValueOrd)] #[allow(missing_docs)] -pub struct Certificate { - pub tbs_certificate: TbsCertificate, +pub struct CertificateInner { + pub tbs_certificate: TbsCertificateInner

, pub signature_algorithm: AlgorithmIdentifierOwned, pub signature: BitString, } #[cfg(feature = "pem")] -impl PemLabel for Certificate { +impl PemLabel for CertificateInner

{ const PEM_LABEL: &'static str = "CERTIFICATE"; } diff --git a/x509-cert/src/serial_number.rs b/x509-cert/src/serial_number.rs index d9cb50523..b14487ac4 100644 --- a/x509-cert/src/serial_number.rs +++ b/x509-cert/src/serial_number.rs @@ -1,6 +1,6 @@ //! X.509 serial number -use core::fmt::Display; +use core::{fmt::Display, marker::PhantomData}; use der::{ asn1::{self, Int}, @@ -8,6 +8,8 @@ use der::{ Writer, }; +use crate::certificate::{Profile, Rfc5280}; + /// [RFC 5280 Section 4.1.2.2.] Serial Number /// /// The serial number MUST be a positive integer assigned by the CA to @@ -25,16 +27,17 @@ use der::{ /// that are negative or zero. Certificate users SHOULD be prepared to /// gracefully handle such certificates. #[derive(Clone, Debug, Eq, PartialEq, ValueOrd, PartialOrd, Ord)] -pub struct SerialNumber { - inner: Int, +pub struct SerialNumber { + pub(crate) inner: Int, + _profile: PhantomData

, } -impl SerialNumber { +impl SerialNumber

{ /// Maximum length in bytes for a [`SerialNumber`] pub const MAX_LEN: Length = Length::new(20); /// See notes in `SerialNumber::new` and `SerialNumber::decode_value`. - const MAX_DECODE_LEN: Length = Length::new(21); + pub(crate) const MAX_DECODE_LEN: Length = Length::new(21); /// Create a new [`SerialNumber`] from a byte slice. /// @@ -47,12 +50,13 @@ impl SerialNumber { // RFC 5280 is ambiguous about whether this is valid, so we limit // `SerialNumber` *encodings* to 20 bytes or fewer while permitting // `SerialNumber` *decodings* to have up to 21 bytes below. - if inner.value_len()? > SerialNumber::MAX_LEN { + if inner.value_len()? > Self::MAX_LEN { return Err(ErrorKind::Overlength.into()); } Ok(Self { inner: inner.into(), + _profile: PhantomData, }) } @@ -63,7 +67,7 @@ impl SerialNumber { } } -impl EncodeValue for SerialNumber { +impl EncodeValue for SerialNumber

{ fn value_len(&self) -> Result { self.inner.value_len() } @@ -73,22 +77,21 @@ impl EncodeValue for SerialNumber { } } -impl<'a> DecodeValue<'a> for SerialNumber { +impl<'a, P: Profile> DecodeValue<'a> for SerialNumber

{ fn decode_value>(reader: &mut R, header: Header) -> Result { let inner = Int::decode_value(reader, header)?; + let serial = Self { + inner, + _profile: PhantomData, + }; - // See the note in `SerialNumber::new`: we permit lengths of 21 bytes here, - // since some X.509 implementations interpret the limit of 20 bytes to refer - // to the pre-encoded value. - if inner.len() > SerialNumber::MAX_DECODE_LEN { - return Err(Tag::Integer.value_error()); - } + P::check_serial_number(&serial)?; - Ok(Self { inner }) + Ok(serial) } } -impl FixedTag for SerialNumber { +impl FixedTag for SerialNumber

{ const TAG: Tag = ::TAG; } @@ -131,7 +134,7 @@ impl_from!(usize); // Implement by hand because the derive would create invalid values. // Use the constructor to create a valid value. #[cfg(feature = "arbitrary")] -impl<'a> arbitrary::Arbitrary<'a> for SerialNumber { +impl<'a, P: Profile> arbitrary::Arbitrary<'a> for SerialNumber

{ fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { let len = u.int_in_range(0u32..=Self::MAX_LEN.into())?; @@ -154,7 +157,7 @@ mod tests { // Creating a new serial with an oversized encoding (due to high MSB) fails. { let too_big = [0x80; 20]; - assert!(SerialNumber::new(&too_big).is_err()); + assert!(SerialNumber::::new(&too_big).is_err()); } // Creating a new serial with the maximum encoding succeeds. @@ -163,7 +166,7 @@ mod tests { 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, ]; - assert!(SerialNumber::new(&just_enough).is_ok()); + assert!(SerialNumber::::new(&just_enough).is_ok()); } } diff --git a/x509-cert/tests/certificate.rs b/x509-cert/tests/certificate.rs index 05fdc43de..a9db930c3 100644 --- a/x509-cert/tests/certificate.rs +++ b/x509-cert/tests/certificate.rs @@ -412,3 +412,27 @@ fn decode_cert_negative_serial_number() { let reencoded = cert.to_der().unwrap(); assert_eq!(der_encoded_cert, reencoded.as_slice()); } + +#[cfg(all(feature = "pem", feature = "hazmat"))] +#[test] +fn decode_cert_overlength_serial_number() { + use der::{pem::LineEnding, DecodePem, EncodePem}; + use x509_cert::certificate::CertificateInner; + + let pem_encoded_cert = include_bytes!("examples/qualcomm.pem"); + + assert!(Certificate::from_pem(pem_encoded_cert).is_err()); + + let cert = CertificateInner::::from_pem(pem_encoded_cert).unwrap(); + assert_eq!( + cert.tbs_certificate.serial_number.as_bytes(), + &[ + 0, 132, 206, 11, 246, 160, 254, 130, 78, 229, 229, 6, 202, 168, 157, 120, 198, 21, 1, + 98, 87, 113 + ] + ); + assert_eq!(cert.tbs_certificate.serial_number.as_bytes().len(), 22); + + let reencoded = cert.to_pem(LineEnding::LF).unwrap(); + assert_eq!(pem_encoded_cert, reencoded.as_bytes()); +} diff --git a/x509-cert/tests/examples/qualcomm.pem b/x509-cert/tests/examples/qualcomm.pem new file mode 100644 index 000000000..5226062ab --- /dev/null +++ b/x509-cert/tests/examples/qualcomm.pem @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE----- +MIICnDCCAiGgAwIBAgIWAITOC/ag/oJO5eUGyqideMYVAWJXcTAKBggqhkjOPQQD +AzB2MSQwIgYDVQQKDBtRdWFsY29tbSBUZWNobm9sb2dpZXMsIEluYy4xKjAoBgNV +BAsMIVF1YWxjb21tIENyeXB0b2dyYXBoaWMgT3BlcmF0aW9uczEiMCAGA1UEAwwZ +UU1DIEF0dGVzdGF0aW9uIFJvb3QgQ0EgNDAeFw0xNzA4MDEyMjE2MzJaFw0yNzA4 +MDEyMjE2MzJaMH4xJDAiBgNVBAoMG1F1YWxjb21tIFRlY2hub2xvZ2llcywgSW5j +LjEqMCgGA1UECwwhUXVhbGNvbW0gQ3J5cHRvZ3JhcGhpYyBPcGVyYXRpb25zMSow +KAYDVQQDDCFRTUMgQXR0ZXN0YXRpb24gUm9vdCBDQSA0IFN1YkNBIDEwdjAQBgcq +hkjOPQIBBgUrgQQAIgNiAAQDsjssSUEFLyyBe17UmO3pMzqKS+V1jfQkhq7a7zmH +LCrPFmfaKLm0/szdzZxn+zwhoYen3fgJIuZUaip8wAQxLe4550c1ZBl3iSTvYUbe +J+gBz2DiJHRBOtY1bQH35NWjZjBkMBIGA1UdEwEB/wQIMAYBAf8CAQAwDgYDVR0P +AQH/BAQDAgEGMB0GA1UdDgQWBBTrVYStHPbaTn4k7bPerqZAmJcuXzAfBgNVHSME +GDAWgBQBBnkODO3o7rgWy996xOf1BxR4VTAKBggqhkjOPQQDAwNpADBmAjEAmpM/ +Xvfawl4/A3jd0VVb6lOBh0Jy+zFz1Jz/hw+Xpm9G4XJCscBE7r7lbe2Xc1DHAjEA +psnskI8pLJQwL80QzAwP3HvgyDUeedNpxnYNK797vqJu6uRMLsZBVHatLM1R4gyE +-----END CERTIFICATE-----