Skip to content

der: add macro for APPLICATION, CONTEXT-SPECIFIC and PRIVATE tags #1819

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions der/src/asn1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
mod internal_macros;

mod any;
mod application;
pub(crate) mod bit_string;
#[cfg(feature = "alloc")]
mod bmp_string;
Expand All @@ -21,6 +22,7 @@ mod octet_string;
mod oid;
mod optional;
mod printable_string;
mod private;
#[cfg(feature = "real")]
mod real;
mod sequence;
Expand All @@ -33,6 +35,7 @@ mod videotex_string;

pub use self::{
any::AnyRef,
application::{Application, ApplicationRef},
bit_string::{BitStringIter, BitStringRef},
choice::Choice,
context_specific::{ContextSpecific, ContextSpecificRef},
Expand All @@ -43,6 +46,7 @@ pub use self::{
null::Null,
octet_string::OctetStringRef,
printable_string::PrintableStringRef,
private::{Private, PrivateRef},
sequence::{Sequence, SequenceRef},
sequence_of::{SequenceOf, SequenceOfIter},
set_of::{SetOf, SetOfIter},
Expand Down
14 changes: 14 additions & 0 deletions der/src/asn1/application.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//! Application class field.

use crate::{
Choice, Class, Decode, DecodeValue, DerOrd, Encode, EncodeValue, EncodeValueRef, Error, Header,
Length, Reader, Tag, TagMode, TagNumber, Tagged, ValueOrd, Writer, asn1::AnyRef,
tag::IsConstructed,
};
use core::cmp::Ordering;

#[cfg(doc)]
use crate::ErrorKind;

impl_custom_class!(Application, Application, "APPLICATION", "0b01000000");
impl_custom_class_ref!(ApplicationRef, Application, "APPLICATION", "0b01000000");
267 changes: 15 additions & 252 deletions der/src/asn1/context_specific.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,258 +7,21 @@ use crate::{
};
use core::cmp::Ordering;

/// Context-specific field which wraps an owned inner value.
///
/// This type decodes/encodes a field which is specific to a particular context
/// and is identified by a [`TagNumber`].
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub struct ContextSpecific<T> {
/// Context-specific tag number sans the leading `0b10000000` class
/// identifier bit and `0b100000` constructed flag.
pub tag_number: TagNumber,

/// Tag mode: `EXPLICIT` VS `IMPLICIT`.
pub tag_mode: TagMode,

/// Value of the field.
pub value: T,
}

impl<T> ContextSpecific<T> {
/// Attempt to decode an `EXPLICIT` ASN.1 `CONTEXT-SPECIFIC` field with the
/// provided [`TagNumber`].
///
/// This method has the following behavior which decodes tag numbers one by one
/// in extension fields, which are denoted in an ASN.1 schema using
/// the `...` ellipsis extension marker:
///
/// - Returns `Ok(None)` if anything other than a [`ContextSpecific`] field
/// is encountered.
/// - Returns `Ok(None)` if a [`ContextSpecific`] field with a different tag
/// number is encountered. These fields are not consumed in this case.
/// - Returns `Err(ErrorKind::Noncanonical)` if constructed bit is primitive.
/// - Returns `Ok(Some(..))` if tag number matches.
pub fn decode_explicit<'a, R: Reader<'a>>(
reader: &mut R,
tag_number: TagNumber,
) -> Result<Option<Self>, T::Error>
where
T: Decode<'a>,
{
if !Tag::peek_matches(reader, Class::ContextSpecific, tag_number)? {
return Ok(None);
}
Ok(Some(Self::decode(reader)?))
}

/// Attempt to decode an `IMPLICIT` ASN.1 `CONTEXT-SPECIFIC` field with the
/// provided [`TagNumber`].
///
/// This method otherwise behaves the same as `decode_explicit`,
/// but should be used in cases where the particular fields are `IMPLICIT`
/// as opposed to `EXPLICIT`.
///
/// Differences from `EXPLICIT`:
/// - Returns `Err(ErrorKind::Noncanonical)` if constructed bit
/// does not match constructed bit of the base encoding.
pub fn decode_implicit<'a, R: Reader<'a>>(
reader: &mut R,
tag_number: TagNumber,
) -> Result<Option<Self>, T::Error>
where
T: DecodeValue<'a> + IsConstructed,
{
// Peek tag number
if !Tag::peek_matches(reader, Class::ContextSpecific, tag_number)? {
return Ok(None);
}
// Decode IMPLICIT header
let header = Header::decode(reader)?;

// read_nested checks if header matches decoded length
let value = reader.read_nested(header.length, |reader| {
// Decode inner IMPLICIT value
T::decode_value(reader, header)
})?;

// the encoding shall be constructed if the base encoding is constructed
if header.tag.is_constructed() != T::CONSTRUCTED {
return Err(header.tag.non_canonical_error().into());
}

Ok(Some(Self {
tag_number,
tag_mode: TagMode::Implicit,
value,
}))
}
}

impl<'a, T> Choice<'a> for ContextSpecific<T>
where
T: Decode<'a> + Tagged,
{
fn can_decode(tag: Tag) -> bool {
tag.is_context_specific()
}
}

impl<'a, T> Decode<'a> for ContextSpecific<T>
where
T: Decode<'a>,
{
type Error = T::Error;

fn decode<R: Reader<'a>>(reader: &mut R) -> Result<Self, Self::Error> {
// Decode EXPLICIT header
let header = Header::decode(reader)?;

// encoding shall be constructed
if !header.tag.is_constructed() {
return Err(header.tag.non_canonical_error().into());
}
match header.tag {
Tag::ContextSpecific { number, .. } => Ok(Self {
tag_number: number,
tag_mode: TagMode::default(),
value: reader.read_nested(header.length, |reader| {
// Decode inner tag-length-value of EXPLICIT
T::decode(reader)
})?,
}),
tag => Err(tag.unexpected_error(None).into()),
}
}
}

impl<T> EncodeValue for ContextSpecific<T>
where
T: EncodeValue + Tagged,
{
fn value_len(&self) -> Result<Length, Error> {
match self.tag_mode {
TagMode::Explicit => self.value.encoded_len(),
TagMode::Implicit => self.value.value_len(),
}
}

fn encode_value(&self, writer: &mut impl Writer) -> Result<(), Error> {
match self.tag_mode {
TagMode::Explicit => self.value.encode(writer),
TagMode::Implicit => self.value.encode_value(writer),
}
}
}

impl<T> Tagged for ContextSpecific<T>
where
T: Tagged,
{
fn tag(&self) -> Tag {
let constructed = match self.tag_mode {
// ISO/IEC 8825-1:2021
// 8.14.3 If implicit tagging (see Rec. ITU-T X.680 | ISO/IEC 8824-1, 31.2.7) was not used in the definition of the type, the
// encoding shall be constructed and the contents octets shall be the complete base encoding [Encode].
TagMode::Explicit => true,

// ISO/IEC 8825-1:2021
// 8.14.4 If implicit tagging was used in the definition of the type, then:
// a) the encoding shall be constructed if the base encoding is constructed, and shall be primitive otherwise; and
// b) the contents octets shall be the same as the contents octets [EncodeValue] of the base encoding.
//
// TODO(dishmaker): use IsConstructed trait for IMPLICIT
TagMode::Implicit => self.value.tag().is_constructed(),
};

Tag::ContextSpecific {
number: self.tag_number,
constructed,
}
}
}

impl<'a, T> TryFrom<AnyRef<'a>> for ContextSpecific<T>
where
T: Decode<'a>,
{
type Error = T::Error;

fn try_from(any: AnyRef<'a>) -> Result<ContextSpecific<T>, Self::Error> {
match any.tag() {
Tag::ContextSpecific {
number,
constructed: true,
} => Ok(Self {
tag_number: number,
tag_mode: TagMode::default(),
value: T::from_der(any.value())?,
}),
tag => Err(tag.unexpected_error(None).into()),
}
}
}

impl<T> ValueOrd for ContextSpecific<T>
where
T: EncodeValue + ValueOrd + Tagged,
{
fn value_cmp(&self, other: &Self) -> Result<Ordering, Error> {
match self.tag_mode {
TagMode::Explicit => self.der_cmp(other),
TagMode::Implicit => self.value_cmp(other),
}
}
}

/// Context-specific field reference.
///
/// This type encodes a field which is specific to a particular context
/// and is identified by a [`TagNumber`].
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub struct ContextSpecificRef<'a, T> {
/// Context-specific tag number sans the leading `0b10000000` class
/// identifier bit and `0b100000` constructed flag.
pub tag_number: TagNumber,

/// Tag mode: `EXPLICIT` VS `IMPLICIT`.
pub tag_mode: TagMode,

/// Value of the field.
pub value: &'a T,
}

