diff options
Diffstat (limited to 'src/header.rs')
-rw-r--r-- | src/header.rs | 490 |
1 files changed, 490 insertions, 0 deletions
diff --git a/src/header.rs b/src/header.rs new file mode 100644 index 0000000..c2b9aa5 --- /dev/null +++ b/src/header.rs @@ -0,0 +1,490 @@ +use crate::ber::*; +use crate::der_constraint_fail_if; +use crate::error::*; +#[cfg(feature = "std")] +use crate::ToDer; +use crate::{BerParser, Class, DerParser, DynTagged, FromBer, FromDer, Length, Tag, ToStatic}; +use alloc::borrow::Cow; +use core::convert::TryFrom; +use nom::bytes::streaming::take; + +/// BER/DER object header (identifier and length) +#[derive(Clone, Debug)] +pub struct Header<'a> { + /// Object class: universal, application, context-specific, or private + pub(crate) class: Class, + /// Constructed attribute: true if constructed, else false + pub(crate) constructed: bool, + /// Tag number + pub(crate) tag: Tag, + /// Object length: value if definite, or indefinite + pub(crate) length: Length, + + /// Optionally, the raw encoding of the tag + /// + /// This is useful in some cases, where different representations of the same + /// BER tags have different meanings (BER only) + pub(crate) raw_tag: Option<Cow<'a, [u8]>>, +} + +impl<'a> Header<'a> { + /// Build a new BER/DER header from the provided values + pub const fn new(class: Class, constructed: bool, tag: Tag, length: Length) -> Self { + Header { + tag, + constructed, + class, + length, + raw_tag: None, + } + } + + /// Build a new BER/DER header from the provided tag, with default values for other fields + #[inline] + pub const fn new_simple(tag: Tag) -> Self { + let constructed = matches!(tag, Tag::Sequence | Tag::Set); + Self::new(Class::Universal, constructed, tag, Length::Definite(0)) + } + + /// Set the class of this `Header` + #[inline] + pub fn with_class(self, class: Class) -> Self { + Self { class, ..self } + } + + /// Set the constructed flags of this `Header` + #[inline] + pub fn with_constructed(self, constructed: bool) -> Self { + Self { + constructed, + ..self + } + } + + /// Set the tag of this `Header` + #[inline] + pub fn with_tag(self, tag: Tag) -> Self { + Self { tag, ..self } + } + + /// Set the length of this `Header` + #[inline] + pub fn with_length(self, length: Length) -> Self { + Self { length, ..self } + } + + /// Update header to add reference to raw tag + #[inline] + pub fn with_raw_tag(self, raw_tag: Option<Cow<'a, [u8]>>) -> Self { + Header { raw_tag, ..self } + } + + /// Return the class of this header. + #[inline] + pub const fn class(&self) -> Class { + self.class + } + + /// Return true if this header has the 'constructed' flag. + #[inline] + pub const fn constructed(&self) -> bool { + self.constructed + } + + /// Return the tag of this header. + #[inline] + pub const fn tag(&self) -> Tag { + self.tag + } + + /// Return the length of this header. + #[inline] + pub const fn length(&self) -> Length { + self.length + } + + /// Return the raw tag encoding, if it was stored in this object + #[inline] + pub fn raw_tag(&self) -> Option<&[u8]> { + self.raw_tag.as_ref().map(|cow| cow.as_ref()) + } + + /// Test if object is primitive + #[inline] + pub const fn is_primitive(&self) -> bool { + !self.constructed + } + + /// Test if object is constructed + #[inline] + pub const fn is_constructed(&self) -> bool { + self.constructed + } + + /// Return error if class is not the expected class + #[inline] + pub const fn assert_class(&self, class: Class) -> Result<()> { + self.class.assert_eq(class) + } + + /// Return error if tag is not the expected tag + #[inline] + pub const fn assert_tag(&self, tag: Tag) -> Result<()> { + self.tag.assert_eq(tag) + } + + /// Return error if object is not primitive + #[inline] + pub const fn assert_primitive(&self) -> Result<()> { + if self.is_primitive() { + Ok(()) + } else { + Err(Error::ConstructUnexpected) + } + } + + /// Return error if object is primitive + #[inline] + pub const fn assert_constructed(&self) -> Result<()> { + if !self.is_primitive() { + Ok(()) + } else { + Err(Error::ConstructExpected) + } + } + + /// Test if object class is Universal + #[inline] + pub const fn is_universal(&self) -> bool { + self.class as u8 == Class::Universal as u8 + } + /// Test if object class is Application + #[inline] + pub const fn is_application(&self) -> bool { + self.class as u8 == Class::Application as u8 + } + /// Test if object class is Context-specific + #[inline] + pub const fn is_contextspecific(&self) -> bool { + self.class as u8 == Class::ContextSpecific as u8 + } + /// Test if object class is Private + #[inline] + pub const fn is_private(&self) -> bool { + self.class as u8 == Class::Private as u8 + } + + /// Return error if object length is definite + #[inline] + pub const fn assert_definite(&self) -> Result<()> { + if self.length.is_definite() { + Ok(()) + } else { + Err(Error::DerConstraintFailed(DerConstraint::IndefiniteLength)) + } + } + + /// Get the content following a BER header + #[inline] + pub fn parse_ber_content<'i>(&'_ self, i: &'i [u8]) -> ParseResult<'i, &'i [u8]> { + // defaults to maximum depth 8 + // depth is used only if BER, and length is indefinite + BerParser::get_object_content(i, self, 8) + } + + /// Get the content following a DER header + #[inline] + pub fn parse_der_content<'i>(&'_ self, i: &'i [u8]) -> ParseResult<'i, &'i [u8]> { + self.assert_definite()?; + DerParser::get_object_content(i, self, 8) + } +} + +impl From<Tag> for Header<'_> { + #[inline] + fn from(tag: Tag) -> Self { + let constructed = matches!(tag, Tag::Sequence | Tag::Set); + Self::new(Class::Universal, constructed, tag, Length::Definite(0)) + } +} + +impl<'a> ToStatic for Header<'a> { + type Owned = Header<'static>; + + fn to_static(&self) -> Self::Owned { + let raw_tag: Option<Cow<'static, [u8]>> = + self.raw_tag.as_ref().map(|b| Cow::Owned(b.to_vec())); + Header { + tag: self.tag, + constructed: self.constructed, + class: self.class, + length: self.length, + raw_tag, + } + } +} + +impl<'a> FromBer<'a> for Header<'a> { + fn from_ber(bytes: &'a [u8]) -> ParseResult<Self> { + let (i1, el) = parse_identifier(bytes)?; + let class = match Class::try_from(el.0) { + Ok(c) => c, + Err(_) => unreachable!(), // Cannot fail, we have read exactly 2 bits + }; + let (i2, len) = parse_ber_length_byte(i1)?; + let (i3, len) = match (len.0, len.1) { + (0, l1) => { + // Short form: MSB is 0, the rest encodes the length (which can be 0) (8.1.3.4) + (i2, Length::Definite(usize::from(l1))) + } + (_, 0) => { + // Indefinite form: MSB is 1, the rest is 0 (8.1.3.6) + // If encoding is primitive, definite form shall be used (8.1.3.2) + if el.1 == 0 { + return Err(nom::Err::Error(Error::ConstructExpected)); + } + (i2, Length::Indefinite) + } + (_, l1) => { + // if len is 0xff -> error (8.1.3.5) + if l1 == 0b0111_1111 { + return Err(::nom::Err::Error(Error::InvalidLength)); + } + let (i3, llen) = take(l1)(i2)?; + match bytes_to_u64(llen) { + Ok(l) => { + let l = + usize::try_from(l).or(Err(::nom::Err::Error(Error::InvalidLength)))?; + (i3, Length::Definite(l)) + } + Err(_) => { + return Err(::nom::Err::Error(Error::InvalidLength)); + } + } + } + }; + let constructed = el.1 != 0; + let hdr = Header::new(class, constructed, Tag(el.2), len).with_raw_tag(Some(el.3.into())); + Ok((i3, hdr)) + } +} + +impl<'a> FromDer<'a> for Header<'a> { + fn from_der(bytes: &'a [u8]) -> ParseResult<Self> { + let (i1, el) = parse_identifier(bytes)?; + let class = match Class::try_from(el.0) { + Ok(c) => c, + Err(_) => unreachable!(), // Cannot fail, we have read exactly 2 bits + }; + let (i2, len) = parse_ber_length_byte(i1)?; + let (i3, len) = match (len.0, len.1) { + (0, l1) => { + // Short form: MSB is 0, the rest encodes the length (which can be 0) (8.1.3.4) + (i2, Length::Definite(usize::from(l1))) + } + (_, 0) => { + // Indefinite form is not allowed in DER (10.1) + return Err(::nom::Err::Error(Error::DerConstraintFailed( + DerConstraint::IndefiniteLength, + ))); + } + (_, l1) => { + // if len is 0xff -> error (8.1.3.5) + if l1 == 0b0111_1111 { + return Err(::nom::Err::Error(Error::InvalidLength)); + } + // DER(9.1) if len is 0 (indefinite form), obj must be constructed + der_constraint_fail_if!( + &i[1..], + len.1 == 0 && el.1 != 1, + DerConstraint::NotConstructed + ); + let (i3, llen) = take(l1)(i2)?; + match bytes_to_u64(llen) { + Ok(l) => { + // DER: should have been encoded in short form (< 127) + // XXX der_constraint_fail_if!(i, l < 127); + let l = + usize::try_from(l).or(Err(::nom::Err::Error(Error::InvalidLength)))?; + (i3, Length::Definite(l)) + } + Err(_) => { + return Err(::nom::Err::Error(Error::InvalidLength)); + } + } + } + }; + let constructed = el.1 != 0; + let hdr = Header::new(class, constructed, Tag(el.2), len).with_raw_tag(Some(el.3.into())); + Ok((i3, hdr)) + } +} + +impl DynTagged for (Class, bool, Tag) { + fn tag(&self) -> Tag { + self.2 + } +} + +#[cfg(feature = "std")] +impl ToDer for (Class, bool, Tag) { + fn to_der_len(&self) -> Result<usize> { + let (_, _, tag) = self; + match tag.0 { + 0..=30 => Ok(1), + t => { + let mut sz = 1; + let mut val = t; + loop { + if val <= 127 { + return Ok(sz + 1); + } else { + val >>= 7; + sz += 1; + } + } + } + } + } + + fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> { + let (class, constructed, tag) = self; + let b0 = (*class as u8) << 6; + let b0 = b0 | if *constructed { 0b10_0000 } else { 0 }; + if tag.0 > 30 { + let b0 = b0 | 0b1_1111; + let mut sz = writer.write(&[b0])?; + let mut val = tag.0; + loop { + if val <= 127 { + sz += writer.write(&[val as u8])?; + return Ok(sz); + } else { + let b = (val & 0b0111_1111) as u8 | 0b1000_0000; + sz += writer.write(&[b])?; + val >>= 7; + } + } + } else { + let b0 = b0 | (tag.0 as u8); + let sz = writer.write(&[b0])?; + Ok(sz) + } + } + + fn write_der_content(&self, _writer: &mut dyn std::io::Write) -> SerializeResult<usize> { + Ok(0) + } +} + +impl DynTagged for Header<'_> { + fn tag(&self) -> Tag { + self.tag + } +} + +#[cfg(feature = "std")] +impl ToDer for Header<'_> { + fn to_der_len(&self) -> Result<usize> { + let tag_len = (self.class, self.constructed, self.tag).to_der_len()?; + let len_len = self.length.to_der_len()?; + Ok(tag_len + len_len) + } + + fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> { + let sz = (self.class, self.constructed, self.tag).write_der_header(writer)?; + let sz = sz + self.length.write_der_header(writer)?; + Ok(sz) + } + + fn write_der_content(&self, _writer: &mut dyn std::io::Write) -> SerializeResult<usize> { + Ok(0) + } + + fn write_der_raw(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> { + // use raw_tag if present + let sz = match &self.raw_tag { + Some(t) => writer.write(t)?, + None => (self.class, self.constructed, self.tag).write_der_header(writer)?, + }; + let sz = sz + self.length.write_der_header(writer)?; + Ok(sz) + } +} + +/// Compare two BER headers. `len` fields are compared only if both objects have it set (same for `raw_tag`) +impl<'a> PartialEq<Header<'a>> for Header<'a> { + fn eq(&self, other: &Header) -> bool { + self.class == other.class + && self.tag == other.tag + && self.constructed == other.constructed + && { + if self.length.is_null() && other.length.is_null() { + self.length == other.length + } else { + true + } + } + && { + // it tag is present for both, compare it + if self.raw_tag.as_ref().xor(other.raw_tag.as_ref()).is_none() { + self.raw_tag == other.raw_tag + } else { + true + } + } + } +} + +impl Eq for Header<'_> {} + +#[cfg(test)] +mod tests { + use crate::*; + use hex_literal::hex; + + /// Generic tests on methods, and coverage tests + #[test] + fn methods_header() { + // Getters + let input = &hex! {"02 01 00"}; + let (rem, header) = Header::from_ber(input).expect("parsing header failed"); + assert_eq!(header.class(), Class::Universal); + assert_eq!(header.tag(), Tag::Integer); + assert!(header.assert_primitive().is_ok()); + assert!(header.assert_constructed().is_err()); + assert!(header.is_universal()); + assert!(!header.is_application()); + assert!(!header.is_private()); + assert_eq!(rem, &input[2..]); + + // test PartialEq + let hdr2 = Header::new_simple(Tag::Integer); + assert_eq!(header, hdr2); + + // builder methods + let hdr3 = hdr2 + .with_class(Class::ContextSpecific) + .with_constructed(true) + .with_length(Length::Definite(1)); + assert!(hdr3.constructed()); + assert!(hdr3.is_constructed()); + assert!(hdr3.assert_constructed().is_ok()); + assert!(hdr3.is_contextspecific()); + let xx = hdr3.to_der_vec().expect("serialize failed"); + assert_eq!(&xx, &[0xa2, 0x01]); + + // indefinite length + let hdr4 = hdr3.with_length(Length::Indefinite); + assert!(hdr4.assert_definite().is_err()); + let xx = hdr4.to_der_vec().expect("serialize failed"); + assert_eq!(&xx, &[0xa2, 0x80]); + + // parse_*_content + let hdr = Header::new_simple(Tag(2)).with_length(Length::Definite(1)); + let (_, r) = hdr.parse_ber_content(&input[2..]).unwrap(); + assert_eq!(r, &input[2..]); + let (_, r) = hdr.parse_der_content(&input[2..]).unwrap(); + assert_eq!(r, &input[2..]); + } +} |