Skip to content

Commit

Permalink
Setup Basic Encoding Rules with DER flavor
Browse files Browse the repository at this point in the history
  • Loading branch information
kellerkindt committed Jan 20, 2024
1 parent 8a40e78 commit df953d4
Show file tree
Hide file tree
Showing 8 changed files with 576 additions and 0 deletions.
10 changes: 10 additions & 0 deletions asn1rs-model/src/asn/tag.rs
Expand Up @@ -73,6 +73,16 @@ impl Tag {
pub const DEFAULT_UNIVERSAL_STRING: Tag = Tag::Universal(28);
/// ITU-T Rec. X.680, 41
pub const DEFAULT_BMP_STRING: Tag = Tag::Universal(30);

#[inline]
pub fn value(self) -> usize {
match self {
Tag::Universal(value) => value,
Tag::Application(value) => value,
Tag::ContextSpecific(value) => value,
Tag::Private(value) => value,
}
}
}

impl<T: Iterator<Item = Token>> TryFrom<&mut Peekable<T>> for Tag {
Expand Down
103 changes: 103 additions & 0 deletions src/protocol/basic/distinguished/mod.rs
@@ -0,0 +1,103 @@
#![allow(clippy::unusual_byte_groupings)]

use crate::protocol::basic::err::Error;
use crate::protocol::basic::{BasicRead, BasicWrite};
use crate::rw::{BasicReader, BasicWriter};
use asn1rs_model::asn::Tag;
use std::io::{Read, Write};

pub type DER = DistinguishedEncodingRules;
pub struct DistinguishedEncodingRules;

impl DistinguishedEncodingRules {
#[inline]
pub fn writer<W: BasicWrite<Flavor = Self>>(write: W) -> BasicWriter<W> {
BasicWriter::from(write)
}

#[inline]
pub fn reader<W: BasicRead<Flavor = Self>>(read: W) -> BasicReader<W> {
BasicReader::from(read)
}
}

const CLASS_BITS_MASK: u8 = 0b_11_000000;
const CLASS_BITS_UNIVERSAL: u8 = 0b_00_000000;
const CLASS_BITS_APPLICATION: u8 = 0b_01_000000;
const CLASS_BITS_CONTEXT_SPECIFIC: u8 = 0b_10_000000;
const CLASS_BITS_PRIVATE: u8 = 0b_11_000000;

const LENGTH_MASK: u8 = 0b_11_000000;
const LENGTH_SHORT_FORM: u8 = 0b_01_000000;

impl<T: Read> BasicRead for T {
type Flavor = DistinguishedEncodingRules;

fn read_identifier(&mut self) -> Result<Tag, Error> {
let mut byte = [0x00];
self.read_exact(&mut byte[..])?;
let class = byte[0] & CLASS_BITS_MASK;
let value = byte[0] & !CLASS_BITS_MASK;
// TODO assumption: number contains the primitive / constructed flag
// TODO assumption: number not greater than the octets remaining bits
Ok(match class {
CLASS_BITS_UNIVERSAL => Tag::Universal(usize::from(value)),
CLASS_BITS_APPLICATION => Tag::Application(usize::from(value)),
CLASS_BITS_CONTEXT_SPECIFIC => Tag::ContextSpecific(usize::from(value)),
CLASS_BITS_PRIVATE => Tag::Private(usize::from(value)),
_ => unreachable!(),
})
}

#[inline]
fn read_length(&mut self) -> Result<usize, Error> {
let mut bytes = [0u8; 1];
self.read_exact(&mut bytes[..])?;
if bytes[0] & LENGTH_MASK == LENGTH_SHORT_FORM {
Ok(usize::from(bytes[0] & !LENGTH_MASK))
} else {
todo!()
}
}

#[inline]
fn read_boolean(&mut self) -> Result<bool, Error> {
let mut byte = [0u8; 1];
self.read_exact(&mut byte[..])?;
Ok(byte[0] != 0x00)
}
}

impl<T: Write> BasicWrite for T {
type Flavor = DistinguishedEncodingRules;

fn write_identifier(&mut self, tag: Tag) -> Result<(), Error> {
let mut identifier_octet: u8 = match tag {
Tag::Universal(_) => CLASS_BITS_UNIVERSAL,
Tag::Application(_) => CLASS_BITS_APPLICATION,
Tag::ContextSpecific(_) => CLASS_BITS_CONTEXT_SPECIFIC,
Tag::Private(_) => CLASS_BITS_PRIVATE,
};
// TODO assumption: number contains the primitive / constructed flag
// TODO assumption: number not greater than the octets remaining bits
identifier_octet |= tag.value() as u8;
Ok(self.write_all(&[identifier_octet])?)
}

#[inline]
fn write_length(&mut self, length: usize) -> Result<(), Error> {
let byte = if length < 64 {
// short form 8.1.3.4
LENGTH_SHORT_FORM | (length as u8)
} else {
// long form 8.1.3.5
todo!()
};
Ok(self.write_all(&[byte])?)
}

#[inline]
fn write_boolean(&mut self, value: bool) -> Result<(), Error> {
Ok(self.write_all(&[if value { 0x01 } else { 0x00 }])?)
}
}
100 changes: 100 additions & 0 deletions src/protocol/basic/err.rs
@@ -0,0 +1,100 @@
use asn1rs_model::asn::Tag;
use backtrace::Backtrace;
use std::fmt::{Debug, Display, Formatter};
use std::ops::Range;

pub struct Error(pub(crate) Box<Inner>);

impl Error {
#[inline]
pub fn kind(&self) -> &ErrorKind {
&self.0.kind
}

#[cold]
#[inline(never)]
pub fn unexpected_tag(expected: Tag, got: Tag) -> Self {
Self::from(ErrorKind::UnexpectedTypeTag { expected, got })
}

#[cold]
#[inline(never)]
pub fn unexpected_length(expected: Range<usize>, got: usize) -> Self {
Self::from(ErrorKind::UnexpectedTypeLength { expected, got })
}
}

impl From<ErrorKind> for Error {
#[inline]
fn from(kind: ErrorKind) -> Self {
Error(Box::new(Inner::from(kind)))
}
}

impl From<std::io::Error> for Error {
#[inline]
fn from(e: std::io::Error) -> Self {
Self::from(ErrorKind::IoError(e))
}
}

impl Debug for Error {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
Display::fmt(self, f)
}
}

