diff --git a/src/lib.rs b/src/lib.rs index df8e237..d973eed 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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 //! @@ -120,6 +127,25 @@ impl<'a> PrivateKeyDer<'a> { } } +#[cfg(feature = "alloc")] +impl DecodePem for PrivateKeyDer<'static> { + fn from_pem_items( + iter: &mut impl Iterator>, + ) -> Result { + 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> for PrivateKeyDer<'a> { fn from(key: PrivatePkcs1KeyDer<'a>) -> Self { Self::Pkcs1(key) @@ -481,6 +507,30 @@ impl<'a> CertificateDer<'a> { } } +#[cfg(feature = "alloc")] +impl DecodePem for Vec> { + /// This returns _all_ certificate items appearing in `pem_slice`. + fn from_pem_items( + iter: &mut impl Iterator>, + ) -> Result { + 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() @@ -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>, + ) -> Result + where + Self: Sized; + + /// Decode this type from PEM contained a byte slice. + fn decode_from_pem(pem: &[u8]) -> Result + 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 + 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 + 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 diff --git a/src/pem.rs b/src/pem.rs index ae05d88..2bb12cf 100644 --- a/src/pem.rs +++ b/src/pem.rs @@ -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 @@ -269,3 +272,29 @@ fn read_until_newline(r: &mut R, buf: &mut Vec) -> pub fn read_all(rd: &mut dyn io::BufRead) -> impl Iterator> + '_ { 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> + '_ { + struct SliceIter<'a> { + current: &'a [u8], + } + + impl Iterator for SliceIter<'_> { + type Item = Result; + + fn next(&mut self) -> Option { + 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 } +}