Skip to content

Commit

Permalink
Directly support PEM decoding of pki-types
Browse files Browse the repository at this point in the history
The `pem::PemObject` trait is the high-level entry point
into this, and allows (eg) an iterator of `CertificateDer`s
to be created from a filename (amongst other options).
This caters to types that are typically plural (certificates,
CRLs) and ones that are singular (keys) -- the later with
APIs that select the first matching object or error if there
are none.

At a lower level, there are iterator-based APIs for consuming
all the supported PEM sections in a file, stream or slice in
one pass.

Simple types who have a one-to-one mapping between PEM section
(ie, `CertificateDer` is only ever from a PEM `BEGIN CERTIFICATE`
section) impl `pem::PemObject` via `pem::PemObjectFilter` which
minimises boilerplate.

`PrivateKeyDer` is more complex, and impls `pem::PemObject` itself
so it can dispatch the three source PEM section types
(`PRIVATE KEY`, `RSA PRIVATE KEY`, & `EC PRIVATE KEY`) to the
right enum variant.
  • Loading branch information
ctz committed Sep 27, 2024
1 parent 02d80f7 commit 85d4d03
Show file tree
Hide file tree
Showing 2 changed files with 371 additions and 72 deletions.
201 changes: 194 additions & 7 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,19 @@
//! base64-encoded DER, PEM objects are delimited by header and footer lines which indicate the type
//! of object contained in the PEM blob.
//!
//! The [rustls-pemfile](https://docs.rs/rustls-pemfile) crate can be used to parse PEM files.
//! Types here can be created from:
//!
//! - DER using (for example) [`PrivatePkcs8KeyDer::from()`].
//! - PEM using (for example) [`pem::PemObject::from_pem_slice()`].
//!
//! The [`pem::PemObject`] trait contains the full selection of ways to construct
//! these types from PEM encodings. That includes ways to open and read from a file,
//! from a slice, or from an `std::io` stream.
//!
//! There is also a lower-level API that allows a given PEM file to be fully consumed
//! in one pass, even if it contains different data types: see [`pem::from_slice()`]
//! and the implementation of the [`pem::PemObject`] trait on the `(pem::SectionKind, Vec<u8>)`
//! tuple.
//!
//! ## Creating new certificates and keys
//!
Expand Down Expand Up @@ -62,6 +74,8 @@ use alloc::vec::Vec;
use core::fmt;
use core::ops::Deref;
use core::time::Duration;
#[cfg(feature = "alloc")]
use pem::{PemObject, PemObjectFilter, SectionKind};
#[cfg(all(
feature = "std",
not(all(target_family = "wasm", target_os = "unknown"))
Expand All @@ -76,20 +90,36 @@ mod server_name;
/// Low-level PEM decoding APIs.
///
/// These APIs allow decoding PEM format in an iterator, which means you
/// can load multiple different types of `Item` from a single file.
/// can load multiple different types of PEM section from a file in a single
/// pass.
#[cfg(feature = "alloc")]
pub mod pem;

#[cfg(feature = "alloc")]
use pem::Item;

pub use server_name::{
AddrParseError, DnsName, InvalidDnsNameError, IpAddr, Ipv4Addr, Ipv6Addr, ServerName,
};

/// A DER-encoded X.509 private key, in one of several formats
///
/// See variant inner types for more detailed information.
///
/// This can load several types of PEM-encoded private key, and then reveal
/// which types were found:
///
/// ```rust
/// # #[cfg(all(feature = "alloc", feature = "std"))] {
/// use rustls_pki_types::{PrivateKeyDer, pem::PemObject};
///
/// // load from a PEM file
/// let pkcs8 = PrivateKeyDer::from_pem_file("tests/data/nistp256key.pkcs8.pem").unwrap();
/// let pkcs1 = PrivateKeyDer::from_pem_file("tests/data/rsa1024.pkcs1.pem").unwrap();
/// let sec1 = PrivateKeyDer::from_pem_file("tests/data/nistp256key.pem").unwrap();
/// assert!(matches!(pkcs8, PrivateKeyDer::Pkcs8(_)));
/// assert!(matches!(pkcs1, PrivateKeyDer::Pkcs1(_)));
/// assert!(matches!(sec1, PrivateKeyDer::Sec1(_)));
/// # }
/// ```

