diff options
Diffstat (limited to 'src/certificate.rs')
-rw-r--r-- | src/certificate.rs | 164 |
1 files changed, 131 insertions, 33 deletions
diff --git a/src/certificate.rs b/src/certificate.rs index cf71948..c5a105d 100644 --- a/src/certificate.rs +++ b/src/certificate.rs @@ -1,17 +1,55 @@ //! Certificate types -use crate::{name::Name, time::Validity}; - +use crate::{name::Name, serial_number::SerialNumber, time::Validity}; use alloc::vec::Vec; -use core::cmp::Ordering; - use const_oid::AssociatedOid; -use der::asn1::{BitStringRef, UIntRef}; -use der::{Decode, Enumerated, Error, ErrorKind, Sequence, ValueOrd}; -use spki::{AlgorithmIdentifier, SubjectPublicKeyInfo}; +use core::{cmp::Ordering, fmt::Debug}; +use der::asn1::BitString; +use der::{Decode, Enumerated, Error, ErrorKind, Sequence, Tag, ValueOrd}; +use spki::{AlgorithmIdentifierOwned, SubjectPublicKeyInfoOwned}; #[cfg(feature = "pem")] -use der::pem::PemLabel; +use der::{ + pem::{self, PemLabel}, + DecodePem, +}; + +/// [`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<Self>) -> 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::<Self>::MAX_DECODE_LEN { + Err(Tag::Integer.value_error()) + } else { + Ok(()) + } + } +} + +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +#[derive(Debug, PartialEq, Eq, Clone)] +/// Parse certificates with rfc5280-compliant checks +pub struct Rfc5280; + +impl Profile for Rfc5280 {} + +#[cfg(feature = "hazmat")] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +#[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<Self>) -> der::Result<()> { + Ok(()) + } +} /// Certificate `Version` as defined in [RFC 5280 Section 4.1]. /// @@ -20,6 +58,7 @@ use der::pem::PemLabel; /// ``` /// /// [RFC 5280 Section 4.1]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.1 +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, Copy, PartialEq, Eq, Enumerated)] #[asn1(type = "INTEGER")] #[repr(u8)] @@ -36,7 +75,7 @@ pub enum Version { impl ValueOrd for Version { fn value_cmp(&self, other: &Self) -> der::Result<Ordering> { - (&(*self as u8)).value_cmp(&(*other as u8)) + (*self as u8).value_cmp(&(*other as u8)) } } @@ -47,6 +86,9 @@ impl Default for Version { } /// X.509 `TbsCertificate` as defined in [RFC 5280 Section 4.1] +pub type TbsCertificate = TbsCertificateInner<Rfc5280>; + +/// X.509 `TbsCertificate` as defined in [RFC 5280 Section 4.1] /// /// ASN.1 structure containing the names of the subject and issuer, a public /// key associated with the subject, a validity period, and other associated @@ -71,9 +113,10 @@ impl Default for Version { /// ``` /// /// [RFC 5280 Section 4.1]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.1 +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, Eq, PartialEq, Sequence, ValueOrd)] #[allow(missing_docs)] -pub struct TbsCertificate<'a> { +pub struct TbsCertificateInner<P: Profile = Rfc5280> { /// The certificate version /// /// Note that this value defaults to Version 1 per the RFC. However, @@ -83,32 +126,30 @@ pub struct TbsCertificate<'a> { #[asn1(context_specific = "0", default = "Default::default")] pub version: Version, - pub serial_number: UIntRef<'a>, - pub signature: AlgorithmIdentifier<'a>, - pub issuer: Name<'a>, + pub serial_number: SerialNumber<P>, + pub signature: AlgorithmIdentifierOwned, + pub issuer: Name, pub validity: Validity, - pub subject: Name<'a>, - pub subject_public_key_info: SubjectPublicKeyInfo<'a>, + pub subject: Name, + pub subject_public_key_info: SubjectPublicKeyInfoOwned, #[asn1(context_specific = "1", tag_mode = "IMPLICIT", optional = "true")] - pub issuer_unique_id: Option<BitStringRef<'a>>, + pub issuer_unique_id: Option<BitString>, #[asn1(context_specific = "2", tag_mode = "IMPLICIT", optional = "true")] - pub subject_unique_id: Option<BitStringRef<'a>>, + pub subject_unique_id: Option<BitString>, #[asn1(context_specific = "3", tag_mode = "EXPLICIT", optional = "true")] - pub extensions: Option<crate::ext::Extensions<'a>>, + pub extensions: Option<crate::ext::Extensions>, } -impl<'a> TbsCertificate<'a> { +impl<P: Profile> TbsCertificateInner<P> { /// Decodes a single extension /// /// Returns an error if multiple of these extensions is present. Returns /// `Ok(None)` if the extension is not present. Returns a decoding error /// if decoding failed. Otherwise returns the extension. - pub fn get<'b: 'a, T: Decode<'a> + AssociatedOid>( - &'b self, - ) -> Result<Option<(bool, T)>, Error> { + pub fn get<'a, T: Decode<'a> + AssociatedOid>(&'a self) -> Result<Option<(bool, T)>, Error> { let mut iter = self.filter::<T>().peekable(); match iter.next() { None => Ok(None), @@ -122,20 +163,25 @@ impl<'a> TbsCertificate<'a> { /// Filters extensions by an associated OID /// /// Returns a filtered iterator over all the extensions with the OID. - pub fn filter<'b: 'a, T: Decode<'a> + AssociatedOid>( - &'b self, - ) -> impl 'b + Iterator<Item = Result<(bool, T), Error>> { + pub fn filter<'a, T: Decode<'a> + AssociatedOid>( + &'a self, + ) -> impl 'a + Iterator<Item = Result<(bool, T), Error>> { self.extensions .as_deref() .unwrap_or(&[]) .iter() .filter(|e| e.extn_id == T::OID) - .map(|e| Ok((e.critical, T::from_der(e.extn_value)?))) + .map(|e| Ok((e.critical, T::from_der(e.extn_value.as_bytes())?))) } } /// 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<Rfc5280>; + +/// X.509 certificates are defined in [RFC 5280 Section 4.1]. +/// /// ```text /// Certificate ::= SEQUENCE { /// tbsCertificate TBSCertificate, @@ -145,17 +191,17 @@ impl<'a> TbsCertificate<'a> { /// ``` /// /// [RFC 5280 Section 4.1]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.1 +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, Eq, PartialEq, Sequence, ValueOrd)] #[allow(missing_docs)] -pub struct Certificate<'a> { - pub tbs_certificate: TbsCertificate<'a>, - pub signature_algorithm: AlgorithmIdentifier<'a>, - pub signature: BitStringRef<'a>, +pub struct CertificateInner<P: Profile = Rfc5280> { + pub tbs_certificate: TbsCertificateInner<P>, + pub signature_algorithm: AlgorithmIdentifierOwned, + pub signature: BitString, } #[cfg(feature = "pem")] -#[cfg_attr(docsrs, doc(cfg(feature = "pem")))] -impl PemLabel for Certificate<'_> { +impl<P: Profile> PemLabel for CertificateInner<P> { const PEM_LABEL: &'static str = "CERTIFICATE"; } @@ -170,4 +216,56 @@ impl PemLabel for Certificate<'_> { /// ``` /// /// [RFC 6066]: https://datatracker.ietf.org/doc/html/rfc6066#section-10.1 -pub type PkiPath<'a> = Vec<Certificate<'a>>; +pub type PkiPath = Vec<Certificate>; + +#[cfg(feature = "pem")] +impl<P: Profile> CertificateInner<P> { + /// Parse a chain of pem-encoded certificates from a slice. + /// + /// Returns the list of certificates. + pub fn load_pem_chain(mut input: &[u8]) -> Result<Vec<Self>, Error> { + fn find_boundary<T>(haystack: &[T], needle: &[T]) -> Option<usize> + where + for<'a> &'a [T]: PartialEq, + { + haystack + .windows(needle.len()) + .position(|window| window == needle) + } + + let mut certs = Vec::new(); + let mut position: usize = 0; + + let end_boundary = &b"-----END CERTIFICATE-----"[..]; + + // Strip the trailing whitespaces + loop { + if input.is_empty() { + break; + } + let last_pos = input.len() - 1; + + match input.get(last_pos) { + Some(b'\r') | Some(b'\n') => { + input = &input[..last_pos]; + } + _ => break, + } + } + + while position < input.len() - 1 { + let rest = &input[position..]; + let end_pos = find_boundary(rest, end_boundary) + .ok_or(pem::Error::PostEncapsulationBoundary)? + + end_boundary.len(); + + let cert_buf = &rest[..end_pos]; + let cert = Self::from_pem(cert_buf)?; + certs.push(cert); + + position += end_pos; + } + + Ok(certs) + } +} |