Skip to content

Rasn 0.4

Compare
Choose a tag to compare
@XAMPPRocky XAMPPRocky released this 28 Sep 10:04
· 350 commits to main since this release

Hello everyone, I'm proud to announce the 0.4.0 version of rasn! rasn is a #[no_std] codec framework for the ASN.1 data model and encoding rules. Allowing you to safely and easily parse ASN.1 data formats such as BER, CER, and DER in safe Rust.

Sponsorship

Work on rasn is partially funded through the support of contributors on GitHub Sponsors. If you would like support work on rasn, tokei, and/or my other open source projects, contributions are always welcome, appreciated, and make a direct impact on my ability to build new projects as well as support existing ones.

New Compile-Time Validation

0.4 comes with a new more complete compile-time validation for types that derive Decode and Encode. Previously the validation only looked one-level deep. Now however we have a new type (TagTree) for representing and validating ASN.1 tags beyond a single type level entirely at const time. How it works is actually pretty straightforward, with the enums shown below, we construct a tree of values, and thanks to Rust’s powerful const we can add a compile-time check that ensures this tree is always unique.

// rasn::types::TagTree
enum TagTree {
    Leaf(Tag),
    Choice(&'static [TagTree]),
}

#[derive(AsnType)]
#[rasn(choice)]
enum Message {
    Int(Integer),
    String(Utf8String),
    List(SequenceOf<Message>),
}

// #[derive(AsnType)] generates…

impl AsnType for Message {
   const TAG: Tag = Tag::CHOICE;
   const TAG_TREE: TagTree = TagTree::Choice(&[
      Integer::TAG_TREE,
      Utf8String::TAG_TREE,
      <SequenceOf<Message>>::TAG_TREE,
   ]);
}

static_assertions::const_assert!(Message::TAG_TREE.is_unique());

SET and ANY Support

This release also adds support for two of the less frequently used types in ASN.1. SET is similar to SEQUENCE except that its fields can be encoded out-of-order (except some formats require it to be encoded by ascending tag [except some formats do not encode the tag so you need to know the order in the type system.])

These exceptions make it incredibly difficult to do correctly, especially across different codecs. Now however with rasn adding this to structures is a single line change if you're using the derive macros. It's also relatively straightforward to implement by hand, with encoding being the same as encoding sequences except for calling encode_set which will behind the scenes ensure that fields are always encoded in the correct order as specified by the encoding rules. For decoding, we require a bit more code, since you can't rely on what order the fields come in. Luckily it's pretty easy to represent this problem using a CHOICE enum for the fields of the SET.

use rasn::prelude::*;

#[derive(AsnType, Encode, Decode)]
#[rasn(set)]
struct Set {
    age: Integer,
    name: Utf8String,
}

// Generates...

impl Encode for Set {
    fn encode_with_tag<EN: rasn::Encoder>(
        &self,
        encoder: &mut EN,
        tag: rasn::Tag,
    ) -> Result<(), EN::Error> {
        // Will always be encoded as `age, name`
        // even if `name.encode` is called first. 
        encoder.encode_set(tag, |encoder| {
            self.age.encode(encoder)?;
            self.name.encode(encoder)?;
            Ok(())
        })?;

         Ok(())
    }
}

impl Decode for Set {
    fn decode_with_tag<D: Decoder>(
        decoder: &mut D, 
        tag: Tag
    ) -> Result<Self, D::Error> {
        #[derive(AsnType, Encode, Decode)]
        #[rasn(choice)]
        enum SetFields {
            Age(Integer),
            Name(String),
        }

        decoder.decode_set::<SetFields, _, _>(tag, |fields| {
            let mut age = None;
            let mut name = None;

            for field in fields {
                match field {
                    SetFields::Age(value) => {
                       if age.replace(value).is_some() {
                            return Err(rasn::de::Error::duplicate_field("age"));
                        }
                    }
                    SetFields::Name(value) => {
                        if name.replace(value).is_some() {
                            return Err(rasn::de::Error::duplicate_field("name"));
                        }
                    }
                }
            }

            let age = age.ok_or_else(|| rasn::de::Error::missing_field("age"))?;
            let name = name.ok_or_else(|| rasn::de::Error::missing_field("name"))?;
            Ok(Self { age, name })
        })
    }
}

The ANYtype is technically deprecated and removed from the current ASN.1 syntax, however it is still used some important specifications and can be used for open type fields.

New LDAPv3 and PKIX crates

With the addition of support for those new types, it is now possible to support both IETF RFC 4511 and IETF RFC 5280 also known as LDAPv3 and PKIX. Like the other standards in the rasn repository, these are not implementations of servers or clients for these standards, but rather solely the data types used in the protocol, and this means it's easy for them to be no_std compatible, codec independent, and sans I/O.

// [dependencies] = rasn, pem, rasn-pkix
use rasn_pkix::Certificate;

let pem_file = pem::parse(std::fs::read("./x509.pem")?)?;
let certificate: Certificate = rasn::der::decode(&pem_file.contents)?;

println!("{}", certificate.tbs_certificate.version);

It's important to note these crates don’t currently implement any non-BER visible constraints on the data types, so not all of these types guarantee a semantically valid message, but they do always ensure that a type is always correctly encoded and decodable. The PKIX crate also currently follows RFC 5280's syntax for ease of implementation of the core certificate types, but will move to be closer to RFC 5912's syntax in the future.

Other Improvements

  • Added a prelude module to rasn that exports the codec traits as well as all builtin rasn types to make it easier to import everything needed when creating an ASN.1 module with rasn.
  • Added SNMPv3 support to rasn-snmp.
  • Improved error messages in proc macro decoding.

Contributors