diff --git a/der/src/asn1.rs b/der/src/asn1.rs index 54120bb91..0f7290c12 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::{bit_string::BitString, set_of::SetOfVec}; +pub use self::{any::Any, bit_string::BitString, octet_string::OctetString, set_of::SetOfVec}; #[cfg(feature = "oid")] #[cfg_attr(docsrs, doc(cfg(feature = "oid")))] diff --git a/der/src/asn1/any.rs b/der/src/asn1/any.rs index c7c3cc7c3..865dc7868 100644 --- a/der/src/asn1/any.rs +++ b/der/src/asn1/any.rs @@ -6,6 +6,9 @@ use crate::{ }; use core::cmp::Ordering; +#[cfg(feature = "alloc")] +use alloc::vec::Vec; + #[cfg(feature = "oid")] use crate::asn1::ObjectIdentifier; @@ -36,7 +39,7 @@ impl<'a> AnyRef<'a> { value: ByteSlice::EMPTY, }; - /// Create a new [`AnyRef`] from the provided [`Tag`] and byte slice. + /// Create a new [`AnyRef`] from the provided [`Tag`] and DER bytes. pub fn new(tag: Tag, bytes: &'a [u8]) -> Result { let value = ByteSlice::new(bytes).map_err(|_| ErrorKind::Length { tag })?; Ok(Self { tag, value }) @@ -200,3 +203,72 @@ impl<'a> TryFrom<&'a [u8]> for AnyRef<'a> { AnyRef::from_der(bytes) } } + +/// ASN.1 `ANY`: represents any explicitly tagged ASN.1 value. +/// +/// This type provides the same functionality as [`AnyRef`] but owns the +/// backing data. +#[cfg(feature = "alloc")] +#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] +#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] +pub struct Any { + /// Tag representing the type of the encoded value. + tag: Tag, + + /// Inner value encoded as bytes. + value: Vec, +} + +#[cfg(feature = "alloc")] +impl Any { + /// Create a new [`Any`] from the provided [`Tag`] and DER bytes. + pub fn new(tag: Tag, bytes: impl Into>) -> Result { + let value = bytes.into(); + + // Ensure the tag and value are a valid `AnyRef`. + AnyRef::new(tag, &value)?; + Ok(Self { tag, value }) + } +} + +#[cfg(feature = "alloc")] +impl Choice<'_> for Any { + fn can_decode(_: Tag) -> bool { + true + } +} + +#[cfg(feature = "alloc")] +impl<'a> Decode<'a> for Any { + fn decode>(reader: &mut R) -> Result { + let header = Header::decode(reader)?; + let value = reader.read_vec(header.length)?; + Self::new(header.tag, value) + } +} + +#[cfg(feature = "alloc")] +impl EncodeValue for Any { + fn value_len(&self) -> Result { + self.value.len().try_into() + } + + fn encode_value(&self, writer: &mut dyn Writer) -> Result<()> { + writer.write(&self.value) + } +} + +#[cfg(feature = "alloc")] +impl<'a> From<&'a Any> for AnyRef<'a> { + fn from(any: &'a Any) -> AnyRef<'a> { + // Ensured to parse successfully in constructor + AnyRef::new(any.tag, &any.value).expect("invalid ANY") + } +} + +#[cfg(feature = "alloc")] +impl Tagged for Any { + fn tag(&self) -> Tag { + self.tag + } +} diff --git a/der/src/asn1/bit_string.rs b/der/src/asn1/bit_string.rs index 5d5ca5bd5..7eea5a596 100644 --- a/der/src/asn1/bit_string.rs +++ b/der/src/asn1/bit_string.rs @@ -198,6 +198,145 @@ impl<'a> FixedTag for BitStringRef<'a> { const TAG: Tag = Tag::BitString; } +/// Owned form of ASN.1 `BIT STRING` type. +/// +/// This type provides the same functionality as [`BitStringRef`] but owns the +/// backing data. +#[cfg(feature = "alloc")] +#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] +#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] +pub struct BitString { + /// 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 BitString { + /// Maximum number of unused bits allowed. + pub const MAX_UNUSED_BITS: u8 = 7; + + /// Create a new ASN.1 `BIT STRING` from a byte slice. + /// + /// Accepts an optional number of "unused bits" (0-7) which are omitted + /// from the final octet. This number is 0 if the value is octet-aligned. + pub fn new(unused_bits: u8, bytes: impl Into>) -> Result { + let inner = bytes.into(); + + // Ensure parameters parse successfully as a `BitStringRef`. + let bit_length = BitStringRef::new(unused_bits, &inner)?.bit_length; + + Ok(BitString { + unused_bits, + bit_length, + inner, + }) + } + + /// Create a new ASN.1 `BIT STRING` from the given bytes. + /// + /// The "unused bits" are set to 0. + pub fn from_bytes(bytes: &[u8]) -> Result { + Self::new(0, bytes) + } + + /// Get the number of unused bits in the octet serialization of this + /// `BIT STRING`. + pub fn unused_bits(&self) -> u8 { + self.unused_bits + } + + /// Is the number of unused bits a value other than 0? + pub fn has_unused_bits(&self) -> bool { + self.unused_bits != 0 + } + + /// Get the length of this `BIT STRING` in bits. + pub fn bit_len(&self) -> usize { + self.bit_length + } + + /// Is the inner byte slice empty? + pub fn is_empty(&self) -> bool { + self.inner.is_empty() + } + + /// Borrow the inner byte slice. + /// + /// Returns `None` if the number of unused bits is *not* equal to zero, + /// i.e. if the `BIT STRING` is not octet aligned. + /// + /// Use [`BitString::raw_bytes`] to obtain access to the raw value + /// regardless of the presence of unused bits. + pub fn as_bytes(&self) -> Option<&[u8]> { + if self.has_unused_bits() { + None + } else { + Some(self.raw_bytes()) + } + } + + /// Borrow the raw bytes of this `BIT STRING`. + pub fn raw_bytes(&self) -> &[u8] { + self.inner.as_slice() + } + + /// Iterator over the bits of this `BIT STRING`. + pub fn bits(&self) -> BitStringIter<'_> { + BitStringRef::from(self).bits() + } +} + +#[cfg(feature = "alloc")] +impl<'a> DecodeValue<'a> for BitString { + fn decode_value>(reader: &mut R, header: Header) -> Result { + let inner_len = (header.length - Length::ONE)?; + let unused_bits = reader.read_byte()?; + let inner = reader.read_vec(inner_len)?; + Self::new(unused_bits, inner) + } +} + +#[cfg(feature = "alloc")] +impl EncodeValue for BitString { + 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 BitString { + const TAG: Tag = Tag::BitString; +} + +#[cfg(feature = "alloc")] +impl<'a> From<&'a BitString> for BitStringRef<'a> { + fn from(bit_string: &'a BitString) -> BitStringRef<'a> { + // Ensured to parse successfully in constructor + BitStringRef::new(bit_string.unused_bits, &bit_string.inner).expect("invalid BIT STRING") + } +} + +#[cfg(feature = "alloc")] +impl ValueOrd for BitString { + 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), + } + } +} + /// Iterator over the bits of a [`BitString`]. pub struct BitStringIter<'a> { /// [`BitString`] being iterated over. @@ -302,95 +441,6 @@ 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 BitString { - /// 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 BitString { - /// 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 BitString { - 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 > BitStringRef::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 BitString { - 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 BitString { - const TAG: Tag = Tag::BitString; -} - -#[cfg(feature = "alloc")] -impl ValueOrd for BitString { - 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::{BitStringRef, Result, Tag}; diff --git a/der/src/asn1/octet_string.rs b/der/src/asn1/octet_string.rs index 9c4f59f35..9f78c9608 100644 --- a/der/src/asn1/octet_string.rs +++ b/der/src/asn1/octet_string.rs @@ -5,7 +5,10 @@ use crate::{ FixedTag, Header, Length, Reader, Result, Tag, Writer, }; -/// ASN.1 `OCTET STRING` type. +#[cfg(feature = "alloc")] +use alloc::vec::Vec; + +/// ASN.1 `OCTET STRING` type: borrowed form. /// /// Octet strings represent contiguous sequences of octets, a.k.a. bytes. /// @@ -94,3 +97,83 @@ impl<'a> From> for &'a [u8] { octet_string.as_bytes() } } + +/// ASN.1 `OCTET STRING` type: owned form.. +/// +/// Octet strings represent contiguous sequences of octets, a.k.a. bytes. +/// +/// This type provides the same functionality as [`OctetStringRef`] but owns +/// the backing data. +#[cfg(feature = "alloc")] +#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] +#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] +pub struct OctetString { + /// Bitstring represented as a slice of bytes. + inner: Vec, +} + +#[cfg(feature = "alloc")] +impl OctetString { + /// Create a new ASN.1 `OCTET STRING`. + pub fn new(bytes: impl Into>) -> Result { + let inner = bytes.into(); + + // Ensure the bytes parse successfully as an `OctetStringRef` + OctetStringRef::new(&inner)?; + + Ok(Self { inner }) + } + + /// Borrow the inner byte slice. + pub fn as_bytes(&self) -> &[u8] { + self.inner.as_slice() + } + + /// Get the length of the inner byte slice. + pub fn len(&self) -> Length { + self.value_len().expect("invalid OCTET STRING length") + } + + /// Is the inner byte slice empty? + pub fn is_empty(&self) -> bool { + self.inner.is_empty() + } +} + +#[cfg(feature = "alloc")] +impl AsRef<[u8]> for OctetString { + fn as_ref(&self) -> &[u8] { + self.as_bytes() + } +} + +#[cfg(feature = "alloc")] +impl<'a> DecodeValue<'a> for OctetString { + fn decode_value>(reader: &mut R, header: Header) -> Result { + Self::new(reader.read_vec(header.length)?) + } +} + +#[cfg(feature = "alloc")] +impl EncodeValue for OctetString { + fn value_len(&self) -> Result { + self.inner.len().try_into() + } + + fn encode_value(&self, writer: &mut dyn Writer) -> Result<()> { + writer.write(&self.inner) + } +} + +#[cfg(feature = "alloc")] +impl FixedTag for OctetString { + const TAG: Tag = Tag::OctetString; +} + +#[cfg(feature = "alloc")] +impl<'a> From<&'a OctetString> for OctetStringRef<'a> { + fn from(octet_string: &'a OctetString) -> OctetStringRef<'a> { + // Ensured to parse successfully in constructor + OctetStringRef::new(&octet_string.inner).expect("invalid OCTET STRING") + } +} diff --git a/der/src/asn1/utf8_string.rs b/der/src/asn1/utf8_string.rs index 2899df5d5..9f7a1bc15 100644 --- a/der/src/asn1/utf8_string.rs +++ b/der/src/asn1/utf8_string.rs @@ -173,6 +173,14 @@ impl<'a> TryFrom> for String { } } +#[cfg(feature = "alloc")] +#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] +impl<'a> DecodeValue<'a> for String { + fn decode_value>(reader: &mut R, header: Header) -> Result { + Ok(String::from_utf8(reader.read_vec(header.length)?)?) + } +} + #[cfg(feature = "alloc")] #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] impl EncodeValue for String { diff --git a/der/src/lib.rs b/der/src/lib.rs index f4f651d16..d569287f4 100644 --- a/der/src/lib.rs +++ b/der/src/lib.rs @@ -39,26 +39,25 @@ //! - [`f64`]: ASN.1 `REAL` (gated on `real` crate feature) //! - [`str`], [`String`][`alloc::string::String`]: ASN.1 `UTF8String`. //! `String` requires `alloc` feature. See also [`Utf8StringRef`]. -//! Requires `alloc` feature. See also [`SetOf`]. //! - [`Option`]: ASN.1 `OPTIONAL`. //! - [`SystemTime`][`std::time::SystemTime`]: ASN.1 `GeneralizedTime`. Requires `std` feature. //! - [`Vec`][`alloc::vec::Vec`]: ASN.1 `SEQUENCE OF`. Requires `alloc` feature. //! - `[T; N]`: ASN.1 `SEQUENCE OF`. See also [`SequenceOf`]. //! //! The following ASN.1 types provided by this crate also impl these traits: -//! - [`AnyRef`]: ASN.1 `ANY` -//! - [`BitStringRef`]: ASN.1 `BIT STRING` -//! - [`GeneralizedTime`]: ASN.1 `GeneralizedTime` -//! - [`Ia5StringRef`]: ASN.1 `IA5String` -//! - [`Null`]: ASN.1 `NULL` -//! - [`ObjectIdentifier`]: ASN.1 `OBJECT IDENTIFIER` -//! - [`OctetStringRef`]: ASN.1 `OCTET STRING` -//! - [`PrintableStringRef`]: ASN.1 `PrintableString` (ASCII subset) -//! - [`SequenceOf`]: ASN.1 `SEQUENCE OF` -//! - [`SetOf`], [`SetOfVec`]: ASN.1 `SET OF` -//! - [`UIntRef`]: ASN.1 unsigned `INTEGER` with raw access to encoded bytes -//! - [`UtcTime`]: ASN.1 `UTCTime` -//! - [`Utf8StringRef`]: ASN.1 `UTF8String` +//! - [`Any`], [`AnyRef`]: ASN.1 `ANY`. +//! - [`BitString`], [`BitStringRef`]: ASN.1 `BIT STRING` +//! - [`GeneralizedTime`]: ASN.1 `GeneralizedTime`. +//! - [`Ia5StringRef`]: ASN.1 `IA5String`. +//! - [`Null`]: ASN.1 `NULL`. +//! - [`ObjectIdentifier`]: ASN.1 `OBJECT IDENTIFIER`. +//! - [`OctetString`], [`OctetStringRef`]: ASN.1 `OCTET STRING`. +//! - [`PrintableStringRef`]: ASN.1 `PrintableString` (ASCII subset). +//! - [`SequenceOf`]: ASN.1 `SEQUENCE OF`. +//! - [`SetOf`], [`SetOfVec`]: ASN.1 `SET OF`. +//! - [`UIntRef`]: ASN.1 unsigned `INTEGER` with raw access to encoded bytes. +//! - [`UtcTime`]: ASN.1 `UTCTime`. +//! - [`Utf8StringRef`]: ASN.1 `UTF8String`. //! //! Context specific fields can be modeled using these generic types: //! - [`ContextSpecific`]: decoder/encoder for owned context-specific fields @@ -315,14 +314,17 @@ //! [A Layman's Guide to a Subset of ASN.1, BER, and DER]: https://luca.ntop.org/Teaching/Appunti/asn1.html //! [A Warm Welcome to ASN.1 and DER]: https://letsencrypt.org/docs/a-warm-welcome-to-asn1-and-der/ //! +//! [`Any`]: asn1::AnyRef //! [`AnyRef`]: asn1::AnyRef //! [`ContextSpecific`]: asn1::ContextSpecific //! [`ContextSpecificRef`]: asn1::ContextSpecificRef +//! [`BitString`]: asn1::BitStringRef //! [`BitStringRef`]: asn1::BitStringRef //! [`GeneralizedTime`]: asn1::GeneralizedTime //! [`Ia5StringRef`]: asn1::Ia5StringRef //! [`Null`]: asn1::Null //! [`ObjectIdentifier`]: asn1::ObjectIdentifier +//! [`OctetString`]: asn1::OctetStringRef //! [`OctetStringRef`]: asn1::OctetStringRef //! [`PrintableStringRef`]: asn1::PrintableStringRef //! [`SequenceOf`]: asn1::SequenceOf diff --git a/der/src/reader.rs b/der/src/reader.rs index a1297769d..23ac5f190 100644 --- a/der/src/reader.rs +++ b/der/src/reader.rs @@ -12,6 +12,9 @@ use crate::{ Result, Tag, TagMode, TagNumber, }; +#[cfg(feature = "alloc")] +use alloc::vec::Vec; + /// Reader trait which reads DER-encoded input. pub trait Reader<'r>: Sized { /// Get the length of the input. @@ -130,6 +133,15 @@ pub trait Reader<'r>: Sized { reader.finish(ret) } + /// Read a byte vector of the given length. + #[cfg(feature = "alloc")] + #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] + fn read_vec(&mut self, len: Length) -> Result> { + let mut bytes = vec![0u8; usize::try_from(len)?]; + self.read_into(&mut bytes)?; + Ok(bytes) + } + /// Get the number of bytes still remaining in the buffer. fn remaining_len(&self) -> Length { debug_assert!(self.position() <= self.input_len());