Skip to content

Encoding

Sylvain Daubert edited this page Mar 11, 2022 · 5 revisions

DER encoding

RASN.1 can only encode ASN.1 data to DER encoding. But, as DER encoding is a subset of BER one, RASN.1 produces outputs which may be read as BER data.

How to encode ASN.1 data - simple way

Let's take an ASN.1 grammar, for example:

seq ::= SEQUENCE {
          present BOOLEAN,
          value   INTEGER OPTIONAL
        }

First, we have to create objects to represent this structure:

present = RASN1::Types::Boolean.new
value = RASN1::Types::Integer.new(optional: true)
seq = RASN1::Types::Sequence.new(value: [present, value])

Here, seq object is a SEQUENCE containing a BOOLEAN (present object) and an INTEGER (value object). To encode, we have to set a value for each one:

present.value = true
value.value = 1_234_567

Then, encoding is as simple as:

seq.to_der   #=> "\x30\x08\x01\x01\xFF\x02\x03\x12\xD6\x87"

Optional values

In our model, value is declared as optional, which means value is encoded only if it has a value:

value.value = nil
seq.to_der   #=> "\x30\x03\x01\x01\xFF"

Default values

ASN.1 permits to define a tag with a default value. If this default value is set, it will not be encoded in DER string:

value = RASN1::Types::Integer.new(default: 0)
value.value = 1
value.to_der   #=> "x02\x01\x01"

value.value = 0
value.to_der   #=> ""

Tagged values

Tagged values may also be defined. Let's take an example from X.509 certificates. Such a certificate have a version number, defined this way:

TBSCertificate  ::=  SEQUENCE  {
    version         [0]  EXPLICIT Version DEFAULT v1,
    ...
}

Version  ::=  INTEGER  {  v1(0), v2(1), v3(2)  }

To encode such a value, do:

VERSION = { 'v1' => 0, 'v2' => 1, 'v3' => 2 }

ver = RASN1::Types::Enumerated.new(enum: VERSION, explicit: 0, class: :context, default: 'v1', constructed: true)
tbs_cert = RASN1::Types::Sequence.new
tbs_cert.value << ver

Here, enumerated is defined from VERSION enum hash. It is an explicit tagged value with tag 0, in context class. Its default value is 'v1' (or 0), and it is a CONSTRUCTED tag (in X.509, all tagged values are constructed).

Thus defined, ver.to_der will give an empty string, as an absent version is equivalent to v1 (default value).

To encode a v3, do:

ver.value = 'v3'   # or 2
ver.to_der     # => "\xA0\x03\x02\x01\x02"

Here, our contructed tag is A0, where 0 is our tag, and A means CONSTRUCTED type in CONTEXT class. 03 is the length of the tagged value, and its value is 020102. This value is also an ASN.1 tag : an INTEGER (02) consisting one 1 byte (01). Its value is 02. So we have our version of value 2 (i.e. v3) in an explicit tagged value.

RASN1 also supports implicit tagged values: use implicit option.

Use SEQUENCE OF and SET OF

A SEQUENCE OF or a SET OF is like an array, but this array may only contain one type of data. This type may be a basic type (BOOLEAN, INTEGER,...) or a user defined type (usually through a SEQUENCE).

These 2 types have same implementation but different tags. Next, I will only write about SEQUENCE OF, but SET OF works exactly the same way.

With PRIMITIVE types

PRIMITIVE types are ASN.1 basic types but SEQUENCE, SET, SEQUENCE OF and SET OF.

Let's take a simple example: a sequence of integers, defined this way in ASN.1 notation:

Integers ::= SEQUENCE OF INTEGER

With RASN1, you can define it this way:

integers = RASN1::Types::SequenceOf.new(RASN1::Types::Integer)

You may set SEQUENCE OF content this way:

integers << RASN1::Types::Integer.new(value: 1)
integers << 2  # a RASN1::Types::Integer is automatically inferred from Ruby integer
integers << 3
integers.to_der   #=> "\x30\x09\x02\x01\x01\x02\x01\x02\x02\x01\x03"

It is encoded as a SEQUENCE of three INTEGERS (yes, encoded as a SEQUENCE, and not a SEQUENCE OF, this is a part of ASN.1 magic!).

With CONSTRUCTED types

A SEQUENCE OF may contain CONSTRUCTED types (i.e SEQUENCE, SET, or even SEQUENCE OF and SET OF).

For this ASN.1 definition:

example ::= SEQUENCE OF MyType

MyType ::= SEQUENCE {
               id     INTEGER,
               data   OCTET STRING
           }

We have this ruby code:

id = RASN1::Types::Integer.new
data = RASN1::Types::OctetString.new
my_type = RASN1::Types::Sequence.new(value: [id, data])
example = RASN1::Types::SequenceOf.new(my_type)

Then, we have to add values to our SEQUENCE OF:

example << [0, 'abcd']
example << [1, 'qwerty']
example.to_der  #=> "\x30\x18\x30\x09\x02\x01\x00\x04\x04abcd\x30\x0b\x02\x01\x01\x04\x06qwerty"

Go further

Types constructors have multiple options, all defined in API documentation (see http://www.rubydoc.info/gems/rasn1/RASN1/Types/Base#initialize).

There is also a more powerful way to define ASN.1 grammar: RASN1 models.

Clone this wiki locally