Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions x509/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions x509/src/certificate.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//! Certificate types

use crate::{name::Name, time::Validity};

use alloc::vec::Vec;
Expand All @@ -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
Expand Down
97 changes: 97 additions & 0 deletions x509/src/certificate/document.rs
Original file line number Diff line number Diff line change
@@ -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<u8>);

impl<'a> TryFrom<&'a [u8]> for Certificate<'a> {
type Error = Error;

fn try_from(bytes: &'a [u8]) -> Result<Self> {
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> {
Self::from_der(bytes)
}
}

impl TryFrom<Certificate<'_>> for CertificateDocument {
type Error = Error;

fn try_from(cert: Certificate<'_>) -> Result<CertificateDocument> {
Self::try_from(&cert)
}
}

impl TryFrom<&Certificate<'_>> for CertificateDocument {
type Error = Error;

fn try_from(cert: &Certificate<'_>) -> Result<CertificateDocument> {
Self::from_msg(cert)
}
}

impl TryFrom<Vec<u8>> for CertificateDocument {
type Error = Error;

fn try_from(bytes: Vec<u8>) -> Result<Self> {
// 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> {
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";
}
3 changes: 1 addition & 2 deletions x509/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,11 @@ pub use der;

pub mod anchor;
pub mod attr;
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};
131 changes: 131 additions & 0 deletions x509/tests/document.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
//! Certificate document tests
use der::Document;
use x509_cert::certificate::document::CertificateDocument;

#[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 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);
}

#[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() {}
}
Binary file added x509/tests/examples/amazon.der
Binary file not shown.
49 changes: 49 additions & 0 deletions x509/tests/examples/amazon.pem
Original file line number Diff line number Diff line change
@@ -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-----