Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up

A Safe #[no_std] ASN.1 Codec Framework

License

Unknown, MIT licenses found

Licenses found

Unknown
LICENCE-APACHE
MIT
LICENCE-MIT
NotificationsYou must be signed in to change notification settings

librasn/rasn

Repository files navigation

crates.ioHelp WantedLines Of CodeDocumentationBenchmarks

Welcome torasn (pronounced "raisin"), a safe#[no_std] ASN.1 codec framework.That 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.

Therasn compiler can be used to generaterasn bindings for ASN.1 modules.

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 withrasn'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, or your own custom encoding.

#[no_std] Support

Rasn is entirely#[no_std], so you can share the same ASN.1 implementation on any Rust target platform that can supportalloc.

Rich Data Types

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

Safe Codecs

The encoder and decoder have been written in 100% safe Rust and fuzzed withAmerican Fuzzy Lop Plus Plus to ensure that the decoder correctly handles random input, and if valid that the encoder can correctly re-encode that value.

Supported Codecs

  • Basic Encoding Rules (BER)
  • Canonical Encoding Rules (CER)
  • Distinguished Encoding Rules (DER)
  • Aligned Packed Encoding Rules (APER)
  • Unaligned Packed Encoding Rules (UPER)
  • JSON Encoding Rules (JER)
  • Octet Encoding Rules (OER)
  • Canonical Octet Encoding Rules (COER)
  • XML Encoding Rules (XER)

RFC implementations

Rasn also provides implementations for a number of IETF RFCs using therasnframework for use out of the box. These crates provide strongly typeddefinitions for the necessary data types. Likerasn they are#[no_std],as well as being transport layer and encoding rule agnostic.

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 atcompile-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 tomisuse. The most common mistakes are around handling the length and ensuring it's correctly encoded and decoded. Inrasn this is completely abstracted away letting you focus on the abstract model. Let's look at what decoding a simple customSEQUENCE type looks like.

Person::=SEQUENCE {  ageINTEGER,  name UTF8String}

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

structPerson{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 andEncode for converting to and from encoding rules, and the sharedAsnType 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.

#structPerson;use rasn::{AsnType, types::Tag};implAsnTypeforPerson{// Default tag for sequences.constTAG:Tag =Tag::SEQUENCE;}

Next is theDecode andEncode 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 associatedAsnType::TAG. Let's look at what the codec implementation ofPerson looks like.

#use rasn::{AsnType, types::{Constructed, fields::{Field,Fields}}};#structPerson{name:Utf8String,age:Integer}#implAsnTypeforPerson{#constTAG:Tag =Tag::SEQUENCE;#constIDENTIFIER:Identifier =Identifier(Some("Person"));#}#implConstructed<2,0>forPerson{#constFIELDS:Fields<2> =Fields::from_static([#Field::new_required(0,Utf8String::TAG,Utf8String::TAG_TREE,"age"),#Field::new_required(1,Integer::TAG,Integer::TAG_TREE,"name"),#]);#}use rasn::{prelude::*, types::{Integer,Utf8String}};implDecodeforPerson{fndecode_with_tag_and_constraints<D:Decoder>(decoder:&mutD,tag:Tag,constraints:Constraints) ->Result<Self,D::Error>{// Accepts a closure that decodes the contents of the sequence.        decoder.decode_sequence(tag,None::<fn() ->Self>, |decoder|{let age =Integer::decode(decoder)?;let name =Utf8String::decode(decoder)?;Ok(Self{ age, name})})}}implEncodeforPerson{fnencode_with_tag_and_constraints<'encoder,E:Encoder<'encoder>>(&self,encoder:&mutE,tag:Tag,constraints:Constraints,identifier:Identifier) ->Result<(),E::Error>{// Accepts a closure that encodes the contents of the sequence.        encoder.encode_sequence::<2,0,Self,_>(tag, |encoder|{self.age.encode(encoder)?;self.name.encode(encoder)?;Ok(())}, identifier)?;Ok(())}}

That's it! We've just created a new ASN.1 that can 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 at ourEncode implementation, what if we forgot to use the encoder we were given inencode_sequence and tried 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 closure124 | |             self.name.encode(sequence)?;125 | |             Ok(())126 | |         })?;    | |__________^ second borrow occurs hereerror[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 here123 |             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 anEncoder 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 is0, 2). Field and variant names are not transmitted in most encoding rules, so this tag is also used to identify fields or variants in aSEQUENCE orCHOICE. This means that in a ASN.1 struct or enum every field and variantmust have a distinct tag for the whole type to be considered valid. For example ; If we changedage inPerson to be aString like below it would be invalid ASN.1 even though it compiles and runs correctly, we have to either use a different type or overrideage's tag to be distinct fromname's. When implementing theAsnType trait yourself this requirement must be checked 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. TheEncode andDecode macros will essentially auto-generate the implementations we showed earlier, but the real magic is theAsnType derive macro. Thanks to thestatic-assertations crate and recent developments inconst fn; theAsnType derive will not only generate yourAsnType implementation, it will also generate a check that asserts that every field or variant has a distinct tag atcompile-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: Option<String>,    name: Option<String>,}

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

