aboutsummaryrefslogtreecommitdiff
path: root/src/certificate.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/certificate.rs')
-rw-r--r--src/certificate.rs164
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)
+ }
+}