impl<'a, T> ContextSpecificRef<'a, T> {
/// Convert to a [`ContextSpecific`].
fn encoder(&self) -> ContextSpecific<EncodeValueRef<'a, T>> {
ContextSpecific {
tag_number: self.tag_number,
tag_mode: self.tag_mode,
value: EncodeValueRef(self.value),
}
}
}

impl<T> EncodeValue for ContextSpecificRef<'_, T>
where
T: EncodeValue + Tagged,
{
fn value_len(&self) -> Result<Length, Error> {
self.encoder().value_len()
}

fn encode_value(&self, writer: &mut impl Writer) -> Result<(), Error> {
self.encoder().encode_value(writer)
}
}

impl<T> Tagged for ContextSpecificRef<'_, T>
where
T: Tagged,
{
fn tag(&self) -> Tag {
self.encoder().tag()
}
}
#[cfg(doc)]
use crate::ErrorKind;

impl_custom_class!(
ContextSpecific,
ContextSpecific,
"CONTEXT-SPECIFIC",
"0b10000000"
);
impl_custom_class_ref!(
ContextSpecificRef,
ContextSpecific,
"CONTEXT-SPECIFIC",
"0b10000000"
);

#[cfg(test)]
#[allow(clippy::unwrap_used)]
Expand Down
Loading