Crate rasn[−][src]
Rasn: A Safe #[no_std]
ASN.1 Codec Framework
Welcome to the rasn
(pronounced “raisin”), a 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.
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, types::Integer}; 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
Encodeimplementation, 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
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
Decodemacros 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
--> tests/derive.rs:80:14
|
80 | #[derive(AsnType, Debug, Default, Decode, Encode, PartialEq)]
| ^^^^^^^ attempt to compute `0_usize - 1_usize` which would overflow
|
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
While not the most obvious error message at the moment, validating your
model at compile-time enables you to work on ASN.1 code without fear that
you’re unintentionally changing something in the background. I bet you’re
wondering now though, how we are supposed to have a struct with two strings
for fields? The answer is thankfully pretty simple, you just add
#[rasn(tag)]
attribute to override the tags of one or more of the types.
However we can actually go further, because in ASN.1 there’s the concept of
having AUTOMATIC TAGS
which essentially tells your ASN.1 compiler to
automatically generate distinct tags for your ASN.1 definition. Now with
rasn you can do that in Rust! Applying #[rasn(automatic_tags)]
to the
container automatically generate tags will apply the same automatic
tagging transformation you’d expect from an ASN.1 compiler.
use rasn::AsnType;
// Valid
#[derive(AsnType)]
struct Person {
#[rasn(tag(context, 0)] // or just #[rasn(tag(0))]
age: String,
name: String,
}
// Also valid
#[derive(AsnType)]
#[rasn(automatic_tags)]
struct Person {
age: String,
name: String,
}
Modules
ber | Basic Encoding Rules |
cer | Canonical Encoding Rules |
de | Generic ASN.1 decoding framework. |
der | Distinguished Encoding Rules |
enc | Generic ASN.1 encoding framework. |
types | Types |
Structs
Tag | An abstract representation of the tag octets used in BER, CER, and DER to identify . |
Traits
AsnType | A trait representing any type that can represented in ASN.1. |
Decode | A data type that can decoded from any ASN.1 format. |
Decoder | A data format decode any ASN.1 data type. |
Encode | A data type that can be encoded to a ASN.1 data format. |
Encoder | A data format encode any ASN.1 data type. |
Derive Macros
AsnType | An automatic derive of the |
Decode | An automatic derive of the |
Encode | An automatic derive of the |