Rasn 0.4
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 ANY
type 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 torasn
that exports the codec traits as well as all builtinrasn
types to make it easier to import everything needed when creating an ASN.1 module withrasn
. - Added SNMPv3 support to
rasn-snmp
. - Improved error messages in proc macro decoding.