diff --git a/der/src/asn1.rs b/der/src/asn1.rs index 72a25b88c..67c707d43 100644 --- a/der/src/asn1.rs +++ b/der/src/asn1.rs @@ -43,7 +43,7 @@ pub use self::{ #[cfg(feature = "alloc")] #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] -pub use self::set_of::SetOfVec; +pub use self::{bit_string::BitStringOwned, set_of::SetOfVec}; #[cfg(feature = "oid")] #[cfg_attr(docsrs, doc(cfg(feature = "oid")))] diff --git a/der/src/asn1/bit_string.rs b/der/src/asn1/bit_string.rs index 7ec1034c3..2ecd6db83 100644 --- a/der/src/asn1/bit_string.rs +++ b/der/src/asn1/bit_string.rs @@ -6,6 +6,9 @@ use crate::{ }; use core::{cmp::Ordering, iter::FusedIterator}; +#[cfg(feature = "alloc")] +use alloc::vec::Vec; + /// ASN.1 `BIT STRING` type. /// /// This type contains a sequence of any number of bits, modeled internally as @@ -297,6 +300,95 @@ where } } +/// Owned form of ASN.1 `BIT STRING` type. +/// +/// This type provides the same functionality as [`BitString`] but owns the +/// backing data. +#[cfg(feature = "alloc")] +#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] +#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] +pub struct BitStringOwned { + /// Number of unused bits in the final octet. + unused_bits: u8, + + /// Length of this `BIT STRING` in bits. + bit_length: usize, + + /// Bitstring represented as a slice of bytes. + inner: Vec, +} + +#[cfg(feature = "alloc")] +impl BitStringOwned { + /// Get the number of unused bits in the octet serialization of this + /// `BIT STRING`. + pub fn unused_bits(&self) -> u8 { + self.unused_bits + } + + /// Borrow the raw bytes of this `BIT STRING`. + pub fn raw_bytes(&self) -> &[u8] { + self.inner.as_slice() + } +} + +#[cfg(feature = "alloc")] +impl<'a> DecodeValue<'a> for BitStringOwned { + fn decode_value>(reader: &mut R, header: Header) -> Result { + let unused_bits = reader.read_byte()?; + let inner_len: usize = (header.length - Length::ONE)?.try_into()?; + + if (unused_bits > BitString::MAX_UNUSED_BITS) || (unused_bits != 0 && inner_len == 0) { + return Err(Self::TAG.value_error()); + } + + let bit_length = inner_len + .checked_mul(8) + .and_then(|n| n.checked_sub(usize::from(unused_bits))) + .ok_or(ErrorKind::Overflow)?; + + let mut inner = vec![0u8; inner_len]; + let actual_len = reader.read_into(&mut inner)?.len(); + + if inner_len == actual_len { + Ok(Self { + unused_bits, + bit_length, + inner, + }) + } else { + Err(Self::TAG.length_error()) + } + } +} + +#[cfg(feature = "alloc")] +impl EncodeValue for BitStringOwned { + fn value_len(&self) -> Result { + Length::ONE + Length::try_from(self.inner.len())? + } + + fn encode_value(&self, writer: &mut dyn Writer) -> Result<()> { + writer.write_byte(self.unused_bits)?; + writer.write(&self.inner) + } +} + +#[cfg(feature = "alloc")] +impl FixedTag for BitStringOwned { + const TAG: Tag = Tag::BitString; +} + +#[cfg(feature = "alloc")] +impl ValueOrd for BitStringOwned { + fn value_cmp(&self, other: &Self) -> Result { + match self.unused_bits.cmp(&other.unused_bits) { + Ordering::Equal => self.inner.der_cmp(&other.inner), + ordering => Ok(ordering), + } + } +} + #[cfg(test)] mod tests { use super::{BitString, Result, Tag}; diff --git a/der/src/asn1/oid.rs b/der/src/asn1/oid.rs index 7faacef04..dd27738b3 100644 --- a/der/src/asn1/oid.rs +++ b/der/src/asn1/oid.rs @@ -1,16 +1,21 @@ //! ASN.1 `OBJECT IDENTIFIER` use crate::{ - asn1::Any, ord::OrdIsValueOrd, ByteSlice, DecodeValue, EncodeValue, Error, FixedTag, Header, - Length, Reader, Result, Tag, Tagged, Writer, + asn1::Any, ord::OrdIsValueOrd, DecodeValue, EncodeValue, Error, FixedTag, Header, Length, + Reader, Result, Tag, Tagged, Writer, }; use const_oid::ObjectIdentifier; impl<'a> DecodeValue<'a> for ObjectIdentifier { fn decode_value>(reader: &mut R, header: Header) -> Result { - Ok(Self::from_bytes( - ByteSlice::decode_value(reader, header)?.as_ref(), - )?) + let mut buf = [0u8; ObjectIdentifier::MAX_SIZE]; + let slice = buf + .get_mut(..header.length.try_into()?) + .ok_or_else(|| Self::TAG.length_error())?; + + let actual_len = reader.read_into(slice)?.len(); + debug_assert_eq!(actual_len, header.length.try_into()?); + Ok(Self::from_bytes(slice)?) } } diff --git a/der/src/decode.rs b/der/src/decode.rs index 039297df4..a84c2115b 100644 --- a/der/src/decode.rs +++ b/der/src/decode.rs @@ -3,10 +3,7 @@ use crate::{Decoder, FixedTag, Header, Reader, Result}; #[cfg(feature = "pem")] -use { - crate::pem::{self, PemLabel}, - zeroize::Zeroize, -}; +use crate::{pem::PemLabel, PemReader}; #[cfg(doc)] use crate::{Length, Tag}; @@ -65,13 +62,9 @@ pub trait DecodePem: DecodeOwned + PemLabel { #[cfg_attr(docsrs, doc(cfg(feature = "pem")))] impl DecodePem for T { fn from_pem(pem: impl AsRef<[u8]>) -> Result { - // TODO(tarcieri): support for decoding directly from PEM (instead of two-pass) - let (label, mut der_bytes) = pem::decode_vec(pem.as_ref())?; - Self::validate_pem_label(label)?; - - let result = T::from_der(&der_bytes); - der_bytes.zeroize(); - result + let mut reader = PemReader::new(pem.as_ref())?; + Self::validate_pem_label(reader.type_label())?; + T::decode(&mut reader) } } diff --git a/der/src/lib.rs b/der/src/lib.rs index 4794d4ee8..1a8a554fb 100644 --- a/der/src/lib.rs +++ b/der/src/lib.rs @@ -396,7 +396,7 @@ pub use const_oid as oid; #[cfg(feature = "pem")] #[cfg_attr(docsrs, doc(cfg(feature = "pem")))] pub use { - crate::{decode::DecodePem, encode::EncodePem, writer::pem::PemWriter}, + crate::{decode::DecodePem, encode::EncodePem, reader::pem::PemReader, writer::pem::PemWriter}, pem_rfc7468 as pem, }; diff --git a/der/src/reader.rs b/der/src/reader.rs index f9431c0b3..a1297769d 100644 --- a/der/src/reader.rs +++ b/der/src/reader.rs @@ -2,6 +2,9 @@ mod nested; +#[cfg(feature = "pem")] +pub(crate) mod pem; + pub(crate) use nested::NestedReader; use crate::{ diff --git a/der/src/reader/pem.rs b/der/src/reader/pem.rs new file mode 100644 index 000000000..01bb4f20e --- /dev/null +++ b/der/src/reader/pem.rs @@ -0,0 +1,83 @@ +//! Streaming PEM reader. + +use super::Reader; +use crate::{ErrorKind, Header, Length, Result}; +use pem_rfc7468::Decoder; + +/// `Reader` type which decodes PEM on-the-fly. +#[cfg(feature = "pem")] +#[cfg_attr(docsrs, doc(cfg(feature = "pem")))] +#[derive(Clone)] +pub struct PemReader<'i> { + /// Inner PEM decoder. + decoder: Decoder<'i>, + + /// Input length (in bytes after Base64 decoding). + input_len: Length, + + /// Position in the input buffer (in bytes after Base64 decoding). + position: Length, +} + +#[cfg(feature = "pem")] +#[cfg_attr(docsrs, doc(cfg(feature = "pem")))] +impl<'i> PemReader<'i> { + /// Create a new PEM reader which decodes data on-the-fly. + /// + /// Uses the default 64-character line wrapping. + pub fn new(pem: &'i [u8]) -> Result { + let decoder = Decoder::new(pem)?; + let input_len = Length::try_from(decoder.remaining_len())?; + + Ok(Self { + decoder, + input_len, + position: Length::ZERO, + }) + } + + /// Get the PEM label which will be used in the encapsulation boundaries + /// for this document. + pub fn type_label(&self) -> &'i str { + self.decoder.type_label() + } +} + +#[cfg(feature = "pem")] +#[cfg_attr(docsrs, doc(cfg(feature = "pem")))] +impl<'i> Reader<'i> for PemReader<'i> { + fn input_len(&self) -> Length { + self.input_len + } + + fn peek_byte(&self) -> Option { + // TODO(tarcieri): lookahead buffer + None + } + + fn peek_header(&self) -> Result
{ + // TODO(tarcieri): lookahead buffer + Err(ErrorKind::Reader.into()) + } + + fn position(&self) -> Length { + self.position + } + + fn read_slice(&mut self, _len: Length) -> Result<&'i [u8]> { + // Can't borrow from PEM because it requires decoding + Err(ErrorKind::Reader.into()) + } + + fn read_into<'o>(&mut self, buf: &'o mut [u8]) -> Result<&'o [u8]> { + let bytes = self.decoder.decode(buf)?; + self.position = (self.position + bytes.len())?; + + debug_assert_eq!( + self.position, + (self.input_len - Length::try_from(self.decoder.remaining_len())?)? + ); + + Ok(bytes) + } +} diff --git a/der/tests/examples/spki.der b/der/tests/examples/spki.der new file mode 100644 index 000000000..1b602ee1f Binary files /dev/null and b/der/tests/examples/spki.der differ diff --git a/der/tests/examples/spki.pem b/der/tests/examples/spki.pem new file mode 100644 index 000000000..6891701f7 --- /dev/null +++ b/der/tests/examples/spki.pem @@ -0,0 +1,3 @@ +-----BEGIN PUBLIC KEY----- +MCowBQYDK2VwAyEATSkWfz8ZEqb3rfopOgUaFcBexnuPFyZ7HFVQ3OhTvQ0= +-----END PUBLIC KEY----- diff --git a/der/tests/pem.rs b/der/tests/pem.rs new file mode 100644 index 000000000..a6a820bfe --- /dev/null +++ b/der/tests/pem.rs @@ -0,0 +1,67 @@ +//! PEM decoding and encoding tests. + +#![cfg(all(feature = "derive", feature = "oid", feature = "pem"))] + +use der::{ + asn1::{BitStringOwned, ObjectIdentifier}, + pem::{LineEnding, PemLabel}, + Decode, DecodePem, EncodePem, Sequence, +}; + +/// Example SPKI document encoded as DER. +const SPKI_DER: &[u8] = include_bytes!("examples/spki.der"); + +/// Example SPKI document encoded as PEM. +const SPKI_PEM: &str = include_str!("examples/spki.pem"); + +/// X.509 `AlgorithmIdentifier` +#[derive(Copy, Clone, Debug, Eq, PartialEq, Sequence)] +pub struct AlgorithmIdentifier { + pub algorithm: ObjectIdentifier, + // pub parameters: ... (not used in spki.pem) +} + +/// X.509 `SubjectPublicKeyInfo` (SPKI) in borrowed form +#[derive(Copy, Clone, Debug, Eq, PartialEq, Sequence)] +pub struct SpkiBorrowed<'a> { + pub algorithm: AlgorithmIdentifier, + #[asn1(type = "BIT STRING")] + pub subject_public_key: &'a [u8], +} + +impl PemLabel for SpkiBorrowed<'_> { + const PEM_LABEL: &'static str = "PUBLIC KEY"; +} + +/// X.509 `SubjectPublicKeyInfo` (SPKI) in owned form +#[derive(Clone, Debug, Eq, PartialEq, Sequence)] +pub struct SpkiOwned { + pub algorithm: AlgorithmIdentifier, + pub subject_public_key: BitStringOwned, +} + +impl PemLabel for SpkiOwned { + const PEM_LABEL: &'static str = "PUBLIC KEY"; +} + +#[test] +fn from_pem() { + // Decode PEM to owned form. + let pem_spki = SpkiOwned::from_pem(SPKI_PEM).unwrap(); + + // Decode DER to borrowed form. + let der_spki = SpkiBorrowed::from_der(SPKI_DER).unwrap(); + + assert_eq!(pem_spki.algorithm, der_spki.algorithm); + assert_eq!( + pem_spki.subject_public_key.raw_bytes(), + der_spki.subject_public_key + ); +} + +#[test] +fn to_pem() { + let spki = SpkiBorrowed::from_der(SPKI_DER).unwrap(); + let pem = spki.to_pem(LineEnding::LF).unwrap(); + assert_eq!(&pem, SPKI_PEM); +}