From 7ed0d8df3df51b948b99df73c3c846d135ff83f0 Mon Sep 17 00:00:00 2001 From: Carl Wallace Date: Mon, 21 Mar 2022 10:22:56 -0400 Subject: [PATCH 1/3] add certificate document support --- x509/Cargo.toml | 2 + x509/src/certificate_document.rs | 97 +++++++++++++++++++++ x509/src/lib.rs | 1 + x509/tests/certificate_document.rs | 131 +++++++++++++++++++++++++++++ x509/tests/examples/amazon.pem | 49 +++++++++++ 5 files changed, 280 insertions(+) create mode 100644 x509/src/certificate_document.rs create mode 100644 x509/tests/certificate_document.rs create mode 100644 x509/tests/examples/amazon.pem diff --git a/x509/Cargo.toml b/x509/Cargo.toml index 3f35e6927..6c8611766 100644 --- a/x509/Cargo.toml +++ b/x509/Cargo.toml @@ -25,7 +25,9 @@ hex-literal = "0.3" rstest = "0.12.0" [features] +alloc = ["der/alloc"] std = ["der/std", "spki/std"] +pem = ["alloc", "der/pem"] [package.metadata.docs.rs] all-features = true diff --git a/x509/src/certificate_document.rs b/x509/src/certificate_document.rs new file mode 100644 index 000000000..f8aabd445 --- /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 = Error; + + fn try_from(bytes: Vec) -> 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 645b656a1..efddf43cf 100644 --- a/x509/src/lib.rs +++ b/x509/src/lib.rs @@ -23,6 +23,7 @@ pub use der; pub mod anchor; pub mod attr; +pub mod certificate_document; pub mod crl; pub mod ext; pub mod name; diff --git a/x509/tests/certificate_document.rs b/x509/tests/certificate_document.rs new file mode 100644 index 000000000..6e15320e7 --- /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_cert::*; + +#[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.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----- From ec1581dad40ff04f72c9de86e47c910d79b290c1 Mon Sep 17 00:00:00 2001 From: Carl Wallace Date: Mon, 21 Mar 2022 11:58:13 -0400 Subject: [PATCH 2/3] add missing file --- x509/tests/examples/amazon.der | Bin 0 -> 2245 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 x509/tests/examples/amazon.der diff --git a/x509/tests/examples/amazon.der b/x509/tests/examples/amazon.der new file mode 100644 index 0000000000000000000000000000000000000000..cc30bd69b4642cb531b4d7c4f14049dd10ea1b80 GIT binary patch literal 2245 zcma)7X;2eq7~b7%B7_9QK)_MCK|Baw5~&ReLO?D@lp}x#LK310kfV?g1BkFi6h%Z7 z4^%+#R;+bwr7Gfy;87J3#j1E##fy3X>R7wWWg`A)W-_zyv(NiI@3rp-Fr$+I8?w8C zgunKnn~)*k&o%+Z6p{d?Fc3im+Y&Gc zgUX=!%NEG|Bq?gUV1<}%14a@siD5Ya2PP{;!el!?U%NmKu(6_Z*lYlP06o9ixXA%L zHjl&g;Cb+718X)DSoEAgqmN~&BnwzVPC84hlmiTQ&<_bFLnI6~gK(URU@#2Ty?(_# zosiM)7qL5PYmrIp$4e)&>V_;nW)XNP1Nyk3ja7F06j*HQ6&j$7d#WK9zmVp57q~|3 zbzypwn|!+XHm2c4mh$>xtvhvFW-d4-%?wpG-{u~^7ZqQB^X=VzzL9(0|KeiW_Hwap zvhR^`nR6nWw4c0hd9ro_h+oYuKcQ_85#3lCW4|MQ2%Y%{e5>)$W0OFqs@rLYS6+={ z%*q*=D0aeC!bx~t9K9em!Db2Iv^fXe0?jwUA{14%KS4 zhKQ9i1s@KD+n^VQy~U^l=g2W5p*)?Ckz?lHipVj#CiSa=95XdISdP(p?LL1Cu`)%a zOcAOj`ab3&jZUOoXaLDnQl-LxMc1kH8D>(Y+~B?C_cD`X%wCvdn6VtQ7iq*v5}m{? zMWPnUlHIftQL4-^u3=J5LPD~{AV1^2x+F+2x?ZZdX)#zINA;SNsKWZa@kf1c-2A3f zIP(2Y5!A6Pp-}%r>_J3>5U~MbJvcnPqb#8YGuO+b55grx=?3ofuk%3o^quxiRauED zU`het5qJPDg|Z-r1|u*S14j49 zhQQ1beW4KbF^I%(W&}|86JZ#Yg89Iy-v)4=b)TbBoT@VL1l|KIB#+adsgOu9)qv>! z-DK5(MnugZs*3<((PmMocu`TfMTejWiopu}p9o8#G-!m$gRDt`2)+JXvZwW6?tmy1? zoZ+JFY<{&cj{oEyV#gs>l)6wOg}II?jP`uD(-%{Wd}_%KA@=5tid}C66_6GRt2*%h zGpySjC9j#da20J^`o$5WFLaeHhxr;JQVVd~JRYUK77{`Y_}7 zx4c3g%*jde7DP%8tyq85d#|Pjr~KTF+50lKuLd32?mqD~7)cdiT>ENc>7Ag-%we)C zyC4nZ+i?EZHiv+aq4irmjrmLGg|b(SAU%IR?VPluNVJa9*(Y4yBOCzy5xZzs^!@Rg zk8A5KN*=a{TNO_+pD9?r_>t$jHRLmE)@ny0c9ZOzRSkE%>RO)6txTJDnl!wt)<2VZ za<&!eZ7|oy=eGX!hu_Hf^#?-@2PHL%Y|FB^C|c~avhLvZ8S4BqM`TyRjH|n+yp{)a zdvq7x-xfPrMyb5IbjQ`GE?P`sr*lYBlp=#y_Yx`HuwHik!i4u558Z6cj?~WL{ACN| zX7`RY)cW|n=`4EYSt1C$-+bUl+TJpP>xPEla$Suh#-xgOO ZIC{n3ahXI=eel)JDI3mhrj%U!@E@F;H{$>R literal 0 HcmV?d00001 From 400108e03cd4bda61a3f93664ffcf920d0dc0376 Mon Sep 17 00:00:00 2001 From: Carl Wallace Date: Mon, 21 Mar 2022 13:54:07 -0400 Subject: [PATCH 3/3] move document impl to certificate/document --- x509/src/certificate.rs | 4 ++++ x509/src/{certificate_document.rs => certificate/document.rs} | 0 x509/src/lib.rs | 4 +--- x509/tests/{certificate_document.rs => document.rs} | 4 ++-- 4 files changed, 7 insertions(+), 5 deletions(-) rename x509/src/{certificate_document.rs => certificate/document.rs} (100%) rename x509/tests/{certificate_document.rs => document.rs} (97%) diff --git a/x509/src/certificate.rs b/x509/src/certificate.rs index f93ab6faf..755a79895 100644 --- a/x509/src/certificate.rs +++ b/x509/src/certificate.rs @@ -1,3 +1,5 @@ +//! Certificate types + use crate::{name::Name, time::Validity}; use alloc::vec::Vec; @@ -7,6 +9,8 @@ use der::asn1::{BitString, UIntBytes}; use der::{Decodable, Enumerated, Error, ErrorKind, Newtype, Sequence}; use spki::{AlgorithmIdentifier, SubjectPublicKeyInfo}; +pub mod document; + /// Certificate `Version` as defined in [RFC 5280 Section 4.1]. /// /// ```text diff --git a/x509/src/certificate_document.rs b/x509/src/certificate/document.rs similarity index 100% rename from x509/src/certificate_document.rs rename to x509/src/certificate/document.rs diff --git a/x509/src/lib.rs b/x509/src/lib.rs index efddf43cf..7b3031564 100644 --- a/x509/src/lib.rs +++ b/x509/src/lib.rs @@ -23,13 +23,11 @@ pub use der; pub mod anchor; pub mod attr; -pub mod certificate_document; +pub mod certificate; pub mod crl; pub mod ext; pub mod name; pub mod request; pub mod time; -mod certificate; - pub use certificate::{Certificate, PkiPath, TbsCertificate, Version}; diff --git a/x509/tests/certificate_document.rs b/x509/tests/document.rs similarity index 97% rename from x509/tests/certificate_document.rs rename to x509/tests/document.rs index 6e15320e7..c50d657b5 100644 --- a/x509/tests/certificate_document.rs +++ b/x509/tests/document.rs @@ -1,6 +1,6 @@ //! Certificate document tests -use crate::certificate_document::CertificateDocument; use der::Document; +use x509_cert::certificate::document::CertificateDocument; #[cfg(all(feature = "pem", any(feature = "alloc", feature = "std")))] use der::Encodable; @@ -31,7 +31,7 @@ fn decode_cert_pem_file() { #[test] #[cfg(all(feature = "std", feature = "alloc"))] fn decode_cert_der_file() { - use crate::certificate_document::CertificateDocument; + use x509_cert::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);