#[non_exhaustive]
#[derive(Debug, PartialEq, Eq)]
pub enum PrivateKeyDer<'a> {
Expand Down Expand Up @@ -123,6 +153,18 @@ impl<'a> PrivateKeyDer<'a> {
}
}

#[cfg(feature = "alloc")]
impl PemObject for PrivateKeyDer<'static> {
fn from_pem(kind: SectionKind, value: Vec<u8>) -> Option<Self> {
match kind {
SectionKind::RsaPrivateKey => Some(Self::Pkcs1(value.into())),
SectionKind::EcPrivateKey => Some(Self::Sec1(value.into())),
SectionKind::PrivateKey => Some(Self::Pkcs8(value.into())),
_ => None,
}
}
}

impl<'a> From<PrivatePkcs1KeyDer<'a>> for PrivateKeyDer<'a> {
fn from(key: PrivatePkcs1KeyDer<'a>) -> Self {
Self::Pkcs1(key)
Expand Down Expand Up @@ -241,8 +283,20 @@ impl<'a> TryFrom<Vec<u8>> for PrivateKeyDer<'a> {
/// A DER-encoded plaintext RSA private key; as specified in PKCS#1/RFC 3447
///
/// RSA private keys are identified in PEM context as `RSA PRIVATE KEY` and when stored in a
/// file usually use a `.pem` or `.key` extension. For more on PEM files, refer to the crate
/// documentation.
/// file usually use a `.pem` or `.key` extension.
///
/// ```rust
/// # #[cfg(all(feature = "alloc", feature = "std"))] {
/// use rustls_pki_types::{PrivatePkcs1KeyDer, pem::PemObject};
///
/// // load from a PEM file
/// PrivatePkcs1KeyDer::from_pem_file("tests/data/rsa1024.pkcs1.pem").unwrap();
///
/// // or from a PEM byte slice...
/// # let byte_slice = include_bytes!("../tests/data/rsa1024.pkcs1.pem");
/// PrivatePkcs1KeyDer::from_pem_slice(byte_slice).unwrap();
/// # }
/// ```
#[derive(PartialEq, Eq)]
pub struct PrivatePkcs1KeyDer<'a>(Der<'a>);

Expand All @@ -259,6 +313,11 @@ impl PrivatePkcs1KeyDer<'_> {
}
}

#[cfg(feature = "alloc")]
impl PemObjectFilter for PrivatePkcs1KeyDer<'static> {
const KIND: SectionKind = SectionKind::RsaPrivateKey;
}

impl<'a> From<&'a [u8]> for PrivatePkcs1KeyDer<'a> {
fn from(slice: &'a [u8]) -> Self {
Self(Der(BytesInner::Borrowed(slice)))
Expand All @@ -285,6 +344,19 @@ impl fmt::Debug for PrivatePkcs1KeyDer<'_> {
/// Sec1 private keys are identified in PEM context as `EC PRIVATE KEY` and when stored in a
/// file usually use a `.pem` or `.key` extension. For more on PEM files, refer to the crate
/// documentation.
///
/// ```rust
/// # #[cfg(all(feature = "alloc", feature = "std"))] {
/// use rustls_pki_types::{PrivateSec1KeyDer, pem::PemObject};
///
/// // load from a PEM file
/// PrivateSec1KeyDer::from_pem_file("tests/data/nistp256key.pem").unwrap();
///
/// // or from a PEM byte slice...
/// # let byte_slice = include_bytes!("../tests/data/nistp256key.pem");
/// PrivateSec1KeyDer::from_pem_slice(byte_slice).unwrap();
/// # }
/// ```
#[derive(PartialEq, Eq)]
pub struct PrivateSec1KeyDer<'a>(Der<'a>);