impl Display for Error {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
writeln!(f, "{}", self.0.kind)?;
let mut backtrace = self.0.backtrace.clone();
backtrace.resolve();
writeln!(f, "{backtrace:?}")
}
}

impl std::error::Error for Error {
fn description(&self) -> &str {
"encoding or decoding with basic rules failed"
}
}

#[derive(Debug)]
pub(crate) struct Inner {
pub(crate) kind: ErrorKind,
pub(crate) backtrace: Backtrace,
}

impl From<ErrorKind> for Inner {
#[inline]
fn from(kind: ErrorKind) -> Self {
Self {
kind,
backtrace: Backtrace::new_unresolved(),
}
}
}

#[derive(Debug)]
pub enum ErrorKind {
UnexpectedTypeTag { expected: Tag, got: Tag },
UnexpectedTypeLength { expected: Range<usize>, got: usize },
IoError(std::io::Error),
}

impl Display for ErrorKind {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
ErrorKind::UnexpectedTypeTag { expected, got } => {
write!(f, "Expected tag {expected:?} but got {got:?}")
}
ErrorKind::UnexpectedTypeLength { expected, got } => {
write!(f, "Expected length in range {expected:?} but got {got:?}")
}
ErrorKind::IoError(e) => {
write!(f, "Experienced underlying IO error: {e:?}")
}
}
}
}
44 changes: 44 additions & 0 deletions src/protocol/basic/mod.rs
@@ -0,0 +1,44 @@
//! This module contains defines traits to encode and decode basic ASN.1 primitives and types of
//! the basic family (BER, DER, CER).

mod distinguished;
mod err;

pub use distinguished::*;
pub use err::Error;

use asn1rs_model::asn::Tag;

/// According to ITU-T X.690
pub trait BasicRead {
type Flavor;

/// According to ITU-T X.690, chapter 8.1.2, an identifier octet contains the class and number
/// of the type.
fn read_identifier(&mut self) -> Result<Tag, Error>;

/// According to ITU-T X.690, chapter 8.1.3, the length is encoded in at least one byte, in
/// either the short (8.1.3.4) or long (8.1.3.5) form
fn read_length(&mut self) -> Result<usize, Error>;

/// According to ITU-T X.690, chapter 8.2, the boolean type is represented in a single byte,
/// where 0 represents `false` and any other value represents `true`.
fn read_boolean(&mut self) -> Result<bool, Error>;
}

/// According to ITU-T X.690
pub trait BasicWrite {
type Flavor;

/// According to ITU-T X.690, chapter 8.1.2, an identifier octet contains the class and number
/// of the type.
fn write_identifier(&mut self, tag: Tag) -> Result<(), Error>;

/// According to ITU-T X.690, chapter 8.1.3, the length is encoded in at least one byte, in
/// either the short (8.1.3.4) or long (8.1.3.5) form
fn write_length(&mut self, length: usize) -> Result<(), Error>;

/// According to ITU-T X.690, chapter 8.2, the boolean type is represented in a single byte,
/// where 0 represents `false` and any other value represents `true`.
fn write_boolean(&mut self, value: bool) -> Result<(), Error>;
}
1 change: 1 addition & 0 deletions src/protocol/mod.rs
Expand Up @@ -6,6 +6,7 @@
//! ::io::... Other ASN.1 representations (e.g der, xer, ber, ...)
//! ```

pub mod basic;
pub mod per;
#[cfg(feature = "protobuf")]
pub mod protobuf;

0 comments on commit df953d4

Please sign in to comment.