Skip to content

Releases: librasn/rasn

rasn-derive-v0.6.1

11 Jul 14:35
3f30cc6
Compare
Choose a tag to compare

Other

  • Add constraints to PKIX
  • clippy
  • Implement Unpacked Encoding Rules (UPER)

rasn-cap-v0.8.0

11 Jul 15:12
Compare
Choose a tag to compare

Other

  • Use workspace metadata
  • Implement Unpacked Encoding Rules (UPER)

v0.5.1

Rasn 0.5

23 Dec 14:01
Compare
Choose a tag to compare

Hello everyone, I'm proud to announce the 0.5 version of rasn! rasn is a code-first #[no_std] codec framework for ASN.1 & Rust. Rasn make it easy to model ASN.1 schemas and support multiple encoding rules, allowing you to safely and easily parse ASN.1 data formats such as BER, CER, and DER in 100% 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 RFC Implementations

The main feature of this release is the addition several new crates and support for several RFC implementations, including support for CMS, S/MIME, CMS, and Kerberos authentication. Like the other core crates, these are the data types for the creating messages for these protocols, these crates, like rasn are #[no_std], and completely I/O and transport protocol agnostic. Check out the documentation for more information on the specific crates.

Funding Announcement

While not technically part of this release, I'm also proud to announce that rasn has received a grant from the NGI Assure Fund established by the NLnet foundation to add support for constraints, Packed Encoding Rules (PER) (both aligned and unaligned variants), and support for the Common Alerting Protocol. The addition of these features will make rasn one of the only open source ASN.1 libraries that allow to write a single model that supports for both X.690 and X.691, and open the possibility for supporting a variety protocols used in mission-critical systems. Support for these features will be added in the coming months, if you'd also like to help support the development of features, GitHub Sponsors are always welcome and appreciated.

Minor Changes

Contributors

Rasn 0.4

28 Sep 10:04
Compare
Choose a tag to compare

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

rasn 0.3.0

18 Jul 19:30
Compare
Choose a tag to compare

Hello everyone, I'm proud to announce the 0.3.0 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 data formats such as BER, CER, and DER in safe Rust.

Sponsorship

Work on rasn is funded through the support of contributors on GitHub Sponsors. If you would like support work on rasn, tokei, 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 support existing ones.

New Crates

This release was focused on building implementations of standards that use these formats as their underlying protocol, which is why I'm also happy to announce the release of three new sibling crates, rasn-smi, rasn-mib, and rasn-snmp. These crates provide the types needed to be able to decode and encode Simple Network Management Protocol (SNMP) messages, SMI object syntax, and MIB objects. It's important to note that this does not contain a implementation of an SNMP proxy or manager, but provides the data types needed to build one. Like rasn, these crates are entirely #[no_std] and are transport-layer and encoding rule agnostic written in 100% safe Rust.

As an example, let's say we wanted to store our SNMPv2c messages containing SNMPv2 Protocol Data Units (PDUs), but we wanted to ensure when is saved it is encoded in DER so that it is "canonical" (meaning the same value always produces the same encoding), but we need to be able to accept messages in BER. Here's how you'd write that with rasn.

use rasn_smi::{v2c::Message, v2::Pdus};

// The SNMP data we received from the wire.
let data = &[...];

// Decode BER message.
let message: Message<Pdus> = rasn::ber::decode(data)?;

// Re-encode it as DER.
let der_data = rasn::der::encode(&message)?;

That's it! All the complexity in encoding and decoding is completely abstracted by the types, letting us focus on the actual implementation of our agent.

With this release, the initial set of Management Information Base (MIB) objects covers the most of what was defined in RFC 3418. Contributions adding more MIB objects from IETF RFCs are both welcome and appreciated.

New Additions

  • Added support for all [T; N] array types for AsnType, Decode, and Encode.
  • Added the Oid type which is reference type equivalent for ObjectIdentifier, and ConstOid, which allows you to define object identifiers in const contexts.
  • Added #[rasn(delegate)] container attribute which is accepted on single value newtype structs (e.g. struct Foo(u8);). This will cause the implementation of of any of the derive macros to directly encode and decode the inner type, and to ignore the wrapper.
  • Added #[rasn(choice)] field attribute which specifies to decode a given field as a CHOICE type.
  • Added #[rasn(default)] field attribute which will use the Default value if it cannot parse the given field.
  • Added the Incomplete error variant to de::Error that provides how many more bytes are needed to potentially decode the message.
  • Added the ExceedsMaxLength error variant to de::Error that provides an error when a list-like type (SEQUENCE OF) is encoded correctly but it's length exceeds the maximum allowed by the type.