Expand All @@ -301,6 +373,11 @@ impl PrivateSec1KeyDer<'_> {
}
}

#[cfg(feature = "alloc")]
impl PemObjectFilter for PrivateSec1KeyDer<'static> {
const KIND: SectionKind = SectionKind::EcPrivateKey;
}

impl<'a> From<&'a [u8]> for PrivateSec1KeyDer<'a> {
fn from(slice: &'a [u8]) -> Self {
Self(Der(BytesInner::Borrowed(slice)))
Expand All @@ -327,6 +404,20 @@ impl fmt::Debug for PrivateSec1KeyDer<'_> {
/// PKCS#8 private keys are identified in PEM context as `PRIVATE KEY` and when stored in a
/// file usually use a `.pem` or `.key` extension. For more on PEM files, refer to the crate
/// documentation.
///
/// ```rust
/// # #[cfg(all(feature = "alloc", feature = "std"))] {
/// use rustls_pki_types::{PrivatePkcs8KeyDer, pem::PemObject};
///
/// // load from a PEM file
/// PrivatePkcs8KeyDer::from_pem_file("tests/data/nistp256key.pkcs8.pem").unwrap();
/// PrivatePkcs8KeyDer::from_pem_file("tests/data/rsa1024.pkcs8.pem").unwrap();
///
/// // or from a PEM byte slice...
/// # let byte_slice = include_bytes!("../tests/data/nistp256key.pkcs8.pem");
/// PrivatePkcs8KeyDer::from_pem_slice(byte_slice).unwrap();
/// # }
/// ```
#[derive(PartialEq, Eq)]
pub struct PrivatePkcs8KeyDer<'a>(Der<'a>);

Expand All @@ -343,6 +434,11 @@ impl PrivatePkcs8KeyDer<'_> {
}
}

#[cfg(feature = "alloc")]
impl PemObjectFilter for PrivatePkcs8KeyDer<'static> {
const KIND: SectionKind = SectionKind::PrivateKey;
}

impl<'a> From<&'a [u8]> for PrivatePkcs8KeyDer<'a> {
fn from(slice: &'a [u8]) -> Self {
Self(Der(BytesInner::Borrowed(slice)))
Expand Down Expand Up @@ -405,9 +501,36 @@ impl TrustAnchor<'_> {
///
/// Certificate revocation lists are identified in PEM context as `X509 CRL` and when stored in a
/// file usually use a `.crl` extension. For more on PEM files, refer to the crate documentation.
///
/// ```rust
/// # #[cfg(all(feature = "alloc", feature = "std"))] {
/// use rustls_pki_types::{CertificateRevocationListDer, pem::PemObject};
///
/// // load several from a PEM file
/// let crls: Vec<_> = CertificateRevocationListDer::pem_file_iter("tests/data/crl.pem")
/// .unwrap()
/// .collect();
/// assert!(crls.len() >= 1);
///
/// // or one from a PEM byte slice...
/// # let byte_slice = include_bytes!("../tests/data/crl.pem");
/// CertificateRevocationListDer::from_pem_slice(byte_slice).unwrap();
///
/// // or several from a PEM byte slice
/// let crls: Vec<_> = CertificateRevocationListDer::pem_slice_iter(byte_slice)
/// .collect();
/// assert!(crls.len() >= 1);
/// # }
/// ```

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CertificateRevocationListDer<'a>(Der<'a>);

#[cfg(feature = "alloc")]
impl PemObjectFilter for CertificateRevocationListDer<'static> {
const KIND: SectionKind = SectionKind::Crl;
}