error[E0080]: evaluation of constant value failed   --> tests/derive.rs:146:10    |146 | #[derive(rasn::AsnType)]    |          ^^^^^^^^^^^^^ the evaluated program panicked at 'Person's fields is not a valid order of ASN.1 tags, ensure that your field's tags and OPTIONALs are correct.', tests/derive.rs:146:10    |    = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info)

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 havingAUTOMATIC 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 will apply the same automatic tagging transformation you'd expect from an ASN.1 compiler.

use rasn::AsnType;// Valid#[derive(AsnType)]structPerson{#[rasn(tag(context,0))]// or just #[rasn(tag(0))]age:Option<String>,name:Option<String>,}// Also valid#[derive(AsnType)]#[rasn(automatic_tags)]structPerson2{age:Option<String>,name:Option<String>,}

Reference

The following table provides a range of examples showing how to declare data types withrasn.

ASN1 rasn
Type alias
Test-type-b::=BOOLEANTest-type-a::= Test-type-b
// eitheruse rasn::prelude::*;typeTestTypeB =bool;typeTestTypeA =TestTypeB;
// oruse rasn::prelude::*;#[derive(AsnType,Decode,Encode)]#[rasn(delegate)]structTestTypeB(pubbool);/// or#[derive(AsnType,Decode,Encode)]#[rasn(delegate)]structTestTypeA(pubTestTypeB);
BOOLEAN type
Test-type-a::=BOOLEAN
// eitheruse rasn::prelude::*;#[derive(AsnType,Decode,Encode)]#[rasn(delegate)]structTestTypeA(pubbool);
// oruse rasn::prelude::*;typeTestTypeA =bool;
NULL type
Test-type-a::=NULL
// eitheruse rasn::prelude::*;#[derive(AsnType,Decode,Encode)]structTestTypeA;
// oruse rasn::prelude::*;#[derive(AsnType,Decode,Encode)]#[rasn(delegate)]structTestTypeA(());
// oruse rasn::prelude::*;typeTestTypeA =();
INTEGER type
Test-type-a::=INTEGER
use rasn::prelude::*;// either#[derive(AsnType,Decode,Encode)]#[rasn(delegate)]structTestTypeA(pubu8/* or any other rust integer type */);
// oruse rasn::prelude::*;#[derive(AsnType,Decode,Encode)]#[rasn(delegate)]structTestTypeA(pubInteger);
// oruse rasn::prelude::*;typeTestTypeA =Integer;
// oruse rasn::prelude::*;typeTestTypeA =u8;// or any other rust integer type
Single-value contraint
Test-type-a::=INTEGER (8)
// eitheruse rasn::prelude::*;#[derive(AsnType,Decode,Encode)]#[rasn(delegate, value("8"))]structTestTypeA(pubu8);
// oruse rasn::prelude::*;#[derive(AsnType,Decode,Encode)]#[rasn(delegate, value("8"))]structTestTypeA(pubInteger);
Value range constraint
Test-type-a::=INTEGER (-8..360)Test-type-b::=INTEGER (MIN..360)Test-type-c::=INTEGER (42..MAX)
use rasn::prelude::*;/// of course a primitive rust integer would still work in these examples#[derive(AsnType,Decode,Encode)]#[rasn(delegate, value("-8..=360"))]structTestTypeA(pubInteger);#[derive(AsnType,Decode,Encode)]#[rasn(delegate, value("..=360"))]structTestTypeB(pubInteger);#[derive(AsnType,Decode,Encode)]#[rasn(delegate, value("42..="))]structTestTypeC(pubInteger);
Extensible value constraint
Test-type-a::=INTEGER (42,...)Test-type-b::=INTEGER (1..360,...)
use rasn::prelude::*;/// of course a primitive rust integer would still work in these examples#[derive(AsnType,Decode,Encode)]#[rasn(delegate, value("42", extensible))]structTestTypeA(pubInteger);#[derive(AsnType,Decode,Encode)]#[rasn(delegate, value("1..=360", extensible))]structTestTypeB(pubInteger);
ENUMERATED type
Test-type-a::=ENUMERATED { seed, grape, raisin }
use rasn::prelude::*;#[derive(AsnType,Decode,Encode,Copy,Clone,PartialEq,Debug)]#[rasn(enumerated, automatic_tags)]/// See belowenumTestTypeA{Seed,Grape,Raisin}
Extensible ENUMERATED type
Test-type-a::=ENUMERATED { seed, grape,..., raisin }
use rasn::prelude::*;#[derive(AsnType,Decode,Encode,Copy,Clone,PartialEq,Debug)]#[rasn(enumerated, automatic_tags)]/// See below#[non_exhaustive]enumTestTypeA{Seed,Grape,#[rasn(extension_addition)]Raisin}
AUTOMATIC TAGS environment
TestModuleDEFINITIONSAUTOMATIC TAGS::=BEGINTest-type-a::=ENUMERATED { seed, grape, raisin }Test-type-b::=ENUMERATED { juice, wine, grappa }END
use rasn::prelude::*;/// The tagging encironment has to be declared for every rasn-annotated struct or enum/// There is no implicit extensibility#[derive(AsnType,Decode,Encode,Copy,Clone,PartialEq,Debug)]#[rasn(enumerated, automatic_tags)]enumTestTypeB{Juice,Wine,Grappa}#[derive(AsnType,Decode,Encode,Copy,Clone,PartialEq,Debug)]#[rasn(enumerated, automatic_tags)]enumTestTypeA{Seed,Grape,Raisin}
EXPLICIT TAGS environment
TestModuleDEFINITIONSEXPLICIT TAGS::=BEGINTest-type-a::= [APPLICATION 1]ENUMERATED { seed, grape, raisin }Test-type-b::= [APPLICATION 2]ENUMERATED { juice, wine, grappa }END
use rasn::prelude::*;/// The tagging encironment has to be declared for every rasn-annotated struct or enum/// There is no implicit extensibility#[derive(AsnType,Decode,Encode,Copy,Clone,PartialEq,Debug)]#[rasn(enumerated, tag(explicit(application,1)))]enumTestTypeB{#[rasn(tag(explicit(0)))]Juice,#[rasn(tag(explicit(1)))]Wine,#[rasn(tag(explicit(2)))]Grappa}#[derive(AsnType,Decode,Encode,Copy,Clone,PartialEq,Debug)]#[rasn(enumerated, tag(explicit(application,2)))]enumTestTypeA{#[rasn(tag(explicit(0)))]Seed,#[rasn(tag(explicit(1)))]Grape,#[rasn(tag(explicit(2)))]Raisin}
IMPLICIT TAGS environment
TestModuleDEFINITIONSIMPLICIT TAGS::=BEGINTest-type-a::= [APPLICATION 1]ENUMERATED { seed, grape, raisin }Test-type-b::= [APPLICATION 2]ENUMERATED { juice, wine, grappa }END
use rasn::prelude::*;/// The tagging encironment has to be declared for every rasn-annotated struct or enum/// There is no implicit extensibility#[derive(AsnType,Decode,Encode,Copy,Clone,PartialEq,Debug)]#[rasn(enumerated, tag(application,1))]enumTestTypeB{Juice =0,Wine =1,Grappa =2}#[derive(AsnType,Decode,Encode,Copy,Clone,PartialEq,Debug)]#[rasn(enumerated, tag(application,2))]enumTestTypeA{Seed =0,Grape =1,Raisin =2}
CHOICE type
Test-type-a::=CHOICE {    seedBOOLEAN,    grapeBIT STRINGSIZE(1,...),    raisinOCTET STRING}Test-type-b::=CHOICE {    juiceINTEGER (0..3,...),    wineOCTET STRING,...,    grappaINTEGER}
use rasn::prelude::*;#[derive(AsnType,Decode,Encode)]#[rasn(choice, automatic_tags)]enumTestTypeA{Seed(bool),#[rasn(size("1", extensible))]Grape(BitString),Raisin(OctetString)}#[derive(AsnType,Decode,Encode)]#[rasn(choice, automatic_tags)]#[non_exhaustive]enumTestTypeB{#[rasn(value("0..3", extensible))]Juice(Integer),Wine(OctetString),#[rasn(extension_addition)]Grappa(Integer)}
SEQUENCE type
Test-type-a::=SEQUENCE {    juiceINTEGER (0..3,...),    wineOCTET STRING,...,    grappaINTEGEROPTIONAL,    waterBIT STRING (SIZE(1))OPTIONAL}
use rasn::prelude::*;#[derive(AsnType,Decode,Encode)]#[rasn(automatic_tags)]#[non_exhaustive]structTestTypeA{#[rasn(value("0..3", extensible))]juice:Integer,wine:OctetString,#[rasn(extension_addition)]grappa:Option<Integer>,#[rasn(extension_addition, size("1"))]water:Option<BitString>}
SET type
Test-type-a::=SET {    seedNULL,    grapeBOOLEAN,    raisinINTEGER}
use rasn::prelude::*;/// the SET declaration is basically identical to a SEQUENCE declaration,/// except for the `set` annotation#[derive(AsnType,Decode,Encode)]#[rasn(set, automatic_tags)]structTestTypeA{seed:(),grape:bool,raisin:Integer}
Renaming fields
Test-type-a::=SEQUENCE {    notQuiteRustCaseINTEGER}
use rasn::prelude::*;#[derive(AsnType,Decode,Encode)]#[rasn(automatic_tags, identifier ="Test-type-a")]structTestTypeA{#[rasn(identifier ="notQuiteRustCase")]rust_case_indeed:Integer}
OPTIONAL and DEFAULT fields
Test-type-a::=SEQUENCE {    seedBOOLEAN DEFAULT TRUE,    grapeINTEGEROPTIONAL,    raisinINTEGER DEFAULT 1}
use rasn::prelude::*;/// DEFAULTs are provided via linked helper functions#[derive(AsnType,Decode,Encode)]#[rasn(automatic_tags)]structTestTypeA{#[rasn(default ="default_seed")]seed:bool,grape:Option<Integer>,#[rasn(default ="default_raisin")]raisin:Integer}fndefault_seed() ->bool{true}fndefault_raisin() ->Integer{1.into()}
SEQUENCE OF type
Test-type-a::=SEQUENCEOFBOOLEANTest-type-b::=SEQUENCEOFINTEGER(1,...)
use rasn::prelude::*;#[derive(AsnType,Decode,Encode)]#[rasn(delegate)]structTestTypeA(pubSequenceOf<bool>);/// Constrained inner primitive types need to be wrapped in a helper newtype#[derive(AsnType,Decode,Encode)]#[rasn(delegate, value("1", extensible))]structInnerTestTypeB(pubInteger);#[derive(AsnType,Decode,Encode)]#[rasn(delegate)]structTestTypeB(pubSequenceOf<InnerTestTypeB>);
Character string types
Test-type-a::= UTF8String
use rasn::prelude::*;/// the other charater types supported by rasn behave exactly the same:/// NumericString, VisibleString, Ia5String, TeletexString, GeneralString, BmpString, PrintableString/// (and also for BIT STRING and OCTET STRING)#[derive(AsnType,Decode,Encode)]#[rasn(delegate)]structTestTypeA(pubUtf8String);
BIT STRING type
Test-type-a::=BIT STRING
use rasn::prelude::*;#[derive(AsnType,Decode,Encode)]#[rasn(delegate)]structTestTypeA(pubBitString);
OCTET STRING type
Test-type-a::=OCTET STRING
use rasn::prelude::*;#[derive(AsnType,Decode,Encode)]#[rasn(delegate)]structTestTypeA(pubOctetString);
Size contraint
Test-type-a::= UTF8String (SIZE (42,...))Test-type-b::=SEQUENCE (SIZE (1..8))OFBOOLEAN
use rasn::prelude::*;/// The size constraint definition behaves similar to the value definition (see above)#[derive(AsnType,Decode,Encode)]#[rasn(delegate, size("42", extensible))]structTestTypeA(pubUtf8String);#[derive(AsnType,Decode,Encode)]#[rasn(delegate, size("1..=8"))]structTestTypeB(pubSequenceOf<bool>);
Permitted alphabet contraint
Test-type-a::= UTF8String (FROM ("A".."Z"))
use rasn::prelude::*;#[derive(AsnType,Decode,Encode)]#[rasn(delegate, from("\u{0041}..\u{005A}"))]structTestTypeA(pubUtf8String);

Sponsorship

This project was funded through theNGI Assure Fund, a fund established by NLnet with financial support from the European Commission's Next Generation Internet programme, under the aegis of DG Communications Networks, Content and Technology under grant agreement No 957073.

Disclaimer

The software is provided "as is" and the authors disclaim all warranties with regard to this software including all implied warranties of merchant-ability and fitness. In no event shall the authors be liable for any special, direct, indirect, or consequential damages or any damages whatsoever resulting from loss of use, data or profits, whether in an action of contract, negligence or other tortuous action, arising out of or in connection with the use or performance of this software.


[8]ページ先頭

©2009-2025 Movatter.jp