Skip to content

Commit

Permalink
WIP: directly support PEM decoding of pki-types
Browse files Browse the repository at this point in the history
WIP: this is incomplete and untested, just two types: private
keys and (plural) certificates, which motivates the trait.
  • Loading branch information
ctz committed Sep 4, 2024
1 parent 59cc6bd commit adedf4a
Show file tree
Hide file tree
Showing 2 changed files with 127 additions and 1 deletion.
99 changes: 98 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,14 @@
//! 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) [`PrivatePkcs8KeyDer::decode_from_pem`] via the [`DecodePem`] extension trait.
//!
//! `decode_from_pem` returns the first matching item from the given input.
//! It is usual for given PEM file to contain multiple items: if you wish
//! to examine all of these you can use the iterator-based API in the [`pem`] module.
//!
//! ## Creating new certificates and keys
//!
Expand Down Expand Up @@ -120,6 +127,25 @@ impl<'a> PrivateKeyDer<'a> {
}
}

#[cfg(feature = "alloc")]
impl DecodePem for PrivateKeyDer<'static> {
fn from_pem_items(
iter: &mut impl Iterator<Item = Result<pem::Item, pem::Error>>,
) -> Result<Self, pem::Error> {
for item in iter {
match item {
Ok(pem::Item::Pkcs1Key(pkcs1)) => return Ok(Self::Pkcs1(pkcs1)),
Ok(pem::Item::Pkcs8Key(pkcs8)) => return Ok(Self::Pkcs8(pkcs8)),
Ok(pem::Item::Sec1Key(sec1)) => return Ok(Self::Sec1(sec1)),
Ok(_) => {}
Err(err) => return Err(err),
}
}

Err(pem::Error::NoItemsFound)
}
}

impl<'a> From<PrivatePkcs1KeyDer<'a>> for PrivateKeyDer<'a> {
fn from(key: PrivatePkcs1KeyDer<'a>) -> Self {
Self::Pkcs1(key)
Expand Down Expand Up @@ -481,6 +507,30 @@ impl<'a> CertificateDer<'a> {
}
}

#[cfg(feature = "alloc")]
impl DecodePem for Vec<CertificateDer<'static>> {
/// This returns _all_ certificate items appearing in `pem_slice`.
fn from_pem_items(
iter: &mut impl Iterator<Item = Result<pem::Item, pem::Error>>,
) -> Result<Self, pem::Error> {
let mut out = Self::new();

for item in iter {
match item {
Ok(pem::Item::X509Certificate(x509)) => out.push(x509),
Ok(_) => {}
Err(err) => return Err(err),
}
}

if out.is_empty() {
Err(pem::Error::NoItemsFound)
} else {
Ok(out)
}
}
}

impl AsRef<[u8]> for CertificateDer<'_> {
fn as_ref(&self) -> &[u8] {
self.0.as_ref()
Expand Down Expand Up @@ -757,6 +807,53 @@ impl UnixTime {
}
}

/// An extension trait for types we can decode from PEM.
#[cfg(feature = "alloc")]
pub trait DecodePem {
/// Underlying function to be implemented by target types.
///
/// This is not intended for direct use, instead use `decode_from_pem`
/// and other provided functions in this trait.
fn from_pem_items(
iter: &mut impl Iterator<Item = Result<pem::Item, pem::Error>>,
) -> Result<Self, pem::Error>
where
Self: Sized;

/// Decode this type from PEM contained a byte slice.
fn decode_from_pem(pem: &[u8]) -> Result<Self, pem::Error>
where
Self: Sized,
{
Self::from_pem_items(&mut pem::read_all_from_slice(pem))
}

/// Decode this type from PEM contained in a `str`.
fn decode_from_pem_str(pem: &str) -> Result<Self, pem::Error>
where
Self: Sized,
{
Self::decode_from_pem(pem.as_bytes())
}

/// Decode this type from PEM present in a buffered reader.
#[cfg(feature = "std")]
fn decode_from_pem_reader(rd: &mut impl std::io::BufRead) -> Result<Self, pem::IoError>
where
Self: Sized,
{
let mut items = Vec::new();
// iterate over items to slough off io errors
for item in pem::read_all(rd) {
match item {
Ok(item) => items.push(Ok(item)),
Err(err) => return Err(err),
}
}
Self::from_pem_items(&mut items.into_iter()).map_err(pem::IoError::Pem)
}
}

/// DER-encoded data, either owned or borrowed
///
/// This wrapper type is used to represent DER-encoded data in a way that is agnostic to whether
Expand Down
29 changes: 29 additions & 0 deletions src/pem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ pub enum Error {

/// base64 decode error
Base64Decode(String),

/// no items found of desired type
NoItemsFound,
}

/// Errors that may arise from reading from a file-like stream
Expand Down Expand Up @@ -269,3 +272,29 @@ fn read_until_newline<R: io::BufRead + ?Sized>(r: &mut R, buf: &mut Vec<u8>) ->
pub fn read_all(rd: &mut dyn io::BufRead) -> impl Iterator<Item = Result<Item, IoError>> + '_ {
iter::from_fn(move || read_one(rd).transpose())
}

/// Iterate over all PEM sections by reading `pem_slice`
pub(crate) fn read_all_from_slice(
pem_slice: &[u8],
) -> impl Iterator<Item = Result<Item, Error>> + '_ {
struct SliceIter<'a> {
current: &'a [u8],
}

impl Iterator for SliceIter<'_> {
type Item = Result<Item, Error>;

fn next(&mut self) -> Option<Self::Item> {
match read_one_from_slice(self.current) {
Ok(Some((item, rest))) => {
self.current = rest;
Some(Ok(item))
}
Ok(None) => None,
Err(err) => Some(Err(err)),
}
}
}

SliceIter { current: pem_slice }
}

0 comments on commit adedf4a

Please sign in to comment.