asn1
is a pure V module for handling Abstract Syntax Notation One (ASN.1) [X.680] objects encoded in Distinguished Encoding Rules (DER) [X.690] encoding scheme.
- About
asn1
module - What is ASN.1
- ASN.1 Encoding
- Basic ASN.1 Type System
- Supported ASN.1 type
- Generic ASN.1 Object
- Basic ASN.1 Constructor
- Encoding of ASN.1 Object
- Decoding of ASN.1 Bytes
- Module Index
- Reference
From Wikipedia says, Abstract Syntax Notation One (ASN.1) is a standard interface description language for defining data structures that can be serialized and deserialized in a cross-platform way. It is broadly used in telecommunications and computer networking, and especially in cryptography.
Encoding of ASN.1 is a set of encoding rules that specify how to represent a data structure as a series of bytes. There are multiple rules available that describes way of serializing ASN.1 object. The standard ASN.1 encoding rules include:
- Basic Encoding Rules (BER)
- Distinguished Encoding Rules (DER)
- Canonical Encoding Rules (CER)
- Basic XML Encoding Rules (XER)
- many other encoding rules availables.
See [X.690] for more information about ASN.1 encoding.
Note
This module only support the DER encoding
Fundamentally, DER encoding of ASN.1 is serialization of a Tag, Length and Value (TLV) triplets. Every ASN.1 object has a tag that represents what is type of the object. The Tag part specifies the type of the data structure being sent, the Length part specifies the number of bytes of content being transferred, and the Value part contains the content. Note that the Value part can be a triplet if it contains a constructed data type.
ASN.1 type has a tag which is byte or series of bytes that describing class of the ASN.1 object, constructed (contains other object) or primitive and a non negative tag number. In this v asn1
module, its support short form tag for tag number below 31 and long form tag (multi byte tag) for representing tag number bigger than 31.
To represent tag, in this asn1
module was using this structure:
struct Tag {
mut:
class Class
constructed bool
number int
}
Where Class
represent class of ASN.1 type. There are four class of ASN.1 type represented in:
enum Class {
universal = 0x00
application = 0x01
context = 0x02
private = 0x03
}
Most of the time, you don't need create tag structure manually, all basic universal type constructor set it for you internally, but for convenience, you can create a new tag, with the following constructor:
fn new_tag(c Class, constructed bool, number int) Tag
where c
is the ASN.1 class this object belong to, constructed
boolean flag tells if this object constructed or primitive, and provided tag number
.
ASN.1 length indicates how many bytes you should read to get values or contents part. It always represents the total number of bytes in the object including all sub-objects but does not include the lengths of the identifier or of the length field itself.
ASN.1 length comes in two form: short and long form, short form fits in single byte for length between 0 and 127, and the others is long form in multi byte form. This module support both of them, but, its only limited to DER encoding of length, ie, use definite length encoding and use the smallest possible length representation.
Basic ASN.1 type was a ASN.1 object which has universal class. It's currently supports following basic ASN1 type:
- Boolean
- BitString
- Integer (through i32, i64, and
big.Integer
) - ObjectIdentifier
- NumericString
- Null
- Enumerated
- IA5String (ascii string)
- OctetString
- PrintableString
- UTF8String
- UTCTime
- GeneralizedTime
- VisibleString
- Sequence,
- SequenceOf
- Set
- SetOf
For the purposes of handling ASN.1 object in general way, we use ASN1Object
that defined as:
struct ASN1Object {
tag Tag
values []u8
}
where:
tag
is the tag of object, andvalues
is the raw bytes array (contents) of the object without tag and length part.
You can create ASN1Object
object with the constructor, provided with parameters :
Class
this object belong to,constructed
boolean flag that tell this object constructed or primitive,tagnum
is the tag number, and,values
is bytes array of contents.
fn new_asn_object(c Class, constructed bool, tagnum int, values []u8) ASN1Object
This object implements Encoder
interface, so you can treat it like other ASN.1 object and serialize it to bytes.
Note
Most of the time, you don't need to use
ASN1Object
directly, but, the recommended way to create ASN.1 object was using most basic type constructor described in [Creating Basic ASN.1 Type] below.
You can use following function to create basic UNIVERSAL ASN.1 type. Most of the constructor return Encoder
interfaces.
Note
By default, all basic constructor has ASN.1 UNIVERSAL class tag, and the tag number is universal tag number defined in
TagType
enum, where constructed flag set totrue
value by default on SEQUENCE (SEQUENCE OF) and SET (SET OF) type. Some of the tag number was not supported in this module.
No | Function | ASN.1 Object | Description |
---|---|---|---|
1 | new_boolean | BOOLEAN | |
2 | new_integer | INTEGER | |
3 | new_bitstring | BITSTRING | its accepts arbitrary v string, not a bit string |
4 | new_octetstring | OCTET STRING | |
5 | new_null | NULL | |
6 | new_oid_from_string | OBJECT IDENTIFIER | |
7 | new_enumerated | ENUMERATED | |
8 | new_utf8string | UTF8STRING | |
9 | new_sequence | SEQUENCE, SEQUENCE OF | for sequence of, you should ensure you add the same object to sequence elements. |
10 | new_set | SET, SET OF | likes a sequence of, ensure add the same object to set elements |
11 | new_numeric_string | NUMERIC STRING | |
12 | new_printable_string | PRINTABLE STRING | |
13 | new_ia5string | IA5STRING | |
14 | new_utctime | UTCTIME | |
15 | new_generalizedtime | GENERALIZED TIME | |
16 | new_visiblestring | VISIBLESTRING |
and for handling EXPLICIT
or IMPLICIT
tagged object, there are two availables constructor:
- new_implicit_context for wraps ASN.1 object in implicit mode.
- new_explicit_context for wraps ASN.1 object in explicit mode.
This section describes a way to do serializing ASN.1 to bytes array,
included serialized tag and length.
The most important to facilitate encoding functionality of the ASN.1 object
we use Encoder
interface.
Encoder
is a main interrface that wraps ASN.1 encoding functionality.
Mostly all of basic types in this module implements this interface.
Encoder
interface defined as;
interface Encoder {
// tag of the underlying ASN.1 object
tag() Tag
// length of ASN.1 object (without tag and length part)
length() int
// length of encoded bytes of the object (included tag and length part)
size() int
// Serializes object to bytes array with DER encoding
encode() ![]u8
}
For serializing ASN.1 object to bytes array, do following step to get bytes:
- create desired ASN.1 object by calling desired constructor.
- call
encode()!
method of the created object in previous step. - get the bytes array ready to transfer.
In the first example, we would create simple object identifier object from string and serializing it to bytes array. For other object, see Basic ASN.1 Constructor.
input := '1.2.840.113549'
src := new_oid_from_string(input)!
out := src.encode()!
exp := [u8(0x06), 0x06, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d]
assert out == exp
In the second example, we would create more complex type, sequence contains other object.
// create universal type sequence
mut seq := new_sequence()
// add three object to the sequence elements.
seq.add(new_utf8string('Hello')!)
seq.add(new_integer(i64(42)))
seq.add(new_explicit_context(new_oid_from_string('1.3.6.1.3')!, 1))
// lets serialize it to bytes
out := seq.encode()!
assert out == [u8(0x30), 18, u8(12), 5, 72, 101, 108, 108, 111, u8(2), 1, 42, u8(0xA1), 6, 6, 4, 43, 6, 1, 3]
In the third example, lets create more complex type where sequence contains another sequence.
// lets create first sequence
mut seq1 := new_sequence()
// add two primitive elements to the sequence
seq1.add(new_boolean(true))
seq1.add(new_boolean(false))
// lets create another sequences, where it contains primitive element and first sequence created above.
mut seq2 := new_sequence()
seq2.add(new_boolean(false))
seq2.add(seq1)
seq2.add(new_boolean(true))
// lets serialize it to bytes
out := seq2.encode()!
expected := [u8(0x30), 14, u8(0x01), 0x01, 0x00, u8(0x30), 6, 0x01, 0x01, 0xff, 0x01, 0x01, 0x00,
u8(0x01), 0x01, 0xff]
// assert for right value
assert seq2.length() == 14
assert seq2.size() == 16
assert out == expected
This section describes how to parse (decode) bytes of data encoded in ASN.1 DER encoding. This module export der_decode
defined below as main routine to do parsing of DER encoded data. Its accepts bytes arrays encoded in DER in src
params and returns Encoder
interfaces object,
so, you should cast it to get underlying type. By default, in context specific class, its try to read as tagged object, whether its explicit or implicit.
der_decode
function signature as below:
fn der_decode(src []u8) !Encoder
We're going to use above data in Example #3 as an example for der_decode
functionality.
// the data we're going to decode, serialized in DER encoding.
data := [u8(0x30), 14, u8(0x01), 0x01, 0x00, u8(0x30), 6, 0x01, 0x01, 0xff, 0x01, 0x01,
0x00, u8(0x01), 0x01, 0xff]
// lets call `der_decode` routine
out := der_decode(data)!
// lets cast it to sequence
seq := out.as_sequence()!
el0 := seq.elements[0].as_boolean()!
assert el0.value == false
el1 := seq.elements[1].as_sequence()!
//dump(el1)
el2 := seq.elements[2].as_boolean()!
assert el2.value == true
If we dump el1
element, we exactly got the structure of sequence, with elements contains two bolean values, like we constructed in Example #3 above.
Similar like this output:
el1: asn1.Sequence{
tag: asn1.Tag{
class: universal
constructed: true
number: 16
}
elements: [asn1.Encoder(asn1.Boolean{
value: true
}), asn1.Encoder(asn1.Boolean{
value: false
})]
This section describes available functions, methods, or others structure to build and mixup functionality in this module.
Class
is ASN.1 tag class. Its represents scope of the type. ASN.1 specs says there are four ASN.1 class,
ie, UNIVERSAL, APPLICATION, CONTEXT SPECIFIC, and PRIVATE class.
Currently most of universal class type supported in this module, with limited support for other class.
enum Class {
universal = 0x00
application = 0x01
context = 0x02
private = 0x03
}
TagType
enum represent ASN.1 UNIVERSAL tag number. Some of them was
deprecated, so its not going to be supported in this module.
enum TagType {
reserved = 0 // reserved for BER
boolean = 1 // BOOLEAN
integer = 2 // INTEGER
bitstring = 3 // BIT STRING
octetstring = 4 // OCTET STRING
null = 5 // NULL
oid = 6 // OBJECT IDENTIFIER
objdesc = 7 // ObjectDescriptor
external = 8 // INSTANCE OF, EXTERNAL
real = 9 // REAL
enumerated = 10 // ENUMERATED
embedded = 11 // EMBEDDED PDV
utf8string = 12 // UTF8String
relativeoid = 13 // RELATIVE-OID
sequence = 16 // SEQUENCE, SEQUENCE OF, Constructed
set = 17 ///SET, SET OF, Constructed
numericstring = 18 // NumericString
printablestring = 19 // PrintableString
t61string = 20 // eletexString, T61String
videotexstring = 21 // VideotexString
ia5string = 22 // IA5String
utctime = 23 // UTCTime
generalizedtime = 24 // GeneralizedTime
graphicstring = 25 // GraphicString
visiblestring = 26 // VisibleString, ISO646String
generalstring = 27 // GeneralString
universalstring = 28 // UniversalString
characterstring = 29 // CHARACTER STRING
bmpstring = 30 // BMPString
}
fn new_oid_from_string(s string) !Encoder
new_oid_from_string creates Oid serializer from string
fn new_visiblestring(s string) !Encoder
fn new_utf8string(s string) !Encoder
fn new_utctime(s string) !Encoder
new_utctime creates new UtcTime from string s.
fn new_tag(c Class, constructed bool, number int) Tag
new_tag
creates new tag with class c
, with constructed or primitive form
through constructed
boolean flag, and tag number
.
fn new_set_with_class(c Class) Set
new_set_with_class creates new set with specific ASN.1 class.
fn new_set() Set
new_set creates universal set.
fn new_sequence_with_class(c Class) Sequence
new_sequence_with_class creates new empty sequence with specific ASN.1 class.
fn new_sequence() Sequence
new_sequence creates empty universal class of sequence type.
for other ASN.1 class, see new_sequence_with_class
fn new_asn_object(cls Class, constructed bool, tagnum int, values []u8) ASN1Object
new_asn_object
creates new ASN.1 Object
fn new_bitstring(s string) !Encoder
fn new_boolean(value bool) Encoder
fn new_enumerated(val int) Encoder
fn new_explicit_context(asn Encoder, tagnum int) Tagged
new_explicit_context creates new explicit mode of context specific class of tagged object from original ASN.1 object with tag number sets to tagnum.
fn new_generalizedtime(s string) !Encoder
fn new_ia5string(s string) !Encoder
fn new_implicit_context(asn Encoder, tagnum int) Tagged
new_implicit_context creates new implicit mode of context specific class of tagged object from original ASN.1 object with new tag number sets to tagnum.
fn new_integer(val AsnInteger) Encoder
new_integer creates asn.1 serializable integer object. Its supports arbitrary integer value, with support from math.big
module for
integer bigger than 64 bit number.
fn new_null() Encoder
fn new_numeric_string(s string) !Encoder
new_numeric_string creates new numeric string
fn new_octetstring(s string) Encoder
new_octetstring creates new octet string
fn new_printable_string(s string) !Encoder
new_printable_string creates PrintableString from the string s
fn read_explicit_context(tag Tag, contents []u8) !Tagged
fn (enc Encoder) contents() ![]u8
contents gets the contents (values) part of ASN.1 object, that is, bytes values of the object without tag and length parts.
fn (e Encoder) as_sequence() !Sequence
as_sequence cast encoder to sequence
fn (e Encoder) as_set() !Set
as_set cast encoder to set
fn (e Encoder) as_boolean() !Boolean
as_boolean cast encoder to ASN.1 boolean
fn (e Encoder) as_integer() !AsnInteger
as_integer cast encoder to ASN.1 integer
fn (e Encoder) as_bitstring() !BitString
as_bitstring cast encoder to ASN.1 bitstring
fn (e Encoder) as_octetstring() !OctetString
as_octetstring cast encoder to ASN.1 OctetString
fn (e Encoder) as_null() !Null
as_null cast encoder to ASN.1 null type
fn (e Encoder) as_oid() !Oid
as_oid cast encoder to ASN.1 object identifier type.
fn (e Encoder) as_utf8string() !UTF8String
as_utf8string cast encoder to ASN.1 UTF8String.
fn (e Encoder) as_numericstring() !NumericString
as_numericstring cast encoder to ASN.1 NumericString.
fn (e Encoder) as_printablestring() !PrintableString
as_printablestring cast encoder to ASN.1 PrintableString.
fn (e Encoder) as_ia5string() !IA5String
as_ia5string cast encoder to ASN.1 IA5String.
fn (e Encoder) as_visiblestring() !VisibleString
as_visiblestring cast encoder to ASN.1 VisibleString.
fn (e Encoder) as_utctime() !UtcTime
as_utctime cast encoder to ASN.1 UtcTime.
fn (e Encoder) as_generalizedtime() !GeneralizedTime
as_generalizedtime cast encoder to ASN.1 GeneralizedTime.
type Enumerated = int
ENUMERATED.
Enumerated type treated as ordinary integer, only differs on tag value.
The encoding of an enumerated value shall be that of the integer value with which it is associated.
NOTE: It is primitive.
fn (en Enumerated) tag() Tag
fn (en Enumerated) length() int
fn (en Enumerated) size() int
fn (en Enumerated) encode() ![]u8
type GeneralizedTime = string
GeneralizedTime.
In DER Encoding scheme, GeneralizedTime should :
- The encoding shall terminate with a "Z"
- The seconds element shall always be present
- The fractional-seconds elements, if present, shall omit all trailing zeros;
- if the elements correspond to 0, they shall be wholly omitted, and the decimal point element also shall be omitted
GeneralizedTime values MUST be:
- expressed in Greenwich Mean Time (Zulu) and MUST include seconds
(i.e., times are
YYYYMMDDHHMMSSZ
), even where the number of seconds is zero. - GeneralizedTime values MUST NOT include fractional seconds.
fn (gt GeneralizedTime) tag() Tag
fn (gt GeneralizedTime) length() int
fn (gt GeneralizedTime) size() int
fn (gt GeneralizedTime) encode() ![]u8
fn (a5 IA5String) tag() Tag
fn (a5 IA5String) length() int
fn (a5 IA5String) size() int
fn (a5 IA5String) encode() ![]u8
type UtcTime = string
For this time, UTCTime represented by simple string with format "YYMMDDhhmmssZ"
- the six digits YYMMDD where YY is the two low-order digits of the Christian year, (RFC 5280 defines it as a range from 1950 to 2049 for X.509), MM is the month (counting January as 01), and DD is the day of the month (01 to 31).
- the four digits hhmm where hh is hour (00 to 23) and mm is minutes (00 to 59); (SEE NOTE BELOW)
- the six digits hhmmss where hh and mm are as in above, and ss is seconds (00 to 59);
- the character Z;
- one of the characters + or -, followed by hhmm, where hh is hour and mm is minutes (NOT SUPPORTED)
- Restrictions employed by DER, the encoding shall terminate with "Z".
- The seconds element shall always be present, and DER (along with RFC 5280) specify that seconds must be present,
- Fractional seconds must not be present.
TODO:
- check for invalid representation of date and hhmmss part.
- represented UTCTime in time.Time
fn (utc UtcTime) tag() Tag
fn (utc UtcTime) length() int
fn (utc UtcTime) size() int
fn (utc UtcTime) encode() ![]u8
fn (bs BitString) tag() Tag
fn (bs BitString) length() int
fn (bs BitString) size() int
fn (bs BitString) encode() ![]u8
fn (ut UTF8String) tag() Tag
fn (ut UTF8String) length() int
fn (ut UTF8String) size() int
fn (ut UTF8String) encode() ![]u8
type AsnInteger = big.Integer | i64 | int
INTEGER.
ASN.1 Integer represented by AsnInteger sum type of int
, i64
and big.Integer
.
Its handles number arbitrary length of number with support of math.big
module.
The encoding of an integer value shall be primitive.
fn (n AsnInteger) tag() Tag
fn (n AsnInteger) length() int
fn (n AsnInteger) size() int
fn (n AsnInteger) encode() ![]u8
fn (n Null) tag() Tag
fn (n Null) length() int
fn (n Null) size() int
fn (n Null) encode() ![]u8
fn (ns NumericString) tag() Tag
fn (ns NumericString) length() int
fn (ns NumericString) size() int
fn (ns NumericString) encode() ![]u8
fn (os OctetString) tag() Tag
fn (os OctetString) length() int
fn (os OctetString) size() int
fn (os OctetString) encode() ![]u8
type Oid = []int
ObjectIdentifier
fn (oid Oid) tag() Tag
fn (oid Oid) length() int
fn (oid Oid) size() int
fn (oid Oid) encode() ![]u8
fn (ps PrintableString) tag() Tag
fn (ps PrintableString) length() int
fn (ps PrintableString) size() int
fn (ps PrintableString) encode() ![]u8
fn (b Boolean) tag() Tag
fn (b Boolean) length() int
fn (b Boolean) size() int
fn (b Boolean) encode() ![]u8
fn (seq Sequence) length() int
fn (seq Sequence) size() int
fn (seq Sequence) encode() ![]u8
fn (mut set Set) add(obj Encoder) Set
fn (mut set Set) add_multi(objs []Encoder) Set
fn (ctx Tagged) tag() Tag
tag returns outer tag
fn (ctx Tagged) inner_tag() Tag
inner_tag return inner tag of the inner object being wrapped
fn (ctx Tagged) as_inner() Encoder
as_inner returns inner object being wrapped
fn (ctx Tagged) length() int
length returns the length of the context tagged object
fn (ctx Tagged) size() int
size returns sizes of context specific tagged object.
When in explicit mode, the size of object was sum of length of the outer tag,
length of the length part and inner size.
and in implicit mode, the size was total (sum) of size of inner object,
and length of outer tag.
fn (ctx Tagged) encode() ![]u8
encode serializes context tagged object to array of bytes.
Its different between tagged mode explicit and implicit.
fn (vs VisibleString) tag() Tag
fn (vs VisibleString) length() int
fn (vs VisibleString) size() int
fn (vs VisibleString) encode() ![]u8
struct ASN1Object {
tag Tag // tag of the ASN.1 object
values []u8 // unencoded values of the object.
}
ASN1Object is generic ASN.1 Object representation.
Its implements Encoder, so it can be used to support other class of der encoded ASN.1 object
other than universal class supported in this module.
fn (obj ASN1Object) tag() Tag
fn (obj ASN1Object) length() int
fn (obj ASN1Object) size() int
fn (obj ASN1Object) encode() ![]u8
encode serialize ASN.1 object to bytes array. its return error on fail.