Breaking Changes

  • Decoder::decode_sequence, no longer returns a Deocder and instead accepts a FnOnce(Decoder) -> Result<Self> function to decode the sequence. This is needed in order to correctly handle nested indefinite length sequences in CER.

Announcement

05 Sep 22:06
Compare
Choose a tag to compare

Introduction

Welcome to the first release of rasn (pronounced "raisin"), a new safe #[no_std] ASN.1 codec framework. Rasn enables you to safely create, share, and handle ASN.1 data types from and to different encoding rules. If you are unfamiliar with ASN.1 and encoding formats like BER/DER, I would recommend reading "A Warm Welcome to ASN.1 and DER" by Let's Encrypt as a quick introduction before continuing. In short it is an Interface Description Language (and data model) with a set of encoding formats (called rules) for that model. It was originally designed in the late 1980s and is used throughout the industry especially in telecommunications and cryptography.

Sponsorship

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

Features

Abstract Codec Data Model

There are quite a few existing ASN.1 related Rust crates already, however they are currently specific to a single format or even a single standard, this makes it hard to share and re-use standards that are specified in ASN.1. Now with rasn's abstract model you can build and share ASN.1 data types as crates that work with any encoder or decoder regardless of the underlying encoding rules, whether it's BER, CER, DER, our your own custom encoding.

#[no_std] Support

Rasn is entirely #[no_std], so you can handle and share the same ASN.1 code with a wide variety of platforms and devices.

Rich Data Types

Rasn currently has support for nearly all of ASN.1's data types. rasn uses popular community libraries such as bitvec, bytes, and chrono for some of its data types as well as providing a couple of its own. Check out the types module for what's currently available.

Safe BER, CER, and DER Codecs

Included with the framework is a implementation of the X.690 standard also known as the Basic Encoding Rules, Canonical Encoding Rules, and Distinguished Encoding Rules codecs. The encoder and decoder have been written in 100% safe Rust and fuzzed with American Fuzzy Lop to ensure that the decoder correctly handles random input, and if valid that the encoder can correctly re-encode that value.

Powerful Derive Macros

Easily model your structs and enums with derive equivalents of all of the traits. These macros provide a automatic implementation that ensures your model is a valid ASN.1 type at compile-time. To explain that though, first we have to explain…

How It Works

The codec API has been designed for ease of use, safety, and being hard to misuse. The most common mistakes are around handling the length and ensuring it's correctly encoded and decoded. In rasn this is completely abstracted away letting you focus on the abstract model. Let's look at what decoding a simple custom SEQUENCE type looks like.

Person ::= SEQUENCE {
  age INTEGER,
  name UTF8String
}

Which we want to map to the following equivalent Rust code.

struct Person {
    age: rasn::types::Integer,
    name: String, // or rasn::types::Utf8String
}

Implementing The Traits

When modelling an ASN.1 data type, there are three traits we'll need to implement. Decode and Encode for converting to and from encoding rules, and the shared AsnType trait; which defines some associated data needed to be given to the encoder and decoder. Currently the only thing we have define is the tag to use to identify our type.

use rasn::{AsnType, Tag};

impl AsnType for Person {
    // Default tag for sequences.
    const TAG: Tag = Tag::SEQUENCE;
} 

Next is the Decode and Encode traits. These are mirrors of each other and both have one provided method (decode/encode) and one required method (decode_with_tag/encode_with_tag). Since in ASN.1 nearly every type can be implicitly tagged allowing anyone to override the tag associated with the type, having *_with_tag as a required method requires the implementer to correctly handle this case, and the provided methods simply calls *_with_tag with the type's associated AsnType::TAG. Let's look at what the codec implementation of Person looks like.

use rasn::{Decode, Decoder, Encode, Encoder, Tag};