impl AsRef<[u8]> for CertificateRevocationListDer<'_> {
fn as_ref(&self) -> &[u8] {
self.0.as_ref()
Expand Down Expand Up @@ -439,9 +562,27 @@ impl<'a> From<Vec<u8>> for CertificateRevocationListDer<'a> {
///
/// Certificate signing requests are identified in PEM context as `CERTIFICATE REQUEST` and when stored in a
/// file usually use a `.csr` extension. For more on PEM files, refer to the crate documentation.
///
/// ```rust
/// # #[cfg(all(feature = "alloc", feature = "std"))] {
/// use rustls_pki_types::{CertificateSigningRequestDer, pem::PemObject};
///
/// // load from a PEM file
/// CertificateSigningRequestDer::from_pem_file("tests/data/csr.pem").unwrap();
///
/// // or from a PEM byte slice...
/// # let byte_slice = include_bytes!("../tests/data/csr.pem");
/// CertificateSigningRequestDer::from_pem_slice(byte_slice).unwrap();
/// # }
/// ```
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CertificateSigningRequestDer<'a>(Der<'a>);

#[cfg(feature = "alloc")]
impl PemObjectFilter for CertificateSigningRequestDer<'static> {
const KIND: SectionKind = SectionKind::Csr;
}

impl AsRef<[u8]> for CertificateSigningRequestDer<'_> {
fn as_ref(&self) -> &[u8] {
self.0.as_ref()
Expand Down Expand Up @@ -474,6 +615,27 @@ impl<'a> From<Vec<u8>> for CertificateSigningRequestDer<'a> {
/// Certificates are identified in PEM context as `CERTIFICATE` and when stored in a
/// file usually use a `.pem`, `.cer` or `.crt` extension. For more on PEM files, refer to the
/// crate documentation.
///
/// ```rust
/// # #[cfg(all(feature = "alloc", feature = "std"))] {
/// use rustls_pki_types::{CertificateDer, pem::PemObject};
///
/// // load several from a PEM file
/// let certs: Vec<_> = CertificateDer::pem_file_iter("tests/data/certificate.chain.pem")
/// .unwrap()
/// .collect();
/// assert_eq!(certs.len(), 3);
///
/// // or one from a PEM byte slice...
/// # let byte_slice = include_bytes!("../tests/data/certificate.chain.pem");
/// CertificateDer::from_pem_slice(byte_slice).unwrap();
///
/// // or several from a PEM byte slice
/// let certs: Vec<_> = CertificateDer::pem_slice_iter(byte_slice)
/// .collect();
/// assert_eq!(certs.len(), 3);
/// # }
/// ```
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CertificateDer<'a>(Der<'a>);

Expand All @@ -484,6 +646,11 @@ impl<'a> CertificateDer<'a> {
}
}

#[cfg(feature = "alloc")]
impl PemObjectFilter for CertificateDer<'static> {
const KIND: SectionKind = SectionKind::Certificate;
}

impl AsRef<[u8]> for CertificateDer<'_> {
fn as_ref(&self) -> &[u8] {
self.0.as_ref()
Expand Down Expand Up @@ -524,9 +691,29 @@ impl CertificateDer<'_> {
pub type SubjectPublicKeyInfo<'a> = SubjectPublicKeyInfoDer<'a>;

/// A DER-encoded SubjectPublicKeyInfo (SPKI), as specified in RFC 5280.
///
/// Public keys are identified in PEM context as a `PUBLIC KEY`.
///
/// ```rust
/// # #[cfg(all(feature = "alloc", feature = "std"))] {
/// use rustls_pki_types::{SubjectPublicKeyInfoDer, pem::PemObject};
///
/// // load from a PEM file
/// SubjectPublicKeyInfoDer::from_pem_file("tests/data/spki.pem").unwrap();
///
/// // or from a PEM byte slice...
/// # let byte_slice = include_bytes!("../tests/data/spki.pem");
/// SubjectPublicKeyInfoDer::from_pem_slice(byte_slice).unwrap();
/// # }
/// ```
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SubjectPublicKeyInfoDer<'a>(Der<'a>);

#[cfg(feature = "alloc")]
impl PemObjectFilter for SubjectPublicKeyInfoDer<'static> {
const KIND: SectionKind = SectionKind::PublicKey;
}

impl AsRef<[u8]> for SubjectPublicKeyInfoDer<'_> {
fn as_ref(&self) -> &[u8] {
self.0.as_ref()
Expand Down
Loading

0 comments on commit 85d4d03

Please sign in to comment.