impl Decode for Person {
    fn decode_with_tag<D: Decoder>(decoder: &mut D, tag: Tag) -> Result<Self, D::Error> {
        // Returns a decoder that contains the data inside the sequence.
        let mut sequence = decoder.decode_sequence(tag)?;
        let age = Integer::decode(&mut sequence)?;
        let name = String::decode(&mut sequence)?;
        
        Ok(Self { age, name })
    }
}

impl Encode for Person {
    fn encode_with_tag<E: Encoder>(&self, encoder: &mut E, tag: Tag) -> Result<(), E::Error> {
        // Creates another Encoder and calls your closure with it.
        encoder.encode_sequence(tag, |sequence| {
            self.age.encode(sequence)?;
            self.name.encode(sequence)?;
            Ok(())
        })?;

        Ok(())
    }
}

That's it! We've just created a new ASN.1 that be encoded and decoded to BER, CER, and DER; and nowhere did we have to check the tag, the length, or whether the string was primitive or constructed encoded. All those nasty encoding rules details are completely abstracted away so your type only has handle how to map to and from ASN.1's data model. With all the actual conversion code isolated to the codec implementations you can know that your model is always safe to use. The API has also been designed to prevent you from making common logic errors that can lead to invalid encoding. For example; if we look back our Encode implementation, and what if we forgot to use the encoder we were given in encode_sequence and tired to use the parent instead?

error[E0501]: cannot borrow `*encoder` as mutable because previous closure requires unique access
   --> tests/derive.rs:122:9
    |
122 |           encoder.encode_sequence(tag, |sequence| {
    |           ^       ---------------      ---------- closure construction occurs here
    |           |       |
    |  _________|       first borrow later used by call
    | |
123 | |             self.age.encode(encoder)?;
    | |                             ------- first borrow occurs due to use of `encoder` in closure
124 | |             self.name.encode(sequence)?;
125 | |             Ok(())
126 | |         })?;
    | |__________^ second borrow occurs here

error[E0500]: closure requires unique access to `encoder` but it is already borrowed
   --> tests/derive.rs:122:38
    |
122 |         encoder.encode_sequence(tag, |sequence| {
    |         ------- ---------------      ^^^^^^^^^^ closure construction occurs here
    |         |       |
    |         |       first borrow later used by call
    |         borrow occurs here
123 |             self.age.encode(encoder)?;
    |                             ------- second borrow occurs due to use of `encoder` in closure

Our code fails to compile! Which, in this case is great, there's no chance that our contents will accidentally be encoded in the wrong sequence because we forgot to change the name of a variable. These ownership semantics also mean that an Encoder can't accidentally encode the contents of a sequence multiple times in their implementation. Let's see how we can try to take this even further.

Compile-Safe ASN.1 With Macros

So far we've shown how rasn's API takes steps to be safe and protect from accidentally creating an invalid model. However, it's often hard to cover everything in an imperative API. Something that is important to understand about ASN.1 that isn't obvious in the above examples is that; in ASN.1, all types can be identified by a tag (essentially two numbers e.g. INTEGER's tag is 0, 2). Field and variant names are not transmitted in most encoding rules, so this tag is also used to identify fields or variants in a SEQUENCE or CHOICE. This means that every that in a ASN.1 struct or enum every field and variant must have a distinct tag for the whole type to be considered valid. For example ; If we changed age in Person to be a String like below it would be invalid ASN.1 even though it compiles and runs correctly, we have to either use a different type or override age's tag to be distinct from name's. When implementing the AsnType trait yourself this requirement must checked by manually, however as we'll see you generally won't need to do that.

Included with rasn is a set of derive macros that enable you to have your ASN.1 model implementation implemented declaratively. The Encode and Decode macros will essentially auto-generate the implementations we showed earlier, but the real magic is the AsnType derive macro. Thanks to the static-assertations crate and recent developments in const fn; the AsnType derive will not only generate your AsnType implementation, it will also generate a check that asserts that every field or variant has a distinct tag at compile-time. This means now if for some reason we made a change to one of the types in person, we don't have re-check that our model is still valid, the compiler takes care of that for us.

// Invalid
#[derive(rasn::AsnType)]
struct Person {
    age: String,
    name: String,
}

We'll now get the following error trying to compile the above definition.

error[E0080]: evaluation of constant value failed
  --> t...
Read more