Initial project import

This commit is contained in:
Agila Forge Importer committed 2026-07-13 15:20:32 +00:00
commit 23f2d29e32
11957 files changed
+1754577

No files matched your search

+496
View File
@@ -0,0 +1,496 @@
# `asn1` module documentation.
> [!CAUTION]
> This module is marked as an experimental, so, its a subject to change (even rapidly).
> Use it with caution, submit when found a bug and gives yours feedback and review.
## About `asn1` module
`asn1` is a `experimental` pure V module for handling Abstract Syntax Notation One (ASN.1) [[X.680]](http://www.itu.int/rec/T-REC-X.680/en) objects encoded in Distinguished Encoding Rules (DER) [[X.690]](https://www.itu.int/rec/T-REC-X.690/en) encoding scheme.
## About this document
This document is intended to serve as documentation (actually as a note) of
internal details of this `asn1` module.
Its describes some parts of the module in the way is implemented,
the lack and also issues or limitation we have found around it.
## Table of Contents
- [About `asn1` module](#about-asn1-module)
- [What is ASN.1](#what-is-asn1)
- [ASN.1 Encoding](#encoding-of-asn1)
- [ASN.1 Type System](#asn1-type-system)
- [Tag handling](#asn1-tag)
- [Limitation](#limitation-of-the-tag-in-this-module)
- [Create ASN.1 tag](#create-asn1-tag)
- [Serializing tag into bytes](#serializing-tag-into-bytes)
- [Read ASN.1 Tag from bytes](#read-asn1-tag-from-bytes)
- [ASN.1 Length handling](#asn1-length-handling)
- [Serializing ASN.1 Length](#serializing-asn1-length)
- [ASN.1 Element](#asn1-element)
- [Build custom element payload](#build-custom-element-payload)
- [Serializing ASN.1 Element](#serializing-asn1-element)
- [Deserializing ASN.1 DER bytes into Element](#deserializing-asn1-der-bytes-into-element)
- [Element Serialization with FieldOptions](#element-serialization-with-fieldoptions)
- [Wrapping an Element through FieldOptions](#wrapping-an-element-through-fieldoptions)
- [Handling optional with FieldOptions](#handling-optional-with-fieldoptions)
- [Handling element with DEFAULT keyword](#handling-element-with-default-keyword)
- [Supported basic UNIVERSAL ASN.1 Type](#supported-basic-universal-asn1-type)
- [Constructor of Universal ASN.1 Type](#constructor-of-universal-asn1-type)
- [Support for non-universal class element](#support-for-non-universal-class-element)
- [Reference](#reference)
## What is ASN.1
From [Wikipedia](https://en.wikipedia.org/wiki/ASN.1) 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
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]](https://www.itu.int/rec/T-REC-X.690/en) for more information about ASN.1 encoding.
> **Note**
>
> This module only support the DER encoding
## ASN.1 Type System
Fundamentally, DERencoding 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 Tag
Every ASN.1 type has a tag which acts as an identifier of some ASN.1 element.
The tag 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.
ASN.1 Tag identifier was represented by this compact structure, ie,
```codeblocks
struct Tag {
mut:
class TagClass
constructed bool
number int
}
```
Where `TagClass` represent class of ASN.1 type. There are four class of ASN.1 type represented in:
```codeblocks
enum TagClass {
universal = 0x00
application = 0x01
context_specific = 0x02
private = 0x03
}
```
### Limitation of the Tag in this module.
There are two form how the ASN.1 tag was represented, ie, short form tag for tag number below 31
and long form tag (multi byte tag) for representing tag number bigger than 31.
This module support both of form, but the size (length) is limited to `max_tag_length` constant,
currently set to 3 bytes length. This effectively limits the tag number supported by this
module to be in 0..16.383 number ranges. See comment on `core.v` file for the background on this.
When your tag has a class of `universal` type, your tag nunber also be limited to be under 255,
hopefully if your tag is universal type, just use universal type supported by this module.
### Create ASN.1 Tag
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:
```codeblocks
fn Tag.new(c TagClass, 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`.
### Serializing tag into bytes
You can serialize (encode) your tga with method defined in this module,
```codeblocks
fn (t Tag) encode(mut dst []u8) !
```
By default, `encode` would try to serialize tag in DER rule into destination buffer provided in
`dst`, or returns error on fails.
### Read ASN.1 Tag from bytes
This module provides routine for reading tag from bytes. You can use
```codeblocks
fn Tag.from_bytes(bytes []u8) !(Tag, []u8)
```
It would create a tag from bytes, return a tag and remaining bytes (bytes after tag) on success,
or returns error on fails.
### ASN.1 Length handling
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.
The standard of X.690 ITU document defines two length types
- definite length, and
- indefinite length.
ASN.1 definite length comes in two form: short and long form. The short form fits in single byte
for length between 0 and 127, and the others is long form in multi byte form.
> **Note**
> This module only support definite length but its only limited to DER encoding of length.
> Theoretically, definite length support for very big number for length value, ie, value between
0 and 2^1008-1, but in this module, this limited to defined constant, `max_definite_length_count`
set to 6 bytes currently, and `max_definite_length_value` set to builtin `max_int`.
ASN.1 length was represented in a simple type, ie,
```codeblocks
type Length = int
```
You can create a length from regular integer with
```codeblocks
fn Length.new(v int) !Length
```
and, you can read a length from bytes with
```codeblocks
fn Length.from_bytes(bytes []u8) !(Length, []u8)
```
It would return a length and remaining bytes on succes or error on fails.
### Serializing ASN.1 Length
This module provides method to serialize the length into destination buffer,
```codeblocks
fn (v Length) encode(mut dst []u8) !
```
## ASN.1 Element
At the core for support handling element in generic and concise way, a fundamental
and abstracted way provided in this module is an `Element` interface, dedined as:
```codeblocks
interface Element {
tag() Tag
payload() ![]u8
}
```
where the `tag` acts as an identifier of the element and `payload` tells the value's part
of the element. The `payload` methods of the `Element` does not dictates on how your element
generates payload. Its up to specific encoding rules or other constraints.
> **Note**
> Most of the functions or methods defined in this module was accept or return an `Element`.
> Most of them is implemented with DER encoding in mind, so, your custom element hopefylly
> would be supported by this functions (methods) if your element correctly
> implemented required constraints in this module.
### Build custom element payload
Its possible to build payload for complex structure, your own defined struct contains multiples
field of elements with the help of function on this modules. Of course, you can build
your payload manually, but this `asn1` module has provides helper routine to do that, in the form:
```codeblocks
fn make_payload[T](val T, kd KeyDefault) ![]u8
```
> ***Note***
> - `T` is struct contains one or more fields that fullfills Element interface.
> - KeyDefault is map of `field.name` key with some element value
> (only the field has DEFAULT keyword) to setup default value.
Its would produces only element's payload without tag or length bytes included.
When your structures does not contains the fields that
fullfills interfaces, it would produces and return empty bytes.
### Serializing ASN.1 Element
This modules provides several functions for serializing ASN.1 Element, in three forms, ie:
```codeblocks
fn encode(el Element) ![]u8
fn encode_with_options(el Element, opt string) ![]u8
fn encode_with_field_options(el Element, fo FieldOptions) ![]u8
```
All of three's functions produces bytes result on success or error on fails.
The two latest form is serialization routines intended for serializing element
with wrapping, optional or default semantic to existing element,
gives you a extra flexibility to the serialization (deserialization) process.
For more information in detail, see [FieldOptions](#element-serialization-with-fieldoptions)
#### Example
A PrintableString containing “hi” was serialized into 13 02 68 69.
```codeblocks
obj := asn1.PrintableString.new('hi')!
output := asn1.encode(obj)!
assert output == [u8(0x13), 0x02, 0x68, 0x69]
```
When your element is tagged type element, defined with `[5] IMPLICIT PrintableString`, you can pass
a string option into `encode`, ie:
```codeblocks
output := encode_with_options(obj, 'context_specific:5;implicit;inner:19')!
assert output == [u8(0x85), 0x02, 0x68, 0x69]
```
Or when its a explicit tagged element defined as `[5] EXPLICIT PrintableString`
```codeblocks
output := encode_with_options(obj, 'context_specific:5;explicit;inner:0x13')!
assert output == [u8(0xA5), 0x04, 0x13, 0x02, 0x68, 0x69]
```
### Deserializing ASN.1 DER bytes into Element
For deserialization purposes, this module provides functions with similar in serialization parts,
ie, in the form:
```codeblocks
fn decode(src []u8) !Element
fn decode_with_options(bytes []u8, opt string) !Element
fn decode_with_field_options(bytes []u8, fo FieldOptions) !Element
```
Technically, the deserialization mechanism is reverse of serialization process.
When you pass an options to decode routine, you should ensure its a same options
used for serialization in `encode` part, or the decode would result in undefined behaviour
if its differs.
The `decode` function families, accepts DER serialized bytes, and an options
(if its should be) and return some `Element`, or return error on fails.
When you get result an `Element` from `decode` routine, you can get underlying object
by calling `into_object` method on the element.
```codeblocks
fn (el Element) into_object[T]() !T
```
Examples:
```codeblocks
el := asn1.decode([u8(0x13), 0x02, 0x68, 0x69])!
ps := el.into_object[asn1.PrintableString]()!
```
So, its also happens to pass an options string when this bytes comes from
serialized tagged type element,
```codeblocks
bytes := [u8(0xA5), 0x04, 0x13, 0x02, 0x68, 0x69]
obj := asn1.decode_with_options(bytes, 'context_specific:5;explicit;inner:0x13')!
```
## Element Serialization with FieldOptions
For supporting more complex scenarios, inspired by the same options used in go version of
`asn1` module, this module comes with support configures serialization (deserialization) process
through configuration options stored in `FieldOptions` structure.
Consider some Certificate structure represents more complex ASN.1 schemas
from [RFC 5280](https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.1.2),
```asn1
Certificate ::= SEQUENCE {
tbsCertificate TBSCertificate,
signatureAlgorithm AlgorithmIdentifier,
signatureValue BIT STRING
}
TBSCertificate ::= SEQUENCE {
version [0] EXPLICIT Version DEFAULT v1,
serialNumber CertificateSerialNumber,
signature AlgorithmIdentifier,
issuer Name,
validity Validity,
subject Name,
subjectPublicKeyInfo SubjectPublicKeyInfo,
issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL
}
```
This schema required support for other machinery in the form of tagged element
with wrapping semantic, OPTIONAL keyword handling, and DEFAULT keyword handling,
Its supported through the `FieldOptions` structures defined as:
```codeblocks
struct FieldOptions {
mut:
// For wrapping purposes
cls string
tagnum int = -1
mode string
inner string
// for OPTIONAL handling
optional bool
present bool
// FOR DEFAULT handling
has_default bool
default_value ?Element
}
```
The main purpose of this options structures is used for:
- handling of wrapping some element, turn some element into another element.
- handling of OPTIONAL element.
- handling of element with DEFAULT keywoard.
### Wrapping an Element through FieldOptions
There are two constructor for construct a `FieldOptions`, ie
```codeblocks
fn FieldOptions.from_string(s string) !FieldOptions
fn FieldOptions.from_attrs(attrs []string) !FieldOptions
```
The first function allowing you pass a string as an options, likes an examples above.
Examples:
```codeblocks
fo := FieldOptions.from_string('context_specific:5;explicit;inner:0x13')!
fo := FieldOptions.from_string('context_specific:5;explicit;inner:0x13;optional')!
```
The second form, is gives more controllable options, and its allowing tag your field of struct
with the supported options,
Examples :
```codeblocks
struct PersonnelRecord {
mut:
name asn1.OctetString @[context_specific: 0; implicit; inner: 4]
location asn1.Integer @[context_specific: 1; implicit; inner: 2]
age asn1.Integer @[context_specific: 2; implicit; inner: 2]
}
attrs := ['context_specific: 0', 'implicit', 'inner: 4']
fo := FieldOptions.from_attrs(attrs)!
// and then you can pass the options to serialization phase
out := asn1.encode_with_field_options(p.name, fo)!
```
### Handling optional with FieldOptions
The field `optional` and `present` of the `FieldOptions` was used for handling element
with OPTINAL keyword within element definition.
The mean of the flags:
- when `optional` bit was set into `true`, thats mean, the element treated as element
with OPTIONAL semantic.
- when `present` bit was set into `true`, this optional element mean was present in
the encoding data, by default optional was not included in the encoding phase (not present)
### Handling element with DEFAULT keyword
Element with DEFAULT keyword, by DER encoding rule, when the element is equal with default value
provided, its not present in the serialized bytes. For this purposes, before serialization,
you should call:
```codeblocks
fn (el Element) set_default_value(mut fo FieldOptions, value Element) !
```
to setup default value within the options for the current element, or would be error
if `has_default` flag is set but default value is not availables.
See [field_options.v](src/field_options.v) for more detail on this options.
## Supported basic UNIVERSAL ASN.1 Type
Basic ASN.1 type was a ASN.1 object which has universal class.
It's currently supports following basic ASN1 type:
- [x] Boolean
- [x] BitString
- [x] Integer (through i32, i64, and `big.Integer`)
- [x] ObjectIdentifier
- [x] NumericString
- [x] Null
- [x] Enumerated
- [x] IA5String (ascii string)
- [x] OctetString
- [x] PrintableString
- [x] UTF8String
- [x] UTCTime
- [x] GeneralizedTime
- [x] VisibleString
- [x] Sequence,
- [x] SequenceOf
- [x] Set
- [x] SetOf
## Constructor of Universal ASN.1 Type
Most Universal class of ASN.1 type supported in this modules comes with builtin constructor.
You should use this constructor when you hope an universal type.
Its comes with common signature of the constructor (not at all, but most of them),
see module doc for detail.
```codeblocks
fn T.new(value) !T
fn T.from_string(string) !T // for string-based type
fn T.from_int(int) !T // for integer-based
fn T.from_bigint(bigint) !T // for integer with big.Integer
```
## Support for non-universal class Element
When your element is non-universal class, this module has a limited support for
this type of element. Its represented in several structures, defined as :
```codeblocks
pub struct RawElement {
mut:
// The tag is the (outer) tag of the TLV, if this a wrpper.
tag Tag
// `content` is the value of a TLV. Its depends on the context.
content []u8
// optional fields
inner_tag ?Tag
mode ?TaggedMode
default_value ?Element
}
pub struct ContextElement {
RawElement
}
pub struct ApplicationElement {
RawElement
}
pub struct PrivateELement {
RawElement
}
```
Intended usage for this non-universal class is for wrapping semantic,
instead create your own non-universal manually. For examples, if you have
some element (maybe non-universal), you want create non-universal element from this element,
you can call (for creating private type element) routine :
```codeblocks
fn PrivateELement.from_element(inner Element, tagnum int, mode TaggedMode) !PrivateELement
```
Its currently support nested wrapping, but the unwrapping process should do by your self.
> **Warning**
> There are some limitation on this wrapping,
> - You can't wrap into universal class
> - You can't wrap an Optional Element.
See [other_element.v](src/other_element.v) on the repo for more details on this.
[[Return to contents]](#table-of-contents)
## Reference
1. [ASN.1](https://en.wikipedia.org/wiki/ASN.1)
2. [A Warm Welcome to ASN.1 and DER](https://letsencrypt.org/docs/a-warm-welcome-to-asn1-and-der/)
3. [A Layman's Guide to a Subset of ASN.1, BER, and DER](https://luca.ntop.org/Teaching/Appunti/asn1.html)
4. [X.690 PDF](https://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf)
5. [X.680 PDF](https://www.itu.int/ITU-T/studygroups/com17/languages/X.680-0207.pdf)
+82
View File
@@ -0,0 +1,82 @@
# asn1
`asn1` is a pure V Language module for X.690 Abstract Syntax Notation One (ASN.1)
Distinguished Encoding Rules (DER) encoding and decoding.
This module provides you with the ability to generate and parse ASN.1 encoded data.
More precisely, it provides you with the ability to generate and parse data encoded
with ASN.1’s DER (Distinguished Encoding Rules) encoding. It does not support other than DER.
> [!CAUTION]
> This module is marked as an experimental, so its subject to change (possibly rapidly).
> Use with caution, submit bugs when found, and please gives feedback.
## Supported ASN.1 Type
Currently supports the following basic ASN1 types:
- [x] Boolean
- [x] BitString
- [x] Integer (through int, i64, and `math.big.Integer`)
- [x] ObjectIdentifier
- [x] NumericString
- [x] Null
- [x] Enumerated
- [x] IA5String (ascii string)
- [x] OctetString
- [x] PrintableString
- [x] UTF8String
- [x] UTCTime
- [x] GeneralizedTime
- [x] VisibleString
- [x] Sequence,
- [x] SequenceOf
- [x] Set
- [x] SetOf
## **Features**
---
- Supports most basic ASN.1 tag types.
- Supports single and multi-byte (high form) tag formats for tag number > 31.
- Serializing and deserializing of ASN.1 objcet to bytes and vice versa.
## Code Examples
Here are some simple usage examples.
```v
import x.encoding.asn1
fn main() {
value := asn1.Integer.from_int(32)
output := asn1.encode(value)!
assert output == [u8(0x02), 0x01, 0x20]
// you can encode (serialize) with string options
output2 := asn1.encode_with_options(value, 'context_specific:5;explicit;inner:2')!
assert output2 == [u8(0xa5), 0x03, 0x02, 0x01, 0x20]
// You can decode (deserialize) back the bytes into Element.
el := asn1.decode_with_options([u8(0xa5), 0x03, 0x02, 0x01, 0x20],
'context_specific:5;explicit;inner:2')!
// el is an Element, turn it into underlying object
int_el := el.into_object[asn1.Integer]()!
int_el_value := int_el.as_i64()!
assert int_el_value == 32
}
```
See more complete examples in the [examples](https://github.com/vlang/v/tree/master/vlib/x/encoding/asn1/examples/) directory.
## Documentation
See the [documentation](https://github.com/vlang/v/blob/master/vlib/x/encoding/asn1/DOCS.md) for more detailed information on
how to use functionality in this module.
## License
This project is licensed under the MIT License (see LICENSE file)
+27
View File
@@ -0,0 +1,27 @@
// Copyright (c) 2022, 2024 blackshirt. All rights reserved.
// Use of this source code is governed by a MIT License
// that can be found in the LICENSE file.
module asn1
// ANY DEFINED BY
//
pub struct AnyDefinedBy {
pub:
// default to null element
params Element = Null{}
}
// AnyDefinedBy.new creates a new ANY DEFINED BY element.
pub fn AnyDefinedBy.new(params Element) AnyDefinedBy {
return AnyDefinedBy{params}
}
// tag returns the underlying tag of ANY DEFINED BY element.
pub fn (a AnyDefinedBy) tag() Tag {
return a.params.tag()
}
// payload returns the underlying payload of ANY DEFINED BY element.
pub fn (a AnyDefinedBy) payload() ![]u8 {
return a.params.payload()!
}
+67
View File
@@ -0,0 +1,67 @@
// Copyright (c) 2022, 2024 blackshirt. All rights reserved.
// Use of this source code is governed by a MIT License
// that can be found in the LICENSE file.
module asn1
// base128_int_length calculates the length of bytes needed to store
// integer v encoded in base 128
fn base128_int_length(v i64) int {
if v == 0 {
return 1
}
mut n := v
mut ret := 0
for n > 0 {
ret += 1
n >>= 7
}
return ret
}
// encode_base128_int serializes integer to bytes array in base 128 integer.
fn encode_base128_int(mut dst []u8, n i64) []u8 {
l := base128_int_length(n)
for i := l - 1; i >= 0; i-- {
mut o := u8(n >> u32(i * 7))
o &= 0x7f
if i != 0 {
o |= 0x80
}
dst << o
}
return dst
}
// decode_base128_int read bytes as base 128 integer for current position `loc`.
// Its returns integer value and the next offset to read from.
fn decode_base128_int(bytes []u8, loc int) !(int, int) {
mut pos := loc
mut r64 := i64(0)
mut ret := 0
for s := 0; pos < bytes.len; s++ {
r64 <<= 7
b := bytes[pos]
if s == 0 && b == 0x80 {
return error('integer is not minimaly encoded')
}
r64 |= i64(b & 0x7f)
pos += 1
if b & 0x80 == 0 {
ret = int(r64)
if r64 > max_i32 {
return error('base 128 integer too large')
}
return ret, pos
}
}
return error('truncated base 128 integer')
}
@@ -0,0 +1,36 @@
Benchmark of `x.encoding.asn1` module
--------------------------
This is a benchmark of `x.encoding.asn1` module compared with go version (on free gitpod workspace)
Regular benchmark produces this result:
```bash
(dev) $ v run bench/bench.v
Benchmarking ASN.1 encode...
Average example encode time: 13 µs
Benchmarking ASN.1 decode (with asn.decode)...
Average (asn1.decode) decode time: 3 µs
Benchmarking ASN.1 decode with Example.decode)...
Average (Example.decode) decode time: 2 µs
```
Build with `-prod` flag and rerun the bench
```bash
$ v -prod benchk/bench.v
gitpod /workspace/asn1 (dev) $ ./bench/bench
Benchmarking ASN.1 encode...
Average example encode time: 3 µs
Benchmarking ASN.1 decode (with asn.decode)...
Average (asn1.decode) decode time: 1 µs
Benchmarking ASN.1 decode with Example.decode)...
Average (Example.decode) decode time: 1 µs
```
The go version produces following result:
```bash
$ go run bench/bench.go
Benchmarking golang Marshal...
Average Marshal time: 1 µs
Benchmarking Unmarshal...
Average Unmarshal time: 0 µs
```
@@ -0,0 +1,53 @@
package main
import (
"encoding/asn1"
"fmt"
"time"
)
type Example struct {
Greeting string `asn1:"utf8"`
Answer int
Tipe asn1.ObjectIdentifier `asn1:"explicit,tag:1"`
}
func main() {
iterations := 1000
expected_output := []byte{0x30, 18, 12, 5, 72, 101, 108, 108, 111, 2, 1, 42, 0xA1,
6, 6, 4, 43, 6, 1, 3}
oid := []int{1, 3, 6, 1, 3}
ex := Example{
Greeting: "Hello",
Answer: 42,
Tipe: asn1.ObjectIdentifier(oid),
}
fmt.Println("Benchmarking golang Marshal...")
var totalMarshalTime int64
for i := 0; i < iterations; i++ {
start := time.Now()
_, err := asn1.Marshal(ex)
if err != nil {
panic(err)
}
totalMarshalTime += time.Since(start).Microseconds()
}
avgMarshalTime := totalMarshalTime / int64(iterations)
fmt.Printf("Average Marshal time: %d µs\n", avgMarshalTime)
fmt.Println("Benchmarking Unmarshal...")
var totalUnmarshalTime int64
var xx Example
for i := 0; i < iterations; i++ {
start := time.Now()
_, err := asn1.Unmarshal(expected_output, &xx)
if err != nil {
panic(err)
}
totalUnmarshalTime += time.Since(start).Microseconds()
}
avgUnmarshalTime := totalUnmarshalTime / int64(iterations)
fmt.Printf("Average Unmarshal time: %d µs\n", avgUnmarshalTime)
}
+108
View File
@@ -0,0 +1,108 @@
import time
import x.encoding.asn1
// This Benchmark was performs serialization and deserialization from ASN.1 schema defined as:
// ```asn.1
// Example ::= SEQUENCE {
// greeting UTF8String,
// answer INTEGER,
// type [1] EXPLICIT OBJECT IDENTIFIER
// }
// ```
struct Example {
greeting asn1.Utf8String
answer asn1.Integer
// you can tag your struct fields with supported options.
tipe asn1.ObjectIdentifier @[context_specific: 1; explicit; inner: 6]
}
fn (ex Example) tag() asn1.Tag {
return asn1.default_sequence_tag
}
// you can build your payload manually or use `asn1.make_payload`, but be aware,
// if your structure contains generics, its may not work (currently).
// Updates: Thank to @felipensp. He has a great job to resolve this issue.
// Its was resolved in [22724](https://github.com/vlang/v/pull/22724)
fn (ex Example) payload() ![]u8 {
kd := asn1.KeyDefault(map[string]asn1.Element{})
payload := asn1.make_payload[Example](ex, kd)!
return payload
}
// You can write custom decode routines for the Example structure. This is only one
// example, but its possible to do this in other ways with help from this module such
// as using the `Parser` codec.
fn Example.decode(bytes []u8) !Example {
// just call raw .decode on bytes
// for example, its should produce sequence type.
elem := asn1.decode(bytes)!
assert elem.tag().equal(asn1.default_sequence_tag) // should true
// cast produced element into Sequence type and get the fields.
seq := elem.into_object[asn1.Sequence]()!
fields := seq.fields()
// and then, turn every field into desired objects based on your schema.
// first two field is not wrapped element, so just turn into real object.
greeting := fields[0].into_object[asn1.Utf8String]()!
answer := fields[1].into_object[asn1.Integer]()!
// the third field is a context_specific wrapped element, just unwrap it with the
// same options used to encode.
oid_tipe := fields[2].unwrap_with_options('context_specific:1;explicit; inner:6')!
tipe := oid_tipe.into_object[asn1.ObjectIdentifier]()!
// then build your Example struct
ex := Example{
greeting: greeting
answer: answer
tipe: tipe
}
return ex
}
fn main() {
expected_output := [u8(0x30), 18, u8(12), 5, 72, 101, 108, 108, 111, u8(2), 1, 42, u8(0xA1),
6, 6, 4, 43, 6, 1, 3]
ex := Example{
greeting: asn1.Utf8String.new('Hello')!
answer: asn1.Integer.from_int(42)
tipe: asn1.ObjectIdentifier.from_ints([1, 3, 6, 1, 3])!
}
iterations := 1000
println('Benchmarking ASN.1 encode...')
mut total_enc_time := i64(0)
for _ in 0 .. iterations {
sw := time.new_stopwatch()
_ := asn1.encode(ex) or { panic(err) }
elapsed := sw.elapsed().microseconds()
total_enc_time += elapsed
}
avg_enc_time := total_enc_time / iterations
println('Average example encode time: ${avg_enc_time} µs')
println('Benchmarking ASN.1 decode (with asn1.decode)...')
mut total_dec_time := i64(0)
for _ in 0 .. iterations {
sw := time.new_stopwatch()
_ := asn1.decode(expected_output) or { panic(err) }
elapsed := sw.elapsed().microseconds()
total_dec_time += elapsed
}
avg_asn1dec_time := total_dec_time / iterations
println('Average (asn1.decode) decode time: ${avg_asn1dec_time} µs')
println('Benchmarking ASN.1 decode with Example.decode)...')
mut total_exdec_time := i64(0)
for _ in 0 .. iterations {
sw := time.new_stopwatch()
_ := asn1.decode(expected_output) or { panic(err) }
elapsed := sw.elapsed().microseconds()
total_exdec_time += elapsed
}
avg_exdec_time := total_exdec_time / iterations
println('Average (Example.decode) decode time: ${avg_exdec_time} µs')
}
+225
View File
@@ -0,0 +1,225 @@
// Copyright (c) 2022, 2024 blackshirt. All rights reserved.
// Use of this source code is governed by a MIT License
// that can be found in the LICENSE file.
module asn1
import arrays
// default_bitstring_tag is the default tag of the ASN.1 BITSTRING type.
pub const default_bitstring_tag = Tag{.universal, false, int(TagType.bitstring)}
// ASN.1 UNIVERSAL CLASS OF BITSTRING TYPE.
//
// The BIT STRING type denotes an arbitrary string of bits (ones and zeroes).
// A BIT STRING value can have any length, including zero. This type is a string type.
// BIT STRING, OCTET STRING, UTCTime, GeneralizedTime, and the various string types can use
// either primitive encoding or constructed encoding, at the sender’s discretion-- in BER.
// However, in DER all types that have an encoding choice between primitive and constructed
// must use the primitive encoding. DER restricts the encoding to primitive only.
// The same applies for BITSTRING. ie, For BIT STRING and OCTET STRING types,
// DER does not allow the constructed form (breaking a string into multiple TLVs)
// or the indefinite length form.
pub struct BitString {
mut:
data []u8
pad u8 // numbers of unused bits
}
// data returns underlying BitString data.
pub fn (bs BitString) data() []u8 {
return bs.data
}
// pad returns underlying BitString pad byte.
pub fn (bs BitString) pad() u8 {
return bs.pad
}
// check performs check internal validity of the BitString data.
fn (bs BitString) check() ! {
// to align with octet size, ie, 8 in length, pad bits only need maximum 7 bits
// and when the data.len is multiples of 8, no need to pad, ie, pad should 0.
if bs.pad > 7 || (bs.data.len == 0 && bs.pad != 0) {
return error('BitString: bad pad bits or zero length')
}
// this check if the pad != 0, whether the last `pad` number of bits of the last byte
// is all bits cleared, and it was not used in the BitString data.
if bs.pad > 0 && (bs.data[bs.data.len - 1]) & ((1 << bs.pad) - 1) != 0 {
return error('BitString: bad args')
}
}
// tag returns the tag of BITSTRING type.
pub fn (bs BitString) tag() Tag {
return default_bitstring_tag
}
// payload returns the payload of BITSTRING instance.
pub fn (bs BitString) payload() ![]u8 {
bs.check()!
mut out := []u8{}
out << bs.pad
out << bs.data
return out
}
// str returns a string representation of the current state of bs.
pub fn (bs BitString) str() string {
return 'BitString: ${bs.data.hex()} (${bs.pad})'
}
// parse BitString using the given Parser.
fn BitString.parse(mut p Parser) !BitString {
tag := p.read_tag()!
if !tag.equal(default_bitstring_tag) {
return error('Get unexpected non bitstring tag')
}
length := p.read_length()!
bytes := if length == 0 {
[]u8{}
} else {
p.read_bytes(length)!
}
bs := BitString.from_bytes(bytes)!
return bs
}
fn BitString.decode(bytes []u8) !(BitString, int) {
bs, next := BitString.decode_with_rule(bytes, .der)!
return bs, next
}
fn BitString.decode_with_rule(bytes []u8, rule EncodingRule) !(BitString, int) {
tag, length_pos := Tag.decode_with_rule(bytes, 0, rule)!
if !tag.equal(default_bitstring_tag) {
return error('Unexpected non-bitstring tag')
}
length, content_pos := Length.decode_with_rule(bytes, length_pos, rule)!
if length < 1 {
return error('BitString: zero length bit string')
}
if content_pos >= bytes.len || content_pos + length > bytes.len {
return error('BitString: truncated payload bytes')
}
payload := unsafe { bytes[content_pos..content_pos + length] }
bs := BitString.new_with_pad(payload[1..], payload[0])!
next := content_pos + length
return bs, next
}
// from_binary_string creates a new BitString from binary bits arrays in s,
// ie, arrays of 1 and 0. If s.len is not a multiple of 8, it will contain non-null pad,
// otherwise, the pad is null.
// The bit string '011010001' will need two content octets: 01101000 10000000 (hexadecimal 68 80);
// seven bits of the last octet are not used and is interpreted as a pad value.
// Example:
// ```v
// import x.encoding.asn1
// bs := asn1.BitString.from_binary_string('011010001')!
// assert bs.str() == 'BitString: 6880 (7)'
// ```
pub fn BitString.from_binary_string(s string) !BitString {
res, pad := parse_bits_string(s)!
return BitString.new_with_pad(res, u8(pad))!
}
// new creates a new BitString from regular string s.
pub fn BitString.new(s string) !BitString {
return BitString.from_bytes(s.bytes())!
}
// from_bytes creates a new BitString from bytes array in src.
// Note: Your first byte of the src as a pad bit.
fn BitString.from_bytes(src []u8) !BitString {
if src.len < 1 {
return error('BitString error: need more bytes')
}
return BitString.new_with_pad(src[1..], src[0])!
}
// new_with_pad creates a new BitString from bytes array in bytes with
// specific padding bits in pad
fn BitString.new_with_pad(bytes []u8, pad u8) !BitString {
bs := BitString{
data: bytes
pad: pad
}
bs.check()!
return bs
}
fn (bs BitString) bytes_len() int {
return bs.data.len + 1
}
// Utility function
// maximum allowed binary bits string length
const max_bitstring_len = 65536
// valid_bitstring checks whether this s string is a valid of arrays of binary string `0` and `1`.
fn valid_bitstring(s string) bool {
return s.contains_only('01') && s.len <= max_bitstring_len
}
// parse_into_u8 parses arrays of binary bits of `0` and '1' with length == 8 into single byte (u8)
// Example: parse_to_u8('01101000')! == u8(0x68) // => true
fn parse_into_u8(s string) !u8 {
if s.len != 8 {
return error('not 8 length')
}
if !valid_bitstring(s) {
return error('not valid bit string: ${s}')
}
mut b := u8(0)
mut ctr := 0
bitmask := 0x01
for bit := 0; bit < s.len; bit++ {
v := u32(s[ctr] & bitmask) << (7 - bit)
b |= u8(v & 0x00ff)
ctr += 1
}
return b
}
// pad_into_octet pads string s by string `0` into new string with size 8
fn pad_into_octet(s string) !string {
if valid_bitstring(s) && s.len > 0 && s.len < 8 {
len := if s.len % 8 == 0 { 0 } else { 8 - s.len % 8 }
pad := '0'.repeat(len)
res := s + pad
return res
}
return error('not valid bit string')
}
// parse_bits_string parses binary bits string s into arrays of byte and number of padding bits
fn parse_bits_string(s string) !([]u8, int) {
if s.len == 0 {
return []u8{}, 0
}
if !valid_bitstring(s) {
return error('not valid bit string')
}
arr := arrays.chunk[u8](s.bytes(), 8)
mut res := []u8{}
pad_len := if s.len % 8 == 0 { 0 } else { 8 - s.len % 8 }
if pad_len > 7 {
return error('pad_len > 7')
}
for item in arr {
if item.len != 8 {
bts := pad_into_octet(item.bytestr())!
val := parse_into_u8(bts)!
res << val
}
if item.len == 8 {
b := parse_into_u8(item.bytestr())!
res << b
}
}
return res, pad_len
}
@@ -0,0 +1,170 @@
// Copyright (c) 2022, 2024 blackshirt. All rights reserved.
// Use of this source code is governed by a MIT License
// that can be found in the LICENSE file.
module asn1
// BITSTRING Test
//
struct BSTest {
inp []u8
pad u8
out BitString
err IError
}
fn test_new_bitstring() ! {
ds := [
BSTest{'abc'.bytes(), 8, BitString{}, error('BitString: bad pad bits or zero length')},
BSTest{''.bytes(), 2, BitString{}, error('BitString: bad pad bits or zero length')},
BSTest{[u8(0xff)], 1, BitString{}, error('BitString: bad args')},
BSTest{[u8(0xff)], 0, BitString{
data: [u8(0xff)]
pad: 0
}, none},
BSTest{[u8(0xfe)], 0, BitString{
data: [u8(0xfe)]
pad: 0
}, none},
BSTest{[u8(0x8e)], 0, BitString{
data: [u8(0x8e)]
pad: 0
}, none},
BSTest{[u8(0x88)], 0, BitString{
data: [u8(0x88)]
pad: 0
}, none},
BSTest{[u8(0x03), 0x02, 0x00, 0x20], 0, BitString{
data: [u8(0x03), 0x02, 0x00, 0x20]
pad: 0
}, none},
BSTest{[u8(0x03), 0x02, 0x05, 0x80], 5, BitString{
data: [u8(0x03), 0x02, 0x05, 0x80]
pad: 5
}, none},
]
for c in ds {
out := BitString.new_with_pad(c.inp, c.pad) or {
assert err == c.err
continue
}
assert out == c.out
}
}
struct BSParse {
inp []u8
src []u8
pad u8
err IError
}
fn test_serialize_and_decode_bitstring() ! {
ds := [
// from rust-asn1
BSParse{[u8(0x03), 0x01, 0x00], ''.bytes(), 0, none},
BSParse{[u8(0x03), 0x02, 0x07, 0x00], [u8(0)], 7, none},
BSParse{[u8(0x03), 0x02, 0x07, 0x80], [u8(0x80)], 7, none},
BSParse{[u8(0x03), 0x03, 0x04, 0x81, 0xf0], [u8(0x81), 0xf0], 4, none},
BSParse{[u8(0x03), 0x00], ''.bytes(), 0, error('BitString: zero length bit string')},
// bad length
BSParse{[u8(0x03), 0x02, 0x07, 0x01], [u8(0x81)], 0, error('BitString: bad args')},
// bad args
BSParse{[u8(0x03), 0x02, 0x07, 0x40], [u8(0x81)], 0, error('BitString: bad args')},
// bad args
BSParse{[u8(0x03), 0x02, 0x08, 0x00], [u8(0x81)], 0, error('BitString: bad pad bits or zero length')},
// bad args
]
for i, c in ds {
bs, idx := BitString.decode(c.inp) or {
assert err == c.err
continue
}
assert bs.tag().tag_number() == int(TagType.bitstring)
// b := new_bitstring_with_pad(c.src, c.pad)!
b := BitString.new_with_pad(c.src, c.pad)!
// b is Encoder, so lets smart cast it to real
assert bs == b
// back
s := encode(b)!
assert s == c.inp
}
}
// This data is taken from https://learn.microsoft.com/en-us/windows/win32/seccertenroll/about-bit-string
// 0299: 03 81 81 ; BIT_STRING (81 Bytes)
// 029c: 00
// 029d: 47 eb 99 5a df 9e 70 0d fb a7 31 32 c1 5f 5c 24
// 02ad: c2 e0 bf c6 24 af 15 66 0e b8 6a 2e ab 2b c4 97
// 02bd: 1f e3 cb dc 63 a5 25 ec c7 b4 28 61 66 36 a1 31
// 02cd: 1b bf dd d0 fc bf 17 94 90 1d e5 5e c7 11 5e c9
// 02dd: 55 9f eb a3 3e 14 c7 99 a6 cb ba a1 46 0f 39 d4
// 02ed: 44 c4 c8 4b 76 0e 20 5d 6d a9 34 9e d4 d5 87 42
// 02fd: eb 24 26 51 14 90 b4 0f 06 5e 52 88 32 7a 95 20
// 030d: a0 fd f7 e5 7d 60 dd 72 68 9b f5 7b 05 8f 6d 1e
fn test_bitstring_from_bytes_arrays() ! {
data := [u8(0x03), 0x81, 0x81, 0x00, 0x47, 0xeb, 0x99, 0x5a, 0xdf, 0x9e, 0x70, 0x0d, 0xfb,
0xa7, 0x31, 0x32, 0xc1, 0x5f, 0x5c, 0x24, 0xc2, 0xe0, 0xbf, 0xc6, 0x24, 0xaf, 0x15, 0x66,
0x0e, 0xb8, 0x6a, 0x2e, 0xab, 0x2b, 0xc4, 0x97, 0x1f, 0xe3, 0xcb, 0xdc, 0x63, 0xa5, 0x25,
0xec, 0xc7, 0xb4, 0x28, 0x61, 0x66, 0x36, 0xa1, 0x31, 0x1b, 0xbf, 0xdd, 0xd0, 0xfc, 0xbf,
0x17, 0x94, 0x90, 0x1d, 0xe5, 0x5e, 0xc7, 0x11, 0x5e, 0xc9, 0x55, 0x9f, 0xeb, 0xa3, 0x3e,
0x14, 0xc7, 0x99, 0xa6, 0xcb, 0xba, 0xa1, 0x46, 0x0f, 0x39, 0xd4, 0x44, 0xc4, 0xc8, 0x4b,
0x76, 0x0e, 0x20, 0x5d, 0x6d, 0xa9, 0x34, 0x9e, 0xd4, 0xd5, 0x87, 0x42, 0xeb, 0x24, 0x26,
0x51, 0x14, 0x90, 0xb4, 0x0f, 0x06, 0x5e, 0x52, 0x88, 0x32, 0x7a, 0x95, 0x20, 0xa0, 0xfd,
0xf7, 0xe5, 0x7d, 0x60, 0xdd, 0x72, 0x68, 0x9b, 0xf5, 0x7b, 0x05, 0x8f, 0x6d, 0x1e]
bs, idx := BitString.decode(data)!
assert idx == data.len
assert bs.tag().tag_number() == int(TagType.bitstring)
assert bs.bytes_len() == 0x81
assert bs.pad == 0x00
// serializes bitstring back
bs_back := encode(bs)!
assert bs_back == data
// with decode
bsd := decode(data)!
assert bsd.tag() == bs.tag()
assert bsd.payload()! == bs.payload()!
// with parse
mut p := Parser.new(data)
bs_parse := BitString.parse(mut p)!
assert bs_parse.tag() == bs.tag()
assert bs_parse.payload()! == bs.payload()!
}
// test for BitString from binary string
struct BinaryStringTest {
bits string
out []u8
err IError
}
const binstring_data = [
// empty bit string
BinaryStringTest{'', [u8(0x03), 0x01, 0x00], none},
// malformed bit string
BinaryStringTest{'aaa', []u8{}, error('not valid bit string')},
// taken examples from internet
BinaryStringTest{'011010001', [u8(0x03), 0x03, 0x07, 0x68, 0x80], none},
BinaryStringTest{'101100001001', [u8(0x03), 0x03, 0x04, 0xb0, 0x90], none},
BinaryStringTest{'011011100101110111', [u8(0x03), 0x04, 0x06, 0x6e, 0x5d, 0xc0], none},
]
fn test_bitstring_from_binary_string() ! {
for item in binstring_data {
bs := BitString.from_binary_string(item.bits) or {
assert err == item.err
continue
}
out := encode(bs)!
assert out == item.out
}
}
+167
View File
@@ -0,0 +1,167 @@
// Copyright (c) 2022, 2024 blackshirt. All rights reserved.
// Use of this source code is governed by a MIT License
// that can be found in the LICENSE file.
module asn1
// default_boolean_tag is the default tag of ASN.1 BOOLEAN type.
pub const default_boolean_tag = Tag{.universal, false, int(TagType.boolean)}
// ASN.1 UNIVERSAL CLASS OF BOOLEAN TYPE.
//
// A Boolean value can take true or false.
// ASN.1 DER encoding restricts encoding of boolean true value into 0xff
// and otherwise, encodes into zero (0x00) for false value.
// The encoding of a boolean value shall be primitive. The contents octets shall consist of a single octet.
pub struct Boolean {
mut:
// boolean value represented in single byte to allow stores multiple value represents
// true value others than 0xff, ie., non-null byte representing true value.
value u8
}
// new creates a new Boolean value from true or false value.
// By default, when you pass true, its would store 0xff as underlying byte value.
// If you want more to be relaxed, see from_u8 to creates with another byte value.
pub fn Boolean.new(value bool) Boolean {
mut ret := Boolean{}
val := if value { u8(0xff) } else { u8(0x00) }
ret.value = val
return ret
}
// from_u8 creates a new Boolean value from single byte value.
fn Boolean.from_u8(value u8) Boolean {
return Boolean{
value: value
}
}
// tag returns the tag of Boolean type.
pub fn (v Boolean) tag() Tag {
return default_boolean_tag
}
fn (v Boolean) str() string {
res := if v.value() { 'Boolean (TRUE)' } else { 'Boolean (FALSE)' }
return res
}
// payload returns the payload of Boolean type in .der rule.
pub fn (b Boolean) payload() ![]u8 {
return b.payload_with_rule(.der)!
}
fn (b Boolean) payload_with_rule(rule EncodingRule) ![]u8 {
// by default, true value is encoded to 0xff
if rule == .der {
if b.value != u8(0xff) && b.value != u8(0x00) {
return error('Boolean: in .der, only 0xff or 0x00 are allowed')
}
}
return [b.value]
}
// parse tries to read a Boolean type from parser or return error on fails
pub fn Boolean.parse(mut p Parser) !Boolean {
tag := p.read_tag()!
if !tag.equal(default_boolean_tag) {
return error('Get unexpected non boolean tag')
}
length := p.read_length()!
if length != 1 {
return error('Bad boolean length')
}
bytes := p.read_bytes(length)!
value := Boolean.from_bytes(bytes)!
return value
}
// from_bytes creates a new ASN.1 BOOLEAN type from bytes b.
// Boolean type should fit in one byte length, otherwise it would return error.
// by default, p.rule == .der to follow DER restriction
fn Boolean.from_bytes(bytes []u8) !Boolean {
return Boolean.from_bytes_with_rule(bytes, .der)
}
fn Boolean.from_bytes_with_rule(bytes []u8, rule EncodingRule) !Boolean {
if bytes.len != 1 {
return error('Boolean: bad bytes')
}
// for DER requirements that "If the encoding represents the boolean value TRUE,
// its single contents octet shall have all eight bits set to one."
// Thus only 0 and 255 are valid encoded values.
// But, we relaxed this requirement to allow other than non-null
// value to be treated as TRUE value, like in BER encoding.
match bytes[0] {
u8(0x00) {
return Boolean.from_u8(0x00)
}
u8(0xff) {
return Boolean.from_u8(0xff)
}
else {
// other non-null value is treated as TRUE boolean value
if rule == .der {
return error('Boolean: in DER, other than 0xff is not allowed for true value')
}
return Boolean.from_u8(bytes[0])
}
}
}
// value gets the boolean value represented by underlying byte value.
// It returns FALSE if the byte == 0x00 and TRUE otherwise.
pub fn (b Boolean) value() bool {
return b.value_with_rule(.der)
}
fn (b Boolean) value_with_rule(rule EncodingRule) bool {
match b.value {
u8(0xff) {
return true
}
u8(0x00) {
return false
}
else {
if rule == .der {
return false
}
// otherwise non-null is considered as true
return true
}
}
}
// decode tries to decode bytes array into Booelan type or error on fails.
fn Boolean.decode(src []u8) !(Boolean, int) {
return Boolean.decode_with_rule(src, 0, .der)!
}
fn Boolean.decode_with_rule(src []u8, loc int, rule EncodingRule) !(Boolean, int) {
if src.len < 3 {
return error('Boolean: bad length bytes')
}
if rule != .der && rule != .ber {
return error('Boolean: not supported rule')
}
tag, length_pos := Tag.decode_with_rule(src, loc, rule)!
if !tag.equal(default_boolean_tag) {
return error('Unexpected non-boolean tag')
}
length, content_pos := Length.decode_with_rule(src, length_pos, rule)!
if length != 1 {
return error('Boolean: should have length 1')
}
if content_pos >= src.len || content_pos + length > src.len {
return error('Boolean: truncated payload bytes')
}
payload := unsafe { src[content_pos..content_pos + length] }
// boolean value should be encoded in single byte
res := Boolean.from_bytes_with_rule(payload, rule)!
next := content_pos + length
return res, next
}
@@ -0,0 +1,43 @@
// Copyright (c) 2022, 2024 blackshirt. All rights reserved.
// Use of this source code is governed by a MIT License
// that can be found in the LICENSE file.
module asn1
struct BooleanTest {
inp []u8
out bool
err IError
}
fn test_encode_decode_boolean_in_der_rule() {
bd := [
BooleanTest{[u8(1), 0x01, 0xff], true, none},
BooleanTest{[u8(1), 0x01, 0x00], false, none},
BooleanTest{[u8(1), 0x01, 0x10], false, error('Boolean: in DER, other than 0xff is not allowed for true value')}, // invalid value
BooleanTest{[u8(1), 0x02, 0x00], false, error('Boolean: should have length 1')}, // bad length
BooleanTest{[u8(1), 0x00, 0x00], false, error('Boolean: should have length 1')}, // bad length
BooleanTest{[u8(2), 0x01, 0x00], false, error('Unexpected non-boolean tag')}, // bad tag number
]
for c in bd {
out, _ := Boolean.decode(c.inp) or {
assert err == c.err
continue
}
// out.value is now u8, call .value() instead
assert out.value() == c.out
}
}
fn test_parse_boolean_with_parser() ! {
data := [u8(0x01), 0x01, 0xff]
mut p := Parser.new(data)
// This is fails too lookup
// out := p.read_element[Boolean]()!
// assert out.str() == 'Boolean (TRUE)'
// THis is ok
mut p2 := Parser.new(data)
out_3th := Boolean.parse(mut p2)!
assert out_3th.str() == 'Boolean (TRUE)'
}
+80
View File
@@ -0,0 +1,80 @@
// Copyright (c) 2022, 2024 blackshirt. All rights reserved.
// Use of this source code is governed by a MIT License
// that can be found in the LICENSE file.
module asn1
const max_choices_size = 255
const default_choices_size = 64
// ASN.1 CHOICE
//
// Choice is an ASN.1 Element
pub struct Choice {
mut:
size int = default_choices_size
choosen Element
choices []Element
}
// new creates a new choice element. It accepts el as a choosen element
// and choices as a source of possible choices list.
pub fn Choice.new(el Element, choices []Element) !Choice {
c := Choice{
choosen: el
choices: choices
}
c.check()!
return c
}
// set_size sets the maximal this choices size
pub fn (mut c Choice) set_size(size int) ! {
if size > max_choices_size {
return error('Your size exceed max_choices_size')
}
c.size = size
c.check()!
}
// choose chooses some element for as a choosen element for the choice c.
pub fn (mut c Choice) choose(some Element) ! {
mut within := false
for el in c.choices {
if el.equal(some) {
within = true
}
}
if within {
c.choosen = some
return
}
return error('element not within choices')
}
fn (c Choice) check() ! {
if c.choices.len == 0 {
return error('bad choices list')
}
if c.choices.len > c.size {
return error('Your choices list exceed size')
}
// check the choosen element is within choices
filtered := c.choices.filter(it.equal(c.choosen))
// nothing found, so..its bad choice
if filtered.len == 0 {
return error('choosen element not within choices list')
}
if filtered.len > 1 {
return error('multiples element within choices matching with choosen element')
}
}
// tag returns the tag of choice element.
pub fn (c Choice) tag() Tag {
return c.choosen.tag()
}
// payload returns the payload of choice element.
pub fn (c Choice) payload() ![]u8 {
return c.choosen.payload()!
}
+153
View File
@@ -0,0 +1,153 @@
// Copyright (c) 2022, 2024 blackshirt. All rights reserved.
// Use of this source code is governed by a MIT License
// that can be found in the LICENSE file.
module asn1
// common parsing routines
//
fn parse_element(tag Tag, content []u8) !Element {
match tag.class {
.universal {
return parse_universal(tag, content)!
}
.context_specific {
return parse_context_specific(tag, content)!
}
.application {
return parse_application(tag, content)!
}
.private {
return parse_private(tag, content)!
}
}
}
fn parse_universal(tag Tag, content []u8) !Element {
if tag.class != .universal {
return error('Non universal class')
}
if tag.constructed {
return parse_universal_constructed(tag, content)!
}
return parse_universal_primitive(tag, content)!
}
fn parse_universal_primitive(tag Tag, content []u8) !Element {
if tag.constructed {
return error('parse on constructed type')
}
match tag.tag_number() {
int(TagType.boolean) {
return Boolean.from_bytes(content)!
}
int(TagType.null) {
return Null.from_bytes(content)!
}
int(TagType.oid) {
return ObjectIdentifier.from_bytes(content)!
}
int(TagType.integer) {
return Integer.from_bytes(content)!
}
int(TagType.enumerated) {
return Enumerated.from_bytes(content)!
}
int(TagType.bitstring) {
return BitString.from_bytes(content)!
}
int(TagType.ia5string) {
return IA5String.from_bytes(content)!
}
int(TagType.utf8string) {
return Utf8String.from_bytes(content)!
}
int(TagType.numericstring) {
return NumericString.from_bytes(content)!
}
int(TagType.printablestring) {
return PrintableString.from_bytes(content)!
}
int(TagType.generalstring) {
return GeneralString.from_bytes(content)!
}
int(TagType.octetstring) {
return OctetString.from_bytes(content)!
}
int(TagType.visiblestring) {
return VisibleString.from_bytes(content)!
}
int(TagType.utctime) {
return UtcTime.from_bytes(content)!
}
int(TagType.generalizedtime) {
return GeneralizedTime.from_bytes(content)!
}
else {
// return the raw element
return RawElement.new(tag, content)!
}
}
}
fn parse_universal_constructed(tag Tag, content []u8) !Element {
if !tag.constructed {
return error('parse on non-constructed type')
}
match tag.tag_number() {
int(TagType.sequence) {
// todo: handle SequenceOf
return Sequence.from_bytes(content)!
}
int(TagType.set) {
return Set.from_bytes(content)!
}
else {
return RawElement.new(tag, content)!
}
}
}
fn parse_private(tag Tag, content []u8) !PrivateELement {
return PrivateELement.new(tag, content)!
}
fn parse_application(tag Tag, content []u8) !ApplicationElement {
return ApplicationElement.new(tag, content)!
}
// parse_context_specific_with_mode parses tag and content as ContextElement when mode is availables
fn parse_context_specific_with_mode(tag Tag, content []u8, inner_tag Tag, mode TaggedMode) !ContextElement {
if tag.tag_class() != .context_specific {
return error('parse on non-context-specific class')
}
if mode == .implicit {
inner := parse_element(inner_tag, content)!
ctx := ContextElement.from_element(inner, tag.number, mode)!
return ctx
}
// explicit
// read an inner tag from content
mut p := Parser.new(content)
intag := p.peek_tag()!
if !intag.equal(inner_tag) {
return error('Get unexpected inner tag')
}
inner_el := p.read_tlv()!
// should finish
p.finish()!
ctx := ContextElement.from_element(inner_el, tag.number, .explicit)!
return ctx
}
// parse_context_specific parses tag and content as ContextElement.
// The info of fields of ContextElement, ie, inner_tag and mode, is not availables here
// You should provides this later with the correct value.
fn parse_context_specific(tag Tag, content []u8) !ContextElement {
// mode and inner_tag is not set here without additional information,
// So its still none here, and you should set it with correct value
ctx := ContextElement.new(tag, content)!
return ctx
}
+564
View File
@@ -0,0 +1,564 @@
// Copyright (c) 2022, 2024 blackshirt. All rights reserved.
// Use of this source code is governed by a MIT License
// that can be found in the LICENSE file.
module asn1
// vfmt off
// bit masking values for ASN.1 tag header
const tag_class_mask = 0xc0 // 192, bits 8-7
const constructed_mask = 0x20 // 32, bits 6
const tag_number_mask = 0x1f // 31, bits 1-5
// vfmt on
// Maximum number of bytes to represent tag number, includes the tag byte.
const max_tag_length = 3
const max_tag_number = 16383
// Maximum value for UNIVERSAL class tag number, see `TagType`,
// Tag number above this number should be considered to other class,
// PRIVATE, CONTEXT_SPECIFIC or APPLICATION class.
const max_universal_tagnumber = 255
// ASN.1 Tag identifier handling.
//
// Tag represents identifier of the ASN.1 element (object).
// ASN.1 Tag number can be represented in two form, the short form and the long form.
// The short form for tag number below <= 30 and stored enough in single byte.
// The long form for tag number > 30, and stored in two or more bytes.
//
// ASN.1 imposes no limit on the tag number, but the NIST Stable Implementation Agreements (1991)
// and its European and Asian counterparts limit the size of tags to 16383.
// see https://www.oss.com/asn1/resources/asn1-faq.html#tag-limitation
// We impose limit on the tag number to be in range 0..16383.
// Its big enough to accomodate and represent different of yours own tag number.
// Its represents 2 bytes length where maximum bytes arrays to represent tag number
// in multibyte (long) form is `[u8(0x1f), 0xff, 0x7f]` or 16383 in base 128.
pub struct Tag {
mut:
class TagClass = .universal
constructed bool
number int
}
// `Tag.new` creates new ASN.1 tag identifier. Its accepts params of TagClass `cls`,
// the tag form in the constructed or primitive form in `constructed` flag, and the integer tag `number`.
pub fn Tag.new(cls TagClass, constructed bool, number int) !Tag {
if number < 0 || number > max_tag_number {
return error('Unallowed tag number, ${number} exceed limit')
}
// validates required criteria
if cls == .universal {
// UNIVERSAL tag number should below max_universal_tagnumber
if number > max_universal_tagnumber {
return error('Not a valid tag number for universal class=${number}')
}
// SEQUENCE (OF) or SET (OF) should be in constructed form
if number == int(TagType.sequence) || number == int(TagType.set) {
if !constructed {
return error('For SEQUENCE(OF) or SET(OF) type, should be in constructed form')
}
}
}
// otherwise, ok
tag := Tag{
class: cls
constructed: constructed
number: number
}
return tag
}
// `tag_class` return the ASN.1 class of this tag
pub fn (t Tag) tag_class() TagClass {
return t.class
}
// `is_constructed` tells us whether this tag is in constructed form
// or the primitive one, ie, not constructed.
pub fn (t Tag) is_constructed() bool {
return t.constructed
}
// `tag_number` return the tag nunber of this tag
pub fn (t Tag) tag_number() int {
return t.number
}
// `encode` serializes the tag into bytes array with default rule, ie, .der.
// Its serializes into dst bytes buffer or return error on fails.
pub fn (t Tag) encode(mut dst []u8) ! {
t.encode_with_rule(mut dst, .der)!
}
// `Tag.from_bytes` creates a new Tag from bytes. Its return newly created tag and
// the rest of remaining bytes on success, or error on failures.
pub fn Tag.from_bytes(bytes []u8) !(Tag, []u8) {
tag, next_pos := Tag.decode(bytes)!
if next_pos < bytes.len {
rest := unsafe { bytes[next_pos..] }
return tag, rest
}
if next_pos == bytes.len {
return tag, []u8{}
}
return error('Tag: too short data')
}
// `equal` checks whether this tag is equal with the other tag
pub fn (t Tag) equal(o Tag) bool {
return t.class == o.class && t.constructed == o.constructed && t.number == o.number
}
// encode_with_rule serializes tag into bytes array
fn (t Tag) encode_with_rule(mut dst []u8, rule EncodingRule) ! {
// we currently only support .der or (stricter) .ber
if rule != .der && rule != .ber {
return error('Tag: unsupported rule')
}
// makes sure tag number is valid
if t.number > max_tag_number {
return error('Tag: tag number exceed limit')
}
// get the class type and constructed bit and build the bytes tag.
// if the tag number > 0x1f, represented in long form required two or more bytes,
// otherwise, represented in short form, fit in single byte.
mut b := (u8(t.class) << 6) & tag_class_mask
if t.constructed {
b |= constructed_mask
}
// The tag is in long form
if t.number >= 0x1f {
b |= tag_number_mask // 0x1f
dst << b
t.to_bytes_in_base128(mut dst)!
} else {
// short form
b |= u8(t.number)
dst << b
}
}
// Tag.decode tries to deserializes bytes into Tag.
// Its return error on fails.
fn Tag.decode(bytes []u8) !(Tag, int) {
tag, next := Tag.decode_from_offset(bytes, 0)!
return tag, next
}
// Tag.decode tries to deserializes bytes into Tag. its return error on fails.
fn Tag.decode_from_offset(bytes []u8, pos int) !(Tag, int) {
// default rule
tag, next := Tag.decode_with_rule(bytes, pos, .der)!
return tag, next
}
// Tag.decode_with_rule deserializes bytes back into Tag structure start from `loc` offset.
// By default, its decodes in .der encoding rule, if you want more control, pass your `Params`.
// Its return Tag and next offset to operate on, and return error if it fails to decode.
fn Tag.decode_with_rule(bytes []u8, loc int, rule EncodingRule) !(Tag, int) {
// preliminary check
if bytes.len < 1 {
return error('Tag: bytes underflow')
}
if rule != .der && rule != .ber {
return error('Tag: unsupported rule')
}
// when accessing byte at ofset `loc` within bytes, ie, `b := bytes[loc]`,
// its maybe can lead to panic when the loc is not be checked.
if loc >= bytes.len {
return error('Tag: invalid pos')
}
mut pos := loc
// first byte of tag bytes
b := bytes[pos]
pos += 1
// First we get the first byte from the bytes, check and gets the class and constructed bits
// and the tag number marker. If this marker == 0x1f, it tells whether the tag number is represented
// in multibyte (long form), or short form otherwise.
class := int((b & tag_class_mask) >> 6)
constructed := b & constructed_mask == constructed_mask
mut number := int(b & tag_number_mask)
// check if this `number` is in long (multibyte) form, and interpretes more bytes as a tag number.
if number == 0x1f {
// we only allowed `max_tag_length` bytes following to represent tag number.
number, pos = Tag.read_tagnum(bytes, pos)!
// pos is the next position to read next bytes, so check tag bytes length
if pos >= max_tag_length + loc + 1 {
return error('Tag: tag bytes is too long')
}
if number < 0x1f && rule == .der {
// requirement for DER encoding.
// TODO: the other encoding may remove this restriction
return error('Tag: non-minimal tag')
}
}
// build the tag
tag := Tag.new(TagClass.from_int(class)!, constructed, number)!
return tag, pos
}
fn (t Tag) str() string {
cls := t.class.str()
form := if t.constructed { 'true' } else { 'false' }
number := t.number.str()
return '${cls}-${form}-${number}'
}
// bytes_len tells amount of bytes needed to store tag in base 128
fn (t Tag) bytes_len() int {
if t.number == 0 {
return 1
}
mut n := t.number
mut ret := 0
for n > 0 {
ret += 1
n >>= 7
}
return ret
}
// `tag_size` informs us how many bytes needed to store this tag includes one byte marker if in the long form.
pub fn (t Tag) tag_size() int {
// when number is greater than 31 (0x1f), its need more bytes
// to represent this number, includes one byte marker for long form tag
len := if t.number < 0x1f { 1 } else { t.bytes_len() + 1 }
return len
}
// to_bytes_in_base128 serializes tag number into bytes in base 128
fn (t Tag) to_bytes_in_base128(mut dst []u8) ! {
n := t.bytes_len()
for i := n - 1; i >= 0; i-- {
mut o := u8(t.number >> u32(i * 7))
o &= 0x7f
if i != 0 {
o |= 0x80
}
dst << o
}
}
// Tag.read_tagnum read the tag number from bytes from offset pos in base 128.
// Its return deserialized Tag number and next offset to process on.
fn Tag.read_tagnum(bytes []u8, pos int) !(int, int) {
tnum, next := Tag.read_tagnum_with_rule(bytes, pos, .der)!
return tnum, next
}
// read_tagnum_with_rule is the main routine to read the tag number part in the bytes source,
// start from offset loc in base 128. Its return the tag number and next offset to process on, or error on fails.
fn Tag.read_tagnum_with_rule(bytes []u8, loc int, rule EncodingRule) !(int, int) {
if loc > bytes.len {
return error('Tag number: invalid pos')
}
mut pos := loc
mut ret := 0
for s := 0; pos < bytes.len; s++ {
ret <<= 7
b := bytes[pos]
if s == 0 && b == 0x80 {
if rule == .der {
// requirement for DER encoding
return error('Tag number: integer is not minimally encoded')
}
}
ret |= b & 0x7f
pos += 1
if b & 0x80 == 0 {
if ret > max_tag_number {
return error('Tag number: base 128 integer too large')
}
if ret < 0 {
return error('Negative tag number')
}
return ret, pos
}
}
return error('Tag: truncated base 128 integer')
}
fn (t Tag) valid_supported_universal_tagnum() bool {
return t.class == .universal && t.number < max_universal_tagnumber
}
// `universal_tag_type` turns this Tag into available UNIVERSAL class of TagType,
// or return error if it is unknown number.
pub fn (t Tag) universal_tag_type() !TagType {
// currently, only support Standard universal tag number
if t.number > max_universal_tagnumber {
return error('Tag number: unknown TagType number=${t.number}')
}
match t.class {
.universal {
match t.number {
// vfmt off
0 { return .reserved }
1 { return .boolean }
2 { return .integer }
3 { return .bitstring }
4 { return .octetstring }
5 { return .null }
6 { return .oid }
7 { return .objdesc }
8 { return .external }
9 { return .real }
10 { return .enumerated }
11 { return .embedded }
12 { return .utf8string }
13 { return .relativeoid }
14 { return .time }
16 { return .sequence }
17 { return .set }
18 { return .numericstring }
19 { return .printablestring }
20 { return .t61string }
21 { return .videotexstring }
22 { return .ia5string }
23 { return .utctime }
24 { return .generalizedtime }
25 { return .graphicstring }
26 { return .visiblestring }
27 { return .generalstring }
28 { return .universalstring }
29 { return .characterstring }
30 { return .bmpstring }
31 { return .date }
32 { return .time_of_day }
33 { return .date_time }
34 { return .duration }
35 { return .i18_oid }
36 { return .relative_i18_oid }
else {
return error('reserved or unknonw number')
}
// vfmt on
}
}
else {
return error('Not universal class type')
}
}
}
// TagClass is an enum of ASN.1 tag class.
//
// To make sure ASN.1 encodings are not ambiguous, every ASN.1 type is associated with a tag.
// A tag consists of three parts: the tag class, tag form and the tag number.
// The following classes are defined in the ASN.1 standard.
pub enum TagClass {
universal = 0x00 // 0b00
application = 0x01 // 0b01
context_specific = 0x02 // 0b10
private = 0x03 // 0b11
}
// `TagClass.from_int` creates TagClass from integer v.
// Its return a new TagClass on success or error on fails.
fn TagClass.from_int(v int) !TagClass {
match v {
// vfmt off
0x00 { return .universal }
0x01 { return .application }
0x02 { return .context_specific }
0x03 { return .private }
else {
return error('Bad class number')
}
// vfmt on
}
}
// `TagClass.from_string` creates a TagClass from string s.
// Its return a new TagClass on success or error on fails.
fn TagClass.from_string(s string) !TagClass {
match s {
// vfmt off
'universal' { return .universal }
'private' { return .private }
'application' { return .application }
'context_specific' { return .context_specific }
else {
return error('bad class string')
}
// vfmt on
}
}
fn (c TagClass) str() string {
match c {
.universal { return 'universal' }
.application { return 'application' }
.context_specific { return 'context_specific' }
.private { return 'private' }
}
}
// TagType is standard UNIVERSAL tag number. Some of them was deprecated,
// so its not going to be supported on this module.
pub enum TagType {
// vfmt off
reserved = 0 // reserved for BER
boolean = 1 // BOOLEAN type
integer = 2 // INTEGER type
bitstring = 3 // BIT STRING
octetstring = 4 // OCTET STRING
null = 5 // NULL
oid = 6 // OBJECT IDENTIFIER
objdesc = 7 // OBJECT DESCRIPTOR
external = 8 // INSTANCE OF, EXTERNAL
real = 9 // REAL
enumerated = 10 // ENUMERATED
embedded = 11 // EMBEDDED PDV
utf8string = 12 // UTF8STRING
relativeoid = 13 // RELATIVE-OID
// deprecated
time = 14
// 0x0f is reserved
sequence = 16 // SEQUENCE, SEQUENCE OF, Constructed
set = 17 // SET, SET OF, Constructed
numericstring = 18 // NUMERICSTRING
printablestring = 19 // PRINTABLESTRING
t61string = 20 // TELETEXSTRING, 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
// unsupported
date = 0x1f
time_of_day = 0x20
date_time = 0x21
duration = 0x22
// Internationalized OID
i18_oid = 0x23
// Internationalized Relative OID
// Reserved 0x25 and above
relative_i18_oid = 0x24
// vfmt on
}
fn (t TagType) str() string {
match t {
.boolean { return 'BOOLEAN' }
.integer { return 'INTEGER' }
.bitstring { return 'BITSTRING' }
.octetstring { return 'OCTETSTRING' }
.null { return 'NULL' }
.oid { return 'OID' }
.objdesc { return 'OBJECT_DESCRIPTOR' }
.external { return 'EXTERNAL' }
.real { return 'REAL' }
.enumerated { return 'ENUMERATED' }
.embedded { return 'EMBEDDED' }
.utf8string { return 'UTF8STRING' }
.relativeoid { return 'RELATIVEOID' }
.time { return 'TIME' }
.sequence { return 'SEQUENCE_OR_SEQUENCEOF' }
.set { return 'SET_OR_SET_OF' }
.numericstring { return 'NUMERICSTRING' }
.printablestring { return 'PRINTABLESTRING' }
.t61string { return 'T61STRING' }
.videotexstring { return 'VIDEOTEXSTRING' }
.ia5string { return 'IA5STRING' }
.utctime { return 'UTCTIME' }
.generalizedtime { return 'GENERALIZEDTIME' }
.graphicstring { return 'GRAPHICSTRING' }
.visiblestring { return 'VISIBLESTRING' }
.generalstring { return 'GENERALSTRING' }
.universalstring { return 'UNIVERSALSTRING' }
.characterstring { return 'CHARACTERSTRING' }
.bmpstring { return 'BMPSTRING' }
else { return 'UNSUPPORTED_TAG_TYPE' }
}
}
// universal_tag_from_int gets default Tag for universal class type from integer value v.
fn universal_tag_from_int(v int) !Tag {
if v < 0 || v > max_universal_tagnumber {
return error('Get unexpected tag number for universal type')
}
match v {
// vfmt off
1 { return default_boolean_tag }
2 { return default_integer_tag }
3 { return default_bitstring_tag }
4 { return default_octetstring_tag }
5 { return default_null_tag }
6 { return default_oid_tag }
10 { return default_enumerated_tag }
12 { return default_utf8string_tag }
16 { return default_sequence_tag }
17 { return default_set_tag }
18 { return default_numericstring_tag }
19 { return default_printablestring_tag }
22 { return default_ia5string_tag }
23 { return default_utctime_tag }
24 { return default_generalizedtime_tag }
26 { return default_visiblestring_tag }
else {
// should in primitive form
return Tag{.universal, false, v}
}
// vfmt on
}
}
// EncodingRule is standard of rule thats drives how some ASN.1
// element was encoded or deserialized.
pub enum EncodingRule {
// Distinguished Encoding Rules (DER)
der = 0
// Basic Encoding Rules (BER)
ber = 1
// Octet Encoding Rules (OER)
oer = 2
// Packed Encoding Rules (PER)
per = 3
// XML Encoding Rules (XER)
xer = 4
}
// EXPLICIT and IMPLICIT MODE
//
// TaggedMode is way of rule how some element is being wrapped (unwrapped).
// Explicit rule add new (outer) tag to the existing element,
// where implicit rule replaces the tag of existing element.
pub enum TaggedMode {
explicit
implicit
}
// `TaggedMode.from_string` creates TaggedMode from string s.
fn TaggedMode.from_string(s string) !TaggedMode {
match s {
'explicit' { return .explicit }
'implicit' { return .implicit }
// empty string marked as .explicit
'' { return .explicit }
else { return error('Bad string for tagged mode') }
}
}
fn (m TaggedMode) str() string {
match m {
.explicit { return 'explicit' }
.implicit { return 'implicit' }
}
}
+162
View File
@@ -0,0 +1,162 @@
// Copyright (c) 2022, 2024 blackshirt. All rights reserved.
// Use of this source code is governed by a MIT License
// that can be found in the LICENSE file.
module asn1
struct TagCreation {
class TagClass
constructed bool
number int
err IError
}
fn test_tag_new_constructor() {
tags := [
TagCreation{.universal, false, 1, none},
TagCreation{.universal, true, 1, none},
// sequence should be constructed set
TagCreation{.universal, true, 16, none},
TagCreation{.universal, false, 16, error('For SEQUENCE(OF) or SET(OF) type, should be in constructed form')},
// set
TagCreation{.universal, true, 17, none},
TagCreation{.universal, false, 17, error('For SEQUENCE(OF) or SET(OF) type, should be in constructed form')},
//
TagCreation{.universal, false, 28, none},
TagCreation{.universal, false, 0x1f, none}, // 31
TagCreation{.universal, false, 0x7f, none},
TagCreation{.universal, false, 255, none}, // 0xff
TagCreation{.universal, true, 255, none}, // 0xff
TagCreation{.universal, true, 16380, error('Not a valid tag number for universal class=16380')},
// Context Specific should be in constructed form
TagCreation{.context_specific, true, 128, none}, // 0x80
TagCreation{.context_specific, false, 128, error('Context Specific should be in constructed form')}, // 0x80
// Others was as is
TagCreation{.private, false, 256, none},
TagCreation{.private, true, 16383, none},
TagCreation{.private, true, 16500, error('Unallowed tag number, 16500 exceed limit')},
TagCreation{.application, false, 16000, none},
TagCreation{.application, false, 16384, error('Unallowed tag number, 16384 exceed limit')},
TagCreation{.application, true, 65535, error('Unallowed tag number, 65535 exceed limit')},
]
for i, c in tags {
// creates tag with constructed bit set
t := Tag.new(c.class, c.constructed, c.number) or {
assert err == c.err
continue
}
assert t.tag_class() == c.class
assert t.is_constructed() == c.constructed
assert t.tag_number() == c.number
}
}
struct TagLengthTest {
class TagClass
number int
explength int
err IError
}
fn test_class_tag_length_handling() ! {
tags := [
TagLengthTest{.universal, 0, 1, none},
TagLengthTest{.universal, 1, 1, none},
TagLengthTest{.universal, 28, 1, none},
TagLengthTest{.universal, 0x1f, 2, none}, // 31
TagLengthTest{.universal, 0x7f, 2, none},
TagLengthTest{.context_specific, 128, 3, none}, // 0x80
TagLengthTest{.universal, 255, 3, none}, // 0xff
TagLengthTest{.private, 256, 3, error('Not a valid tag number for universal class=256')},
TagLengthTest{.universal, 16380, 3, error('Not a valid tag number for universal class=16380')},
TagLengthTest{.private, 16383, 3, none},
TagLengthTest{.application, 16384, 4, error('Unallowed tag number, 16384 exceed limit')},
TagLengthTest{.application, 65535, 4, error('Unallowed tag number, 65535 exceed limit')},
]
for i, c in tags {
// creates tag with constructed bit set
t := Tag.new(c.class, true, c.number) or {
assert err == c.err
continue
}
n := t.tag_size()
assert n == c.explength
}
}
struct TagDecodeTest {
bytes []u8
class TagClass
constructed bool
number int
lastpos int
err IError
}
fn test_tag_decode() ! {
data := [
// 0b1000_000 telss its class is .context_specific, but constructed bit was not set
TagDecodeTest{[u8(0x80), 0x01], .context_specific, false, 0, 1, none},
TagDecodeTest{[u8(0xa0), 0x01], .context_specific, true, 0, 1, none}, //{2, 0, 1, true}},
TagDecodeTest{[u8(0x02), 0x00], .universal, false, 2, 1, none},
TagDecodeTest{[u8(0xfe), 0x00], .private, true, 30, 1, none},
TagDecodeTest{[u8(0x1f), 0x1f, 0x00], .universal, false, 31, 2, none}, // high tag form
TagDecodeTest{[u8(0x1f), 0x81, 0x00, 0x00], .universal, false, 128, 3, none},
TagDecodeTest{[u8(0x1f), 0x81, 0x80, 0x01, 0x00], .universal, false, 16385, 4, error('Tag number: base 128 integer too large')}, // 1x128^2 + 0x128^1 + 1x128*0
TagDecodeTest{[u8(0x00), 0x81, 0x80], .universal, false, 0, 1, none},
TagDecodeTest{[u8(0x00), 0x83, 0x01, 0x00], .universal, false, 0, 1, none},
TagDecodeTest{[u8(0x1f), 0x85], .universal, false, 0, 1, error('Tag: truncated base 128 integer')},
TagDecodeTest{[u8(0x1f), 0x85, 0x81], .universal, false, 0, 0, error('Tag: truncated base 128 integer')},
TagDecodeTest{[u8(0x30), 0x80], .universal, true, 0x10, 1, none},
TagDecodeTest{[u8(0xa0), 0x82, 0x00, 0xff], .context_specific, true, 0, 1, none},
]
for i, c in data {
// dump(i)
tag, pos := Tag.decode(c.bytes) or {
assert err == c.err
continue
}
assert tag.class == c.class
assert tag.constructed == c.constructed
assert tag.number == c.number
assert pos == c.lastpos
}
}
struct TagNumberTest {
num int
class TagClass
constructed bool
exp []u8
err IError
}
fn test_serialize_tag() ! {
data := [
TagNumberTest{0, .universal, false, [u8(0x00)], none},
TagNumberTest{32, .universal, false, [u8(0x1f), 0x20], none}, // multibyte tag: 0x1f 0x20
TagNumberTest{255, .universal, false, [u8(0x1f), 0x81, 0x7f], none}, // multibyte tag: 0x1f 0x81 0x7f
TagNumberTest{0, .universal, true, [u8(0x20)], none}, // bits 6 set, 0010 0000 == 32
TagNumberTest{1, .universal, true, [u8(0x21)], none}, // bits 6 set, 0010 0001 == 31
TagNumberTest{32, .universal, true, [u8(0x3f), 0x20], none}, // multibyte tag: 00111111 0x20
TagNumberTest{32, .application, true, [u8(0x7f), 0x20], none}, // multibyte tag: 127 (01111111) 0x20
TagNumberTest{32, .context_specific, true, [u8(0xbf), 0x20], none}, // multibyte tag: 197 (10111111) 0x20
TagNumberTest{32, .private, true, [u8(0xff), 0x20], none}, // multibyte tag: 255 (11111111) 0x20
TagNumberTest{255, .context_specific, true, [u8(0xbf), 0x81, 0x7f], none}, // multibyte tag: 0xbf 0x81 0x7f
// private class 0xdf = 0b1101_1111
TagNumberTest{16383, .private, false, [u8(0xdf), 0xff, 0x7f], none}, // multibyte tag: 0x1f 0xff 0x7f
// overflow max_tag_value
TagNumberTest{16385, .context_specific, true, [u8(0x1f), 0xff, 0x7f], error('Unallowed tag number, 16385 exceed limit')}, // multibyte tag: 0x1f 0xff 0x7f
]
for i, c in data {
mut dst := []u8{}
tag := Tag.new(c.class, c.constructed, c.num) or {
assert err == c.err
continue
}
tag.encode(mut dst)!
assert dst == c.exp
}
}
+300
View File
@@ -0,0 +1,300 @@
// Copyright (c) 2022, 2024 blackshirt. All rights reserved.
// Use of this source code is governed by a MIT License
// that can be found in the LICENSE file.
module asn1
// for bytes compare
import crypto.internal.subtle { constant_time_compare }
// This file contains structures and routines for handling ASN.1 Element.
// Its includes:
// - basic Element interface, for support ASN.1 element in more generic way
// - arrays of Element in the form of ElementList
// - basic raw element in the RawElement structure, for handling arbitrary class
// and other undefined (unsupported) generic ASN.1 Element in this module.
// - others structures, likes an Choice, AnyDefinedBy, Optional for representing other
// element
// Element represents a generic ASN.1 Element.
// Most of the standard Universal class element defined on this module
// satisfies this interface. This interface was also expanded by methods
// defined on this interface.
pub interface Element {
// tag tells the ASN.1 identity of this Element.
tag() Tag
// payload tells the payload (values) of this Element.
// The element's size was calculated implicitly from payload.len
// Its depends on the tag how interpretes this payload.
payload() ![]u8
}
// from_object[T] transforms and creates a new Element from generic type (maybe universal type, like an OctetString).
// Its accepts generic element t that you should pass to this function. You should make sure if this element implements
// required methods of the Element, or an error would be returned.
// FIXME: its not tested.
// Examples:
// ```v
// oc := asn1.OctetString.new("xxx")!
// el := asn1.Element.from_object[OctetString](oc)!
// ```
// and then treats your OctetString as an Element
pub fn Element.from_object[T](t T) !Element {
$if T !is Element {
return error('Not holding element')
}
return t
}
// into_object[T] transforms and tries to cast element el into generic object T
// if the element not holding object T, it would return error.
// NOTE: Not tested.
// Examples:
// ```v
// oc := asn1.OctetString.new("xxx")!
// el := asn1.Element.from_object[OctetString](oc)!
// ```
// cast back the element into OctetString.
// ```v
// os := el.into_object[OctetString]()!
// ```
// and then treats os as an OctetString.
pub fn (el Element) into_object[T]() !T {
if el is T {
return *el
}
return error('Element el does not holding T}')
}
// length tells the payload length of this element.
pub fn (el Element) length() !int {
return el.payload()!.len
}
// UTILITY HELPER FOR ELEMENT
//
// Helper for validates FieldOptions.
//
// validate_options validates FieldOptions is a valid option agains current element.
fn (el Element) validate_options(fo FieldOptions) ! {
el.validate_wrapper(fo)!
el.validate_optional(fo)!
el.validate_default(fo)!
}
// validate_wrapper validates wrapper's part of fields options.
fn (el Element) validate_wrapper(fo FieldOptions) ! {
// Validates wrapper part
// Its discard all check when fo.cls is empty string, its marked as non-wrapped element.
if fo.cls != '' {
fo.check_wrapper()!
}
}
// validate_default validates has_default part of field options
fn (el Element) validate_default(fo FieldOptions) ! {
// Validates default part
if fo.has_default {
if fo.default_value == none {
return error('has_default withoud default value')
}
def := fo.default_value or { return err }
if !el.tag().equal(def.tag()) {
return error('You provides different tag of default_value with tag of current element')
}
}
}
fn (el Element) validate_optional(fo FieldOptions) ! {
// Validates Optional part
// If the element is already optional, you cant make it optional again by setting optional=true
if fo.optional {
if el is Optional {
return error('You cant mark Optional element as nested Optional')
}
}
}
// KeyDefault is map of string (field.name) into Element for element with default semantic.
// its is to be used for building payload of complex structures like sequence.
// see `make_payload` below.
pub type KeyDefault = map[string]Element
// new_key_default creates empty KeyDefault maps.
pub fn new_key_default() KeyDefault {
return KeyDefault(map[string]Element{})
}
// `make_payload` builds bytes of payload for some structures contains field of Elements.
// Consider this examples from RFC 5280 defines schema.
// ```v
// Certificate ::= SEQUENCE {
// tbsCertificate TBSCertificate,
// signatureAlgorithm AlgorithmIdentifier,
// signatureValue BIT STRING }
// ```
// where your structure defined as:.
// ```v
// struct Certificate {
// tbs_certificate TBSCertificate
// signature_algorithm AlgorithmIdentifier
// signature_value BitString
// }
// ```
//
// Usually you can do.
//
// ```v
// cert := Certificate.new()!
// payload := asn1.make_payload[Certificate](cert)!
// ```
//
// and then you can use the produced payload.
//
// BUG: there are some issues would you encounter when your T contains
// fields that have generic in it, its would produce unexpected result,
// see detail bug on: https://github.com/vlang/v/issues/22721.
// So, just use this when your T.fields is not contains generic within it.
// UPDATED: This issue has been fixed in this PR [#22724](https://github.com/vlang/v/pull/22724)
// Thanks to @felipensp
pub fn make_payload[T](val T, kd KeyDefault) ![]u8 {
mut out := []u8{}
$for field in val.fields {
// only serialiaze field that implement interfaces
// Issue: `$if field.typ is Element` check would false when field.type contains generic structure.
// even the `field.type` is fullfills the interfaces.
// see detail bug on: https://github.com/vlang/v/issues/22721
$if field.typ is Element {
// if there attributes option
if field.attrs.len != 0 {
mut fo := FieldOptions.from_attrs(field.attrs)!
// TODO: add keyDefault support
if fo.has_default {
// install default by getting default element from map
key := field.name
def_elem := kd[key] or { return error('missing defaul element') }
fo.install_default(def_elem, false)!
}
current := encode_with_field_options(val.$(field.name), fo)!
out << current
} else {
// without option
current := encode(val.$(field.name))!
out << current
}
}
}
return out
}
// `encoded_len` calculates the size in bytes when the el element was serialized.
pub fn encoded_len(el Element) int {
return el.encoded_len()
}
// `encoded_len` calculates the length of bytes when this element was serialized.
pub fn (el Element) encoded_len() int {
return el.encoded_len_with_rule(.der)
}
// encoded_len_with_rule informs us the length of bytes when this element serialized into bytes.
// Different rule maybe produces different result.
fn (el Element) encoded_len_with_rule(rule EncodingRule) int {
mut n := 0
n += el.tag().tag_size()
payload := el.payload() or { panic(err) }
length := Length.new(payload.len) or { panic(err) }
n += length.length_size_with_rule(rule) or { panic(err) }
n += payload.len
return n
}
// equal checks whether this two element equal and holds the same tag and content
pub fn (el Element) equal(other Element) bool {
return (el.tag().equal(other.tag())) && (el.equal_payload(other))
}
fn (el Element) equal_payload(other Element) bool {
// taken from crypto.internal.subtle
x := el.payload() or { panic(err) }
y := other.payload() or { panic(err) }
return constant_time_compare(x, y) == 1
}
fn Element.decode(src []u8) !(Element, int) {
return Element.decode_with_rule(src, 0, .der)!
}
// decode deserializes back bytes in src from offet `loc` into Element.
// Basically, its tries to parse a Universal class Element when it is possible.
fn Element.decode_with_rule(src []u8, loc int, rule EncodingRule) !(Element, int) {
tag, length_pos := Tag.decode_with_rule(src, loc, rule)!
length, content_pos := Length.decode_with_rule(src, length_pos, rule)!
// get the bytes
bytes := if length == 0 {
[]u8{}
} else {
if content_pos >= src.len || content_pos + length > src.len {
return error('Need more bytes to read content')
}
unsafe { src[content_pos..content_pos + length] }
}
next_pos := content_pos + length
elem := parse_element(tag, bytes)!
return elem, next_pos
}
// ElementList.
//
// ElementList is arrays of Element.
// Many places maybe required this wells, likes Sequence or Set fields.
pub type ElementList = []Element
// payload produces bytes array from arays of Element.
pub fn (els ElementList) payload() ![]u8 {
return els.payload_with_rule(.der)!
}
fn (els ElementList) payload_with_rule(rule EncodingRule) ![]u8 {
mut out := []u8{}
for el in els {
bytes := encode_with_rule(el, rule)!
out << bytes
}
return out
}
// encoded_len tells the length of bytes when the ElementList was serialized.
pub fn (els ElementList) encoded_len() int {
mut n := 0
for el in els {
n += el.encoded_len()
}
return n
}
// ElementList.from_bytes parses bytes in src as series of Element or return error on fails.
// Its does not support trailing data.
pub fn ElementList.from_bytes(src []u8) ![]Element {
mut els := []Element{}
if src.len == 0 {
// empty list
return els
}
mut i := int(0)
for i < src.len {
el, pos := Element.decode_with_rule(src, i, .der)!
i = pos
els << el
}
if i > src.len {
return error('i > src.len')
}
if i < src.len {
return error('The src contains unprocessed bytes')
}
return els
}
@@ -0,0 +1,202 @@
// Copyright (c) 2022, 2024 blackshirt. All rights reserved.
// Use of this source code is governed by a MIT License
// that can be found in the LICENSE file.
module asn1
// Handling of deserialization of bytes array into some Element.
//
// decode decodes single element from bytes, its not allowing trailing data.
//
// Examples:
//
// Original object was Utf8String with tag == 12 (0c)
// ```v
// import x.encoding.asn1
//
// original_obj := asn1.Utf8String.new('hi')!
// bytes_data := [u8(0x0C), 0x02, 0x68, 0x69]
// decoded_obj := asn1.decode(bytes_data)!
// assert decoded_obj.equal(original_obj)
// ```
pub fn decode(bytes []u8) !Element {
// call Element.decode_with_rule directly
el, pos := Element.decode_with_rule(bytes, 0, .der)!
if pos > bytes.len {
return error('decode on data with trailing data')
}
return el
}
// decode_with_options decodes single element from bytes with options support, its not allowing trailing data.
// Its accepts options string to drive decoding process.
//
// Examples:
//
// `UTF8String` with implicit tagging definded as `[5] IMPLICIT UTF8String` was encoded into `85 02 68 69`
//
// ```v
// original_obj := asn1.Utf8String.new('hi')!
// implicit_bytes := [u8(0x85), 0x02, 0x68, 0x69]
// obj_2 := asn1.decode_with_options(implicit_bytes, 'context_specific:5;implicit;inner:12')!
//
// assert obj_2.equal(original_obj)
// dump(obj_2) // Output: obj_2: asn1.Element(Utf8String: (hi))
// ```
//
// `UTF8String` with explicit tagging defined as `[5] EXPLICIT UTF8String` was encoded into `A5 04 0C 02 68 69`
//
// ```v
// explicit_bytes := [u8(0xA5), 0x04, 0x0C, 0x02, 0x68, 0x69]
// obj_3 := asn1.decode_with_options(explicit_bytes, 'context_specific:5;explicit;inner:0x0c')!
//
// assert obj_3.equal(original_obj)
// dump(obj_3) // output: obj_3: asn1.Element(Utf8String: (hi))
// ```
pub fn decode_with_options(bytes []u8, opt string) !Element {
// if null-option length, call Element.decode_with_rule directly
if opt.len == 0 {
el, pos := Element.decode_with_rule(bytes, 0, .der)!
if pos > bytes.len {
return error('decode on data with trailing data')
}
return el
}
fo := FieldOptions.from_string(opt)!
return decode_with_field_options(bytes, fo)!
}
// decode_with_field_options is similar to `decode_with_options`, but its more controllable through FieldOptions.
pub fn decode_with_field_options(bytes []u8, fo FieldOptions) !Element {
// TODO
if bytes.len == 0 {
return error('Empty bytes')
}
fo.check_wrapper()!
// check for optional, and return it, maybe nil optional
// expected optional tag is outer wrapper tag
wrp_tag := fo.wrapper_tag()!
if fo.optional {
opt := decode_optional(bytes, wrp_tag)!
return opt
}
// read an element from bytes
mut p := Parser.new(bytes)
tlv := p.read_tlv()!
p.finish()!
// semantically no wraps
if fo.cls == '' {
return tlv
}
// wrapped
if tlv.tag().class != wrp_tag.class {
return error('Get different class')
}
if tlv.tag().number != wrp_tag.number {
return error('Get different tag number')
}
// TODO: default
return tlv.unwrap_with_field_options(fo)!
}
fn decode_optional(bytes []u8, expected_tag Tag) !Element {
mut p := Parser.new(bytes)
ct := p.peek_tag()!
// when the tag is equal expected_tag, this mean, present this optional element
if ct.equal(expected_tag) {
// present
el := p.read_tlv()!
mut opt := Optional.new(el, true)!
return opt
}
// optional element with no-presence semantic
el := RawElement.new(expected_tag, []u8{})!
opt := Optional.new(el, false)!
return opt
}
// unwrap_with_options performs unwrapping operations to the element with options provided.
// Its technically reverse operation of the `.wrap()` applied to the element
// with the same options. If you provide with diferent options,
// the result is in undesired behaviour, even its success
pub fn (el Element) unwrap_with_options(opt string) !Element {
if opt.len == 0 {
return el
}
fo := FieldOptions.from_string(opt)!
return el.unwrap_with_field_options(fo)!
}
// unwrap_with_field_options performs unwrapping operations with FieldOptions.
pub fn (el Element) unwrap_with_field_options(fo FieldOptions) !Element {
if fo.cls == '' {
// no unwrap
return el
}
el.validate_options(fo)!
// first, checks class of the element being to unwrap, should not universal class.
if el.tag().class == .universal {
return error('you cant unwrap universal element')
}
// its also happens to fo.cls, should not be an universal class
if fo.cls == 'universal' {
return error('you cant unwrap universal element')
}
// element being unwrap should have matching with tag within options.
mode := TaggedMode.from_string(fo.mode)!
inner_tag := fo.inner_tag()!
if mode == .explicit {
if !el.tag().constructed {
return error('explicit mode should have constructed tag')
}
// checks inner tag from payload
tag, _ := Tag.decode_with_rule(el.payload()!, 0, .der)!
if !tag.equal(inner_tag) {
return error('Get unexpected inner tag from payload')
}
}
inner_form := inner_tag.constructed
constructed := if mode == .explicit { true } else { inner_form }
// check for tag equality
// The tag of element being unwrapped with tag from FieldOptions should matching.
// Its should comes from same options on wrapping.
cls := TagClass.from_string(fo.cls)!
if el.tag().class != cls {
return error('unmatching outer tag class')
}
built_tag := Tag.new(cls, constructed, fo.tagnum)!
// check outer tag equality
if !el.tag().equal(built_tag) {
return error('Element tag unequal with tag from options')
}
// return unwrapped element
return unwrap(el, mode, inner_tag)!
}
// unwrap the provided element, turn into inner element.
fn unwrap(el Element, mode TaggedMode, inner_tag Tag) !Element {
match mode {
.explicit {
// el.payload is serialized of inner element
bytes := el.payload()!
inner_elem := decode(bytes)!
// recheck the tag
if !inner_elem.tag().equal(inner_tag) {
return error('unmatching inner_tag')
}
return inner_elem
}
.implicit {
// el.payload() is content of inner element
bytes := el.payload()!
inner_elem := parse_element(inner_tag, bytes)!
return inner_elem
}
}
}
@@ -0,0 +1,29 @@
// Copyright (c) 2022, 2024 blackshirt. All rights reserved.
// Use of this source code is governed by a MIT License
// that can be found in the LICENSE file.
module asn1
// examples data was taken examples from https://letsencrypt.org/id/docs/a-warm-welcome-to-asn1-and-der/#explicit-vs-implicit
fn test_decode_element_with_or_without_options() ! {
// Utf8String with tag == 12 (0c)
original_obj := Utf8String.new('hi')!
// without options
normal := [u8(0x0C), 0x02, 0x68, 0x69]
obj_1 := decode(normal)!
assert obj_1.equal(original_obj)
// with implicit definded as [5] IMPLICIT UTF8String was serialized into 85 02 68 69
implicit_data := [u8(0x85), 0x02, 0x68, 0x69]
obj_2 := decode_with_options(implicit_data, 'context_specific:5;implicit;inner:12')!
assert obj_2.equal(original_obj)
// dump(obj_2) // Output: obj_2: asn1.Element(Utf8String: (hi))
// as explicit tagging defined as [5] EXPLICIT UTF8String encoded into A5 04 0C 02 68 69
explicit_data := [u8(0xA5), 0x04, 0x0C, 0x02, 0x68, 0x69]
obj_3 := decode_with_options(explicit_data, 'context_specific:5;explicit;inner:0x0c')!
assert obj_3.equal(original_obj)
// dump(obj_3) // output: obj_3: asn1.Element(Utf8String: (hi))
}
@@ -0,0 +1,192 @@
// Copyright (c) 2022, 2024 blackshirt. All rights reserved.
// Use of this source code is governed by a MIT License
// that can be found in the LICENSE file.
module asn1
// Handling functionality of Element's serialization.
//
// `encode` serializes element into bytes array. By default, its encode in .der rule with empty options.
// See `encode_with_options` if you want pass an option string. See `field.v` for more option in detail.
//
// Examples:
//
// ```v
// import x.encoding.asn1
//
// obj := asn1.Utf8String.new('hi')!
// out := asn1.encode(obj)!
// assert out == [u8(0x0C), 0x02, 0x68, 0x69]
// ```
pub fn encode(el Element) ![]u8 {
// without options, we call `.encode_with_rule` directly on element.
return encode_with_rule(el, .der)!
}
// `encode_with_options` serializes element into bytes array with options string passed to drive the result.
//
// Examples:
//
// `Utf8String` defined as `[5] IMPLICIT UTF8String` was encoded into `85 02 68 69`.
// `Utf8String` defined as `[5] EXPLICIT UTF8String` was encoded into `A5 04 0C 02 68 69`.
//
// ```v
// obj := asn1.Utf8String.new('hi')!
// implicit_out := asn1.encode_with_options(obj, 'context_specific:5;implicit;inner:12')!
// assert implicit_out == [u8(0x85), 0x02, 0x68, 0x69]
//
// explicit_out := asn1.encode_with_options(obj, 'context_specific:5;explicit;inner:0x0c')!
// assert explicit_out == [u8(0xA5), 0x04, 0x0C, 0x02, 0x68, 0x69]
// ```
pub fn encode_with_options(el Element, opt string) ![]u8 {
// treated as without option when empty
if opt.len == 0 {
return encode_with_rule(el, .der)!
}
fo := FieldOptions.from_string(opt)!
return encode_with_field_options(el, fo)!
}
// `encode_with_field_options` serializes this element into bytes array with options defined in fo.
pub fn encode_with_field_options(el Element, fo FieldOptions) ![]u8 {
// validates options again this element.
el.validate_options(fo)!
// check for default_value for this element
// if we have it matching with current element,
// by default, in .der mode, it should not be serialized.
if fo.has_default {
def_element := fo.default_value or { return error('bad default_value') }
// If this element is equal with default_value, by default its should not be serialized.
if el.equal(def_element) {
return []u8{}
}
}
// apply field options, turns this element
// into optional, wrapped element or original one.
new_el := el.apply_field_options(fo)!
// if new_el is Optional, encode with optional behaviour
if new_el is Optional {
return new_el.encode()!
}
// otherwise, just serializing it
return encode_with_rule(new_el, .der)!
}
// Helper for wrapping element
//
//
// into_optional turns this element into Optional with present bit.
// When you set with_present into true, its makes this optional was present.
fn (el Element) into_optional(with_present bool) !Element {
if el is Optional {
return error('already optional element')
}
return Optional.new(el, with_present)!
}
// apply_field_options applies rules in field options into current element
// and turns this into another element.
// by default, optional attribute is more higher precedence over wrapper attribut, ie,
// take the wrap step and then turn into optional (if true)
fn (el Element) apply_field_options(fo FieldOptions) !Element {
el.validate_options(fo)!
// if there a wrapper
if fo.cls != '' {
wrapped_el := el.wrap_with_options(fo)!
if fo.optional {
return wrapped_el.into_optional(fo.present)!
}
// not-optional, just return wrapped element
return wrapped_el
}
// no-wrapper, check for optional
if fo.optional {
return el.into_optional(fo.present)!
}
// otherwise, its no-wrapper and non-optional
return el
}
// set_default_value installs default value within FieldOptions for the element
pub fn (el Element) set_default_value(mut fo FieldOptions, value Element) ! {
// the default tag should match with the current tag
if !el.tag().equal(value.tag()) {
return error('unmatching tag of default value')
}
fo.install_default(value, false)!
el.validate_default(fo)!
}
// wrap_with_rule wraps universal element into another class.
// we prohibit dan defines some rules when its happen and returns an error instead
// 1. wrapping into .universal class is not allowed
// 2. wrapping with the same class is not allowed too
// 3. wrapping non-universal class element is not allowed (maybe removed on futures.)
// Notes :
// Three additional information about tagging:
// CHOICEs are always explicitly tagged even if implicit tagging is in effect.
// EXPLICIT TAGs are always constructed, they encapsulate the TLV they prefix.
// An IMPLICIT TAG 'inherits' the constructed bit of the TLV whose 'T' is overwritten,
// examples:
// a) '[5] IMPLICIT INTEGER' has tag 0x85 (overwriting 0x02 = INTEGER)
// b) '[5] IMPLICIT SEQUENCE' has tag 0xA5 (overwriting 0x30 = SEQUENCE, CONSTRUCTED)
fn (el Element) wrap_with_options(fo FieldOptions) !Element {
// validates options.
el.validate_options(fo)!
mode := TaggedMode.from_string(fo.mode)!
cls := TagClass.from_string(fo.cls)!
return wrap(el, cls, fo.tagnum, mode)!
}
// wrao performs wrapping to element and turns this element into another one.
fn wrap(el Element, cls TagClass, number int, mode TaggedMode) !Element {
if el is Optional {
return error('Optional cant be wrapped')
}
if cls == .universal {
return error('you cant wrap into universal')
}
match cls {
.context_specific {
return ContextElement.from_element(el, number, mode)!
}
.application {
return ApplicationElement.from_element(el, number, mode)!
}
.private {
return PrivateELement.from_element(el, number, mode)!
}
else {
return error('Wraps to the wrong class')
}
}
}
// encode_with_rule encodes element into bytes array with rule.
fn encode_with_rule(el Element, rule EncodingRule) ![]u8 {
if rule != .der && rule != .ber {
return error('Element: unsupported rule')
}
mut dst := []u8{}
// when this element is Optional without presence flag, by default would
// serialize this element into empty bytes otherwise, would serialize underlying element.
if el is Optional {
return el.encode()!
}
// otherwise, just serializes as normal
el.tag().encode_with_rule(mut dst, rule)!
// calculates the length of element, and serialize this length
payload := el.payload()!
length := Length.new(payload.len)!
length.encode_with_rule(mut dst, rule)!
// append the element payload to destination
dst << payload
return dst
}
@@ -0,0 +1,22 @@
module asn1
// taken examples from https://letsencrypt.org/id/docs/a-warm-welcome-to-asn1-and-der/#explicit-vs-implicit
fn test_encode_with_or_without_options() ! {
// Utf8String with tag == 12 (0c)
obj := Utf8String.new('hi')!
// without options
out_1 := encode(obj)!
normal := [u8(0x0C), 0x02, 0x68, 0x69]
assert out_1 == normal
// with implicit definded as [5] IMPLICIT UTF8String was serialized into 85 02 68 69
impl_expected := [u8(0x85), 0x02, 0x68, 0x69]
out_2 := encode_with_options(obj, 'context_specific:5;implicit;inner:12')!
assert out_2 == impl_expected
// as explicit tagging defined as [5] EXPLICIT UTF8String encoded into A5 04 0C 02 68 69
expl_expected := [u8(0xA5), 0x04, 0x0C, 0x02, 0x68, 0x69]
out_3 := encode_with_options(obj, 'context_specific:5;explicit;inner:0x0c')!
assert out_3 == expl_expected
}
+160
View File
@@ -0,0 +1,160 @@
module asn1
type MyOct = string
fn (mo MyOct) tag() Tag {
return default_octetstring_tag
}
fn (mo MyOct) payload() ![]u8 {
return mo.bytes()
}
type MyStr = string
fn (ms MyStr) tag() Tag {
return default_utf8string_tag
}
fn (ms MyStr) payload() ![]u8 {
return ms.bytes()
}
struct TestStruct {
n int
a MyOct
b MyStr
}
fn (t TestStruct) tag() Tag {
return Tag{.universal, true, int(TagType.sequence)} // 0x30
}
fn (t TestStruct) payload() ![]u8 {
kd := KeyDefault(map[string]Element{})
out := make_payload[TestStruct](t, kd)!
return out
}
fn test_struct_make_payload() ! {
st := TestStruct{
a: MyOct('aku')
b: MyStr('dia')
}
// TestStruct is sequence
out := encode(st)!
expected := [u8(0x30), 0x0a, (0x04), 0x03, 0x61, 0x6b, 0x75, (0x0c), 0x03, 0x64, 0x69, 0x61]
// without field option passed, its should be expected value
assert out == expected
}
fn test_into_optional() ! {
el := Boolean.new(true)
orig_expected := [u8(0x01), 0x01, 0xff]
without_option := encode(el)!
assert without_option == orig_expected
// marked this element as optional, make its serialized into empty bytes
with_option_1 := encode_with_options(el, 'optional')!
assert with_option_1 == []u8{}
}
// test for wrapping functionality
struct WrapperTest {
attr string
err IError
out []u8
}
fn test_wrapping_functionality() ! {
// raw boolean element
elem := Boolean.new(true)
orig_expected := [u8(0x01), 0x01, 0xff]
data := [
WrapperTest{'', none, orig_expected},
// Tag{.contex_specific, true, 1} = 0b1010_0001
WrapperTest{'context_specific:1; explicit; inner:1', none, [
u8(0xa1),
0x03,
0x01,
0x01,
0xff,
]},
// Tag{.contex_specific, false, 1} // 0b1000_0001 = 0x81 = 129
WrapperTest{'context_specific:1; implicit; inner:1', none, [
u8(0x81), //
0x01,
0xff,
]},
//
WrapperTest{'context_specific:1;inner:2', error('Invalid zonk or uncorerct mode value'), [
u8(0xa1),
0x01,
0xff,
]},
WrapperTest{'application:5;explicit;inner:1', none, [
u8(0x65),
0x03,
0x01,
0x01,
0xff,
]},
// same as above, but with implicit mode, 0b_01_0_00101
WrapperTest{'application:5;implicit;inner:1', none, [
u8(0x45),
0x01,
0xff,
]},
// marked as optional would not be encoded,
WrapperTest{'application:5; optional;inner:2', error('Invalid zonk or uncorerct mode value'), []u8{}},
WrapperTest{'application:5; explicit;inner:2', none, [
u8(0x65),
0x03,
0x01,
0x01,
0xff,
]},
// wrapped into universal is error
WrapperTest{'universal:50; explicit; inner:3', error('You have not provides correct options marker'), orig_expected},
// marked as an optional
WrapperTest{'private:10; optional', error('Invalid zonk or uncorerct mode value'), []u8{}},
WrapperTest{'application:5;implicit', error('You provides incorrect inner number'), []u8{}},
// 0b_11_0_00101 = c5
WrapperTest{'private:5; implicit; inner:1', none, [
u8(0xC5),
0x01,
0xff,
]},
]
for i, item in data {
out := encode_with_options(elem, item.attr) or {
assert err == item.err
continue
}
assert out == item.out
}
}
// test for ElementList
fn test_for_element_list() ! {
mut fields := ElementList([]Element{})
a := Boolean.new(true)
b := Boolean.new(false)
fields << a
fields << b
fields_payload := fields.payload()!
expected := [u8(0x01), 0x01, 0xff, u8(0x01), 1, 0]
assert fields_payload == expected
assert fields.encoded_len() == 6
els := ElementList.from_bytes(expected)!
assert els.len == 2
assert els[0] is Boolean
assert els[1] is Boolean
}
+154
View File
@@ -0,0 +1,154 @@
// Copyright (c) 2022, 2024 blackshirt. All rights reserved.
// Use of this source code is governed by a MIT License
// that can be found in the LICENSE file.
module asn1
// The default tag of ASN.1 ENUMERATED type.
const default_enumerated_tag = Tag{.universal, false, int(TagType.enumerated)}
// ASN.1 ENUMERATED TYPE.
//
// 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. In DER rule, Enumerated type should be primitive type.
pub struct Enumerated {
pub:
value int
}
fn (e Enumerated) str() string {
return 'Enumerated (${e.value})'
}
// new creates a new Enumerated from int value.
pub fn Enumerated.new(val int) Enumerated {
return Enumerated{
value: val
}
}
// The tag of enumerated element.
pub fn (e Enumerated) tag() Tag {
return default_enumerated_tag
}
// The payload of the enumerated element.
pub fn (e Enumerated) payload() ![]u8 {
return e.payload_with_rule(.der)!
}
fn Enumerated.from_bytes(bytes []u8) !Enumerated {
if !valid_bytes(bytes, true) {
return error('Enumerated: failed check')
}
mut ret := int(0)
for i := 0; i < bytes.len; i++ {
ret <<= 8
ret |= int(bytes[i])
}
ret <<= 64 - u8(bytes.len) * 8
ret >>= 64 - u8(bytes.len) * 8
if ret != int(int(ret)) {
return error('integer too large')
}
return Enumerated{
value: int(ret)
}
}
// parse into Enumerated type from parser p.
fn Enumerated.parse(mut p Parser) !Enumerated {
tag := p.read_tag()!
if !tag.equal(default_enumerated_tag) {
return error('Bad Enumerated tag')
}
length := p.read_length()!
bytes := p.read_bytes(length)!
res := Enumerated.from_bytes(bytes)!
return res
}
fn Enumerated.decode(src []u8) !(Enumerated, int) {
return Enumerated.decode_with_rule(src, .der)!
}
fn Enumerated.decode_with_rule(bytes []u8, rule EncodingRule) !(Enumerated, int) {
tag, length_pos := Tag.decode_with_rule(bytes, 0, rule)!
if !tag.equal(default_enumerated_tag) {
return error('Unexpected non-Enumerated tag')
}
length, content_pos := Length.decode_with_rule(bytes, length_pos, rule)!
content := if length == 0 {
[]u8{}
} else {
if content_pos >= bytes.len || content_pos + length > bytes.len {
return error('Enumerated: truncated payload bytes')
}
unsafe { bytes[content_pos..content_pos + length] }
}
val := Enumerated.from_bytes(content)!
next := content_pos + length
return val, next
}
fn (e Enumerated) payload_with_rule(rule EncodingRule) ![]u8 {
if rule != .der && rule != .ber {
return error('Enumerated.pack: unsupported rule')
}
mut n := e.enumerated_len()
mut dst := []u8{len: n}
for j := 0; j < n; j++ {
dst[j] = u8(e.value >> u32(n - 1 - j) * 8)
}
return dst
}
fn (e Enumerated) enumerated_len() int {
mut i := e.value
mut n := 1
for i > 127 {
n++
i >>= 8
}
for i < -128 {
n++
i >>= 8
}
return n
}
// Utility function
//
// valid_bytes validates bytes meets some requirement for BER/DER encoding.
fn valid_bytes(src []u8, signed bool) bool {
// Requirement for der encoding
// The contents octets shall consist of one or more octets.
if src.len == 0 {
return false
}
// check for minimaly encoded
// If the contents octets of an integer value encoding consist of more
// than one octet, then the bits of the first octet and bit 8 of
// the second octets shall not all be ones; and shall not all be zero.
if src.len > 1 && ((src[0] == 0 && src[1] & 0x80 == 0)
|| (src[0] == 0xff && src[1] & 0x80 == 0x80)) {
return false
}
// reject negative for unsigned type
if !signed && src[0] & 0x80 == 0x80 {
return false
}
return true
}
@@ -0,0 +1,18 @@
This file contains examples on about how to use this `asn1` module to write your ASN.1 element,
how to serialize it and write routines how to decode it. Its contains 4 of examples, ie:
- `example0.v` contains examples on how to write simple thing for some parts of
Kerberos Network Authentication protocol parser, how to define your structure
based on the ASN.1 Schema, use tagged field for wrapping element, how to do serialization
and deserialization of this object, etc.
- `example1.v` contains sample from already availables on the internet world, taken from
[oss](https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/sequence.html).
Its contains more complex structure, write serializer and deserializer for that.
- `example2.v` contains samples encoding for DER mode comes from `ITU X.690 Document`,
Especially from Annex A. Example of encodings of the document.
This examples contains some complex nested elements wrapping detailed in step by step
for building the ASN.1 DER serializer with this modules.
- `examples3.v` is an example from README files.
@@ -0,0 +1,113 @@
module main
import x.encoding.asn1
// Previously, defined type KerberosString = asn1.GeneralString directly
// without reduplicating required Element methods, would produces
// RUNTIME ERROR: invalid memory access.
// **Updates**
// Its has been resolved in [22901](https://github.com/vlang/v/issues/22901)
// Thanks to @felipensp
type KerberosString = asn1.GeneralString
fn KerberosString.new(s string) !KerberosString {
return KerberosString(asn1.GeneralString.new(s)!)
}
type KerberosStringList = []KerberosString
fn (ksl KerberosStringList) tag() asn1.Tag {
return asn1.default_sequence_tag
}
fn (ksl KerberosStringList) payload() ![]u8 {
mut out := []u8{}
for item in ksl {
// maybe produces x00000000: at ???: RUNTIME ERROR: invalid memory access
// `item` cannot be used as interface object outside `unsafe` blocks as it
// might be stored on stack. Consider declaring `KerberosString` as `@[heap]`
obj := unsafe { item }
out << asn1.encode(obj)!
}
return out
}
// PrincipalName ::= SEQUENCE {
// name-type [0] Int32,
// name-string [1] SEQUENCE OF KerberosString
// }
struct PrincipalName {
name_type asn1.Integer @[context_specific: 0; explicit; inner: 2] // integer tag = (universal, false, 2)
// defines name_string as asn1.SequenceOf[KerberosString] would produces unexpected result
// when you build payload with `asn1.make_payload`,
// see issues at https://github.com/vlang/v/issues/22721
// so we build with KerberosStringList
name_string KerberosStringList @[context_specific: 1; explicit; inner: 16] // sequence tag = (universal, true, 16)
}
fn (pn PrincipalName) tag() asn1.Tag {
return asn1.default_sequence_tag
}
fn (pn PrincipalName) payload() ![]u8 {
kd := asn1.new_key_default()
payload := asn1.make_payload[PrincipalName](pn, kd)!
return payload
}
fn PrincipalName.decode(bytes []u8) !PrincipalName {
// decode should produces Sequence type
elem := asn1.decode(bytes)!
assert elem.tag().equal(asn1.default_sequence_tag)
// cast it into Sequence type and get the fields
seq := elem.into_object[asn1.Sequence]()!
fields := seq.fields()
// every fields of the sequence is raw of wrapped element, so we should unwrap it with
// the same options used to wrap in encode step, and turn to the real underlying object.
el_name_type := fields[0].unwrap_with_options('context_specific: 0; explicit; inner: 2')!
name_type := el_name_type.into_object[asn1.Integer]()!
// tag 16 is sequence tag, its decoded into Sequence type
el_name_string := fields[1].unwrap_with_options('context_specific: 1; explicit; inner: 16')!
el_seq := el_name_string.into_object[asn1.Sequence]()!
// KerberosString string has a general_string tag, so it would be parsed as asn1.GeneralString
// so, we transforms it into KerberosString back and build KerberosStringList.
mut a := []KerberosString{}
for item in el_seq.fields() {
gst := item.into_object[asn1.GeneralString]()!
obj := KerberosString(gst)
a << obj
}
name_string_list := KerberosStringList(a)
return PrincipalName{
name_type: name_type
name_string: name_string_list
}
}
fn main() {
// Basically this is a Kerberos PrincipalName data
data := [u8(0x30), 0x15, 0xa0, 0x03, 0x02, 0x01, 0x01, 0xa1, 0x0e, 0x30, 0x0c, 0x1b, 0x0a,
0x62, 0x6f, 0x62, 0x62, 0x61, 0x2d, 0x66, 0x65, 0x74, 0x74]
els := [KerberosString.new('bobba-fett')!]
pn := PrincipalName{
name_type: asn1.Integer.from_int(1)
name_string: KerberosStringList(els)
}
out1 := asn1.encode(pn)!
back := PrincipalName.decode(data)!
dump(back == pn) // should assert to true
dump(out1.hex() == data.hex()) // should assert to true
// run this produces:
// [examples/examples_0.v:128] back == pn: true
// [examples/examples_0.v:129] out1.hex() == data.hex(): true
}
@@ -0,0 +1,105 @@
module main
import x.encoding.asn1
// This example was taken from https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/sequence.html
// But little modified with removed optional key in the structure and serialization.
// Example schema:
//
// ModuleName DEFINITIONS AUTOMATIC TAGS ::= BEGIN
// PersonelEntry ::= SEQUENCE {
// name OCTET STRING,
// location INTEGER { home(0), field(1), roving(2)} OPTIONAL,
// age INTEGER OPTIONAL
// }
// END
// The syntax above is equivalent with the following one:
// PersonelEntry ::= SEQUENCE {
// name [0] IMPLICIT OCTET STRING,
// location [1] IMPLICIT INTEGER {home(0), field(1), roving(2)} OPTIONAL,
// age [2] IMPLICIT INTEGER OPTIONAL
// }
struct PersonelEntry {
mut:
name asn1.OctetString @[context_specific: 0; implicit; inner: 4]
location asn1.Integer @[context_specific: 1; implicit; inner: 2]
age asn1.Integer @[context_specific: 2; implicit; inner: 2]
}
fn (pr PersonelEntry) tag() asn1.Tag {
return asn1.default_sequence_tag
}
fn (pr PersonelEntry) payload() ![]u8 {
mut out := []u8{}
// by default, in .der, optional element was not included in the output, so, we remove optional options here.
out << asn1.encode_with_options(pr.name, 'context_specific:0; implicit; inner:4')!
out << asn1.encode_with_options(pr.location, 'context_specific:1; implicit; inner:2')!
// the example the third element is optional, but in .der it would not be serializable until you set it to present
out << asn1.encode_with_options(pr.age, 'context_specific:2; implicit; inner:2')!
return out
}
// This is an example of way how we can write routine for decode PersonelEntry from bytes.
fn PersonelEntry.decode(bytes []u8) !PersonelEntry {
// decode should produces Sequence type
elem := asn1.decode(bytes)!
assert elem.tag().equal(asn1.default_sequence_tag)
// cast it into Sequence type and get the fields
seq := elem as asn1.Sequence
fields := seq.fields()
// every fields of the sequence is raw of wrapped element, so we should unwrap it with
// the same options used to wrap in encode step, and turn to the real underlying object.
el_name := fields[0].unwrap_with_options('context_specific:0; implicit; inner:4')!
name := el_name.into_object[asn1.OctetString]()!
el_location := fields[1].unwrap_with_options('context_specific:1; implicit; inner:2')!
location := el_location.into_object[asn1.Integer]()!
el_age := fields[2].unwrap_with_options('context_specific:2; implicit; inner:2')!
age := el_age.into_object[asn1.Integer]()!
return PersonelEntry{
name: name
location: location
age: age
}
}
// expected output :
// 30 10
// 80 08 6269672068656164 // bytestr: 'big head'
// 81 01 02
// 82 01 1A
fn main() {
expected_output := [u8(0x30), 0x10, u8(0x80), 0x08, 0x62, 0x69, 0x67, 0x20, 0x68, 0x65, 0x61,
0x64, u8(0x81), 0x01, 0x02, u8(0x82), 0x01, 0x1A]
rock_star1 := PersonelEntry{
name: asn1.OctetString.from_hexstring('6269672068656164')!
location: asn1.Integer.from_int(2)
age: asn1.Integer.from_int(26)
}
// serializes the object into bytes array and check the result
out := asn1.encode(rock_star1)!
dump(out == expected_output) // out == expected_output: true
// deserializes bytes back into PersonelEntry object.
out_back := PersonelEntry.decode(out)!
dump(out_back)
// out_back: PersonelEntry{
// name: OctetString (big head)
// location: Integer 2
// age: Integer 26
// }
dump(out_back.name == rock_star1.name) // true
dump(out_back.location == rock_star1.location) // true
dump(out_back.age == rock_star1.age) // true
}
@@ -0,0 +1,380 @@
module main
import x.encoding.asn1
// This example takes more complex scenario, in the sense of nested wrapping, the use of
// other class Element supported in this module.
//
// This examples is taken from ITU-T X.690 Information technology – ASN.1 encoding rules:
// Specification of Basic Encoding Rules (BER), Canonical Encoding Rules (CER) and
// Distinguished Encoding Rules (DER) document.
//
// Especially from Annex A. Example of encodings of the document.
// from A.1 ASN.1 description of the record structure.
// The structure of the hypothetical personnel record is formally described below using ASN.1 specified in
// ITU-T Rec. X.680 | ISO/IEC 8824-1 for defining types.
//
// PersonnelRecord ::= [APPLICATION 0] IMPLICIT SET {
// name Name,
// title [0] VisibleString,
// number EmployeeNumber,
// dateOfHire [1] Date,
// nameOfSpouse [2] Name,
// children [3] IMPLICIT SEQUENCE OF ChildInformation DEFAULT {}
// }
//
struct PersonnelRecord {
name Name
title asn1.VisibleString
number EmployeeNumber
date_of_hire Date
name_of_spouse Name
children asn1.SequenceOf[ChildInformation]
}
fn (pr PersonnelRecord) tag() asn1.Tag {
return asn1.default_set_tag
}
fn (pr PersonnelRecord) payload() ![]u8 {
mut out := []u8{}
out << asn1.encode(pr.name)!
out << asn1.encode_with_options(pr.title, 'context_specific:0;explicit;inner:26')!
out << asn1.encode(pr.number)!
out << asn1.encode_with_options(pr.date_of_hire,
'context_specific: 1; explicit; inner:application,false,3')!
out << asn1.encode_with_options(pr.name_of_spouse,
'context_specific: 2; explicit; inner:application,true,1')!
out << asn1.encode_with_options(pr.children, 'context_specific: 3; implicit; inner:16')!
return out
}
// deserializer of PersonellRecord bytes
fn PersonnelRecord.decode(bytes []u8) !PersonnelRecord {
// we perfrom decoding as a reverse of the serialization with the same options.
// after that, we get an Element (with the Set type of PersonellRecord)
el := asn1.decode_with_options(bytes, 'application:0;implicit;inner:17')!
assert el.tag() == asn1.default_set_tag
set := el.into_object[asn1.Set]()!
// fields is series of element ([]Element), PersonellRecord fields
fields := set.fields()
// we turn series of element into underlying desired type.
// its rather clumsy to transforms it, because Sequenve (and Set) fields is an []Element, so you should care
// to turn this into desired object
name_app := fields[0].into_object[asn1.ApplicationElement]()!
name := Name(name_app)
// Title
title_elem := fields[1].unwrap_with_options('context_specific:0;explicit;inner:26')!
title := title_elem.into_object[asn1.VisibleString]()!
// EmployeNumber
emp_num := fields[2].into_object[asn1.ApplicationElement]()!
employe_number := EmployeeNumber(emp_num)
// dateOfHire
doh :=
fields[3].unwrap_with_options('context_specific: 1; explicit; inner:application,false,3')!
doh_app := doh.into_object[asn1.ApplicationElement]()!
date_of_hire := Date(doh_app)
// nameOfSpouse
nosp :=
fields[4].unwrap_with_options('context_specific: 2; explicit; inner:application,true,1')!
nosp_app := nosp.into_object[asn1.ApplicationElement]()!
name_of_spouse := Name(nosp_app)
// The children is Sequence of Set, first we unwrap it then turn into sequence.
children := fields[5].unwrap_with_options('context_specific: 3; implicit; inner:16')!
children_seq := children.into_object[asn1.Sequence]()!
childset_fields := children_seq.fields() // []Element
mut childset := []ChildInformation{}
for item in childset_fields {
// item is an Element
obj := item.into_object[asn1.Set]()!
i := ChildInformation.from_set(obj)!
childset << i
}
pr := PersonnelRecord{
name: name
title: title
number: employe_number
date_of_hire: date_of_hire
name_of_spouse: name_of_spouse
children: asn1.SequenceOf.from_list[ChildInformation](childset)!
}
return pr
}
// ChildInformation ::= SET {
// name Name,
// dateOfBirth [0] Date
// }
//
// Name ::= [APPLICATION 1] IMPLICIT SEQUENCE {
// givenName VisibleString,
// initial VisibleString,
// familyName VisibleString
// }
//
// EmployeeNumber ::= [APPLICATION 2] IMPLICIT INTEGER
// Date ::= [APPLICATION 3] IMPLICIT VisibleString -- YYYYMMDD
// ChildInformation ::= SET {
// name Name,
// dateOfBirth [0] Date
// }
struct ChildInformation {
name Name
date_of_birth Date
}
// s should Set with series of Element with []ChildInformation within underlying fields.
fn ChildInformation.from_set(s asn1.Set) !ChildInformation {
if s.fields().len != 2 {
return error('Bad ChildInformation set')
}
fields := s.fields() // serialized name and dateOfBirth of ChildInformation
name := fields[0].into_object[asn1.ApplicationElement]()!
doh :=
fields[1].unwrap_with_options('context_specific: 0; explicit; inner:application,false,3')!
date := doh.into_object[asn1.ApplicationElement]()!
ch := ChildInformation{
name: Name(name)
date_of_birth: Date(date)
}
return ch
}
fn (ci ChildInformation) tag() asn1.Tag {
return asn1.default_set_tag
}
fn (ci ChildInformation) payload() ![]u8 {
mut out := []u8{}
out << asn1.encode(ci.name)!
out << asn1.encode_with_options(ci.date_of_birth,
'context_specific: 0; explicit; inner:application,false,3')!
return out
}
// EmployeeNumber ::= [APPLICATION 2] IMPLICIT INTEGER
type EmployeeNumber = asn1.ApplicationElement
fn EmployeeNumber.new(val asn1.Integer) !asn1.ApplicationElement {
return asn1.ApplicationElement.from_element(val, 2, .implicit)!
}
// Date ::= [APPLICATION 3] IMPLICIT VisibleString -- YYYYMMDD
type Date = asn1.ApplicationElement
fn Date.new(val asn1.VisibleString) !asn1.ApplicationElement {
return asn1.ApplicationElement.from_element(val, 3, .implicit)!
}
// Name ::= [APPLICATION 1] IMPLICIT SEQUENCE {
// givenName VisibleString,
// initial VisibleString,
// familyName VisibleString
// }
type Name = asn1.ApplicationElement
fn Name.new(el NameEntry) !asn1.ApplicationElement {
return asn1.ApplicationElement.from_element(el, 1, .implicit)!
}
struct NameEntry {
given_name asn1.VisibleString
initial asn1.VisibleString
family_name asn1.VisibleString
}
fn (n NameEntry) tag() asn1.Tag {
return asn1.default_sequence_tag
}
fn (n NameEntry) payload() ![]u8 {
mut out := []u8{}
out << asn1.encode(n.given_name)!
out << asn1.encode(n.initial)!
out << asn1.encode(n.family_name)!
return out
}
// The value of John Smith's personnel record is formally described below using ASN.1.
// { name {givenName "John",initial "P",familyName "Smith"},
// title "Director",
// number 51,
// dateOfHire "19710917",
// nameOfSpouse {givenName "Mary",initial "T",familyName "Smith"},
// children {
// { name {givenName "Ralph",initial "T",familyName "Smith"},
// dateOfBirth "19571111"
// },
// { name {givenName "Susan",initial "B",familyName "Jones"},
// dateOfBirth "19590717"
// }
// }
// }
// Representation of this record value
//
// 60 8185
// 61 10 1A 04 'John' // name
// 1A 01 'P'
// 1A 05 'Smith'
// A0 0A 1A 08 'Director' // title
// 42 01 33 // number
// A1 0A 43 08 '19710917' // dateOfHire
// A2 12 61 10 1A 04 'Mary' // nameOfSpouse
// 1A 01 'T'
// 1A 05 'Smith'
// A3 42 31 1F 61 11 1A 05 'Ralph' => 52 61 6c 70 68 // children
// 1A 01 'T' => 54
// 1A 05 'Smith' => 53 6d 69 74 68
// A0 0A 43 08 '19571111' => 31 39 35 37 31 31 31 31
// 31 1F 61 11 1A 05 'Susan' => 53 75 73 61 6e
// 1A 01 'B' => 42
// 1A 05 'Jones' => 4a 6f 6e 65 73
// A0 0A 43 08 '19590717' => 31 39 35 39 30 37 31 37
fn main() {
// We detailed every pieces of element
// PersonnelRecord.name
pr_name := Name.new(NameEntry{
given_name: asn1.VisibleString.new('John')!
initial: asn1.VisibleString.new('P')!
family_name: asn1.VisibleString.new('Smith')!
})!
// 61 10 1A 04 'John' => 4a 6f 68 6e // name
// 1A 01 'P' => 50
// 1A 05 'Smith' => 53 6d 69 74 68
pr_name_bytes := [u8(0x61), 0x10, 0x1A, 0x04, 0x4a, 0x6f, 0x68, 0x6e, 0x1A, 0x01, 0x50, 0x1A,
0x05, 0x53, 0x6d, 0x69, 0x74, 0x68]
assert asn1.encode(pr_name)! == pr_name_bytes
// PersonnelRecord.title
// A0 0A 1A 08 'Director' => 44 69 72 65 63 74 6f 72 // title
title := asn1.VisibleString.new('Director')!
title_bytes := [u8(0xA0), 0x0A, 0x1A, 0x08, u8(0x44), 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72]
assert asn1.encode_with_options(title, 'context_specific:0;explicit;inner:26')! == title_bytes
// PersonnelRecord.EmployeeNumber
// 42 01 33 // number
emp_num := EmployeeNumber.new(asn1.Integer.from_int(51))!
emp_bytes := [u8(0x42), 0x01, 0x33]
assert asn1.encode(emp_num)! == emp_bytes
// PersonnelRecord.dateOfHire
// A1 0A 43 08 '19710917' => 31 39 37 31 30 39 31 37 // dateOfHire
// dateOfHire [1] Date,
doh := Date.new(asn1.VisibleString.new('19710917')!)!
doh_bytes := [u8(0xA1), 0x0A, 0x43, 0x08, 0x31, 0x39, 0x37, 0x31, 0x30, 0x39, 0x31, 0x37]
assert asn1.encode_with_options(doh, 'context_specific: 1; explicit; inner:application,false,3')! == doh_bytes
// PersonnelRecord.nameOfSpouse
// A2 12 61 10 1A 04 'Mary' => 4d 61 72 79 // nameOfSpouse
// 1A 01 'T' => 54
// 1A 05 'Smith' => 53 6d 69 74 68
nosp := Name.new(NameEntry{
given_name: asn1.VisibleString.new('Mary')!
initial: asn1.VisibleString.new('T')!
family_name: asn1.VisibleString.new('Smith')!
})!
nosp_bytes := [u8(0xA2), 0x12, 0x61, 0x10, 0x1A, 0x04, 0x4d, 0x61, 0x72, 0x79, 0x1A, 0x01,
0x54, 0x1A, 0x05, 0x53, 0x6d, 0x69, 0x74, 0x68]
// nameOfSpouse [2] Name,
assert asn1.encode_with_options(nosp, 'context_specific: 2; explicit; inner:application,true,1')! == nosp_bytes
// { name {givenName "Ralph",initial "T",familyName "Smith"},
// dateOfBirth "19571111"
// },
childinfo0 := ChildInformation{
name: Name.new(NameEntry{
given_name: asn1.VisibleString.new('Ralph')!
initial: asn1.VisibleString.new('T')!
family_name: asn1.VisibleString.new('Smith')!
})!
date_of_birth: Date.new(asn1.VisibleString.new('19571111')!)!
}
// 31 1F 61 11 1A 05 'Ralph' => 52 61 6c 70 68
// 1A 01 'T' => 54
// 1A 05 'Smith' => 53 6d 69 74 68
// A0 0A 43 08 '19571111' => 31 39 35 37 31 31 31 31
ch0_bytes := [u8(0x31), 0x1F, 0x61, 0x11, 0x1A, 0x05, 0x52, 0x61, 0x6c, 0x70, 0x68, 0x1A, 0x01,
0x54, 0x1A, 0x05, 0x53, 0x6d, 0x69, 0x74, 0x68, 0xA0, 0x0A, 0x43, 0x08, 0x31, 0x39, 0x35,
0x37, 0x31, 0x31, 0x31, 0x31]
assert asn1.encode(childinfo0)! == ch0_bytes
childinfo1 := ChildInformation{
name: Name.new(NameEntry{
given_name: asn1.VisibleString.new('Susan')!
initial: asn1.VisibleString.new('B')!
family_name: asn1.VisibleString.new('Jones')!
})!
date_of_birth: Date.new(asn1.VisibleString.new('19590717')!)!
}
// 31 1F 61 11 1A 05 'Susan' => 53 75 73 61 6e
// 1A 01 'B' => 42
// 1A 05 'Jones' => 4a 6f 6e 65 73
// A0 0A 43 08 '19590717' => 31 39 35 39 30 37 31 37
ch1_bytes := [u8(0x31), 0x1F, 0x61, 0x11, 0x1A, 0x05, 0x53, 0x75, 0x73, 0x61, 0x6e, 0x1A, 0x01,
0x42, 0x1A, 0x05, 0x4a, 0x6f, 0x6e, 0x65, 0x73, 0xA0, 0x0A, 0x43, 0x08, 0x31, 0x39, 0x35,
0x39, 0x30, 0x37, 0x31, 0x37]
assert asn1.encode(childinfo1)! == ch1_bytes
// PersonnelRecord.children
children := asn1.SequenceOf.from_list[ChildInformation]([childinfo0, childinfo1])!
// A3 42 31 1F 61 11 1A 05 'Ralph' => 52 61 6c 70 68 // children
// 1A 01 'T' => 54
// 1A 05 'Smith' => 53 6d 69 74 68
// A0 0A 43 08 '19571111' => 31 39 35 37 31 31 31 31
// 31 1F 61 11 1A 05 'Susan' => 53 75 73 61 6e
// 1A 01 'B' => 42
// 1A 05 'Jones' => 4a 6f 6e 65 73
// A0 0A 43 08 '19590717' => 31 39 35 39 30 37 31 37
children_bytes := [u8(0xA3), 0x42, u8(0x31), 0x1F, 0x61, 0x11, 0x1A, 0x05, 0x52, 0x61, 0x6c,
0x70, 0x68, 0x1A, 0x01, 0x54, 0x1A, 0x05, 0x53, 0x6d, 0x69, 0x74, 0x68, 0xA0, 0x0A, 0x43,
0x08, 0x31, 0x39, 0x35, 0x37, 0x31, 0x31, 0x31, 0x31, u8(0x31), 0x1F, 0x61, 0x11, 0x1A,
0x05, 0x53, 0x75, 0x73, 0x61, 0x6e, 0x1A, 0x01, 0x42, 0x1A, 0x05, 0x4a, 0x6f, 0x6e, 0x65,
0x73, 0xA0, 0x0A, 0x43, 0x08, 0x31, 0x39, 0x35, 0x39, 0x30, 0x37, 0x31, 0x37]
assert asn1.encode_with_options(children, 'context_specific: 3; implicit; inner:16')! == children_bytes
// PersonnelRecord entries
pr := PersonnelRecord{
name: pr_name
title: title
number: emp_num
date_of_hire: doh
name_of_spouse: nosp
children: children
}
mut expected_record_bytes := [u8(0x60), 0x81, 0x85]
expected_record_bytes << pr_name_bytes
expected_record_bytes << title_bytes
expected_record_bytes << emp_bytes
expected_record_bytes << doh_bytes
expected_record_bytes << nosp_bytes
expected_record_bytes << children_bytes
// PersonnelRecord ::= [APPLICATION 0] IMPLICIT SET {
pr_record_output := asn1.encode_with_options(pr, 'application:0;implicit;inner:17')!
assert pr_record_output == expected_record_bytes
pr_record := PersonnelRecord.decode(pr_record_output)!
pr_record_encoded_back :=
asn1.encode_with_options(pr_record, 'application:0;implicit;inner:17')!
dump(pr_record_encoded_back == expected_record_bytes) // true
}
@@ -0,0 +1,88 @@
module main
import x.encoding.asn1
// This example of encode a sequence containing a UTF-8 string, an integer
// and an explicitly tagged object identifier, conforming to the following
// ASN.1 schema specification:
// ```asn.1
// Example ::= SEQUENCE {
// greeting UTF8String,
// answer INTEGER,
// type [1] EXPLICIT OBJECT IDENTIFIER
// }
// ```
// You can represent above structure with related structure in `v`, similar like:
struct Example {
greeting asn1.Utf8String
answer asn1.Integer
// you can tag your struct fields with supported options.
tipe asn1.ObjectIdentifier @[context_specific: 1; explicit; inner: 6]
}
fn (ex Example) tag() asn1.Tag {
return asn1.default_sequence_tag
}
// you can build your payload manually or use `asn1.make_payload`, but with aware,
// if your structure contains generic, its maybe not work (currently).
fn (ex Example) payload() ![]u8 {
kd := asn1.KeyDefault(map[string]asn1.Element{})
payload := asn1.make_payload[Example](ex, kd)!
return payload
}
// You can write routines for deserialize Example structure. This is only examples way,
// but its possible to use other way with the help from this module, like use
// `Parser` codec.
fn Example.decode(bytes []u8) !Example {
// just call raw .decode on bytes
// by example, its should produce sequence type.
elem := asn1.decode(bytes)!
assert elem.tag().equal(asn1.default_sequence_tag) // should true
// cast produced element into Sequence type and get the fields.
seq := elem.into_object[asn1.Sequence]()!
fields := seq.fields()
// and then, turn every field into desired object based your schema.
// first two field is not wrapped element, so just turn into real object
greeting := fields[0].into_object[asn1.Utf8String]()!
answer := fields[1].into_object[asn1.Integer]()!
// the third field is context_specific wrapped element, just unwrap it with the
// same options used to encode
oid_tipe := fields[2].unwrap_with_options('context_specific:1;explicit; inner:6')!
tipe := oid_tipe.into_object[asn1.ObjectIdentifier]()!
// then build your Example structure
ex := Example{
greeting: greeting
answer: answer
tipe: tipe
}
return ex
}
fn main() {
expected_output := [u8(0x30), 18, u8(12), 5, 72, 101, 108, 108, 111, u8(2), 1, 42, u8(0xA1),
6, 6, 4, 43, 6, 1, 3]
ex := Example{
greeting: asn1.Utf8String.new('Hello')!
answer: asn1.Integer.from_int(42)
tipe: asn1.ObjectIdentifier.new('1.3.6.1.3')!
}
// serialize the Example object
out := asn1.encode(ex)!
assert out == expected_output
// test with data
example_obj := Example.decode(out)!
dump(ex.greeting == example_obj.greeting)
dump(ex.answer == example_obj.answer)
dump(ex.tipe == example_obj.tipe)
}
+207
View File
@@ -0,0 +1,207 @@
module asn1
import encoding.hex
import crypto.pem
fn test_rsa_public_key() ! {
// from https://asecuritysite.com/digitalcert/sigs4cd
/*
// from https://asecuritysite.com/ecc/sigs4?a0=30819f300d06092a864886f70d010101050003818d0030818902818100a399caf6d93b62a6b6a5311efe93c4d647397ca05a98fa5cddb72d6816ab16fc85f940efe9cf2233975c8925c60f4cd356767cc8445686313a0caeae32930070ca90591a1b249c2fcef9280f5a11d8f1990579d86a05b2523f52c4a876da2d635ca27fbff195e6f7015f834928f033a20b2cd0216a852958b3e58d0f9bd542330203010001
DER: 30819f300d06092a864886f70d010101050003818d0030818902818100a399caf6d93b62a6b6a5311efe93c4d647397ca05a98fa5cddb72d6816ab16fc85f940efe9cf2233975c8925c60f4cd356767cc8445686313a0caeae32930070ca90591a1b249c2fcef9280f5a11d8f1990579d86a05b2523f52c4a876da2d635ca27fbff195e6f7015f834928f033a20b2cd0216a852958b3e58d0f9bd542330203010001
[U] SEQUENCE (30)
[U] SEQUENCE (30)
[U] OBJECT (06): 1.2.840.113549.1.1.1 - RSA Encryption
[U] NULL: None
[U] BIT STRING (03): 0xb'0030818902818100A399CAF6D93B62A6B6A5311EFE93C4D647397CA05A98FA5CDDB72D6816AB16FC85F940EFE9CF2233975C8925C60F4CD356767CC8445686313A0CAEAE32930070CA90591A1B249C2FCEF9280F5A11D8F1990579D86A05B2523F52C4A876DA2D635CA27FBFF195E6F7015F834928F033A20B2CD0216A852958B3E58D0F9BD542330203010001'
81
RSA Modulus (1024) bits: a399caf6d93b62a6b6a5311efe93c4d647397ca05a98fa5cddb72d6816ab16fc85f940efe9cf2233975c8925c60f4cd356767cc8445686313a0caeae32930070ca90591a1b249c2fcef9280f5a11d8f1990579d86a05b2523f52c4a876da2d635ca27fbff195e6f7015f834928f033a20b2cd0216a852958b3e58d0f9bd54233
RSA e: 10001
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCjmcr22TtipralMR7+k8TWRzl8oFqY+lzdty1oFqsW/IX5QO/pzyIzl1yJJcYPTNNWdnzIRFaGMToMrq4ykwBwypBZGhsknC/O+SgPWhHY8ZkFedhqBbJSP1LEqHbaLWNcon+/8ZXm9wFfg0ko8DOiCyzQIWqFKViz5Y0Pm9VCMwIDAQAB
-----END PUBLIC KEY-----
*/
data := '30819f300d06092a864886f70d010101050003818d0030818902818100a399caf6d93b62a6b6a5311efe93c4d647397ca05a98fa5cddb72d6816ab16fc85f940efe9cf2233975c8925c60f4cd356767cc8445686313a0caeae32930070ca90591a1b249c2fcef9280f5a11d8f1990579d86a05b2523f52c4a876da2d635ca27fbff195e6f7015f834928f033a20b2cd0216a852958b3e58d0f9bd542330203010001'
bytes := hex.decode(data)!
seq, n := Sequence.decode(bytes)!
els := seq.fields()
assert els.len == 2
assert els[0] is Sequence
assert els[1] is BitString
els0 := els[0] as Sequence
assert els0.fields.len == 2
assert els0.fields[0] is ObjectIdentifier
oid := els0.fields[0] as ObjectIdentifier
assert oid.str() == '1.2.840.113549.1.1.1'
assert els0.fields[1] is Null
}
// NOTE: Need to be fixed
fn test_x25519_private_key() ! {
// taken from https://www.rfc-editor.org/rfc/rfc8410#section-10
// 10.3 Examples of Ed25519 Private Key
data := '-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VwBCIEINTuctv5E1hK1bbY8fdp+K06/nwoy/HU++CXqI9EdVhC
-----END PRIVATE KEY-----'
// The same item dumped as asn1 yields:
/*
0 30 46: SEQUENCE {
2 02 1: INTEGER 0
5 30 5: SEQUENCE {
7 06 3: OBJECT IDENTIFIER
: Ed 25519 signature algorithm { 1 3 101 112 }
: }
12 04 34: OCTET STRING
: 04 20 D4 EE 72 DB F9 13 58 4A D5 B6 D8 F1 F7 69
: F8 AD 3A FE 7C 28 CB F1 D4 FB E0 97 A8 8F 44 75
: 58 42
: }
*/
block, _ := pem.decode(data)?
seq, n := Sequence.decode(block.data)!
assert seq.payload()!.len == 46
els := seq.fields()
assert els[0] is Integer
assert els[1] is Sequence
b := els[1] as Sequence
assert b.fields()[0] is ObjectIdentifier
assert b.fields()[0].length()! == 3
oid := b.fields()[0] as ObjectIdentifier
assert oid.str() == '1.3.101.112'
assert els[2] is OctetString
assert els[2].payload()!.len == 34
}
/*
// NEED TO BE FIXED
// TODO: This test still failed on `Sequence.parse_contents` with error `next: truncated bytes`
// FIXME: need to be investigated, bad data, or sequence handling or others source of fail
// so just disable this test, would be moved into related module when ready
fn test_example_x25519_certificate() {
// taken from https://www.rfc-editor.org/rfc/rfc8410.html#section-10
// 10.2. Example X25519 Certificate
data := '-----BEGIN CERTIFICATE-----
MIIBLDCB36ADAgECAghWAUdKKo3DMDAFBgMrZXAwGTEXMBUGA1UEAwwOSUVURiBUZX
N0IERlbW8wHhcNMTYwODAxMTIxOTI0WhcNNDAxMjMxMjM1OTU5WjAZMRcwFQYDVQQD
DA5JRVRGIFRlc3QgRGVtbzAqMAUGAytlbgMhAIUg8AmJMKdUdIt93LQ+91oNvzoNJj
ga9OukqY6qm05qo0UwQzAPBgNVHRMBAf8EBTADAQEAMA4GA1UdDwEBAAQEAwIDCDAg
BgNVHQ4BAQAEFgQUmx9e7e0EM4Xk97xiPFl1uQvIuzswBQYDK2VwA0EAryMB/t3J5v
/BzKc9dNZIpDmAgs3babFOTQbs+BolzlDUwsPrdGxO3YNGhW7Ibz3OGhhlxXrCe1Cg
w1AH9efZBw==
-----END CERTIFICATE-----'
// The same item dumped as asn1 yields:
/*
0 300: SEQUENCE {
4 223: SEQUENCE {
7 3: [0] {
9 1: INTEGER 2
: }
12 8: INTEGER 56 01 47 4A 2A 8D C3 30
22 5: SEQUENCE {
24 3: OBJECT IDENTIFIER
: Ed 25519 signature algorithm { 1 3 101 112 }
: }
29 25: SEQUENCE {
31 23: SET {
33 21: SEQUENCE {
35 3: OBJECT IDENTIFIER commonName (2 5 4 3)
40 14: UTF8String 'IETF Test Demo'
: }
: }
: }
56 30: SEQUENCE {
58 13: UTCTime 01/08/2016 12:19:24 GMT
73 13: UTCTime 31/12/2040 23:59:59 GMT
: }
88 25: SEQUENCE {
90 23: SET {
92 21: SEQUENCE {
94 3: OBJECT IDENTIFIER commonName (2 5 4 3)
99 14: UTF8String 'IETF Test Demo'
: }
: }
: }
115 42: SEQUENCE {
117 5: SEQUENCE {
119 3: OBJECT IDENTIFIER
: ECDH 25519 key agreement { 1 3 101 110 }
: }
124 33: BIT STRING
: 85 20 F0 09 89 30 A7 54 74 8B 7D DC B4 3E F7 5A
: 0D BF 3A 0D 26 38 1A F4 EB A4 A9 8E AA 9B 4E 6A
: }
159 69: [3] {
161 67: SEQUENCE {
163 15: SEQUENCE {
165 3: OBJECT IDENTIFIER basicConstraints (2 5 29 19)
170 1: BOOLEAN TRUE
173 5: OCTET STRING, encapsulates {
175 3: SEQUENCE {
177 1: BOOLEAN FALSE
: }
: }
: }
180 14: SEQUENCE {
182 3: OBJECT IDENTIFIER keyUsage (2 5 29 15)
187 1: BOOLEAN FALSE
190 4: OCTET STRING, encapsulates {
192 2: BIT STRING 3 unused bits
: '10000'B (bit 4)
: }
: }
196 32: SEQUENCE {
198 3: OBJECT IDENTIFIER subjectKeyIdentifier (2 5 29 14)
203 1: BOOLEAN FALSE
206 22: OCTET STRING, encapsulates {
208 20: OCTET STRING
: 9B 1F 5E ED ED 04 33 85 E4 F7 BC 62 3C 59 75
: B9 0B C8 BB 3B
: }
: }
: }
: }
: }
230 5: SEQUENCE {
232 3: OBJECT IDENTIFIER
: Ed 25519 signature algorithm { 1 3 101 112 }
: }
237 65: BIT STRING
: AF 23 01 FE DD C9 E6 FF C1 CC A7 3D 74 D6 48 A4
: 39 80 82 CD DB 69 B1 4E 4D 06 EC F8 1A 25 CE 50
: D4 C2 C3 EB 74 6C 4E DD 83 46 85 6E C8 6F 3D CE
: 1A 18 65 C5 7A C2 7B 50 A0 C3 50 07 F5 E7 D9 07
: }
*/
block, _ := pem.decode(data)?
seq, n := Sequence.decode(block.data)!
assert seq.payload()!.len == 302
// certificate is arrays of 3 element
assert seq.fields().len == 3
els := seq.fields()
// last element
assert els[2] is BitString
bts := BitString.from_bytes(seq.fields[2].payload()!)!
exp := [u8(0xAF), 0x23, 0x01, 0xFE, 0xDD, 0xC9, 0xE6, 0xFF, 0xC1, 0xCC, 0xA7, 0x3D, 0x74, 0xD6,
0x48, 0xA4, 0x39, 0x80, 0x82, 0xCD, 0xDB, 0x69, 0xB1, 0x4E, 0x4D, 0x06, 0xEC, 0xF8, 0x1A,
0x25, 0xCE, 0x50, 0xD4, 0xC2, 0xC3, 0xEB, 0x74, 0x6C, 0x4E, 0xDD, 0x83, 0x46, 0x85, 0x6E,
0xC8, 0x6F, 0x3D, 0xCE, 0x1A, 0x18, 0x65, 0xC5, 0x7A, 0xC2, 0x7B, 0x50, 0xA0, 0xC3, 0x50,
0x07, 0xF5, 0xE7, 0xD9, 0x07]
assert bts.data == exp
}
*/
@@ -0,0 +1,568 @@
module asn1
// This file is for supporting configure through string options.
// so, you can tag your struct field with supported attributes defined here.
// Limit of string option length
const max_string_option_length = 255
const max_attributes_length = 5
// Configuration format for field tagging.
//
// Currently, this configurations option support following string config, ie.
//
// - `class:number`, for wrapping the element with other non-universal class, for examole: `private:100`.
// - `explicit` or `implicit` mode.
// - `inner:5` for universal class form or `inner:class,constructed,number` for extended form.
// - `optional` or `optional:present' tagging for element with OPTIONAL behaviour.
// - `has_default` tagging for element with DEFAULT behaviour.
//
// Field options attributes handling.
//
// FieldOptions is a structure to accomodate and allowing configures your complex structures
// through string or arrays of string stored in FieldOptions fields.
// For example, you can tagging your fields of some element with tagging
// like `@[context_specific:10; explicit; inner:5; optional]`.
// Its will be parsed and can be used to drive encoding or decoding of Element.
pub struct FieldOptions {
mut:
// The fields `cls`, `tagnum`, `mode` and `inner` was used
// for wrapping (and unwrapping) purposes. turn some element
// into another element configured with this options.
// In the encoding (decoding) phase, it would be checked
// if this options meet required criteria.
// Limitation applied on the wrapper fields:
// 1. Wrap into UNIVERSAL is not allowed (cls != universal)
// 2. You should provide mode for wrapping, explicit or implicit.
// 3. If cls == '', no wrapping is performed, discarding all wrapper options
cls string // should cls != 'universal'
tagnum int = -1 // Provides with wrapper tag number, as n outer tag number.
mode string // explicit or implicit, depends on definition schema.
// inner should valid inner tag format, ie, universal form in single value 'number'
// or extended form in triplet value of 'class,form,number' format.
inner string
// optional field applied to element with OPTIONAL behaviour, with or without DEFAULT value.
// Set `optional` to true when this element has OPTIONAL keyword in the definition of element.
// Usually element with OPTIONAL keyword is not presents in the encoding (decoding) data.
optional bool
present bool
// This field applied to element with DEFAULT keyword behaviour.
// Its applied into wrapping of element or optionality of the element.
// If some element has DEFAULT keyword, set this field to true and gives default element
// into `default_value` field.
has_default bool
default_value ?Element
}
// `from_string` parses string as an attribute of field options.
// Its allows string similar to `application:4; optional; has_default`
// to be treated as an field options.
// See FieldOptions in `field_options.v` for more detail.
pub fn FieldOptions.from_string(s string) !FieldOptions {
if s.len == 0 {
return FieldOptions{}
}
if s.len > max_string_option_length {
return error('string option exceed limit')
}
trimmed := s.trim_space()
// check whether this string is a valid one.
if !is_asn1_options_marker(trimmed) {
return error('You have not provides correct options marker')
}
attrs := trimmed.split(';')
opt := FieldOptions.from_attrs(attrs)!
// no need to check, from_attrs already called it internally.
// opt.check_wrapper()!
return opt
}
// filtered_attrs filters and takes only supported asn1 marker from arrays of string.
fn filtered_attrs(attrs []string) []string {
mut filtered := []string{}
for attr in attrs {
item := attr.trim_space()
if is_asn1_options_marker(item) {
filtered << item
}
}
return filtered
}
// `from_attrs` parses and validates []string into FieldOptions.
pub fn FieldOptions.from_attrs(attrs []string) !FieldOptions {
mut fo := FieldOptions{}
if attrs.len == 0 {
return fo
}
mut tag_ctr := 0 // tag marker counter
mut opt_ctr := 0 // optional marker counter
mut def_ctr := 0 // has_default marker counter
mut mod_ctr := 0 // mode marker counter
mut inn_ctr := 0 // inner counter
// take only valid supported asn1 marker
filtered := filtered_attrs(attrs)
if filtered.len > max_attributes_length {
return error('max allowed filtered.len')
}
// The item has space-trimmed
for item in filtered {
if is_tag_marker(item) {
cls, num := parse_tag_marker(item)!
tag_ctr += 1
if tag_ctr > 1 {
return error('multiples tag format defined')
}
tnum := num.int()
if tnum < 0 {
return error('bad tag number')
}
fo.cls = cls
fo.tagnum = tnum
}
if is_mode_marker(item) {
value := parse_mode_marker(item)!
mod_ctr += 1
if mod_ctr > 1 {
return error('multiples mode key defined')
}
if !valid_mode_value(value) {
return error('Bad mode values')
}
fo.mode = value
}
if is_inner_tag_marker(item) {
_, value := parse_inner_tag_marker(item)!
inn_ctr += 1
if inn_ctr > 1 {
return error('multiples inner tag format defined')
}
fo.inner = value
}
if is_optional_marker(item) {
_, value := parse_optional_marker(item)!
opt_ctr += 1
if opt_ctr > 1 {
return error('multiples optional tag')
}
// when this present, its an optional
fo.optional = true
present := if valid_optional_present_bit_marker(value) { true } else { false }
fo.present = present
}
if is_default_marker(item) {
default_marker := parse_default_marker(item)!
def_ctr += 1
if def_ctr > 1 {
return error('multiples has_default flag')
}
has_default := if valid_default_marker(default_marker) { true } else { false }
fo.has_default = has_default
}
}
// check
fo.check_wrapper()!
return fo
}
// wrapper_tag makes a wrapper Tag from FieldOptions for current element.
// The form of wrapper tag depends on tagged mode being supplied,
// if implicit, its follows the inner tag being wrapped (primitive or constructed one)
// If explicit, the wrapper tag would in non-primitive form.
fn (fo FieldOptions) wrapper_tag() !Tag {
if fo.cls == '' {
return error('You cant build wrapper tag from empty string')
}
fo.check_wrapper()!
cls := TagClass.from_string(fo.cls)!
if !valid_mode_value(fo.mode) {
return error('Invalid mode')
}
inner_tag := fo.inner_tag()!
form := inner_tag.constructed
if fo.mode == 'implicit' {
// implicit tagging allows to be applied on non-constructed element, inherited from inner.
return Tag.new(cls, form, fo.tagnum)!
}
return Tag.new(cls, true, fo.tagnum)!
}
// inner_tag gets inner Tag from FieldOptions.
fn (fo FieldOptions) inner_tag() !Tag {
// universal or extended form of inner value
st := fo.inner.trim_space()
if st == '' {
return error('invalid empty inner value')
}
if valid_inner_universal_form(st) {
val := st.int()
if val < 0 || val > max_universal_tagnumber {
return error('You cant create universal tag from invalid inner value')
}
utag := universal_tag_from_int(val)!
return utag
}
if !valid_extended_inner_form(st) {
return error('invalid extended inner value')
}
c, form, n := parse_inner_extended_form(st)!
cls := TagClass.from_string(c)!
constructed := if form == 'true' { true } else { false }
number := n.int()
inn_tag := Tag.new(cls, constructed, number)!
return inn_tag
}
// install_default tries to install and sets element el as a default value when has_default flag of FieldOptions
// has been set into true, or error if has_default is false.
// When default_value has been set with some value before this, its would return error until you force it
// by setingt force flag into true.
pub fn (mut fo FieldOptions) install_default(el Element, force bool) ! {
if fo.has_default {
if fo.default_value == none {
fo.default_value = el
return
}
// not nil
if !force {
return error('set force to overide')
}
// replace the old one, or should we check its matching tag ?
fo.default_value = el
}
return error('you can not install default value when has_default being not set')
}
// check_wrapper validates wrapper's part of fields options.
fn (fo FieldOptions) check_wrapper() ! {
// Validates wrapper part
// Its discard all check when fo.cls is empty string, its marked as non-wrapped element.
if fo.cls != '' {
if !valid_tagclass_name(fo.cls) {
return error('Get unexpected fo.cls value')
}
// provides the tag number
if fo.tagnum < 0 {
return error('Get unexpected fo.tagnum}')
}
// wraps into UNIVERSAL type is not allowed
if fo.cls == 'universal' {
return error('wraps into universal class is not allowed')
}
if !valid_mode_value(fo.mode) {
return error('Invalid zonk or uncorerct mode value')
}
// when wrapped, you should provide inner tag number value.
if fo.inner == '' {
return error('You provides incorrect inner number')
}
// inner := fo.inner.trim_space()
if !valid_inner_universal_form(fo.inner) && !valid_extended_inner_form(fo.inner) {
return error('invalid inner value format')
}
if valid_inner_universal_form(fo.inner) {
// val := fo.inner.trim_space()
num := fo.inner.int()
if num > max_universal_tagnumber {
return error('Inner number exceed universal limit')
}
}
}
}
// WRAPPING (UNWRAPPING) OPTIONS.
//
// parse 'application=number' format
// format: `class=number` without constructed keyword.
fn parse_tag_marker(attr string) !(string, string) {
src := attr.trim_space()
if is_tag_marker(src) {
field := src.split(':')
if field.len != 2 {
return error('bad tag marker length')
}
first := field[0].trim_space()
if !valid_tagclass_name(first) {
return error('bad tag name')
}
second := field[1].trim_space()
if !valid_string_tag_number(second) {
return error('bad tag number')
}
return first, second
}
return error('not a tag marker')
}
fn is_tag_marker(attr string) bool {
return attr.starts_with('application') || attr.starts_with('private')
|| attr.starts_with('context_specific')
}
fn valid_tagclass_name(tag string) bool {
return tag == 'application' || tag == 'private' || tag == 'context_specific'
}
// it should be represented in int or hex number
fn valid_string_tag_number(s string) bool {
return s.is_int() || s.is_hex()
}
// EXPLICIT OR IMPLICIT OPTIONS.
//
// parse 'explicit [or implicit]' format.
fn parse_mode_marker(s string) !string {
item := s.trim_space()
if is_mode_marker(item) {
if !valid_mode_value(item) {
return error('bad mode value')
}
return item
}
return error('not mode marker')
}
fn is_mode_marker(attr string) bool {
return attr.starts_with('explicit') || attr.starts_with('implicit')
}
fn valid_mode_value(s string) bool {
return s == 'explicit' || s == 'implicit'
}
// INNER TAG OPTIONS.
//
// parse inner value to be used by decoder.
// support two format:
// - unviersal inner format in the form `inner:number`, where number is universal class number.
// - extended inner format in the form 'inner:class,form,number' for more broad support of the inner class.
fn parse_inner_tag_marker(src string) !(string, string) {
if is_inner_tag_marker(src) {
item := src.split(':')
if item.len != 2 {
return error('bad inner tag marker length')
}
// check for inner part
key := item[0].trim_space()
if !valid_inner_tag_key(key) {
return error('bad inner key')
}
// check for universal or extended form.
// item is comma separated value.
value := item[1].trim_space()
items := value.split(',')
if items.len == 0 {
return error('no inner value')
}
// length should 1 (universal) or 3 (extended)
if items.len != 1 && items.len != 3 {
return error('Invalid items.len')
}
// if its in universal form, should be a number
if items.len == 1 {
if !valid_inner_universal_form(value) {
return error('Get invalid universal inner value')
}
}
// extended form
if items.len == 3 {
if !is_extended_inner_cls_marker(items[0].trim_space()) {
return error('Your first ext inner is not extended cls')
}
if !valid_extended_inner_cls_marker(items[0].trim_space()) {
return error('Your first ext inner is not valid ext cls')
}
// second form should be 'true' or 'false'
if !valid_extended_inner_form_marker(items[1].trim_space()) {
return error('Your ext inner form is invalid')
}
// third item should be a number
if !valid_extended_inner_number_marker(items[2].trim_space()) {
return error('invalid ext inner number part')
}
}
return key, value
}
return error('not inner tag marker')
}
fn is_inner_tag_marker(s string) bool {
return s.starts_with('inner')
}
fn valid_inner_tag_key(s string) bool {
return s == 'inner'
}
fn valid_inner_universal_form(value string) bool {
// 'inner: number' part
return valid_string_tag_number(value)
}
fn valid_extended_inner_form(value string) bool {
// 'inner:class,form,number' part in comma separated value.
items := value.split(',')
if items.len != 3 {
return false
}
if !is_extended_inner_cls_marker(items[0].trim_space()) {
return false
}
if !valid_extended_inner_cls_marker(items[0].trim_space()) {
return false
}
// second form should be 'true' or 'false'
if !valid_extended_inner_form_marker(items[1].trim_space()) {
return false
}
// third item should be a number
if !valid_extended_inner_number_marker(items[2].trim_space()) {
return false
}
return true
}
fn parse_inner_extended_form(s string) !(string, string, string) {
// 'inner:class,form,number' part in comma separated value.
items := s.split(',')
if items.len != 3 {
return error('invalid extended form length')
}
cls := items[0].trim_space()
if !is_extended_inner_cls_marker(cls) {
return error('Your first ext inner is not extended cls')
}
if !valid_extended_inner_cls_marker(cls) {
return error('Your first ext inner is not valid ext cls')
}
// second form should be 'true' or 'false'
form := items[1].trim_space()
if !valid_extended_inner_form_marker(form) {
return error('Your ext inner form is invalid')
}
// third item should be a number
third := items[2].trim_space()
if !valid_extended_inner_number_marker(third) {
return error('invalid ext inner number part')
}
return cls, form, third
}
// allows universal class as an inner.
fn is_extended_inner_cls_marker(attr string) bool {
return is_tag_marker(attr) || attr.starts_with('universal')
}
fn valid_extended_inner_cls_marker(attr string) bool {
return valid_tagclass_name(attr) || attr == 'universal'
}
fn valid_extended_inner_form_marker(s string) bool {
return s == 'true' || s == 'false'
}
fn valid_extended_inner_number_marker(s string) bool {
return valid_string_tag_number(s)
}
// OPTIONAL.
//
// support two form of optional marker.
// - the only optional key 'optional' marker, and
// - extended bit of presence of optional, 'optional:present'
fn parse_optional_marker(attr string) !(string, string) {
values := attr.split(':')
if values.len != 1 && values.len != 2 {
return error('Bad optional length')
}
if values.len == 1 {
key := values[0].trim_space()
if is_optional_marker(key) {
if !valid_optional_key(key) {
return error('bad optional key')
}
return key, ''
}
}
if values.len == 2 {
first := values[0].trim_space()
if !valid_optional_key(first) {
return error('bad optional key')
}
second := values[1].trim_space()
if !is_optional_present_bit_marker(second) {
return error('Non optional presence bit marker')
}
if !valid_optional_present_bit_marker(second) {
return error('Not valid optional presence bit marker')
}
return first, second
}
return error('not optional marker')
}
fn is_optional_marker(attr string) bool {
return attr.starts_with('optional')
}
fn valid_optional_key(attr string) bool {
return attr == 'optional'
}
fn is_optional_present_bit_marker(attr string) bool {
return attr.starts_with('present')
}
fn valid_optional_present_bit_marker(attr string) bool {
return attr == 'present'
}
// DEFAULT OPTIONS.
//
// parse 'has_default' marker
fn parse_default_marker(attr string) !string {
item := attr.trim_space()
if is_default_marker(item) {
if !valid_default_marker(item) {
return error('bad has_default marker')
}
return item
}
return error('not has_default marker')
}
fn is_default_marker(attr string) bool {
return attr.starts_with('has_default')
}
fn valid_default_marker(attr string) bool {
return attr == 'has_default'
}
// UTILTIY
//
// is_asn1_options_marker checks if provided string is valid supported field options string.
fn is_asn1_options_marker(item string) bool {
// item := s.trim_space()
// belowng to one of five supported marker.
valid := is_tag_marker(item) || is_mode_marker(item) || is_inner_tag_marker(item)
|| is_optional_marker(item) || is_default_marker(item)
return valid
}
@@ -0,0 +1,191 @@
module asn1
struct StringOption {
src string
cls string
tagnum int
mode string
inner string
optional bool
has_default bool
err IError
}
fn test_parse_string_option() ! {
data := [
// should parseable
StringOption{'application:20;explicit;inner:5', 'application', 20, 'explicit', '5', false, false, none},
StringOption{'private:0x20;implicit;inner:5', 'private', 32, 'implicit', '5', false, false, none},
StringOption{'context_specific:0x20;implicit;inner:5', 'context_specific', 32, 'implicit', '5', false, false, none},
StringOption{'private:0x20;implicit;inner:5; optional', 'private', 32, 'implicit', '5', true, false, none},
StringOption{'private:0x20;implicit;inner:5; has_default', 'private', 32, 'implicit', '5', false, true, none},
StringOption{'private:0x20;implicit;inner:5; optional; has_default', 'private', 32, 'implicit', '5', true, true, none},
// not parseable
// Without mode or inner
StringOption{'application:20', 'application', 0, '', '5', false, false, error('Invalid zonk or uncorerct mode value')},
StringOption{'application:20; inner:4', 'application', 0, '', '0', false, false, error('Invalid zonk or uncorerct mode value')},
StringOption{'application:20; implicit', 'application', 0, '', '0', false, false, error('You provides incorrect inner number')},
StringOption{'implicit;inner:33', '', -1, 'implicit', '33', false, false, error('You provides incorrect inner number')},
]
for i, item in data {
// dump(i)
fo := FieldOptions.from_string(item.src) or {
assert err == item.err
continue
}
assert fo.cls == item.cls
assert fo.tagnum == item.tagnum
assert fo.optional == item.optional
assert fo.has_default == item.has_default
assert fo.mode == item.mode
assert fo.inner == item.inner
}
}
struct TagMarker {
attr string
cls string
num int
err IError
}
fn test_tag_marker_parsing() ! {
data := [
// normal
TagMarker{'application:100', 'application', 100, none},
TagMarker{'context_specific:100', 'context_specific', 100, none},
// normal with hex number
TagMarker{'private:0x54', 'private', 0x54, none},
// normal with spaces should be allowed
TagMarker{'application: 0x20', 'application', 0x20, none},
TagMarker{' application : 0x20 ', 'application', 0x20, none},
// universal should not allowed
TagMarker{'universal:0x5f', 'universal', 0x5f, error('not a tag marker')},
// bad tag key should error
TagMarker{'embuh: 0x20', '', 0x20, error('not a tag marker')},
TagMarker{'private_embuh: 0x20', '', 0x20, error('bad tag name')},
// key without number also error
TagMarker{'private_embuh', '', 0, error('bad tag marker length')},
TagMarker{'private:', 'private', 0, error('bad tag number')},
TagMarker{'private:bb', 'private', 0, error('bad tag number')},
]
for item in data {
k, v := parse_tag_marker(item.attr) or {
assert err == item.err
continue
}
assert k == item.cls
assert v.int() == item.num
}
}
struct ModeMarker {
attr string
value string
err IError
}
fn test_mode_marker_parsing() ! {
data := [
// the normal right thing
ModeMarker{'explicit', 'explicit', none},
ModeMarker{'implicit', 'implicit', none},
// with spaces is allowed
ModeMarker{' implicit ', 'implicit', none},
ModeMarker{' explicit ', 'explicit', none},
// bad key or value
ModeMarker{'xx_implicit', '', error('not mode marker')},
ModeMarker{'implicitkey', '', error('bad mode value')},
ModeMarker{'exoplicit implicit', '', error('not mode marker')},
]
for i, item in data {
// dump(i)
v := parse_mode_marker(item.attr) or {
assert err == item.err
continue
}
assert valid_mode_value(v) == true
assert v == item.value
}
}
struct InnerMarker {
src string
result string
err IError
}
fn test_for_inner_tag_marker() ! {
data := [InnerMarker{'', '', error('not inner tag marker')},
InnerMarker{'inner:0', '0', none}, InnerMarker{'inner:12', '12', none},
InnerMarker{'inner:private,true,14', 'private,true,14', none},
InnerMarker{'inner:universal,true,14', 'universal,true,14', none},
InnerMarker{'inner:invalid,true,14', '', error('Your first ext inner is not extended cls')}]
for i, item in data {
// dump(i)
k, v := parse_inner_tag_marker(item.src) or {
assert err == item.err
continue
}
assert v == item.result
}
}
struct HasDefaultMarker {
attr string
err IError
}
fn test_has_default_marker_parsing() ! {
data := [
HasDefaultMarker{'has_default', none},
HasDefaultMarker{' has_default ', none},
HasDefaultMarker{'', error('not has_default marker')},
HasDefaultMarker{'has_default:', error('bad has_default marker')},
HasDefaultMarker{'has_defaultaa', error('bad has_default marker')},
]
for item in data {
s := parse_default_marker(item.attr) or {
assert err == item.err
continue
}
assert valid_default_marker(s) == true
}
}
struct OptionalMarker {
attr string
valid bool
present bool
err IError
}
fn test_optional_marker_parsing() ! {
data := [
// exactly matching key
OptionalMarker{'optional', true, false, none},
OptionalMarker{'optional: present ', true, true, none},
// matching key contains spaces is allowed
OptionalMarker{'optional ', true, false, none},
OptionalMarker{' optional ', true, false, none},
OptionalMarker{'optional:present ', true, true, none},
// contains another key is not allowed
OptionalMarker{'optional: true ', false, false, error('Non optional presence bit marker')},
OptionalMarker{'optional-- ', false, false, error('bad optional key')},
// this should not allowed
OptionalMarker{'', false, false, error('not optional marker')},
OptionalMarker{'optional_aaa', false, false, error('bad optional key')},
OptionalMarker{'opt', false, false, error('not optional marker')},
OptionalMarker{'xx_optional_ ', false, false, error('not optional marker')},
]
for i, item in data {
// dump(i)
key, prs := parse_optional_marker(item.attr) or {
assert err == item.err
continue
}
assert valid_optional_key(key) == item.valid
assert valid_optional_present_bit_marker(prs) == item.present
}
}
@@ -0,0 +1,121 @@
// Copyright (c) 2022, 2024 blackshirt. All rights reserved.
// Use of this source code is governed by a MIT License
// that can be found in the LICENSE file.
module asn1
// default_generalstring_tag is the default tag of ASN.1 GENERALSTRING type.
pub const default_generalstring_tag = Tag{.universal, false, int(TagType.generalstring)}
// ASN.1 GENERALSTRING Handling
// It may contain any characters from a "G" and "C" set of any standardized character sets.
// A "G" set contains some specified set of graphic (i.e., printable) characters,
// while a "C" set contains a group of control characters.
// For example, the "G" set in the ASCII character set consists of the characters with ASCII numbers 33 through 126,
// while the "C" set is those characters with ASCII numbers 0 through 31.
// For historical reasons, the characters SPACE (number 32) and DELETE (number 127)
// are not considered to be in either the C set or the G set, but instead stand on their own
// We only treated GeneralString as an us-ascii charset
pub struct GeneralString {
pub:
value string
}
fn (g GeneralString) check() ! {
if !g.value.is_ascii() {
return error('GeneralString: contains non-ascii chars')
}
}
// new creates a GeneralString element from string s.
pub fn GeneralString.new(s string) !GeneralString {
if !s.is_ascii() {
return error('GeneralString: contains non-ascii chars')
}
return GeneralString{
value: s
}
}
fn (gst GeneralString) str() string {
if gst.value.len == 0 {
return 'GeneralString (<empty>)'
}
return 'GeneralString (${gst.value})'
}
// tag returns the tag of GeneralString type element.
pub fn (gst GeneralString) tag() Tag {
return default_generalstring_tag
}
// payload returns the payload of GeneralString type element.
pub fn (gst GeneralString) payload() ![]u8 {
return gst.payload_with_rule(.der)!
}
fn (gst GeneralString) payload_with_rule(rule EncodingRule) ![]u8 {
if rule != .der && rule != .ber {
return error('GeneralString: not supported rule')
}
gst.check()!
return gst.value.bytes()
}
// from_bytes creates GeneralString from bytes b
fn GeneralString.from_bytes(b []u8) !GeneralString {
if b.any(it < u8(` `) || it > u8(`~`)) {
return error('GeneralString: bytes contains non-ascii chars')
}
return GeneralString{
value: b.bytestr()
}
}
// parse tries to read into GeneralString from parser p or return error on fails.
fn GeneralString.parse(mut p Parser) !GeneralString {
tag := p.read_tag()!
if !tag.equal(default_generalstring_tag) {
return error('Bad GeneralString tag')
}
length := p.read_length()!
bytes := p.read_bytes(length)!
res := GeneralString.from_bytes(bytes)!
return res
}
// decode tries to decode bytes array into GeneralString or return error on fails.
fn GeneralString.decode(src []u8) !(GeneralString, int) {
return GeneralString.decode_with_rule(src, .der)!
}
fn GeneralString.decode_with_rule(bytes []u8, rule EncodingRule) !(GeneralString, int) {
tag, length_pos := Tag.decode_with_rule(bytes, 0, rule)!
if !tag.equal(default_generalstring_tag) {
return error('Unexpected non-generalstring tag')
}
length, content_pos := Length.decode_with_rule(bytes, length_pos, rule)!
content := if length == 0 {
[]u8{}
} else {
// non-null length should contains non-null bytes
if content_pos >= bytes.len || content_pos + length > bytes.len {
return error('GeneralString: truncated payload bytes')
}
unsafe { bytes[content_pos..content_pos + length] }
}
gst := GeneralString.from_bytes(content)!
next := content_pos + length
return gst, next
}
// Utility function
fn validate_general_string(s string) bool {
if !s.is_ascii() {
return false
}
return true
}
+101
View File
@@ -0,0 +1,101 @@
// Copyright (c) 2022, 2024 blackshirt. All rights reserved.
// Use of this source code is governed by a MIT License
// that can be found in the LICENSE file.
module asn1
// default_ia5string_tag is the default tag of ASN.1 IA5STRING type.
pub const default_ia5string_tag = Tag{.universal, false, int(TagType.ia5string)}
// ASN.1 IA5String type handling routine.
// IA5String is a standard ASCII characters
pub struct IA5String {
pub:
value string
}
// new creates a IA5String element from string s.
pub fn IA5String.new(s string) !IA5String {
if !valid_ia5string(s) {
return error('IA5String: contains non-ascii chars')
}
return IA5String{
value: s
}
}
// tag returns the tag of IA5String type element.
pub fn (v IA5String) tag() Tag {
return default_ia5string_tag
}
// payload returns the payload of IA5String type element.
pub fn (v IA5String) payload() ![]u8 {
if !v.value.is_ascii() {
return error('IA5String: contains non-ascii chars')
}
return v.value.bytes()
}
fn (v IA5String) str() string {
if v.value.len == 0 {
return 'IA5String (<empty>)'
}
return 'IA5String (${v.value})'
}
fn IA5String.parse(mut p Parser) !IA5String {
tag := p.read_tag()!
if !tag.equal(default_ia5string_tag) {
return error('Bad Ia5String tag')
}
length := p.read_length()!
bytes := p.read_bytes(length)!
res := IA5String.from_bytes(bytes)!
return res
}
fn IA5String.decode(bytes []u8) !(IA5String, int) {
bs, next := IA5String.decode_with_rule(bytes, .der)!
return bs, next
}
fn IA5String.decode_with_rule(bytes []u8, rule EncodingRule) !(IA5String, int) {
tag, length_pos := Tag.decode_with_rule(bytes, 0, rule)!
if !tag.equal(default_ia5string_tag) {
return error('Unexpected non-ia5string tag')
}
length, content_pos := Length.decode_with_rule(bytes, length_pos, rule)!
// if the length is 0, this mean if the payload is empty
// otherwise, check for bound
content := if length == 0 {
[]u8{}
} else {
if content_pos >= bytes.len || content_pos + length > bytes.len {
return error('IA5String: truncated payload bytes')
}
unsafe { bytes[content_pos..content_pos + length] }
}
result := IA5String.from_bytes(content)!
next := content_pos + length
return result, next
}
// from_bytes creates a new IA5String from bytes b
fn IA5String.from_bytes(b []u8) !IA5String {
if b.any(it < u8(` `) || it > u8(`~`)) {
return error('IA5String: bytes contains non-ascii chars')
}
return IA5String{
value: b.bytestr()
}
}
// Utility function
fn valid_ia5string(s string) bool {
return s.is_ascii()
}
@@ -0,0 +1,43 @@
// Copyright (c) 2022, 2024 blackshirt. All rights reserved.
// Use of this source code is governed by a MIT License
// that can be found in the LICENSE file.
module asn1
struct IA5StringTest {
src string
out []u8
err IError
}
fn test_ia5string_handling() ! {
data := [
IA5StringTest{'test', [u8(22), 4, 116, 101, 115, 116], none},
IA5StringTest{'abc', '\x16\x03abc'.bytes(), none},
IA5StringTest{`🚀`.str(), []u8{}, error('IA5String: contains non-ascii chars')},
IA5StringTest{')', '\x16\x01)'.bytes(), none},
IA5StringTest{'\x13\x03ab\x00', []u8{}, error('IA5String: contains non-ascii chars')},
]
for c in data {
s := IA5String.new(c.src) or {
assert err == c.err
continue
}
out := encode(s) or {
assert err == c.err
continue
}
assert out == c.out
// unpack back
mut p := Parser.new(out)
ret := IA5String.parse(mut p) or {
assert err == c.err
continue
}
assert ret.tag().tag_number() == 22
assert ret.tag().tag_class() == TagClass.universal
assert ret.tag().is_constructed() == false
}
}
+447
View File
@@ -0,0 +1,447 @@
// Copyright (c) 2022, 2024 blackshirt. All rights reserved.
// Use of this source code is governed by a MIT License
// that can be found in the LICENSE file.
module asn1
import math.big
import crypto.internal.subtle
// default_integer_tag is the default of ASN.1 INTEGER type.
pub const default_integer_tag = Tag{.universal, false, int(TagType.integer)}
// ASN.1 INTEGER.
//
// The INTEGER type value can be a positive or negative number.
// There are no limits imposed on the magnitude of INTEGER values in the ASN.1 standard.
// Its handles number arbitrary length of number with support of `math.big` module.
// But, for sake of safety, we limit the INTEGER limit to follow allowed length in
// definite form of Length part, ie, 1008 bit, or 126 bytes
// The encoding of an integer number shall be primitive.
// Limit of length of INTEGER type, in bytes
// Known big RSA keys is 4096 bits, ie, 512 bytes
const max_integer_length = 2048
// Integer represent Universal class of arbitrary length type of ASN.1 INTEGER.
// The encoding of an integer value shall be primitive.
// If the contents octets of an integer value encoding consist of more than one octet,
// then the bits of the first octet and bit 8 of the second octet.
// a) shall not all be ones; and.
// b) shall not all be zero.
// NOTE – These rules ensure that an integer value is always encoded in
// the smallest possible number of octets.
pub struct Integer {
pub:
// underlying integer value with support from `i64` and `big.Integer`
value IntValue
}
// hex returns Integer value as a hex string.
pub fn (v Integer) hex() string {
match v.value {
i64 {
val := v.value as i64
return val.hex_full()
}
big.Integer {
val := v.value as big.Integer
return val.hex()
}
}
}
fn (v Integer) str() string {
return 'Integer ${v.value.str()}'
}
// IntValue represents arbitrary integer value, currently we support
// through primitive 164 type for integer value below < max_i64, and
// use `big.Integer` for support arbitrary length of integer values.
type IntValue = big.Integer | i64
fn (v IntValue) str() string {
match v {
i64 {
val := v as i64
return val.str()
}
big.Integer {
val := v as big.Integer
return val.str()
}
}
}
// bytes get the bytes representation from underlying IntValue.
fn (v IntValue) bytes() []u8 {
match v {
i64 {
return i64_to_bytes(v)
}
big.Integer {
// if v == big.zero_int or similar big.Integer values that produces empty bytes,
// returns v.bytes() directly can lead to undesired behavior thats doesn't aligned with
// ASN.1 INTEGER requirement. See the discussion on the discord about the issues
// at https://discord.com/channels/592103645835821068/592294828432424960/1230460279733620777
// so, we do some hack to get the correct value
// TODO: find the correct way to tackle this
if v == big.zero_int {
return [u8(0x00)]
}
// todo: proper check of 0 bytes length
if v.bit_len() == 0 {
return [u8(0x00)]
}
// otherwise, we use v.bytes() directly
b, _ := v.bytes()
return b
}
}
}
// from_i64 creates new a ASN.1 Integer from i64 v.
pub fn Integer.from_i64(v i64) Integer {
return Integer{
value: IntValue(v)
}
}
// from_int creates a new ASN.1 Integer from int v.
pub fn Integer.from_int(v int) Integer {
return Integer{
value: IntValue(i64(v))
}
}
// from_bigint creates a new ASN.1 Integer from big.Integer b
pub fn Integer.from_bigint(b big.Integer) Integer {
return Integer{
value: IntValue(b)
}
}
// from_string creates a new ASN.1 Integer from decimal string s.
// If your string value is below max_i64, use from_i64 instead
pub fn Integer.from_string(s string) !Integer {
v := big.integer_from_string(s)!
return Integer{
value: IntValue(v)
}
}
// from_hex creates a new ASN.1 Integer from hex string in x
// where x is a valid hex string without `0x` prefix.
// If your string value is below max_i64, use from_i64 instead
pub fn Integer.from_hex(x string) !Integer {
s := big.integer_from_radix(x, 16)!
return Integer{
value: IntValue(s)
}
}
// from_bytes creates a new ASN.1 Integer from bytes array in b.
// Its try to parse bytes as in two's complement form.
fn Integer.from_bytes(b []u8) !Integer {
return Integer.unpack_from_twoscomplement_bytes(b)!
}
// unpack_from_twoscomplement_bytes parses the bytes in b into the Integer
// value in the big-endian two's complement way. If b[0]&80 != 0, the number
// is negative. If b is empty it would be error.
fn Integer.unpack_from_twoscomplement_bytes(b []u8) !Integer {
// FIXME: should we return error instead ?
if b.len == 0 {
return error('Integer: null bytes')
}
if b.len > 7 {
mut num := big.integer_from_bytes(b)
// negative number
if b.len > 0 && b[0] & 0x80 > 0 {
sub := big.one_int.left_shift(u32(b.len) * 8)
num -= sub
}
return Integer{
value: IntValue(num)
}
}
// use i64
val := read_i64(b)!
res := Integer.from_i64(val)
return res
}
// bytes return underlying bytes array
fn (v Integer) bytes() []u8 {
return v.value.bytes()
}
// bytes_len returns underlying bytes length
fn (v Integer) bytes_len() int {
b := v.value.bytes()
return b.len
}
// tag returns the tag of Integer type element
pub fn (v Integer) tag() Tag {
return default_integer_tag
}
// payload returns the payload of Integer type element.
pub fn (v Integer) payload() ![]u8 {
bytes, _ := v.pack_into_twoscomplement_form()!
return bytes
}
// pack_into_twoscomplement_form serialize Integer in two's-complement rules.
// - The integer value contains the encoded integer if it is positive, or its two's complement if it is negative.
// - If the integer is positive but the high order bit is set to 1, a leading 0x00 is added to the content
// to indicate that the number is not negative.
// - If the number is negative after applying two's-complement rules, and the most-significant-bit of the
// the high order bit of the bytes results isn't set, pad it with 0xff in order to keep the number negative.
fn (v Integer) pack_into_twoscomplement_form() !([]u8, int) {
match v.value {
i64 {
val := v.value as i64
mut bytes := i64_to_bytes(val)
return bytes, bytes.len
}
big.Integer {
match v.value.signum {
0 {
return [u8(0x00)], 1
}
1 {
mut b := v.bytes()
// handle the zero issues
if b.len == 0 {
return [u8(0x00)], 1
}
// If the integer is positive but the high order bit is set to 1, a leading 0x00 is added
// to the content to indicate that the number is not negative
if b[0] & 0x80 > 0 {
b.prepend(u8(0x00))
}
return b, b.len
}
-1 {
// A negative number has to be converted to two's-complement form.
// by invert the number and then subtract it with big(1), or with other mean
// Flip all of the bits in the value and then add one to the resulting value.
// If the most-significant-bit isn't set then we'll need to pad the
// beginning with 0xff in order to keep the number negative.
negv := v.value.neg()
negvminus1 := negv - big.one_int
mut bytes, _ := negvminus1.bytes()
for i, _ in bytes {
bytes[i] ^= 0xff
}
if bytes.len == 0 || bytes[0] & 0x80 == 0 {
bytes.prepend(u8(0xff))
}
return bytes, bytes.len
}
else {
return error('should unreachable')
}
}
}
}
}
// equal do checking if integer n was equal to integer m.
// ISSUE?: There are some issues when compared n == m directly,
// its fails even internally its a same, so we provide and use equality check
pub fn (n Integer) equal(m Integer) bool {
nbytes := n.bytes()
mbytes := m.bytes()
// todo: check sign equality
// m.tag() == n.tag() by definition, no need to check
return subtle.constant_time_compare(nbytes, mbytes) == 1
}
// Integer.unpack_and_validate deserializes bytes in b into Integer
// in two's complement way and perform validation on this bytes to
// meet der requirement.
fn Integer.unpack_and_validate(b []u8) !Integer {
if !valid_bytes(b, true) {
return error('Integer: check return false')
}
ret := Integer.unpack_from_twoscomplement_bytes(b)!
return ret
}
// as_bigint casts Integer value to big.Integer or error on fails.
pub fn (v Integer) as_bigint() !big.Integer {
if v.value is big.Integer {
val := v.value as big.Integer
return val
}
return error('Integer not hold big.Integer type')
}
// as_i64 casts Integer value to i64 value or error on fails.
pub fn (v Integer) as_i64() !i64 {
if v.value is i64 {
val := v.value as i64
return val
}
return error('Integer not hold i64 type')
}
// parse tries to read and parse into Integer type or return error on fails.
fn Integer.parse(mut p Parser) !Integer {
tag := p.read_tag()!
if !tag.equal(default_integer_tag) {
return error('Get unexected non Integer tag')
}
length := p.read_length()!
if length < 1 {
return error('Get length < 1 for Integer length')
}
bytes := p.read_bytes(length)!
ret := Integer.from_bytes(bytes)!
return ret
}
// decode tries to decode bytes array into Integer type or error on fails
fn Integer.decode(bytes []u8) !(Integer, int) {
return Integer.decode_with_rule(bytes, 0, .der)!
}
// decode_with_rule tries to decode bytes back into ASN.1 Integer.
// Its accepts `loc` params, the location (offset) within bytes where the unpack start from.
// If not sure set to 0 to drive unpacking and rule of `Encodingrule`, currently only support`.der`.
fn Integer.decode_with_rule(bytes []u8, loc int, rule EncodingRule) !(Integer, int) {
if bytes.len < 3 {
return error('Integer: bad bytes length')
}
tag, length_pos := Tag.decode_with_rule(bytes, loc, rule)!
if !tag.equal(default_integer_tag) {
return error('Get unexpected Integer tag')
}
length, content_pos := Length.decode_with_rule(bytes, length_pos, rule)!
payload := if length == 0 {
[]u8{}
} else {
if content_pos + length > bytes.len {
return error('Not enought bytes to read on')
}
unsafe { bytes[content_pos..content_pos + length] }
}
// buf := trim_bytes(payload)!
next := content_pos + length
result := Integer.from_bytes(payload)!
return result, next
}
// Utility function
//
fn is_highest_bit_set(src []u8) bool {
if src.len > 0 {
return src[0] & 0x80 == 0
}
return false
}
fn trim_bytes(src []u8) ![]u8 {
if src.len == 0 {
return error('bad src')
}
// TODO: removes prepended bytes when its meet criteria
// positive value but its prepended with 0x00
if src.len > 1 && src[0] == 0x00 && src[1] & 0x80 > 0 {
bytes := src[1..]
return bytes
}
// TODO: how to do with multiples 0xff
if src.len > 1 && src[0] == 0xff && src[1] & 0x80 == 0 {
bytes := src[1..]
return bytes
}
return src
}
// length_i64 gets bytes length needed to reperesent this i64 value
fn length_i64(val i64) int {
mut i := val
mut n := 1
for i > 127 {
n++
i >>= 8
}
for i < -128 {
n++
i >>= 8
}
return n
}
// i64_to_bytes transforms i64 value into bytes representation
fn i64_to_bytes(i i64) []u8 {
mut n := length_i64(i)
mut dst := []u8{len: n}
for j := 0; j < n; j++ {
dst[j] = u8(i >> u32((n - 1 - j) * 8))
}
return dst
}
// read_i64 read src as signed i64
fn read_i64(src []u8) !i64 {
if !valid_bytes(src, true) {
return error('i64 check return false')
}
mut ret := i64(0)
if src.len > 8 {
return error('too large integer')
}
for i := 0; i < src.len; i++ {
ret <<= 8
ret |= i64(src[i])
}
ret <<= 64 - u8(src.len) * 8
ret >>= 64 - u8(src.len) * 8
// try to serialize back, and check its matching original one
// and gives a warning when its not match.
dst := i64_to_bytes(ret)
if dst != src {
eprintln('maybe integer bytes not in shortest form')
}
return ret
}
// i32 handling
//
// read_i32 read from bytes
fn read_i32(src []u8) !int {
if !valid_bytes(src, true) {
return error('i32 check return false')
}
ret := read_i64(src)!
if ret != i64(int(ret)) {
return error('integer too large')
}
return int(ret)
}
fn length_i32(v i32) int {
return length_i64(i64(v))
}
fn i32_to_bytes(v i32) []u8 {
return i64_to_bytes(i64(v))
}
+268
View File
@@ -0,0 +1,268 @@
// Copyright (c) 2022, 2024 blackshirt All rights reserved.
// Use of this source code is governed by a MIT License
// that can be found in the LICENSE file.
module asn1
import math.big
struct TwoFormTest {
value string
expected string
}
const string_data = [
TwoFormTest{'0', '\x00'},
TwoFormTest{'25', '\x19'},
TwoFormTest{'100', '\x64'},
TwoFormTest{'-1042342234234123423435647768234', '\xF2\xD8\x02\xB6R\x7F\x99\xEE\x98#\x99\xA9V'},
TwoFormTest{'-12095473475870063', '\xD5\a;\x20\x14\xA2\x91'},
TwoFormTest{'12095473475870063', '*\xF8\xC4\xDF\xEB]o'},
TwoFormTest{'12438789579431234124191998', '\nJ\x04"^\x91\x04\x8a\xb1\x18\xfe'},
TwoFormTest{'-112233441191', '\xe5\xde]\x98Y'},
TwoFormTest{'64206', '\x00\xfa\xce'},
TwoFormTest{'-100', '\x9C'},
TwoFormTest{'100', '\x64'},
TwoFormTest{'255', '\x00\xFF'},
TwoFormTest{'0', '\x00'},
TwoFormTest{'-2', '\xfe'},
TwoFormTest{'-1', '\xff'},
TwoFormTest{'-16', '\xf0'},
TwoFormTest{'-256', '\xff\x00'},
TwoFormTest{'-255', '\xff\x01'},
TwoFormTest{'-32768', '\x80\x00'},
TwoFormTest{'-128', '\x80'},
TwoFormTest{'-129', '\xff\x7f'},
TwoFormTest{'-127', '\x81'},
]
fn test_integer_pack_n_unpack_from_n_into_2form() ! {
for i, c in string_data {
v := Integer.from_string(c.value)!
out, _ := v.pack_into_twoscomplement_form()!
assert out == c.expected.bytes()
// unpack back
b := Integer.unpack_from_twoscomplement_bytes(c.expected.bytes())!
assert b.value.str() == c.value
}
}
struct UnpackTest {
val i64
out []u8
}
// from python asn1tools
//
const unpack_data = [
UnpackTest{32768, [u8(0x02), 0x03, 0x00, 0x80, 0x00]},
UnpackTest{32767, [u8(0x02), 0x02, 0x7f, 0xff]},
UnpackTest{256, [u8(0x02), 0x02, 0x01, 0x00]},
UnpackTest{255, [u8(0x02), 0x02, 0x00, 0xff]},
UnpackTest{128, [u8(0x02), 0x02, 0x00, 0x80]},
UnpackTest{127, [u8(0x02), 0x01, 0x7f]},
UnpackTest{1, [u8(0x02), 0x01, 0x01]},
UnpackTest{0, [u8(0x02), 0x01, 0x00]},
UnpackTest{-1, [u8(0x02), 0x01, 0xff]},
UnpackTest{-128, [u8(0x02), 0x01, 0x80]},
UnpackTest{-129, [u8(0x02), 0x02, 0xff, 0x7f]},
UnpackTest{-256, [u8(0x02), 0x02, 0xff, 0x00]},
UnpackTest{-32768, [u8(0x02), 0x02, 0x80, 0x00]},
UnpackTest{-32769, [u8(0x02), 0x03, 0xff, 0x7f, 0xff]},
]
fn test_asn1_integer_unencode() ! {
for i, c in unpack_data {
n := Integer.from_i64(c.val)
to := encode(n)!
assert to == c.out
}
}
struct ASNIntegerTest {
bytes []u8
err IError
expected Integer
}
const integer_test_data = [
ASNIntegerTest{[u8(0x00)], none, Integer.from_int(0)},
ASNIntegerTest{[u8(0x7f)], none, Integer.from_int(127)},
ASNIntegerTest{[u8(0x00), 0x80], none, Integer.from_int(128)},
ASNIntegerTest{[u8(0x01), 0x00], none, Integer.from_int(256)},
ASNIntegerTest{[u8(0x80)], none, Integer.from_int(-128)},
ASNIntegerTest{[u8(0xff), 0x7f], none, Integer.from_int(-129)},
ASNIntegerTest{[u8(0xff)], none, Integer.from_int(-1)},
ASNIntegerTest{[u8(0x80), 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], none, Integer{
value: big.integer_from_string('-9223372036854775808') or { panic(err) }
}},
ASNIntegerTest{[u8(0x80), 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], none, Integer{
value: big.integer_from_string('-2361183241434822606848') or { panic(err) }
}},
ASNIntegerTest{[], error('Integer: check return false'), Integer.from_i64(0)},
ASNIntegerTest{[u8(0x00), 0x7f], error('Integer: check return false'), Integer.from_int(127)}, // non-minimal form
ASNIntegerTest{[u8(0xff), 0xf0], error('Integer: check return false'), Integer.from_int(-16)}, // non-minimal form
ASNIntegerTest{[], error('Integer: check return false'), Integer.from_i64(0)}, // empty integer
ASNIntegerTest{[u8(0x00)], none, Integer.from_i64(0)},
ASNIntegerTest{[u8(0x7f)], none, Integer.from_int(127)},
ASNIntegerTest{[u8(0x00), 0x80], none, Integer.from_int(128)},
ASNIntegerTest{[u8(0x01), 0x00], none, Integer.from_int(256)},
ASNIntegerTest{[u8(0x80)], none, Integer.from_int(-128)},
ASNIntegerTest{[u8(0xff), 0x7f], none, Integer.from_int(-129)},
ASNIntegerTest{[u8(0x80), 0x00, 0x00, 0x00], none, Integer.from_string('-2147483648') or {
panic(err)
}},
ASNIntegerTest{[u8(0x80), 0x00, 0x00, 0x00, 0x00], none, Integer.from_i64(-549755813888)},
ASNIntegerTest{[u8(0x00), 0x7f], error('Integer: check return false'), Integer.from_i64(0)},
ASNIntegerTest{[u8(0xff), 0xf0], error('Integer: check return false'), Integer.from_i64(0)}, // not minimally
]
// from golang encoding/asn1 test
fn test_asn1_unpack_and_validate() {
for i, v in integer_test_data {
ret := Integer.unpack_and_validate(v.bytes) or {
assert err == v.err
continue
}
// compared directly, with ret == v.expected, failed with -cstrict option
// assert ret == v.expected
// > assert ret == v.expected
// Left value (len: 28): `Integer -9223372036854775808`
// Right value (len: 28):`Integer -9223372036854775808`
assert ret.equal(v.expected)
}
}
fn test_asn1_integer_simple_long_integer_pack_unpack() ! {
num := Integer.from_hex('0102030405060708090a0b0c0d0e0f')!
dst := encode(num)!
expected := '\x02\x0f\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f'.bytes()
assert dst == expected
// unpack back
val, pos := Integer.decode(expected)!
assert val == num
assert pos == 17
// test with negative value
negnum := Integer.from_hex('-0102030405060708090a0b0c0d0e0f')!
out := encode(negnum)!
expneg := '\x02\x0f\xfe\xfd\xfc\xfb\xfa\xf9\xf8\xf7\xf6\xf5\xf4\xf3\xf2\xf1\xf1'.bytes()
assert out == expneg
// unpack back a negative number
val2, pos2 := Integer.decode(expneg)!
assert val2 == negnum
assert pos2 == 17
}
// from asn-one test cases
// FIXME: Its still need a fix, remove additional 0x00 before 0x8f in unpack operation
fn test_integer_large_int() ! {
bytes := [u8(0x02), 0x81, 0x81, 0x00, 0x8f, 0xe2, 0x41, 0x2a, 0x08, 0xe8, 0x51, 0xa8, 0x8c,
0xb3, 0xe8, 0x53, 0xe7, 0xd5, 0x49, 0x50, 0xb3, 0x27, 0x8a, 0x2b, 0xcb, 0xea, 0xb5, 0x42,
0x73, 0xea, 0x02, 0x57, 0xcc, 0x65, 0x33, 0xee, 0x88, 0x20, 0x61, 0xa1, 0x17, 0x56, 0xc1,
0x24, 0x18, 0xe3, 0xa8, 0x08, 0xd3, 0xbe, 0xd9, 0x31, 0xf3, 0x37, 0x0b, 0x94, 0xb8, 0xcc,
0x43, 0x08, 0x0b, 0x70, 0x24, 0xf7, 0x9c, 0xb1, 0x8d, 0x5d, 0xd6, 0x6d, 0x82, 0xd0, 0x54,
0x09, 0x84, 0xf8, 0x9f, 0x97, 0x01, 0x75, 0x05, 0x9c, 0x89, 0xd4, 0xd5, 0xc9, 0x1e, 0xc9,
0x13, 0xd7, 0x2a, 0x6b, 0x30, 0x91, 0x19, 0xd6, 0xd4, 0x42, 0xe0, 0xc4, 0x9d, 0x7c, 0x92,
0x71, 0xe1, 0xb2, 0x2f, 0x5c, 0x8d, 0xee, 0xf0, 0xf1, 0x17, 0x1e, 0xd2, 0x5f, 0x31, 0x5b,
0xb1, 0x9c, 0xbc, 0x20, 0x55, 0xbf, 0x3a, 0x37, 0x42, 0x45, 0x75, 0xdc, 0x90, 0x65]
expected_integer :=
Integer.from_string('101038645214968213029489864879507742420925199145132483818978980455132582258676381289000109319204510275496178360219909358646064503513889573494768497419381751359787623037449375660247011308028102339473875820259375735204357343091558075960601364303443174344509161224592926325506446708043127306053676664799729848421')!
out, pos := Integer.decode(bytes)!
assert pos == bytes.len
assert out.tag() == expected_integer.tag() // success
outbytes := out.bytes()
expbytes := expected_integer.bytes()
assert outbytes == expbytes // success
// this direct assert fails
// BUG?: there are some issues when compared out == expected directly, even internally its a same,
// but it fails to assert, so we provide and use equality check
// assert out == expected_integer
// dump(out)
// dump(expected_integer)
assert out.equal(expected_integer)
// pack back
dst := encode(expected_integer)!
assert dst == bytes
dst2 := encode(out)!
assert dst2 == bytes
}
// This test taken from: https://learn.microsoft.com/id-id/windows/win32/seccertenroll/about-integer
// 02 81 81 ; INTEGER (81 Bytes)
// | 00
// | 8f e2 41 2a 08 e8 51 a8 8c b3 e8 53 e7 d5 49 50
// | b3 27 8a 2b cb ea b5 42 73 ea 02 57 cc 65 33 ee
// | 88 20 61 a1 17 56 c1 24 18 e3 a8 08 d3 be d9 31
// | f3 37 0b 94 b8 cc 43 08 0b 70 24 f7 9c b1 8d 5d
// | d6 6d 82 d0 54 09 84 f8 9f 97 01 75 05 9c 89 d4
// | d5 c9 1e c9 13 d7 2a 6b 30 91 19 d6 d4 42 e0 c4
// | 9d 7c 92 71 e1 b2 2f 5c 8d ee f0 f1 17 1e d2 5f
// | 31 5b b1 9c bc 20 55 bf 3a 37 42 45 75 dc 90 65
fn test_integer_decode_encode_with_real_data() ! {
data := [u8(0x02), 0x81, 0x81, 0x00, 0x8f, 0xe2, 0x41, 0x2a, 0x08, 0xe8, 0x51, 0xa8, 0x8c,
0xb3, 0xe8, 0x53, 0xe7, 0xd5, 0x49, 0x50, 0xb3, 0x27, 0x8a, 0x2b, 0xcb, 0xea, 0xb5, 0x42,
0x73, 0xea, 0x02, 0x57, 0xcc, 0x65, 0x33, 0xee, 0x88, 0x20, 0x61, 0xa1, 0x17, 0x56, 0xc1,
0x24, 0x18, 0xe3, 0xa8, 0x08, 0xd3, 0xbe, 0xd9, 0x31, 0xf3, 0x37, 0x0b, 0x94, 0xb8, 0xcc,
0x43, 0x08, 0x0b, 0x70, 0x24, 0xf7, 0x9c, 0xb1, 0x8d, 0x5d, 0xd6, 0x6d, 0x82, 0xd0, 0x54,
0x09, 0x84, 0xf8, 0x9f, 0x97, 0x01, 0x75, 0x05, 0x9c, 0x89, 0xd4, 0xd5, 0xc9, 0x1e, 0xc9,
0x13, 0xd7, 0x2a, 0x6b, 0x30, 0x91, 0x19, 0xd6, 0xd4, 0x42, 0xe0, 0xc4, 0x9d, 0x7c, 0x92,
0x71, 0xe1, 0xb2, 0x2f, 0x5c, 0x8d, 0xee, 0xf0, 0xf1, 0x17, 0x1e, 0xd2, 0x5f, 0x31, 0x5b,
0xb1, 0x9c, 0xbc, 0x20, 0x55, 0xbf, 0x3a, 0x37, 0x42, 0x45, 0x75, 0xdc, 0x90, 0x65]
// try to decode
val, pos := Integer.decode(data)!
value := val.as_bigint()! // big.Integer
assert pos == data.len
// serialized back
int_element := Integer.from_bigint(value)
serialized_int := encode(int_element)!
assert serialized_int == data
// with decode
el := decode(data)!
assert el.equal(val)
}
// This is taken from https://letsencrypt.org/id/docs/a-warm-welcome-to-asn1-and-der/
// for integer encoding
fn test_integer_encoding() ! {
data := [u8(0x02), 0x09, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]
// try to decode
val, pos := Integer.decode(data)!
value := val.as_bigint()! // big.Integer
assert pos == data.len
// serialized back
int_element := Integer.from_bigint(value)
serialized_int := encode(int_element)!
assert serialized_int == data
// with decode
el := decode(data)!
assert el.equal(val)
}
// taken from https://github.com/PeculiarVentures/ASN1.js test.
fn test_negative_number() ! {
value := Integer.from_string('-9150748177064392952')!
out := encode(value)!
assert out.hex() == '02088102030405060708'
back := decode(out)!
back_value := back.into_object[Integer]()!
assert back_value.value.str() == '-9150748177064392952'
assert back_value.value == value.value
}
+207
View File
@@ -0,0 +1,207 @@
// Copyright (c) 2022, 2024 blackshirt. All rights reserved.
// Use of this source code is governed by a MIT License
// that can be found in the LICENSE file.
module asn1
// max_definite_length_count is a limit tells how many bytes to represent this length.
// We're going to limit this to 6 bytes following when the length is in long-definite form.
const max_definite_length_count = 6
const max_definite_length_value = max_int
// ASN.1 length handling routines.
//
// The standard of X.690 ITU document defines two length types - definite and indefinite.
// In DER encoding only uses the definite length. The length field must be encoded in the minimum number of octets.
// There are two forms of definite length octets: short (for lengths value between 0 and 127),
// and long definite (for lengths value between 0 and 2^1008 -1).
// In short form contains one octet. Bit 8 has value "0" and bits 7-1 give the length (length value from 0 to 127)
// In long form, its required two or more octets, limited to 127 octets. Bit 8 of first octet has value "1" and bits 7-1 gives
// the number of additional length octets.
// Second and following octets give the length, base 256, most significant digit first.
//
// This module only support definite length, in short or long form. Its required for DER encoding
// the length octets should be in definite length.
//
// Length represent ASN.1 length value
pub type Length = int
// new creates Length from integer value. Passing negative value (<0) for length
// is not make a sense, so just return error instead if it happen.
pub fn Length.new(v int) !Length {
if v < 0 {
return error('Length: supply with positive int')
}
if v > max_definite_length_value {
return error('Length: value provided exceed limit')
}
return Length(v)
}
// Length.from_bytes tries to read length from bytes array and return the length
// and the rest of remaining bytes on success, or error on fails.
pub fn Length.from_bytes(bytes []u8) !(Length, []u8) {
length, next := Length.decode(bytes)!
if next < bytes.len {
rest := unsafe { bytes[next..] }
return length, rest
}
if next == bytes.len {
return length, []u8{}
}
return error('Length: too short data')
}
// encode serializes the length v into bytes array stored into buffer buf in .der rule.
pub fn (v Length) encode(mut dst []u8) ! {
v.encode_with_rule(mut dst, .der)!
}
// encode_with_rule serializes Length v into bytes and append it into `dst`.
// it would use rule of `Encodingrule` to drive how encode operation would be done.
// By default the .der rule is only currently supported.
fn (v Length) encode_with_rule(mut dst []u8, rule EncodingRule) ! {
// we currently only support .der and (stricter) .ber
if rule != .der && rule != .ber {
return error('Length: unsupported rule')
}
// TODO: add supports for undefinite form
// Long form
if v >= 128 {
// First, we count how many bytes occupied by this length value.
// if the count exceed the limit, we return error.
count := v.bytes_len()
if count > max_definite_length_count {
return error('something bad in your length')
}
// In definite long form, msb bit of first byte is set into 1, and the remaining bits
// of first byte tells exact count how many bytes following representing this length value.
dst << 0x80 | u8(count)
v.to_bytes(mut dst)
} else {
// short form, already tells the length value.
dst << u8(v)
}
}
// bytes_len tells how many bytes needed to represent this length
fn (v Length) bytes_len() int {
mut i := v
mut num := 1
for i > 255 {
num++
i >>= 8
}
return num
}
// to_bytes packs Length v into bytes and apends it into `dst` bytes.
fn (v Length) to_bytes(mut dst []u8) {
mut n := v.bytes_len()
for ; n > 0; n-- {
// pay attention to the brackets
dst << u8(v >> ((n - 1) * 8))
}
}
// length_size gets length of length v in .der rule
fn (v Length) length_size() !int {
n := v.length_size_with_rule(.der)!
return n
}
// length_size_with_rule calculates the length of bytes needed to store the Length value `v`
// includes one byte marker for long definite form of length value, for value >= 128
fn (v Length) length_size_with_rule(rule EncodingRule) !int {
// we currently only support .der or (stricter) .ber
if rule != .der && rule != .ber {
return error('Length: unsupported rule')
}
n := if v < 128 { 1 } else { v.bytes_len() + 1 }
return n
}
// decode read length from bytes src with default context or return error on fails.
fn Length.decode(src []u8) !(Length, int) {
ret, next := Length.decode_from_offset(src, 0)!
return ret, next
}
// decode_from_offset read length from src bytes start from offset pos or return error on fails.
fn Length.decode_from_offset(src []u8, pos int) !(Length, int) {
ret, next := Length.decode_with_rule(src, pos, .der)!
return ret, next
}
// decode_with_rule tries to decode and deserializes buffer in src into Length form, start from offset loc in the buffer.
// Its return Length and next offset in the buffer to process on, or return error on fails.
fn Length.decode_with_rule(src []u8, loc int, rule EncodingRule) !(Length, int) {
if src.len < 1 {
return error('Length: truncated length')
}
// preliminary check
if rule != .der && rule != .ber {
return error('Length: unsupported rule')
}
// consider b := src[loc] would lead to panic
if loc >= src.len {
return error('Length: invalid pos')
}
mut pos := loc
mut b := src[pos]
pos += 1
mut length := 0
// check for the most bit is set or not
if b & 0x80 == 0 {
// for lengths between 0 and 127, the one-octet short form can be used.
// The bit 7 of the length octet is set to 0, and the length is encoded
// as an unsigned binary value in the octet's rightmost seven bits.
length = b & 0x7f
} else {
if pos > src.len {
return error('truncated tag or length')
}
// Otherwise, its a Long definite form or undefinite form
num_bytes := b & 0x7f
if num_bytes == 0 {
// TODO: add support for undefinite length
return error('Length: unsupported undefinite length')
}
if num_bytes == 0x7f {
return error('Length: 0x7f is for reserved use')
}
// we limit the bytes count for length definite form to `max_definite_length_count`
if num_bytes > max_definite_length_count {
return error('Length: count bytes exceed limit')
}
for i := 0; i < num_bytes; i++ {
if pos >= src.len {
return error('Length: truncated length')
}
b = src[pos]
pos += 1
// currently, we're only support limited length.
// The length is in int range
if length > max_definite_length_value {
return error('Length: length exceed limit value')
}
length <<= 8
length |= b
if length == 0 {
// TODO: leading zeros is allowed in Long form of BER encoding, but
// not allowed in DER encoding
if rule == .der {
return error('Length: leading zeros')
}
}
}
// do not allow values < 0x80 to be encoded in long form
if length < 0x80 {
// TODO: allow in BER
return error('Length: dont needed in long form')
}
}
ret := Length.new(length)!
return ret, pos
}
+222
View File
@@ -0,0 +1,222 @@
// Copyright (c) 2022, 2023 blackshirt. All rights reserved.
// Use of this source code is governed by a MIT License
// that can be found in the LICENSE file.
module asn1
struct TagAndLengthTest {
bytes []u8
tag Tag
explength i64
lastpos int
err IError
}
fn test_tagandlength_handling() ! {
// from golang asn.1 test
bs := [
// Context Specific should be in constructed form
TagAndLengthTest{[u8(0x80), 0x01], Tag{.context_specific, false, 0}, 1, 2, none}, // 0b_10_0_00000
TagAndLengthTest{[u8(0xa0), 0x01], Tag{.context_specific, true, 0}, 1, 2, none}, // 0b_10_1_00000
TagAndLengthTest{[u8(0x02), 0x00], Tag{.universal, false, 2}, 0, 2, none},
TagAndLengthTest{[u8(0xfe), 0x00], Tag{.private, true, 30}, 0, 2, none},
TagAndLengthTest{[u8(0x1f), 0x1f, 0x00], Tag{.universal, false, 31}, 0, 3, none}, // high tag form
TagAndLengthTest{[u8(0x1f), 0x81, 0x00, 0x01], Tag{.universal, false, 128}, 1, 4, none},
// the last byte tells its length in long form
TagAndLengthTest{[u8(0x1f), 0x81, 0x00, 0x81], Tag{.universal, false, 128}, 1, 4, error('Length: truncated length')},
TagAndLengthTest{[u8(0x1f), 0x81, 0x80, 0x01, 0x00], Tag{.universal, false, 16385}, 0, 5, error('Tag number: base 128 integer too large')}, // 1x128^2 + 0x128^1 + 1x128*0
TagAndLengthTest{[u8(0x00), 0x81, 0x80], Tag{.universal, false, 0}, 128, 3, none},
// need one byte length
TagAndLengthTest{[u8(0x00), 0x83, 0x01, 0x00], Tag{.universal, false, 0}, 2, 1, error('Length: truncated length')},
// normal version above
TagAndLengthTest{[u8(0x00), 0x83, 0x01, 0x01, 0x01], Tag{.universal, false, 0}, 65793, 5, none}, // length = 1x256^2 + 1x256^1 + 1x256^0
TagAndLengthTest{[u8(0x1f), 0x85], Tag{.universal, false, 0}, 0, 2, error('Tag: truncated base 128 integer')},
TagAndLengthTest{[u8(0x1f), 0x85, 0x81], Tag{.universal, false, 0}, 0, 0, error('Tag: truncated base 128 integer')},
// this last bytes tell the length is in undefinite length, 0x80
TagAndLengthTest{[u8(0x30), 0x80], Tag{.universal, true, 0x10}, 0, 2, error('Length: unsupported undefinite length')},
// still truncated length part
TagAndLengthTest{[u8(0x30), 0x81], Tag{.universal, true, 0x10}, 0, 2, error('Length: truncated length')},
// still in uneeded form of length
TagAndLengthTest{[u8(0x30), 0x81, 0x01], Tag{.universal, true, 0x10}, 1, 3, error('Length: dont needed in long form')},
// its fullfill the der requirement
TagAndLengthTest{[u8(0x30), 0x81, 0x80], Tag{.universal, true, 0x10}, 128, 3, none},
// this tell two bytes of length contains leading spurious zero's
TagAndLengthTest{[u8(0xa0), 0x82, 0x00, 0xff], Tag{.context_specific, true, 0}, 255, 1, error('Length: leading zeros')},
TagAndLengthTest{[u8(0xa0), 0x82, 0x01, 0xff], Tag{.context_specific, true, 0}, 511, 4, none},
// Superfluous zeros in the length should be an error.
TagAndLengthTest{[u8(0xa0), 0x82, 0x00, 0xff], Tag{.context_specific, true, 0}, 0, 4, error('Length: leading zeros')}, //{}},
// Lengths up to the maximum size of an int should work.
TagAndLengthTest{[u8(0xa0), 0x84, 0x7f, 0xff, 0xff, 0xff], Tag{.context_specific, true, 0}, 0x7fffffff, 6, none}, //{2, 0, 0x7fffffff, true}},
// Lengths that would overflow an int should be rejected.
TagAndLengthTest{[u8(0xa0), 0x84, 0x80, 0x00, 0x00, 0x00], Tag{.context_specific, true, 0}, 2147483648, 6, error('Length: dont needed in long form')}, //{}},
// Long length form may not be used for lengths that fit in short form.
TagAndLengthTest{[u8(0xa0), 0x81, 0x7f], Tag{.context_specific, true, 0}, 0, 0, error('Length: dont needed in long form')}, //{}},
// Tag numbers which would overflow int32 are rejected. (The number below is 2^31.)
TagAndLengthTest{[u8(0x1f), 0x88, 0x80, 0x80, 0x80, 0x00, 0x00], Tag{.universal, false, 0}, 0, 0, error('Negative tag number')}, //{}},
// Tag numbers that fit in an int32 are valid. (The number below is 2^31 - 1.) but its bigger than max_tag_bytes_length
TagAndLengthTest{[u8(0x1f), 0x87, 0xFF, 0xFF, 0xFF, 0x7F, 0x00], Tag{.universal, false, 2147483647}, 0, 7, error('Tag number: base 128 integer too large')},
// Long tag number form may not be used for tags that fit in short form.
TagAndLengthTest{[u8(0x1f), 0x1e, 0x00], Tag{.universal, false, 0}, 0, 0, error('Tag: non-minimal tag')}, //{}},
]
for i, c in bs {
// dump(i)
tag, pos := Tag.decode(c.bytes) or {
assert err == c.err
continue
}
assert tag == c.tag
length, idx := Length.decode_from_offset(c.bytes, pos) or {
assert err == c.err
continue
}
assert idx == c.lastpos
}
}
struct LengthPackTest {
value int
expected []u8
err IError
}
fn test_length_pack_and_unpack_tofrom_asn() ! {
edata := [
LengthPackTest{0, [u8(0x00)], none},
LengthPackTest{10, [u8(0x0a)], none},
LengthPackTest{127, [u8(0x7f)], none},
LengthPackTest{255, [u8(0x81), 0xff], none},
LengthPackTest{256, [u8(0x82), 0x01, 0x00], none},
LengthPackTest{383, [u8(0x82), 0x01, 127], none},
LengthPackTest{257, [u8(0x82), 0x01, 0x01], none},
LengthPackTest{65535, [u8(0x82), 0xff, 0xff], none},
LengthPackTest{65536, [u8(0x83), 0x01, 0x00, 0x00], none},
LengthPackTest{16777215, [u8(0x83), 0xff, 0xff, 0xff], none},
]
for i, c in edata {
mut dst := []u8{}
s := Length.new(c.value)!
s.encode(mut dst)!
assert dst == c.expected
length, idx := Length.decode(dst)!
assert length == c.value
assert idx == c.expected.len
}
}
struct ByteLengthTest {
value int
expected []u8
}
fn test_basic_simple_length_unpack() {
data := [u8(0x82), 0x01, 0x7F]
n, pos := Length.decode(data)!
assert n == 383
assert pos == 3
data2 := [u8(0x82), 0x01, 0x31]
n2, pos2 := Length.decode(data2)!
assert n2 == 305
assert pos2 == 3
}
fn test_length_pack_and_append() ! {
bdata := [
ByteLengthTest{1, [u8(1)]},
ByteLengthTest{127, [u8(0x7f)]},
ByteLengthTest{255, [u8(0xff)]},
ByteLengthTest{256, [u8(0x01), 0x00]},
ByteLengthTest{383, [u8(0x01), 127]},
ByteLengthTest{257, [u8(0x01), 0x01]},
ByteLengthTest{7967, [u8(0x1f), 0x1f]},
ByteLengthTest{65535, [u8(0xff), 0xff]},
ByteLengthTest{65537, [u8(0x01), 0x00, 0x01]},
ByteLengthTest{16777215, [u8(0xff), 0xff, 0xff]},
]
for v in bdata {
mut dst := []u8{}
ln := Length.new(v.value)!
ln.to_bytes(mut dst)
assert dst == v.expected
}
}
struct LengthTest {
value int
expected int
}
fn test_length_bytes_len() ! {
ldata := [
LengthTest{1, 1},
LengthTest{128, 1},
LengthTest{255, 1},
LengthTest{256, 2},
LengthTest{383, 2},
LengthTest{65535, 2},
LengthTest{65536, 3},
LengthTest{16777215, 3},
LengthTest{16777216, 4},
LengthTest{2147483647, 4}, // math.max_i32
]
for c in ldata {
len := Length.new(c.value)!
out := len.bytes_len()
assert out == c.expected
}
}
fn test_calc_length_of_length() ! {
data := [
LengthTest{1, 1},
LengthTest{128, 2},
LengthTest{255, 2},
LengthTest{256, 3},
LengthTest{383, 3},
LengthTest{65535, 3},
LengthTest{65536, 4},
LengthTest{16777215, 4},
LengthTest{16777216, 5},
LengthTest{2147483647, 5}, // math.max_i32
]
for c in data {
len := Length.new(c.value)!
out := len.length_size()!
assert out == c.expected
}
}
// ASN.1 Test Suite from https://github.com/YuryStrozhevsky/asn1-test-suite
fn test_tc3_absence_standard_length_block() ! {
value := []u8{}
_, _ := Length.decode(value) or {
assert err == error('Length: truncated length')
return
}
}
fn test_tc5_unnecessary_usage_long_of_length_form() ! {
value := [u8(0x7f), 0xff, 0x7f, 0x81, 0x01, 0x40]
tag, pos := Tag.decode(value)!
// 0x9f == 0b0111_1111
assert tag.class == .application
assert tag.constructed == true
assert pos == 3
// the length bytes, [0x81, 0x01] dont needed in long form.
_, _ := Length.decode_with_rule(value, pos, .der) or {
assert err == error('Length: dont needed in long form')
return
}
}
+62
View File
@@ -0,0 +1,62 @@
// Copyright (c) 2022, 2023 blackshirt. All rights reserved.
// Use of this source code is governed by a MIT License
// that can be found in the LICENSE file.
module asn1
// default_null_tag is the default tag of ASN.1 NULL type.
pub const default_null_tag = Tag{.universal, false, int(TagType.null)}
// ASN.1 UNIVERSAL CLASS OF NULL TYPE.
//
// The ASN.1 NULL type is a placeholder used when there is no value.
// It's a simple, non-string type with the UNIVERSAL TAG number 5.
// The NULL type can be used in situations where the presence of a type is important, but no concrete value is needed.
pub struct Null {}
// tag returns the tag of Null element.
pub fn (n Null) tag() Tag {
return default_null_tag
}
// payload returns the payload of the Null element, its should empty bytes.
pub fn (n Null) payload() ![]u8 {
return []u8{}
}
fn (n Null) str() string {
return 'NULL'
}
// `Null.parse` tries to read into Null type from ongoing parser.
fn Null.parse(mut p Parser) !Null {
tag := p.read_tag()!
if !tag.equal(default_null_tag) {
return error('Get unexpected null tag')
}
length := p.read_length()!
if length != 0 {
return error('Get unexpected non-null length for Null type')
}
return Null{}
}
// Null.decode read Null from bytes.
fn Null.decode(bytes []u8) !(Null, int) {
tag, length_pos := Tag.decode(bytes)!
if !tag.equal(default_null_tag) {
return error('Null: get unexpected tag')
}
length, content_pos := Length.decode_from_offset(bytes, length_pos)!
if length != 0 {
return error('Null with non-null length')
}
next := content_pos + length
return Null{}, next
}
fn Null.from_bytes(b []u8) !Null {
if b.len != 0 {
return error('Null: bad non-null bytes')
}
return Null{}
}
@@ -0,0 +1,114 @@
// Copyright (c) 2022, 2024 blackshirt. All rights reserved.
// Use of this source code is governed by a MIT License
// that can be found in the LICENSE file.
module asn1
// default_numericstring_tag is the default tag of ASN.1 NUMERICSTRING type.
pub const default_numericstring_tag = Tag{.universal, false, int(TagType.numericstring)}
// NumericString.
//
// NumericString was restricted character string types
// restricted to sequences of zero, one or more characters from some
// specified collection of characters.
// That was : digit : 0,1,..9 and spaces char (0x20)
pub struct NumericString {
pub:
value string
}
// new creates a new NumericString element from string s.
pub fn NumericString.new(s string) !NumericString {
if !all_numeric_string(s.bytes()) {
return error('NumericString: contains non-numeric string')
}
return NumericString{
value: s
}
}
// tag returns the tag of NumericString element.
pub fn (nst NumericString) tag() Tag {
return default_numericstring_tag
}
// payload returns the payload of NumericString element.
pub fn (nst NumericString) payload() ![]u8 {
return nst.payload_with_rule(.der)!
}
fn (nst NumericString) str() string {
if nst.value.len == 0 {
return 'NumericString (<empty>)'
}
return 'NumericString (${nst.value})'
}
fn (nst NumericString) payload_with_rule(rule EncodingRule) ![]u8 {
bytes := nst.value.bytes()
if !all_numeric_string(bytes) {
return error('NumericString: contains non-numeric string')
}
if rule != .der && rule != .ber {
return error('NumericString: bad rule')
}
return bytes
}
fn NumericString.parse(mut p Parser) !NumericString {
tag := p.read_tag()!
if !tag.equal(default_numericstring_tag) {
return error('Bad NumericString tag')
}
length := p.read_length()!
bytes := p.read_bytes(length)!
res := NumericString.from_bytes(bytes)!
return res
}
fn NumericString.from_bytes(bytes []u8) !NumericString {
if !all_numeric_string(bytes) {
return error('NumericString: contains non-numeric string')
}
return NumericString{
value: bytes.bytestr()
}
}
fn NumericString.decode(bytes []u8) !(NumericString, int) {
ns, next := NumericString.decode_with_rule(bytes, .der)!
return ns, next
}
fn NumericString.decode_with_rule(bytes []u8, rule EncodingRule) !(NumericString, int) {
tag, length_pos := Tag.decode_with_rule(bytes, 0, rule)!
if !tag.equal(default_numericstring_tag) {
return error('Unexpected non-numericstring tag')
}
length, content_pos := Length.decode_with_rule(bytes, length_pos, rule)!
content := if length == 0 {
[]u8{}
} else {
if content_pos >= bytes.len || content_pos + length > bytes.len {
return error('NumericString: truncated payload bytes')
}
unsafe { bytes[content_pos..content_pos + length] }
}
ns := NumericString.from_bytes(content)!
next := content_pos + length
return ns, next
}
// Utility function
//
fn all_numeric_string(bytes []u8) bool {
return bytes.all(is_numericstring(it))
}
fn is_numericstring(c u8) bool {
return c.is_digit() || c == u8(0x20)
}
@@ -0,0 +1,77 @@
// Copyright (c) 2022, 2024 blackshirt. All rights reserved.
// Use of this source code is governed by a MIT License
// that can be found in the LICENSE file.
module asn1
fn test_encode_decode_numericstring_basic() {
str := '98'
exp := [u8(0x12), 0x02, 57, 56]
ns := NumericString.new(str)!
out := encode(ns)!
assert out == exp
// decode back
nsback, _ := NumericString.decode(out)!
assert nsback.tag().tag_class() == .universal
assert nsback.tag().is_constructed() == false
assert nsback.tag().tag_number() == int(TagType.numericstring)
assert nsback.value == str
}
struct NumericalTest {
inp string
exp_length int
exp_bytelength []u8
exp_values []u8
exp_out []u8
err IError
}
fn test_encode_decode_numericstring_advanced() ! {
// maps string to repeat
m := {
'1': 1
'2': 10
'3': 128
'4': 256
'5': 65536 // its too long to repeat
//'6': 16777215
}
mut exp := []NumericalTest{}
for k, v in m {
s := k.repeat(v) // strings.repeat_string(k, v)
b := s.bytes()
ln := Length.new(b.len)!
mut dst := []u8{}
ln.encode(mut dst)!
d := NumericalTest{
inp: s
exp_length: dst.len
exp_bytelength: dst
exp_values: b
err: none
}
exp << d
}
for c in exp {
mut exp_out := [u8(TagType.numericstring)]
exp_out << c.exp_bytelength
exp_out << c.exp_values
ns := NumericString.new(c.inp) or {
assert err == c.err
continue
}
out := encode(ns)!
assert out == exp_out
// decode back
back, _ := NumericString.decode(out)!
assert back.value == c.inp
}
}
+114
View File
@@ -0,0 +1,114 @@
// Copyright (c) 2022, 2024 blackshirt. All rights reserved.
// Use of this source code is governed by a MIT License
// that can be found in the LICENSE file.
module asn1
import encoding.hex
// default_octetstring_tag is the default tag of ASN.1 OCTETSTRING type.
pub const default_octetstring_tag = Tag{.universal, false, int(TagType.octetstring)}
const max_octetstring_size = 1 << 32 - 1
// ASN.1 UNIVERSAL TYPE OF OCTETSTRING.
//
// The ASN.1 OCTET STRING type contains arbitrary strings of octets.
// This type is very similar to BIT STRING, except that all values must
// be an integral number of eight bits.
// You can use constraints to specify a maximum length for an OCTET STRING type.
pub struct OctetString {
pub:
value string
}
// tag returns the tag of OctetString type.
pub fn (oct OctetString) tag() Tag {
return default_octetstring_tag
}
// payload returns the payload of OctetString type.
pub fn (oct OctetString) payload() ![]u8 {
return oct.payload_with_rule(.der)!
}
fn (oct OctetString) payload_with_rule(rule EncodingRule) ![]u8 {
if rule != .der && rule != .ber {
return error('not supported rule')
}
return oct.value.bytes()
}
fn (oct OctetString) str() string {
if oct.value.len == 0 {
return 'OctetString (<empty>)'
}
return 'OctetString (${oct.value})'
}
// new creates a new OctetString element from string s.
pub fn OctetString.new(s string) !OctetString {
if !valid_octet_string(s) {
return error('not valid octet string')
}
return OctetString{
value: s
}
}
// from_hexstring creates a new OctetString element from valid hex string or error on fails.
pub fn OctetString.from_hexstring(hs string) !OctetString {
bytes := hex.decode(hs)!
oct := OctetString.from_bytes(bytes)!
return oct
}
// parse an OctetString from ongoing Parser
fn OctetString.parse(mut p Parser) !OctetString {
tag := p.read_tag()!
if !tag.equal(default_octetstring_tag) {
return error('Bad octetstring tag')
}
length := p.read_length()!
content := p.read_bytes(length)!
payload := if length == 0 { []u8{} } else { content }
oct := OctetString.from_bytes(payload)!
return oct
}
fn OctetString.decode(src []u8) !(OctetString, int) {
return OctetString.decode_with_rule(src, .der)!
}
fn OctetString.decode_with_rule(bytes []u8, rule EncodingRule) !(OctetString, int) {
tag, length_pos := Tag.decode_with_rule(bytes, 0, rule)!
if !tag.equal(default_octetstring_tag) {
return error('Unexpected non-octetstring tag')
}
length, content_pos := Length.decode_with_rule(bytes, length_pos, rule)!
content := if length == 0 {
[]u8{}
} else {
if content_pos >= bytes.len || content_pos + length > bytes.len {
return error('OctetString: truncated payload bytes')
}
unsafe { bytes[content_pos..content_pos + length] }
}
os := OctetString.from_bytes(content)!
next := content_pos + length
return os, next
}
fn OctetString.from_bytes(src []u8) !OctetString {
return OctetString.new(src.bytestr())!
}
// UTILITY for OCTETSTRING
fn valid_octet_string(_s string) bool {
// just return true
return true
}
@@ -0,0 +1,29 @@
// Copyright (c) 2022, 2024 blackshirt. All rights reserved.
// Use of this source code is governed by a MIT License
// that can be found in the LICENSE file.
module asn1
struct OctetStringTest {
inp string
exp []u8
err IError
}
fn test_octetstring_handling() ! {
data := [
OctetStringTest{'', [u8(0x04), 0x00], none},
OctetStringTest{'abc', [u8(0x04), 0x03, 97, 98, 99], none},
]
for o in data {
os := OctetString.new(o.inp)!
out := encode(os) or {
assert err == o.err
continue
}
assert out == o.exp
outback, _ := OctetString.decode(out)!
assert outback.value == o.inp
}
}
+206
View File
@@ -0,0 +1,206 @@
// Copyright (c) 2022, 2024 blackshirt. All rights reserved.
// Use of this source code is governed by a MIT License
// that can be found in the LICENSE file.
module asn1
// TODO: doing check for limiting oid array length.
const max_oid_length = 128
// default_oid_tag is the default tag of ASN.1 OBJECTIDENTIFIER type.
pub const default_oid_tag = Tag{.universal, false, int(TagType.oid)}
// ASN.1 ObjectIdentifier type.
//
// The ASN. 1 OBJECT IDENTIFIER type is used when you need to provide a unique identifier.
pub struct ObjectIdentifier {
mut:
value []int
}
// value returns underlying ObjectIdentifier values as arrays of int.
pub fn (o ObjectIdentifier) value() []int {
return o.value
}
// tag returns the tag of ObjectIdentifier type.
pub fn (oid ObjectIdentifier) tag() Tag {
return default_oid_tag
}
// payload the payload of ObjectIdentifier type.
pub fn (oid ObjectIdentifier) payload() ![]u8 {
return oid.pack_into_bytes()!
}
// new creates a new ObjectIdentifier type from dots (`.`) separated string.
pub fn ObjectIdentifier.new(s string) !ObjectIdentifier {
if s.len < 2 {
return error('ObjectIdentifier: bad string oid length')
}
mut result := []int{}
src := s.split('.')
for n in src {
v := n.parse_int(10, 32)!
result << int(v)
}
oid := ObjectIdentifier{
value: result
}
if !oid.validate() {
return error('ObjectIdentifier: bad oid string')
}
return oid
}
// from_ints creates a new ObjectIdentifier type from arrays of int.
pub fn ObjectIdentifier.from_ints(src []int) !ObjectIdentifier {
// allowed value of first int was 0, 1 or 2,
// and when first=2, second int was not limited.
// contrary, when first < 2, second <= 39
if src.len < 2 || src[0] > 2 || (src[0] < 2 && src[1] >= 40) {
return error('ObjectIdentifier: bad oid int array')
}
// doing check for overflow
for k in src {
if k > max_i32 {
return error('ObjectIdentifier: overflow parse_int result')
}
}
oid := ObjectIdentifier{
value: src
}
if !oid.validate() {
return error('ObjectIdentifier: bad oid int array')
}
return oid
}
fn ObjectIdentifier.from_bytes(src []u8) !ObjectIdentifier {
// maybe two integer fits in 1 bytes
if src.len == 0 {
return error('ObjectIdentifier: bad string oid length')
}
mut s := []int{len: src.len + 1}
mut val, mut pos := decode_base128_int(src, 0)!
if val < 80 {
s[0] = val / 40
s[1] = val % 40
} else {
s[0] = 2
s[1] = val - 80
}
mut i := 2
for ; pos < src.len; i++ {
val, pos = decode_base128_int(src, pos)!
s[i] = val
}
s = unsafe { s[0..i] }
oid := ObjectIdentifier{
value: s
}
if !oid.validate() {
return error('ObjectIdentifier: failed to validate')
}
return oid
}
fn (oid ObjectIdentifier) pack_into_bytes() ![]u8 {
if !oid.validate() {
return error('ObjectIdentifier: failed to validate')
}
mut dst := []u8{}
// the first two components (a.b) of ObjectIdentifier are encoded as 40*a+b
encode_base128_int(mut dst, i64(oid.value[0] * 40 + oid.value[1]))
for i := 2; i < oid.value.len; i++ {
encode_base128_int(mut dst, i64(oid.value[i]))
}
return dst
}
fn ObjectIdentifier.parse(mut p Parser) !ObjectIdentifier {
tag := p.read_tag()!
if !tag.equal(default_oid_tag) {
return error('Bad ObjectIdentifier tag')
}
length := p.read_length()!
bytes := p.read_bytes(length)!
res := ObjectIdentifier.from_bytes(bytes)!
return res
}
fn ObjectIdentifier.decode(src []u8) !(ObjectIdentifier, int) {
return ObjectIdentifier.decode_with_rule(src, .der)!
}
fn ObjectIdentifier.decode_with_rule(bytes []u8, rule EncodingRule) !(ObjectIdentifier, int) {
tag, length_pos := Tag.decode_with_rule(bytes, 0, rule)!
if !tag.equal(default_oid_tag) {
return error('Unexpected non-oid tag')
}
length, content_pos := Length.decode_with_rule(bytes, length_pos, rule)!
content := if length == 0 {
[]u8{}
} else {
if content_pos >= bytes.len || content_pos + length > bytes.len {
return error('ObjectIdentifier: truncated payload bytes')
}
unsafe { bytes[content_pos..content_pos + length] }
}
oid := ObjectIdentifier.from_bytes(content)!
next := content_pos + length
return oid, next
}
// equal checks whether two ObjectIdentifier was equal.
pub fn (oid ObjectIdentifier) equal(oth ObjectIdentifier) bool {
if !oid.tag().equal(oth.tag()) {
return false
}
if oid.value.len != oth.value.len {
return false
}
for i := 0; i < oid.value.len; i++ {
if oid.value[i] != oth.value[i] {
return false
}
}
return true
}
fn (oid ObjectIdentifier) str() string {
if oid.value.len == 0 {
return 'OID <nil>'
}
mut s := []string{}
for i in oid.value {
s << i.str()
}
res := s.join('.')
return '${res}'
}
fn (oid ObjectIdentifier) validate() bool {
if oid.value.len > max_oid_length {
return false
}
if oid.value.len < 2 || oid.value[0] > 2 || (oid.value[0] < 2 && oid.value[1] >= 40) {
return false
}
return true
}
fn (oid ObjectIdentifier) oid_length() int {
mut n := base128_int_length(i64(oid.value[0] * 40 + oid.value[1]))
for i := 2; i < oid.value.len; i++ {
n += base128_int_length(i64(oid.value[i]))
}
return n
}
+260
View File
@@ -0,0 +1,260 @@
// Copyright (c) 2022, 2024 blackshirt. All rights reserved.
// Use of this source code is governed by a MIT License
// that can be found in the LICENSE file.
module asn1
struct OidWriteTest {
inp []int
exp []u8
err IError
}
fn assert_no_error(err IError) {
assert err.str() == 'none'
}
fn test_write_oid() ! {
dt := [
OidWriteTest{[], [], error('ObjectIdentifier: bad oid int array')}, // empty arc
OidWriteTest{[0], [u8(0x00)], error('ObjectIdentifier: bad oid int array')}, // only root arc
OidWriteTest{[0, 0], [u8(0x00)], none},
OidWriteTest{[3, 0], [u8(0x00)], error('ObjectIdentifier: bad oid int array')}, // first arc, 3 is not allowed value
OidWriteTest{[0, 40], [u8(0x00)], error('ObjectIdentifier: bad oid int array')}, // second arc, 40 is not allowed (its should <= 39)
OidWriteTest{[1, 40], [u8(0x00)], error('ObjectIdentifier: bad oid int array')}, // second arc, 40 is not allowed (its should <= 39)
OidWriteTest{[1, 2], [u8(0x2a)], none},
OidWriteTest{[2, 5], [u8(0x55)], none},
OidWriteTest{[1, 2, 840], [u8(0x2a), 0x86, 0x48], none},
OidWriteTest{[1, 2, 840, 113549], [u8(0x2a), 0x86, 0x48, 0x86, 0xF7, 0x0D], none},
OidWriteTest{[1, 2, 840, 113549, 1], [u8(0x2a), 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01], none},
]
for item in dt {
oid := ObjectIdentifier.from_ints(item.inp) or {
assert err == item.err
continue
}
assert_no_error(item.err)
dst := oid.payload()!
assert dst == item.exp
}
}
struct BuildOidTest {
inp []int
out ObjectIdentifier
err IError
}
fn test_oid_from_ints() ! {
td := [
BuildOidTest{[1, 2], ObjectIdentifier{
value: [1, 2]
}, none},
BuildOidTest{[1, 2, 3], ObjectIdentifier{
value: [1, 2, 3]
}, none},
BuildOidTest{[1, 4, 4], ObjectIdentifier{
value: [1, 4, 4]
}, none},
BuildOidTest{[1, 39, 6, 256], ObjectIdentifier{
value: [1, 39, 6, 256]
}, none},
// second >= 40 when first < 2 not allowed
BuildOidTest{[1, 40, 4], ObjectIdentifier{
value: [1, 40, 4]
}, error('ObjectIdentifier: bad oid int array')},
// first value bigger than 2 was not allowed
BuildOidTest{[4, 5, 6], ObjectIdentifier{
value: [4, 5, 6]
}, error('ObjectIdentifier: bad oid int array')},
// second value >= 40 was not allowed when first < 2
BuildOidTest{[1, 40, 6], ObjectIdentifier{
value: [1, 40, 6]
}, error('ObjectIdentifier: bad oid int array')},
// when the first arc is 2, the second arc is not limited to 0..39
BuildOidTest{[2, 50, 6], ObjectIdentifier{
value: [2, 50, 6]
}, none},
// still valid: value is well below the i32 limit checked by from_ints
BuildOidTest{[1, 4, 863123683], ObjectIdentifier{
value: [1, 4, 863123683]
}, none},
BuildOidTest{[4, 0xab, 4], ObjectIdentifier{
value: [4, 0xab, 4]
}, error('ObjectIdentifier: bad oid int array')},
BuildOidTest{[4, 0x0c, 4], ObjectIdentifier{
value: [4, 0x0c, 4]
}, error('ObjectIdentifier: bad oid int array')},
BuildOidTest{[2], ObjectIdentifier{
value: [2]
}, error('ObjectIdentifier: bad oid int array')},
]
for c in td {
s := ObjectIdentifier.from_ints(c.inp) or {
assert err == c.err
continue
}
assert_no_error(c.err)
assert s == c.out
}
}
struct OidStrTest {
inp string
out ObjectIdentifier
err IError
}
fn test_oid_from_string() ! {
td := [
OidStrTest{'1.2.840.113549', ObjectIdentifier{
value: [1, 2, 840, 113549]
}, none},
OidStrTest{'1.3.6.1.3', ObjectIdentifier{
value: [1, 3, 6, 1, 3]
}, none},
OidStrTest{'1.2', ObjectIdentifier{
value: [1, 2]
}, none},
OidStrTest{'1.4.4', ObjectIdentifier{
value: [1, 4, 4]
}, none},
OidStrTest{'1.4.x', ObjectIdentifier{
value: [1, 4, 4]
}, error('common_parse_uint: syntax error x')}, // invalid char
OidStrTest{'4.4.4', ObjectIdentifier{
value: [4, 4, 4]
}, error('ObjectIdentifier: bad oid string')},
OidStrTest{'1.4.863123683', ObjectIdentifier{
value: [1, 4, 863123683]
}, none},
OidStrTest{'4.ab.4', ObjectIdentifier{
value: [4, 0xab, 4]
}, error('common_parse_uint: syntax error ab')}, // invalid char
OidStrTest{'4.c.4', ObjectIdentifier{
value: [4, 0x0c, 4]
}, error('common_parse_uint: syntax error c')}, // invalid char
OidStrTest{'2', ObjectIdentifier{
value: [2]
}, error('ObjectIdentifier: bad string oid length')},
]
for s in td {
v := ObjectIdentifier.new(s.inp) or {
assert err == s.err
continue
}
assert_no_error(s.err)
assert v == s.out
}
}
fn test_serialize_oid_basic() {
// https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/object-identifier.html
inp := [1, 0, 8571, 2, 1]
exp := [u8(6), 5, 0x28, 0xC2, 0x7B, 0x02, 0x01]
oid := ObjectIdentifier.from_ints(inp)!
out := encode(oid)!
assert out == exp
}
struct OidSerializeTest {
inp []int
exp []u8
err IError
}
fn test_serialize_decode_oid() {
td := [
OidSerializeTest{[0, 0], [u8(0x06), 0x01, 0x00], none},
OidSerializeTest{[1, 2, 3], [u8(0x06), 0x02, 0x2a, 0x03], none},
OidSerializeTest{[1, 3, 6, 1, 3], [u8(0x06), 0x04, 0x2b, 0x06, 1, 3], none},
OidSerializeTest{[2, 999, 1234], [u8(0x06), 0x04, 0x88, 0x37, 0x89, 0x52], none},
OidSerializeTest{[2, 999, 3], [u8(0x06), 0x03, 0x88, 0x37, 0x03], none}, // Example of ITU-T X.690
// from https://learn.microsoft.com/en-us/windows/win32/seccertenroll/about-object-identifier
OidSerializeTest{[1, 3, 6, 1, 4, 1, 311, 21, 20], [u8(0x06), 0x09, 0x2b, 0x06, 0x01, 0x04,
0x01, 0x82, 0x37, 0x15, 0x14], none},
// from rust-asn1 test data
OidSerializeTest{[1, 2, 840, 113549], [u8(0x06), 0x06, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d], none},
OidSerializeTest{[1, 2, 3, 4], [u8(0x06), 0x03, 0x2a, 0x03, 0x04], none},
OidSerializeTest{[1, 2, 840, 133549, 1, 1, 5], [u8(0x06), 0x09, 0x2a, 0x86, 0x48, 0x88,
0x93, 0x2d, 0x01, 0x01, 0x05], none},
OidSerializeTest{[2, 100, 3], [u8(0x06), 0x03, 0x81, 0x34, 0x03], none},
OidSerializeTest{[1, 100, 3], [u8(0x06), 0x03, 0x81, 0x34, 0x03], error('ObjectIdentifier: bad oid int array')},
OidSerializeTest{[4, 100, 3], [u8(0x06), 0x03, 0x81, 0x34, 0x03], error('ObjectIdentifier: bad oid int array')},
]
for t in td {
// dump(t.inp)
oid := ObjectIdentifier.from_ints(t.inp) or {
assert err == t.err
continue
}
out := encode(oid) or {
assert err == t.err
continue
}
assert_no_error(t.err)
assert out == t.exp
// dump(out)
// decode back
oidback, next := ObjectIdentifier.decode(out)!
assert oidback.tag().tag_number() == int(TagType.oid)
assert oidback == oid
}
}
fn test_oid_encode_decode() ! {
inp := '1.2.840.113549'
src := ObjectIdentifier.new(inp)!
out := encode(src)!
exp := [u8(0x06), 0x06, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d]
assert out == exp
oidback, _ := ObjectIdentifier.decode(out)!
assert oidback.str() == '${inp}'
assert oidback.tag().tag_number() == 6
}
fn test_tc21_long_format_of_oid_encoding_should_error_in_der() ! {
data := [u8(0x06), 0x06, 0x80, 0x80, 0x51, 0x80, 0x80, 0x01]
_, _ := ObjectIdentifier.decode(data) or {
assert err == error('integer is not minimaly encoded')
return
}
}
fn test_tc22_too_big_value_oid() ! {
data := [u8(0x06), 0x10, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0f,
0x85, 0x03, 0x02, 0x02, 0x03]
_, _ := ObjectIdentifier.decode(data) or {
assert err == error('integer is not minimaly encoded')
return
}
}
// Taken from https://letsencrypt.org/id/docs/a-warm-welcome-to-asn1-and-der/
//
// OID 1.2.840.113549.1.1.11 (representing sha256WithRSAEncryption) is encoded like so:
// 06 09 2a 86 48 86 f7 0d 01 01 0b
fn test_sha256withrsaencryption_oid() ! {
oid := ObjectIdentifier.new('1.2.840.113549.1.1.11')!
expected := [u8(0x06), 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b]
out := encode(oid)!
assert out == expected
// decode back
ob, _ := ObjectIdentifier.decode(out)!
assert ob.equal(oid)
}
+79
View File
@@ -0,0 +1,79 @@
module asn1
// ASN.1 OPTIONAL Element.
//
// note: At the abstract ASN.1 level the absence of a DEFAULT value in an encoding is the same as its being present.
// Contrast this with OPTIONAL, where a value being present in the encoding is semantically distinct from its being absent.
// In some encoding rules (like BER/PER) it is at the whim of the sender whether a DEFAULT value is encoded or not
// (except for primitive type values in PER which are required by the PER standard to be absent in the encoding),
// while with others (like DER) the DEFAULT value is NEVER encoded. For all encoding rules,
// if the component that has a DEFAULT value is not encoded the receiving application must behave as though the DEFAULT value had been encoded.
pub struct Optional {
// underlying element marked as an optional
elem Element
mut:
// presence of this flag negates optionality of this elemeent.
// set to true when its should present, if not sure, just set to to false
present bool
}
// new creates and marked element as an Optional element.
pub fn Optional.new(el Element, with_present bool) !Optional {
if el is Optional {
return error('recursive optional is not allowed')
}
return Optional{
elem: el
present: with_present
}
}
// set_present_bit sets this Optional element with flag in present.
pub fn (mut opt Optional) set_present_bit(present bool) {
opt.present = present
}
// tag returns the tag of Optional element.
pub fn (opt Optional) tag() Tag {
return opt.elem.tag()
}
// payload the payload of Optional element.
pub fn (opt Optional) payload() ![]u8 {
return opt.elem.payload()!
}
// encode serializes this Optional element into bytes array.
pub fn (opt Optional) encode() ![]u8 {
return opt.encode_with_rule(.der)!
}
fn (opt Optional) encode_with_rule(_rule EncodingRule) ![]u8 {
if opt.present {
// elem := opt.into_element()!
return encode_with_rule(opt, .der)!
}
// not present
return []u8{}
}
// into_element turns this optional into Element.
pub fn (opt Optional) into_element() !Element {
return parse_element(opt.tag(), opt.payload()!)!
}
// into_object tries to turns this optional into real underlying object T.
// Its return object T on success or error on fails.
pub fn (opt Optional) into_object[T]() !T {
$if T !is Element {
return error('T is not element')
}
$if T is Optional {
return error('T is optional')
}
elem := opt.into_element()!
if elem is Optional {
return error('elem is also optional')
}
return elem.into_object[T]()!
}
@@ -0,0 +1,373 @@
// Copyright (c) 2022, 2024 blackshirt. All rights reserved.
// Use of this source code is governed by a MIT License
// that can be found in the LICENSE file.
module asn1
// This file contains structures and routines for (limited) support for
// other class (type) of ASN.1 Element.
//
// ASN.1 Raw Element.
pub struct RawElement {
mut:
// The tag is the (outer) tag of the TLV, if this a wrpper.
tag Tag
// `content` is the value of a TLV. Its depends on the context.
content []u8
// Optional fields
inner_tag ?Tag
mode ?TaggedMode
default_value ?Element
}
// new creates a RawElement from tag and content. If your element is universal class,
// use universal type constructor instead, provided in this module.
pub fn RawElement.new(tag Tag, content []u8) !RawElement {
if tag.number < 0 || tag.number > max_tag_number {
return error('Unallowed tagnum was provided')
}
// universal class with constructed form only valid for sequence(of) and set(of) type.
if tag.class == .universal {
if tag.number > max_universal_tagnumber {
return error('Tag number for universal class was exceed limit')
}
if tag.constructed {
if tag.number != int(TagType.sequence) && tag.number != int(TagType.set) {
return error('unexpected_tag_value required sequence or set number')
}
}
if tag.number == int(TagType.sequence) || tag.number == int(TagType.set) {
if !tag.constructed {
return error('unexpected_tag_value sequence or set number should in constructed tag')
}
}
}
// otherwise, treats as a RawElement
return RawElement{
tag: tag
content: content
}
}
// from_element creates a RawElement from another element (with wrapping semantic).
pub fn RawElement.from_element(el Element, cls TagClass, tagnum int, mode TaggedMode) !RawElement {
// wrapping into .universal is not allowed.
if cls == .universal {
return error('wrap with universal class is unallowed')
}
inner_form := el.tag().constructed
constructed := if mode == .explicit { true } else { inner_form }
content := if mode == .explicit { encode_with_rule(el, .der)! } else { el.payload()! }
outer_tag := Tag.new(cls, constructed, tagnum)!
raw := RawElement{
tag: outer_tag
content: content
inner_tag: el.tag()
mode: mode
}
return raw
}
// tag returns the tag of the RawElement, ie, outer tag when its a wrapper.
pub fn (r RawElement) tag() Tag {
return r.tag
}
// payload returns the payload of the RawElement.
pub fn (r RawElement) payload() ![]u8 {
return r.content
}
// set_mode sets the RawElement tagged mode, in explicit or implicit mode. If the mode has been set,
// it would drop into error until you forces it by setting force flag into true value, ie, replaces the old one.
pub fn (mut r RawElement) set_mode(mode TaggedMode, force bool) ! {
if r.tag.class == .universal {
return error('No need it on universal class')
}
if r.mode != none {
if !force {
return error('unallowed_operation r.mode != none')
}
}
r.mode = mode
}
// set_inner_tag sets the inner tag of the RawElement into inner_tag value. If it has been already set,
// it would be an error until you setting force flag into true value to replace the old one.
pub fn (mut r RawElement) set_inner_tag(inner_tag Tag, force bool) ! {
// not needed in universal class
if r.tag.class == .universal {
return error('No need it on universal class')
}
mode := r.mode or { return error('You dont set any mode') }
// when its in explicit mode, compares the provided tag with tag from the inner element.
if mode == .explicit {
if !r.tag.constructed {
return error('unmeet_requirement, explicit should be constructed')
}
// check inner_tag
itt, _ := Tag.decode(r.content)!
if !itt.equal(inner_tag) {
return error('unmeet_requirement, unequal supplied tag')
}
}
if r.inner_tag != none {
if !force {
return error('.unallowed_operation, r.inner_tag != none')
}
}
r.inner_tag = inner_tag
}
// force_set_default_value forces set default value of this RawElement into value.
fn (mut r RawElement) set_default_value(value Element, force bool) ! {
// default value of this element should have an equal tag.
if !value.tag().equal(r.tag) {
return error('You provides unequal tag for default value')
}
if r.default_value != none {
if !force {
return error('The RawElement already default_value being set')
}
}
r.default_value = value
}
// inner_tag returns the inner tag of the RawElement if it exists, or error on fails.
pub fn (r RawElement) inner_tag() ?Tag {
return r.inner_tag
}
// inner_element returns the inner element of the RawElement if its exists.
pub fn (r RawElement) inner_element() !Element {
if r.tag.class == .universal {
return error('inner element from universal class is not availables')
}
inner_tag := r.inner_tag or { return err }
mode := r.mode or { return error('You dont set any mode') }
if mode == .explicit {
if !r.tag.constructed {
return error('tag should be constructed when in explicit')
}
}
// in implicit, r.content is inner element content with inner tag
if mode == .implicit {
elem := parse_element(inner_tag, r.content)!
return elem
}
// otherwise, treats it in explicit mode.
// read an inner tag from r.content
mut p := Parser.new(r.content)
tag := p.peek_tag()!
if !tag.equal(inner_tag) {
return error('gets unequal inner_tag')
}
el := p.read_tlv()!
// should finish
p.finish()!
return el
}
fn (r RawElement) check_inner_tag() ! {
if r.tag.class == .universal {
return error('Universal class does not have inner tag')
}
mode := r.mode or { return error('You dont set any mode') }
if mode != .explicit {
return
}
// read an inner tag from content
tag, _ := Tag.decode_with_rule(r.content, 0, .der)!
inner_tag := r.inner_tag or { return error('You dont set an inner_tag') }
if !tag.equal(inner_tag) {
return error('Get unexpected inner tag from bytes')
}
}
// ContextSpecific tagged type element.
pub struct ContextElement {
RawElement
}
// new creates a raw context specific element. Use `ContextElement.from_element` instead
// if your context specific element is wrapper of another element.
pub fn ContextElement.new(tag Tag, content []u8) !ContextElement {
if tag.class != .context_specific {
return error('Your tag is not .context_specific')
}
raw := RawElement.new(tag, content)!
return ContextElement{raw}
}
// from_element creates a new tagged type of ContextElement from some element in inner.
pub fn ContextElement.from_element(inner Element, tagnum int, mode TaggedMode) !ContextElement {
raw := RawElement.from_element(inner, .context_specific, tagnum, mode)!
ctx := ContextElement{raw}
return ctx
}
// tag returns the tag of context specific element.
pub fn (ctx ContextElement) tag() Tag {
return ctx.RawElement.tag
}
// payload returns the payload of the context specific element.
pub fn (ctx ContextElement) payload() ![]u8 {
return ctx.RawElement.content
}
// explicit_context creates a new ContextElement with explicit mode.
pub fn ContextElement.explicit_context(inner Element, tagnum int) !ContextElement {
return ContextElement.from_element(inner, tagnum, .explicit)!
}
// implicit_context creates new ContextElement with implicit mode.
pub fn ContextElement.implicit_context(inner Element, tagnum int) !ContextElement {
return ContextElement.from_element(inner, tagnum, .implicit)!
}
fn ContextElement.decode_raw(bytes []u8) !(ContextElement, int) {
tag, length_pos := Tag.decode_with_rule(bytes, 0, .der)!
if tag.class != .context_specific {
return error('tag required to be context_specific')
}
length, content_pos := Length.decode_with_rule(bytes, length_pos, .der)!
content := if length == 0 {
[]u8{}
} else {
if content_pos >= bytes.len || content_pos + length > bytes.len {
return error('ContextElement: truncated payload bytes')
}
unsafe { bytes[content_pos..content_pos + length] }
}
next := content_pos + length
// Raw ContextElement, you should provide mode and inner tag.
ctx := ContextElement{
tag: tag
content: content
}
return ctx, next
}
fn ContextElement.decode_with_options(bytes []u8, opt string) !(ContextElement, int) {
if opt.len == 0 {
return ContextElement.decode_raw(bytes)!
}
fo := FieldOptions.from_string(opt)!
// get mode and inner tag
if !valid_mode_value(fo.mode) {
return error('Get unexpected mode option for ContextElement')
}
mode := TaggedMode.from_string(fo.mode)!
inner_tag := fo.inner_tag()!
// outer tag from bytes
tag, length_pos := Tag.decode_with_rule(bytes, 0, .der)!
if tag.class != .context_specific {
return error('Get non ContextSpecific tag')
}
// if mode is explicit without constructed form, its would return on error.
if mode == .explicit {
if !tag.constructed {
return error('explicit need constructed form')
}
}
length, content_pos := Length.decode_with_rule(bytes, length_pos, .der)!
content := if length == 0 {
[]u8{}
} else {
if content_pos >= bytes.len || content_pos + length > bytes.len {
return error('ContextElement: truncated payload bytes')
}
unsafe { bytes[content_pos..content_pos + length] }
}
next := content_pos + length
if mode == .implicit {
ctx := ContextElement{
tag: tag
content: content
inner_tag: inner_tag
mode: .implicit
}
return ctx, next
}
// explicit one, build ContextElement and performs checks for inner_tag validity.
ctx := ContextElement{
tag: tag
content: content
inner_tag: inner_tag
mode: .explicit
}
ctx.check_inner_tag()!
return ctx, next
}
// Limited support for APPLICATION CLASS Element.
pub struct ApplicationElement {
RawElement
}
// new creates a new application class element.
pub fn ApplicationElement.new(tag Tag, content []u8) !ApplicationElement {
if tag.class != .application {
return error('Your tag is not .application')
}
raw := RawElement.new(tag, content)!
return ApplicationElement{raw}
}
// from_element creates application class element from some element.
pub fn ApplicationElement.from_element(inner Element, tagnum int, mode TaggedMode) !ApplicationElement {
raw := RawElement.from_element(inner, .application, tagnum, mode)!
app := ApplicationElement{raw}
return app
}
// tag returns the tag of the application class element.
pub fn (app ApplicationElement) tag() Tag {
return app.RawElement.tag
}
// payload returns the payload of application class element.
pub fn (app ApplicationElement) payload() ![]u8 {
return app.RawElement.content
}
// Limited support for PRIVATE CLASS Element.
pub struct PrivateELement {
RawElement
}
// new creates a raw private class element.
pub fn PrivateELement.new(tag Tag, content []u8) !PrivateELement {
if tag.class != .private {
return error('Your tag is not private')
}
raw := RawElement.new(tag, content)!
return PrivateELement{raw}
}
// from_element creates a new private class element from another element.
pub fn PrivateELement.from_element(inner Element, tagnum int, mode TaggedMode) !PrivateELement {
raw := RawElement.from_element(inner, .private, tagnum, mode)!
app := PrivateELement{raw}
return app
}
// tag returns the tag of the private element.
pub fn (prv PrivateELement) tag() Tag {
return prv.RawElement.tag
}
// payload returns the payload of the private element.
pub fn (prv PrivateELement) payload() ![]u8 {
return prv.RawElement.content
}
+139
View File
@@ -0,0 +1,139 @@
module asn1
import io
// Parser is ongoing ASN.1 parser.
// Its capables parsing ASN.1 element through availables methods.
pub struct Parser {
mut:
data []u8
}
// new creates a new Parser from bytes array in data.
pub fn Parser.new(data []u8) &Parser {
return &Parser{
data: data
}
}
// reset resets internal data of parser to empty buffer.
pub fn (mut p Parser) reset() {
p.data = unsafe { p.data[..0] }
}
// peek_tag lookups the tag from the parser without updates internal parser data.
pub fn (mut p Parser) peek_tag() !Tag {
tag, _ := Tag.from_bytes(p.data)!
return tag
}
// read_tag lookups the tag from the current parser and updates internal parser data.
pub fn (mut p Parser) read_tag() !Tag {
tag, rest := Tag.from_bytes(p.data)!
p.data = rest
return tag
}
// read_length read the Length from the current parser data.
pub fn (mut p Parser) read_length() !Length {
length, rest := Length.from_bytes(p.data)!
p.data = rest
return length
}
// read_bytes read length bytes from the current parser data.
pub fn (mut p Parser) read_bytes(length int) ![]u8 {
if length > p.data.len {
return error('Parser: too short data to read ${length} bytes')
}
result := p.data[0..length]
rest := if length == p.data.len { []u8{} } else { unsafe { p.data[length..] } }
p.data = rest
return result
}
// BUG: This is not supported with error:
// ```bash
// error: unknown function: asn1.Boolean__static__parse
// 57 | // Note: somes builtin have not this method.
// 58 | fn (mut p Parser) read_element[T]() !T {
// 59 | result := T.parse(mut p)!
// | ~~~~~~~~~~~~~~
// 60 | return result
// 61 | }
// vlib/x/encoding/asn1/parser.v:59:26: error: unexpected `!`,
// the function `T__static__parse` does not return a Result
// 57 | // Note: somes builtin have not this method.
// 58 | fn (mut p Parser) read_element[T]() !T {
// 59 | result := T.parse(mut p)!
// | ^
// 60 | return result
// 61 | }
// vlib/x/encoding/asn1/parser.v:59:9: error: assignment mismatch: 1 variable
// but `T.parse()` returns 0 values
// 57 | // Note: somes builtin have not this method.
// 58 | fn (mut p Parser) read_element[T]() !T {
// 59 | result := T.parse(mut p)!
// | ~~
// 60 | return result
// 61 | }
// ```
// read_element read an element T from the current parser.
// Note: somes builtin have not this method.
// fn (mut p Parser) read_element[T]() !T {
// result := T.parse(mut p)!
// return result
// }
// read_tlv read an Element from the parser data.
// Its return an Element, you should cast it to underlying data if you need.
pub fn (mut p Parser) read_tlv() !Element {
tag := p.read_tag()!
length := p.read_length()!
content := p.read_bytes(length)!
elem := parse_element(tag, content)!
return elem
}
// finish end this parser or error if not empty.
pub fn (mut p Parser) finish() ! {
if !p.is_empty() {
return error('not empty on finish')
}
}
// is_empty checks whether the parser has empty buffer data.
pub fn (mut p Parser) is_empty() bool {
return p.data.len == 0
}
// read_from read up to buf.len bytes from reader r, places them into buf and then appends
// to current Parser data.
fn (mut p Parser) read_from(mut r io.Reader, mut buf []u8) !int {
n := r.read(mut buf)!
p.data << buf
return n
}
fn parse_single[T](data []u8) !T {
mut p := Parser.new(data)!
out := p.read_element[T]()!
return out
}
fn parse[T](data []u8, callback fn (mut p Parser) !T) !T {
mut p := Parser.new(data)
result := callback(mut p)!
p.finish()!
return result
}
/*
fn strip_tlv(data []u8) !(Element, []u8) {
mut p := Parser.new(data)
tlv := p.read_element[Asn1Element]()!
return tlv, p.data
}
*/
@@ -0,0 +1,105 @@
// Copyright (c) 2022, 2024 blackshirt. All rights reserved.
// Use of this source code is governed by a MIT License
// that can be found in the LICENSE file.
module asn1
const printable_symbols = r"(')+,-./:=?".bytes()
// default_printablestring_tag is the default tag of ASN.1 PRINTABLESTRING type.
pub const default_printablestring_tag = Tag{.universal, false, int(TagType.printablestring)}
// ASN.1 UNIVERSAL CLASS OF PRINTABLESTRING TYPE.
//
// PrintableString consists of:
// Latin capital letters A, B, ... Z
// Latin small letters a, b, ... z
// Digits 0, 1, ... 9
// symbols: (space) ' ( ) + , - . / : = ?
//
pub struct PrintableString {
pub:
value string
}
// new creates new PrintableString element from string s.
pub fn PrintableString.new(s string) !PrintableString {
return PrintableString.from_bytes(s.bytes())!
}
fn (pst PrintableString) str() string {
if pst.value.len == 0 {
return 'PrintableString (<empty>)'
}
return 'PrintableString (${pst.value})'
}
// tag returns the tag of PrintableString element.
pub fn (pst PrintableString) tag() Tag {
return default_printablestring_tag
}
// payload returns the payload of PrintableString element.
pub fn (pst PrintableString) payload() ![]u8 {
if !printable_chars(pst.value.bytes()) {
return error('PrintableString: contains non-printable string')
}
return pst.value.bytes()
}
fn PrintableString.from_bytes(src []u8) !PrintableString {
if !printable_chars(src) {
return error('PrintableString: contains non-printable string')
}
return PrintableString{
value: src.bytestr()
}
}
// parse an PrintableString from on going Parser
fn PrintableString.parse(mut p Parser) !PrintableString {
tag := p.read_tag()!
if !tag.equal(default_printablestring_tag) {
return error('Unexpected non-printablestring tag')
}
length := p.read_length()!
content := p.read_bytes(length)!
payload := if length == 0 { []u8{} } else { content }
pst := PrintableString.from_bytes(payload)!
return pst
}
fn PrintableString.decode(src []u8) !(PrintableString, int) {
return PrintableString.decode_with_rule(src, .der)!
}
fn PrintableString.decode_with_rule(bytes []u8, rule EncodingRule) !(PrintableString, int) {
tag, length_pos := Tag.decode_with_rule(bytes, 0, rule)!
if !tag.equal(default_printablestring_tag) {
return error('Unexpected non-printablestring tag')
}
length, content_pos := Length.decode_with_rule(bytes, length_pos, rule)!
content := if length == 0 {
[]u8{}
} else {
if content_pos >= bytes.len || content_pos + length > bytes.len {
return error('PrintableString: truncated payload bytes')
}
unsafe { bytes[content_pos..content_pos + length] }
}
pst := PrintableString.from_bytes(content)!
next := content_pos + length
return pst, next
}
// utility function
fn printable_chars(bytes []u8) bool {
return bytes.all(is_printablestring(it))
}
fn is_printablestring(c u8) bool {
return c.is_alnum() || c == u8(0x20) || c in printable_symbols
}
@@ -0,0 +1,54 @@
// Copyright (c) 2022, 2024 blackshirt. All rights reserved.
// Use of this source code is governed by a MIT License
// that can be found in the LICENSE file.
module asn1
// Tests case for PrintableString
fn test_encode_printablestring_basic() ! {
s := 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
ps := PrintableString.new(s)!
buf := encode(ps)!
mut out := [u8(0x13)]
length := [u8(0x81), u8(s.len)]
out << length
out << s.bytes()
// dump(out)
assert out == buf
psback, _ := PrintableString.decode(buf)!
assert psback.tag().tag_number() == int(TagType.printablestring)
assert psback.tag().tag_class() == .universal
assert psback.value == s
}
struct EncodingTest[T] {
input T
exp []u8
}
fn test_encode_printablestring_generic() {
data := [
// from dart asn1lib test
EncodingTest[string]{'TheTestString', [u8(0x13), 13, 84, 104, 101, 84, 101, 115, 116, 83,
116, 114, 105, 110, 103]},
EncodingTest[string]{'Test User 1', [u8(0x13), 0x0b, 84, 101, 115, 116, 32, 85, 115, 101,
114, 32, 49]},
]
for t in data {
ps := PrintableString.new(string(t.input))!
out := encode(ps)!
// out := serialize_printablestring(string(t.input))!
assert out == t.exp
// decode back
psback, _ := PrintableString.decode(out)!
assert psback.value == t.input
assert psback.tag().tag_number() == int(TagType.printablestring)
assert psback.tag().is_constructed() == false
}
}
@@ -0,0 +1,80 @@
import x.encoding.asn1
// This example test taken from https://en.wikipedia.org/wiki/ASN.1#Example_encoded_in_DER
// for testing public api of the `x.encoding.asn1` module.
// FooQuestion ::= SEQUENCE {
// trackingNumber INTEGER(0..199),
// question IA5String
// }
// Instance of FooQuestion
// myQuestion FooQuestion ::= {
// trackingNumber 5,
// question "Anybody there?"
// }
// Below is the data structure shown above as myQuestion encoded in DER format
// (all numbers are in hexadecimal):
// 30 13 02 01 05 16 0e 41 6e 79 62 6f 64 79 20 74 68 65 72 65 3f
// Above schema can be represented in v struct,
struct FooQuestion {
tracking_number asn1.Integer
question asn1.IA5String
}
// To be supported as an Element by this module, your FooQuestion should fullfills
// required method of Element interface by providing `tag()` and `payload()` methods.
fn (foo FooQuestion) tag() asn1.Tag {
return asn1.default_sequence_tag
}
fn (foo FooQuestion) payload() ![]u8 {
mut out := []u8{}
out << asn1.encode(foo.tracking_number)!
out << asn1.encode(foo.question)!
return out
}
// FooQuestion.decode is a routine decoder from bytes into FooQuestion.
fn FooQuestion.decode(bytes []u8) !FooQuestion {
// decode the bytes, and check for sequence tag
elem := asn1.decode(bytes)!
assert elem.tag().equal(asn1.default_sequence_tag)
// turn into Sequence
seq := elem.into_object[asn1.Sequence]()!
// get the fields, ie, arrays of Element
fields := seq.fields()
// turns the every sequence's fields into desired object
trk_num := fields[0].into_object[asn1.Integer]()!
question := fields[1].into_object[asn1.IA5String]()!
foo := FooQuestion{
tracking_number: trk_num
question: question
}
return foo
}
fn test_asn1_public_api_usage() ! {
expected_foo := [u8(0x30), 0x13, 0x02, 0x01, 0x05, 0x16, 0x0e, 0x41, 0x6e, 0x79, 0x62, 0x6f,
0x64, 0x79, 0x20, 0x74, 0x68, 0x65, 0x72, 0x65, 0x3f]
foo_question := FooQuestion{
tracking_number: asn1.Integer.from_int(5)
question: asn1.IA5String.new('Anybody there?')!
}
foo_encoded := asn1.encode(foo_question)!
assert foo_encoded == expected_foo
// decode back
foo_decoded := FooQuestion.decode(expected_foo)!
assert foo_decoded.tracking_number.as_i64()! == 5
assert foo_decoded.question.value == 'Anybody there?'
}
+282
View File
@@ -0,0 +1,282 @@
// Copyright (c) 2022, 2024 blackshirt. All rights reserved.
// Use of this source code is governed by a MIT License
// that can be found in the LICENSE file.
module asn1
// default_sequence_tag is the default tag of ASN.1 SEQUENCE (SEQUENCE OF) type.
pub const default_sequence_tag = Tag{.universal, true, int(TagType.sequence)}
// constant for sequence(of) and set(of) internal value
// vfmt off
const max_sequence_size = 256 // max of seq size
const max_sequence_bytes_length = (1 << 23 - 1) //
const default_sequence_size = 64 // default size
// vfmt on
// ASN.1 UNIVERSAL CLASS OF SEQUENCE and SEQUENCE OF TYPE.
//
// https://letsencrypt.org/docs/a-warm-welcome-to-asn1-and-der
// These are two very different types.
// A SEQUENCE is equivalent to “struct” in most programming languages.
// It holds a fixed number of fields of different types.
// A SEQUENCE OF, holds an arbitrary number of fields of a single type.
// This is analogous to an array or a list in a programming language.
// Sequence structure can represents both SEQUENCE and SEQUENCE OF type.
// The encoding of a sequence value shall be constructed.
// in DER encoded of SEQUENCE or SET, never encode a default value.
pub struct Sequence {
mut:
// maximal size of this sequence fields
size int = default_sequence_size
// fields is the elements of the sequence
fields []Element
}
// new creates new Sequence with default size.
pub fn Sequence.new() !Sequence {
return Sequence.new_with_size(default_sequence_size)!
}
// from_list creates new Sequence from list of elements.
pub fn Sequence.from_list(els []Element) !Sequence {
if els.len > max_sequence_size {
return error('Sequence size exceed limit')
}
return Sequence{
fields: els
}
}
fn Sequence.new_with_size(size int) !Sequence {
if size > max_sequence_size {
return error('size is exceed limit')
}
if size < 0 {
return error('Provides with correct size')
}
// if size is 0, use default_sequence_size
limit := if size == 0 { default_sequence_size } else { size }
return Sequence{
size: limit
}
}
// tag returns the tag of Sequence element.
pub fn (seq Sequence) tag() Tag {
return default_sequence_tag
}
// payload returns the payload of Sequence element.
pub fn (seq Sequence) payload() ![]u8 {
return seq.payload_with_rule(.der)!
}
fn (seq Sequence) payload_with_rule(rule EncodingRule) ![]u8 {
mut out := []u8{}
for el in seq.fields {
obj := encode_with_rule(el, rule)!
out << obj
}
return out
}
// encoded_len tells the length of serialized Sequence element in bytes.
pub fn (seq Sequence) encoded_len() int {
mut n := 0
n += seq.tag().tag_size()
fields := ElementList(seq.fields)
len := fields.encoded_len()
length := Length.new(len) or { panic(err) }
n += length.length_size() or { panic(err) }
n += len
return n
}
// fields returns the Sequences fields.
pub fn (seq Sequence) fields() []Element {
return seq.fields
}
// parse tries to parse into Sequence from ongoing Parser p.
// Uts return a parsed Sequence or error on fails.
fn Sequence.parse(mut p Parser) !Sequence {
tag := p.read_tag()!
if !tag.equal(default_sequence_tag) {
return error('Get non Sequence tag')
}
length := p.read_length()!
content := p.read_bytes(length)!
seq := Sequence.from_bytes(content)!
return seq
}
// decode tries to decode bytes into Sequence.
// Its return a decoded Sequence and next offset to read on
// if possible, or return error on fails.
fn Sequence.decode(bytes []u8) !(Sequence, int) {
return Sequence.decode_with_rule(bytes, 0, .der)!
}
fn Sequence.decode_with_rule(bytes []u8, loc int, rule EncodingRule) !(Sequence, int) {
tag, length_pos := Tag.decode_with_rule(bytes, loc, rule)!
if !tag.equal(default_sequence_tag) {
return error('Get unexpected non-sequence tag')
}
length, content_pos := Length.decode_with_rule(bytes, length_pos, rule)!
payload := if length == 0 {
[]u8{}
} else {
if content_pos + length > bytes.len {
return error('Not enought bytes to read on')
}
unsafe { bytes[content_pos..content_pos + length] }
}
next := content_pos + length
seq := Sequence.from_bytes(payload)!
return seq, next
}
// bytes should seq.fields payload, not includes the tag
fn Sequence.from_bytes(bytes []u8) !Sequence {
mut seq := Sequence{}
if bytes.len == 0 {
return seq
}
mut i := 0
for i < bytes.len {
el, pos := Element.decode_with_rule(bytes, i, .der)!
i = pos
seq.add_element(el)!
}
if i > bytes.len {
return error('i > bytes.len')
}
if i < bytes.len {
return error('The src contains unprocessed bytes')
}
return seq
}
// set_size sets maximal size of this sequence fields.
pub fn (mut seq Sequence) set_size(size int) ! {
if size <= 0 {
return error('provides with correct limit')
}
if size > max_sequence_size {
return error('Provided limit was exceed current one')
}
seq.size = size
}
// add_element adds an element el into Sequence fields.
// By default its allows adding element with the same tag.
pub fn (mut seq Sequence) add_element(el Element) ! {
seq.relaxed_add_element(el, true)!
}
// add_element allows adding a new element into current sequence fields.
// Its does not allow adding element when is already the same tag in the fields.
// but, some exception when you set relaxed to true
fn (mut seq Sequence) relaxed_add_element(el Element, relaxed bool) ! {
// todo: check against size
if seq.fields.len == 0 {
// just adds it then return
seq.fields << el
return
}
// for item in seq.fields {
// if item.equal_with(el) {
// return error('has already in the fields')
// }
// }
filtered_by_tag := seq.fields.filter(it.tag().equal(el.tag()))
if filtered_by_tag.len == 0 {
seq.fields << el
return
} else {
if !relaxed {
return error('You can not insert element without forcing')
}
seq.fields << el
return
}
}
// is_sequence_of[T] checks whether this sequence is SequenceOf[T] type.
pub fn (seq Sequence) is_sequence_of[T]() bool {
return seq.fields.all(it is T)
}
// into_sequence_of[T] turns this sequence into SequenceOf[T] element.
pub fn (seq Sequence) into_sequence_of[T]() !SequenceOf[T] {
if seq.is_sequence_of[T]() {
return error('This sequence is not SequenceOf[T]')
}
mut sqof := SequenceOf[T]{}
for el in seq.fields {
obj := el.into_object[T]()!
sqof.fields << obj
}
return sqof
}
// ASN.1 SEQUENCE OF TYPE.
// SequenceOf[T] is an arrays of generic T, so the generic T should fullfill Element interface.
// We dont use generic aliases because generic type aliases are not yet implemented.
pub struct SequenceOf[T] {
mut:
size int = default_sequence_size
fields []T
}
// SequenceOf.new creates a new SequenceOf[T]
pub fn SequenceOf.new[T]() SequenceOf[T] {
return SequenceOf[T]{}
}
// SequenceOf.from_list creates a new SequenceOf[T] from arrays of T type.
pub fn SequenceOf.from_list[T](els []T) !SequenceOf[T] {
if els.len > max_sequence_size {
return error('SequenceOf size exceed limit')
}
$if T !is Element {
return error('T not hold element')
}
return SequenceOf[T]{
fields: els
}
}
// The tag of SequenceOf element.
pub fn (so SequenceOf[T]) tag() Tag {
return default_sequence_tag
}
// The payload of SequenceOf element.
pub fn (so SequenceOf[T]) payload() ![]u8 {
return so.payload_with_rule(.der)!
}
fn (so SequenceOf[T]) payload_with_rule(rule EncodingRule) ![]u8 {
$if T !is Element {
return error('T is not an element')
}
mut out := []u8{}
for el in so.fields {
// placing el directly bring into error: `el` cannot be used as interface object
// outside `unsafe` blocks as it might be stored on stack.
curr := unsafe { el }
obj := encode_with_rule(curr, rule)!
out << obj
}
return out
}
// fields returns underlying arrays of T from the SequenceOf[T].
pub fn (so SequenceOf[T]) fields() []T {
return so.fields
}
@@ -0,0 +1,371 @@
// Copyright (c) 2022, 2024 blackshirt. All rights reserved.
// Use of this source code is governed by a MIT License
// that can be found in the LICENSE file.
module asn1
import math.big
fn test_sequence_with_multi_items() ! {
mut seq := Sequence{}
o1 := Boolean.new(true) // 3
o2 := UtcTime.new('191215190210Z')! // 15
o3 := GeneralizedTime.new('20100102030405Z')! // 17
// we are going to add with add_element to allow adding element with the same tag
seq.add_element(o1)!
seq.add_element(o2)!
seq.add_element(o3)!
assert seq.payload()!.len == 3 + 15 + 17 // 35
assert encoded_len(seq) == 2 + 35
out := encode(seq)!
exp := [u8(0x30), 35, u8(0x01), 0x01, 0xff, u8(0x17), 0x0D, 49, 57, 49, 50, 49, 53, 49, 57,
48, 50, 49, 48, 90, u8(0x18), 0x0f, 50, 48, 49, 48, 48, 49, 48, 50, 48, 51, 48, 52, 48,
53, 90]
assert out == exp
}
fn test_sequence_contains_other_seq() ! {
// lets create first sequence
mut seq1 := Sequence{}
// add two primitive elements to the sequence
seq1.add_element(Boolean.new(true))!
seq1.add_element(Null{})!
seq1.add_element(Boolean.new(false))!
// lets create another sequences, where it contains primitive element and first sequence created above.
mut seq2 := Sequence{}
seq2.add_element(Boolean.new(false))!
seq2.add_element(seq1)!
// you should force add element to allow add second Boolean, by default its not allowed
seq2.add_element(Boolean.new(true))!
// lets serialize it to bytes
out := encode(seq2)!
expected := [u8(0x30), 16, u8(0x01), 0x01, 0x00, u8(0x30), 8, 0x01, 0x01, 0xff, u8(0x05), 0x00,
u8(0x01), 0x01, 0x00, u8(0x01), 0x01, 0xff]
// assert for right value
assert seq2.payload()!.len == 16
assert encoded_len(seq2) == 18
assert out == expected
seq2_back, _ := Sequence.decode(expected)!
assert seq2_back.fields()[1] is Sequence
}
fn test_sequence_der_decode() ! {
data := [u8(0x30), 16, u8(0x01), 0x01, 0x00, u8(0x30), 8, u8(0x01), 0x01, 0xff, u8(0x05), 0x00,
u8(0x01), 0x01, 0x00, u8(0x01), 0x01, 0xff]
seq, n := Sequence.decode(data)!
assert seq.tag().is_constructed() == true
assert seq.tag().tag_number() == int(TagType.sequence)
assert n == 18
assert seq.fields().len == 3
els := seq.fields()
assert els[0].tag() == Tag.new(.universal, false, int(TagType.boolean))!
assert els[0].payload()! == [u8(0x00)]
el1 := els[1] as Sequence
assert el1.fields().len == 3 // [true, null, false]
el10 := el1.fields()[0] as Boolean
assert el10.value() == true
el11 := el1.fields()[1] as Null
assert el11 == Null{}
el12 := el1.fields()[2] as Boolean
assert el12.value() == false
el2 := els[2] as Boolean
assert el2.value() == true
}
fn test_sequence_add_and_encode_boolean() {
o1 := Boolean.new(false)
o2 := Boolean.new(true)
o3 := Null{}
mut seq := Sequence{}
seq.add_element(o1)!
seq.add_element(o2)!
seq.add_element(o3)!
length := seq.payload()!.len
assert length == 8
size := encoded_len(seq)
assert size == 10
out := encode(seq)!
exp := [u8(0x30), 0x08, 0x01, 0x01, 0x00, 0x01, 0x01, 0xff, 0x05, 0x00]
assert out == exp
assert exp.len == size
back, n := Sequence.decode(out)!
assert n == exp.len
assert back.fields().len == 3
assert back.tag().number == 0x10
assert back.tag().constructed == true
assert back.tag().class == .universal
assert back.fields()[0].tag().class == .universal
assert back.fields()[0].tag().constructed == false
assert back.fields()[0].tag().number == 0x01
assert back.fields()[1].tag().class == .universal
assert back.fields()[1].tag().constructed == false
assert back.fields()[1].tag().number == 0x01
assert back.fields()[2].tag().number == 0x05
assert back.fields()[2].tag().constructed == false
}
fn test_sequence_add_encode_oid() ! {
mut seq := Sequence{}
o1 := ObjectIdentifier.new('1.2.3')! // size = 4
o2 := ObjectIdentifier.new('1.2.4')! // size = 4
o3 := Boolean.new(true) // size = 3
seq.add_element(o1)!
seq.add_element(o2)!
seq.add_element(o3)!
assert seq.tag() == Tag.new(.universal, true, int(TagType.sequence))!
assert seq.payload()!.len == 11
assert encoded_len(seq) == 13
mut out := encode(seq)!
exp := [u8(0x30), 0x0b, u8(0x06), 0x02, 0x2a, 0x03, u8(0x06), 0x02, 0x2a, 0x04, u8(0x01), 0x01,
0xff]
assert out == exp
back, n := Sequence.decode(out)!
assert n == exp.len
//(back)
assert back.fields().len == 3
assert back.tag().constructed == true
//
out.clear()
out = encode(back.fields()[0])!
assert out == [u8(0x06), 0x02, 0x2a, 0x03]
//
out.clear()
out = encode(back.fields()[1])!
assert out == [u8(0x06), 0x02, 0x2a, 0x04]
//
out.clear()
out = encode(back.fields()[2])!
assert out == [u8(0x01), 0x01, 0xff]
}
fn test_sequence_add_encode_integer() ! {
mut seq := Sequence.new()!
o1 := Integer.from_i64(127)
o2 := Boolean.new(true)
o3 := Integer.from_i64(max_i64)
seq.add_element(o1)!
seq.add_element(o2)!
seq.add_element(o3)!
assert seq.tag() == Tag.new(.universal, true, int(TagType.sequence))!
assert seq.payload()!.len == 16
assert encoded_len(seq) == 18
mut out := encode(seq)!
// math.max_i64 serialize to 02087fffffffffffffff
exp := [u8(0x30), 0x10, u8(0x02), 0x01, 0x7f, u8(0x01), 0x01, 0xff, u8(0x02), 0x08, 0x7f, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff]
assert out == exp
back, n := Sequence.decode(out)!
assert n == exp.len
assert back.fields().len == 3
assert back.tag().number == 16
assert back.tag().constructed == true
}
fn test_sequence_integer_bigint() ! {
inp :=
big.integer_from_string('84885164052257330097714121751630835360966663883732297726369399')!
mut seq := Sequence.new()!
o1 := Integer.from_bigint(inp)
o2 := Boolean.new(true)
o3 := Null{}
seq.add_element(o1)!
seq.add_element(o2)!
seq.add_element(o3)!
mut out := encode(seq)!
assert seq.payload()!.len == 28 + 3 + 2
assert encoded_len(seq) == 2 + 28 + 3 + 2
exp := [u8(0x30), 33, u8(0x02), 26, 52, 210, 252, 160, 105, 66, 145, 88, 8, 53, 227, 150, 221,
98, 149, 87, 146, 121, 109, 20, 162, 246, 230, 65, 30, 119, u8(0x01), 0x01, 0xff, u8(0x05),
0x00]
assert out == exp
back, n := Sequence.decode(out)! // Sequence
assert n == exp.len
// clear out
out.clear()
out = encode(back)!
assert out == exp
assert back.fields().len == 3
assert back.tag().number == 16
assert back.tag().constructed == true
// clear out
out.clear()
out = encode(back.fields()[1])!
assert out == [u8(0x01), 0x01, 0xff]
}
fn test_sequence_of_string() ! {
str := 'iloveyou' // 8
mut seq := Sequence.new()!
o1 := Null{}
o2 := Utf8String.new(str)!
o3 := IA5String.new(str)!
seq.add_element(o1)!
seq.add_element(o2)!
seq.add_element(o3)!
assert seq.payload()!.len == 22
assert seq.encoded_len() == 24
mut out := encode(seq)!
exp := [u8(0x30), 22, u8(0x05), 0x00, u8(12), 8, u8(105), 108, 111, 118, 101, 121, 111, 117,
u8(22), 8, u8(105), 108, 111, 118, 101, 121, 111, 117]
assert out == exp
back, n := Sequence.decode(out)!
assert n == exp.len
// clears out
out.clear()
out = encode(back)!
assert out == exp
}
fn test_sequnce_of_sequence() {
mut seq := Sequence.new()!
seq.add_element(Null{})!
seq.add_element(Boolean.new(false))!
mut out := encode(seq)!
assert out == [u8(0x30), 5, 5, 0, 1, 1, 0]
mut seq2 := Sequence.new()!
seq2.add_element(Integer.from_i64(5))!
seq2.add_element(Integer.from_i64(i64(86424278346)))!
// clear out
out.clear()
out = encode(seq2)!
assert out == [u8(0x30), 10, 2, 1, 5, 2, 5, 0x14, 0x1f, 0x49, 0xd5, 0x4a]
seq.add_element(seq2)!
// clear out
out.clear()
out = encode(seq)!
assert out == [u8(0x30), 17, 5, 0, 1, 1, 0, u8(0x30), 10, 2, 1, 5, 2, 5, 0x14, 0x1f, 0x49,
0xd5, 0x4a]
back_seq := decode(out)!
assert back_seq.equal(seq)
back := back_seq.into_object[Sequence]()!
assert back.fields().len == 3
assert back.fields()[0] is Null
assert back.fields()[1] is Boolean
assert back.fields()[2] is Sequence
two := back.fields()[2]
if two is Sequence {
assert two.fields()[0] is Integer
assert two.fields()[1] is Integer
assert two.fields()[1].payload()!.len == 5
}
}
// Taken from https://letsencrypt.org/id/docs/a-warm-welcome-to-asn1-and-der/
//
// As an example, RFC 5280 defines AlgorithmIdentifier as a SEQUENCE:
//
// AlgorithmIdentifier ::= SEQUENCE {
// algorithm OBJECT IDENTIFIER,
// parameters ANY DEFINED BY algorithm OPTIONAL
// }
// Here’s the encoding of the AlgorithmIdentifier containing 1.2.840.113549.1.1.11.
// RFC 8017 says “parameters” should have the type NULL for this algorithm.
// was serialized into: 30 0d 06 09 2a 86 48 86 f7 0d 01 01 0b 05 00
struct AlgorithmIdentifier {
algorithm ObjectIdentifier
parameters AnyDefinedBy
}
fn (a AlgorithmIdentifier) tag() Tag {
return default_sequence_tag
}
fn (a AlgorithmIdentifier) payload() ![]u8 {
mut out := []u8{}
out << encode(a.algorithm)!
out << encode(a.parameters)!
return out
}
fn test_sequence_algorithm_identifier() ! {
expected := [u8(0x30), 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b,
0x05, 0x00]
algo := AlgorithmIdentifier{
algorithm: ObjectIdentifier.new('1.2.840.113549.1.1.11')!
parameters: AnyDefinedBy.new(Null{})
}
out := encode(algo)!
assert out == expected
sback := decode(out)!
seq := sback as Sequence
s0 := seq.fields[0] as ObjectIdentifier
assert s0 == algo.algorithm
s1 := seq.fields[1] as Null
prm := algo.parameters.params as Null
assert s1 == prm
}
// Here is the encoding of a SEQUENCE OF INTEGER containing the numbers 7, 8, and 9:
//
// encoded into: 30 09 02 01 07 02 01 08 02 01 09
fn test_sequence_of_integer() ! {
expected := [u8(0x30), 0x09, 0x02, 0x01, 0x07, 0x02, 0x01, 0x08, 0x02, 0x01, 0x09]
mut els := []Integer{}
els << Integer.from_int(7)
els << Integer.from_int(8)
els << Integer.from_int(9)
seqof := SequenceOf.from_list[Integer](els)!
out := encode(seqof)!
assert out == expected
}
+340
View File
@@ -0,0 +1,340 @@
// Copyright (c) 2022, 2024 blackshirt. All rights reserved.
// Use of this source code is governed by a MIT License
// that can be found in the LICENSE file.
module asn1
// default_set_tag is the default tag of ASN.1 SET (SET OF) type.
pub const default_set_tag = Tag{.universal, true, int(TagType.set)}
const default_set_size = 64
const max_set_size = 255
// ASN.1 UNIVERSAL SET and SET OF TYPE.
//
// SET and SET OF contains an unordered series of fields of one or more types.
// This differs from a SEQUENCE which contains an ordered list.
// in DER encoding, SET types elements are sorted into tag order, and,
// for SET OF types elements are sorted into ascending order of encoding.
pub struct Set {
mut:
// maximal size of this set fields
size int = default_set_size
// fields is the elements of the set
fields []Element
}
// creates a new Set with default size.
pub fn Set.new() !Set {
return Set.new_with_size(default_set_size)!
}
// from_list creates a new Set from arrays of element in els.
pub fn Set.from_list(els []Element) !Set {
if els.len > max_set_size {
return error('Sequence size exceed limit')
}
return Set{
fields: els
}
}
fn Set.new_with_size(size int) !Set {
if size > max_set_size {
return error('size is exceed limit')
}
if size < 0 {
return error('Provides with correct size')
}
// if size is 0, use default_set_size
limit := if size == 0 { default_set_size } else { size }
return Set{
size: limit
}
}
// tag returns the tag of Set element.
pub fn (s Set) tag() Tag {
return default_set_tag
}
// payload returns the payload of the Set element.
//
// Note: Required for DER encoding.
// The encodings of the component values of a set value shall appear in an order determined by their tags.
// The canonical order for tags is based on the outermost tag of each type and is defined as follows:
// a) those elements or alternatives with universal class tags shall appear first, followed by those with
// application class tags, followed by those with context-specific tags, followed by those with private class
// tags;
// b) within each class of tags, the elements or alternatives shall appear in ascending order of their tag
// numbers.
pub fn (s Set) payload() ![]u8 {
// workaround by working with the copy of Set, changing it to 'mut s Set` would encounter some issues
// ie, with messages `asn1.Set` incorrectly implements method `payload` of interface `asn1.Element`:
// expected `asn1.Element` which is immutable, not `mut &asn1.Set`
mut set := s
return set.payload_with_rule(.der)!
}
fn (mut s Set) payload_with_rule(rule EncodingRule) ![]u8 {
// first, we sort the set.fields and then serializing it.
s.sort_set_fields()
mut out := []u8{}
for field in s.fields {
item := unsafe { field }
obj := encode_with_rule(item, rule)!
out << obj
}
return out
}
// fields return the arrays of element's content in the Set.fields.
pub fn (set Set) fields() []Element {
return set.fields
}
fn Set.parse(mut _p Parser) !Set {
return error('not yet implemented')
}
fn Set.decode(bytes []u8) !(Set, int) {
return Set.decode_with_rule(bytes, 0, .der)!
}
fn Set.decode_with_rule(bytes []u8, loc int, rule EncodingRule) !(Set, int) {
tag, length_pos := Tag.decode_with_rule(bytes, loc, rule)!
if !tag.equal(default_set_tag) {
return error('Get unexpected non-set tag')
}
length, content_pos := Length.decode_with_rule(bytes, length_pos, rule)!
payload := if length == 0 {
[]u8{}
} else {
if content_pos + length > bytes.len {
return error('Not enought bytes to read on')
}
unsafe { bytes[content_pos..content_pos + length] }
}
next := content_pos + length
set := Set.from_bytes(payload)!
return set, next
}
// bytes should seq.fields payload, not includes the tag
fn Set.from_bytes(bytes []u8) !Set {
mut set := Set{}
if bytes.len == 0 {
return set
}
mut i := 0
for i < bytes.len {
el, pos := Element.decode_with_rule(bytes, i, .der)!
i = pos
set.add_element(el)!
}
if i > bytes.len {
return error('i > bytes.len')
}
if i < bytes.len {
return error('The src contains unprocessed bytes')
}
return set
}
fn (s Set) equal(o Set) bool {
for i, item in s.fields {
for j, obj in o.fields {
if i == j {
if !item.equal(obj) {
return false
}
} else {
continue
}
}
}
return true
}
// add_element adds an element el into Set.
// By default it allowing adds element with the same tag
pub fn (mut set Set) add_element(el Element) ! {
set.relaxed_add_element(el, true)!
}
// add_element allows adding a new element into current Set fields.
// Its does not allow adding element when is already the same tag in the fields.
// but, some exception when you set relaxed to true
fn (mut set Set) relaxed_add_element(el Element, relaxed bool) ! {
if set.fields.len == 0 {
// just adds it then return
set.fields << el
return
}
// remove this checks
// for item in set.fields {
// if item.equal(el) {
// return error('has already in the fields')
// }
// }
filtered_by_tag := set.fields.filter(it.tag().equal(el.tag()))
if filtered_by_tag.len == 0 {
set.fields << el
return
} else {
if !relaxed {
return error('You can not insert element without forcing')
}
set.fields << el
return
}
}
// `set_size` set internal maximal size of this set fields.
pub fn (mut set Set) set_size(size int) ! {
if size < 0 {
return error('provides the correct size')
}
if size > max_set_size {
return error('Provided limit was exceed current one')
}
set.size = size
}
// sort_set_fields sort the Set fields in places with sort_with_compare support
fn (mut set Set) sort_set_fields() {
// without &, its return an error: sort_with_compare callback function parameter
// `x` with type `asn1.Element` should be `&asn1.Element`
set.fields.sort_with_compare(fn (x &Element, y &Element) int {
if x.tag().class != y.tag().class {
s := if int(x.tag().class) < int(y.tag().class) { -1 } else { 1 }
return s
}
if x.tag() == y.tag() {
// compare by contents instead just return 0
xx := encode(x) or { panic(err) }
yy := encode(y) or { panic(err) }
return xx.bytestr().compare(yy.bytestr())
}
q := if x.tag().number < y.tag().number { -1 } else { 1 }
return q
})
}
// ASN.1 SET OF
//
pub struct SetOf[T] {
mut:
size int = default_set_size
fields []T
}
// new creates an empty SetOf type T.
pub fn SetOf.new[T]() !SetOf[T] {
$if T !is Element {
return error('Yur T is not Element')
}
return SetOf[T]{}
}
// from_list creates new SetOf type T from arrays of T.
pub fn SetOf.from_list[T](els []T) !SetOf[T] {
$if T !is Element {
return error('Yur T is not Element')
}
if els.len > max_set_size {
return error('[]T length is exceed limit')
}
return SetOf[T]{
fields: els
}
}
// tag returns the tag of SetOf[T] element.
pub fn (so SetOf[T]) tag() Tag {
return default_set_tag
}
// fields returns underlying arrays of T from the SetOf[T].
pub fn (so SetOf[T]) fields() []T {
return so.fields
}
// payload returns the payload of SetOf[T] element.
pub fn (so SetOf[T]) payload() ![]u8 {
mut sto := so
return sto.payload_with_rule(.der)!
}
// issues: el cannot be used as interface object outside `unsafe` blocks
// as it might be stored on stack.
// Consider wrapping the `T` object in a `struct` declared as `@[heap]`
fn (mut so SetOf[T]) payload_with_rule(rule EncodingRule) ![]u8 {
$if T !is Element {
return error('T is not an element')
}
// sort the fields and serialize
so.sort_setof_fields()
mut out := []u8{}
for el in so.fields {
// we encounter issues above at here
// we can not directly pass el to encode routine
elem := unsafe { el }
obj := encode_with_rule(elem, rule)!
out << obj
}
return out
}
fn (mut so SetOf[T]) sort_setof_fields() {
so.fields.sort_with_compare(fn [T](x &T, y &T) int {
xx := Element.from_object[T](x) or { panic(err) }
yy := Element.from_object[T](y) or { panic(err) }
aa := encode(xx) or { panic(err) }
bb := encode(yy) or { panic(err) }
return aa.bytestr().compare(bb.bytestr())
})
}
// add_element adds an element el into SetOf[T] fields.
pub fn (mut so SetOf[T]) add_element(el Element) ! {
so.relaxed_add_element(el, true)!
}
// add_element allows adding a new element into current Set fields.
// Its does not allow adding element when is already the same tag in the fields.
// but, some exception when you set relaxed to true
fn (mut so SetOf[T]) relaxed_add_element(el Element, relaxed bool) ! {
the_t := el.into_object[T]()!
if so.fields.len == 0 {
// just adds it then return
so.fields << the_t
return
}
mut filtered_by_tag := []T{}
for field in so.fields {
item := Element.from_object(field)!
if item.equal(el) {
return error('has already in the fields')
}
if item.tag().equal(el.tag()) {
// add this to filtered
filtered_by_tag << field
}
}
if filtered_by_tag.len == 0 {
so.fields << the_t
return
} else {
if !relaxed {
return error('You can not insert element without forcing')
}
so.fields << the_t
return
}
}
+106
View File
@@ -0,0 +1,106 @@
// Copyright (c) 2022, 2023 blackshirt. All rights reserved.
// Use of this source code is governed by a MIT License
// that can be found in the LICENSE file.
module asn1
fn test_sort_the_set() {
mut objs := Set.new()!
val12 := Integer.from_i64(12)
val32 := Integer.from_i64(32)
valbol := Boolean.new(false)
valnull := Null{}
valapp := RawElement.new(Tag{.application, false, 34}, [u8(44), 45])!
valctx := RawElement.new(Tag{.context_specific, false, 35}, [u8(50), 55])!
objs.add_element(val12)! // tag: 2
objs.add_element(val32)! // tag: 2
objs.add_element(valctx)! //
objs.add_element(valapp)! //
objs.add_element(valbol)! // tag: 1
objs.add_element(valnull)! // tag: 5
awal := objs.fields.clone()
mut exp := Set.new()!
exp.add_element(valbol)!
exp.add_element(val12)!
exp.add_element(val32)!
exp.add_element(valnull)!
exp.add_element(valapp)!
exp.add_element(valctx)!
// [valbol, val12, val32, valnull, valapp, valctx]}
objs.sort_set_fields()
// this not compiled with -cstrict option
// assert awal != objs.fields
assert objs.equal(exp)
}
fn test_sort_the_setof() ! {
mut objs := SetOf.new[Integer]()!
val1 := Integer.from_i64(1)
val2 := Integer.from_i64(12)
val3 := Integer.from_i64(323)
val4 := Integer.from_i64(4325)
val5 := Integer.from_i64(44446)
val0 := Integer.from_i64(0)
val6 := Boolean.new(false)
// randomly added to array
objs.add_element(val4)! // tag: 2
objs.add_element(val2)! // tag: 2
objs.add_element(val5)! // tag: 2
objs.add_element(val1)! // tag: 2
objs.add_element(val3)! // tag: 2
objs.add_element(val0)! // tag: 2
awal := objs.fields.clone()
awalexp := [val4, val2, val5, val1, val3, val0]
assert awal == awalexp
// dump(objs)
objs.sort_setof_fields()
// dump(objs)
exp := [val0, val1, val2, val3, val4, val5]
assert awal != objs.fields
assert objs.fields == exp
}
fn test_set_encode() ! {
mut set1 := Set.new()!
set1.add_element(Boolean.new(false))!
set1.add_element(Null{})!
set1.add_element(Integer.from_int(4))!
// boolean tag:1 length: 3, integer tag:2 length: 3, null tag: 5 length: 2, total length: 8
// so, it should sort to
// [boolean, integer, null]
exp := [u8(0x31), 8, 1, 1, 0, 2, 1, 4, 5, 0]
out := encode(set1)!
assert out == exp
back, _ := Set.decode(exp)!
backout := encode(back)!
assert out == backout
}
fn test_setof_encode() ! {
mut set1 := SetOf.new[Integer]()!
set1.add_element(Integer.from_int(55))!
set1.add_element(Integer.from_int(4))!
set1.add_element(Integer.from_int(666))!
// 666 serialized to [2, 154]
// encoded sort: [int1, int2, int3]
exp := [u8(0x31), 10, 2, 1, 4, 2, 1, 55, 2, 2, 2, 0x9a]
out := encode(set1)!
assert out == exp
// todo : decode back
}
@@ -0,0 +1,69 @@
// Copyright (c) 2022, 2024 blackshirt. All rights reserved.
// Use of this source code is governed by a MIT License
// that can be found in the LICENSE file.
module asn1
fn test_explicit_context_null_pack_unpack() ! {
el := Null{}
ex1 := ContextElement.from_element(el, 0, .explicit)!
out := encode(ex1)!
exp := [u8(0xa0), 0x02, 0x05, 0x00]
assert out == exp
// unpack back
ttback := decode(out)!
// , 'context_specific:0;explicit;inner:5')!
expc := ttback.unwrap_with_options('context_specific:0;explicit;inner:5')!
assert expc.tag().number == int(TagType.null)
assert ttback.equal(ex1)
}
fn test_explicit_context_nested_pack_unpack() ! {
el := Null{}
ex1 := ContextElement.from_element(el, 1, .explicit)!
out := encode(ex1)!
assert out == [u8(0xa1), 0x02, u8(0x05), 0x00]
}
fn test_asn1_example() ! {
/*
```asn.1
Example ::= SEQUENCE {
greeting UTF8String,
answer INTEGER,
type [1] EXPLICIT OBJECT IDENTIFIER
}
```*/
oid := ObjectIdentifier.new('1.3.6.1.3')!
expl := ContextElement.from_element(oid, 1, .explicit)!
mut seq := Sequence{}
seq.add_element(Utf8String.new('Hello')!)! // tag : 12
seq.add_element(Integer.from_i64(i64(42)))! // tag 2
seq.add_element(expl)!
mut out := encode(seq)!
exp := [u8(0x30), 18, u8(12), 5, 72, 101, 108, 108, 111, u8(2), 1, 42, u8(0xA1), 6, 6, 4, 43,
6, 1, 3]
assert out == exp
back, n := Sequence.decode(out)!
assert n == exp.len
els := back.fields()
assert els[0] is Utf8String
assert els[1] is Integer
mut els2 := els[2] as ContextElement
els2.set_mode(.explicit, true)!
els2.set_inner_tag(default_oid_tag, true)!
out.clear()
out = encode(els2)!
expected_ctx_type := [u8(0xA1), 6, 6, 4, 43, 6, 1, 3]
assert out == expected_ctx_type
}
+364
View File
@@ -0,0 +1,364 @@
// Copyright (c) 2022, 2024 blackshirt. All rights reserved.
// Use of this source code is governed by a MIT License
// that can be found in the LICENSE file.
module asn1
import time
// default_utctime_tag is the default tag of ASN.1 UTCTIME type.
pub const default_utctime_tag = Tag{.universal, false, int(TagType.utctime)}
// default_generalizedtime_tag is the default tag of ASN.1 GENERALIZEDTIME type.
pub const default_generalizedtime_tag = Tag{.universal, false, int(TagType.generalizedtime)}
const default_utctime_format = 'YYMMDDHHmmssZ'
const default_genztime_format = 'YYYYMMDDHHmmssZ'
// ASN.1 UNIVERSAL CLASS OF UTCTIME TYPE.
//
// 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)
//
// NOTE
// -----
// - 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
pub struct UtcTime {
pub:
value string
}
// new_utctime creates a new UtcTime element from string s.
pub fn UtcTime.new(s string) !UtcTime {
valid := validate_utctime(s)!
if !valid {
return error('UtcTime: fail on validate utctime')
}
return UtcTime{
value: s
}
}
// from_time creates a new UtcTime element from standard `time.Time` in UTC time format.
pub fn UtcTime.from_time(t time.Time) !UtcTime {
// changes into utc time
utime := t.local_to_utc()
s := utime.custom_format(default_utctime_format) // 20241113060446+0
// Its rather a hack, not efieient as should be.
// TODO: make it better
str := s.split_any('+-')
val := str[0] + 'Z'
utc := UtcTime.new(val)!
return utc
}
fn UtcTime.from_bytes(b []u8) !UtcTime {
return UtcTime.new(b.bytestr())!
}
fn (utc UtcTime) str() string {
if utc.value.len == 0 {
return 'UtcTime (<empty>)'
}
return 'UtcTime (${utc.value})'
}
// tag retursn the tag of the UtcTime element.
pub fn (utc UtcTime) tag() Tag {
return default_utctime_tag
}
// payload returns the payload of the UtcTime element.
pub fn (utc UtcTime) payload() ![]u8 {
return utc.payload_with_rule(.der)!
}
// into_utctime turns this UtcTime into corresponding UTC time.
pub fn (utc UtcTime) into_utctime() !time.Time {
valid := validate_utctime(utc.value)!
if !valid {
return error('UtcTime: fail on validate utctime value')
}
st := time.parse_format(utc.value, default_utctime_format)!
utime := st.local_to_utc()
return utime
}
fn (utc UtcTime) payload_with_rule(_rule EncodingRule) ![]u8 {
valid := validate_utctime(utc.value)!
if !valid {
return error('UtcTime: fail on validate utctime')
}
return utc.value.bytes()
}
fn UtcTime.parse(mut p Parser) !UtcTime {
tag := p.read_tag()!
if !tag.equal(default_utctime_tag) {
return error('Bad UtcTime tag')
}
length := p.read_length()!
bytes := p.read_bytes(length)!
res := UtcTime.from_bytes(bytes)!
return res
}
// UtcTime.decode tries to decode bytes into UtcTime with DER rule
fn UtcTime.decode(src []u8) !(UtcTime, int) {
return UtcTime.decode_with_rule(src, .der)!
}
fn UtcTime.decode_with_rule(bytes []u8, rule EncodingRule) !(UtcTime, int) {
tag, length_pos := Tag.decode_with_rule(bytes, 0, rule)!
if !tag.equal(default_utctime_tag) {
return error('Unexpected non-utctime tag')
}
length, content_pos := Length.decode_with_rule(bytes, length_pos, rule)!
content := if length == 0 {
[]u8{}
} else {
if content_pos >= bytes.len || content_pos + length > bytes.len {
return error('UtcTime: truncated payload bytes')
}
unsafe { bytes[content_pos..content_pos + length] }
}
utc := UtcTime.from_bytes(content)!
next := content_pos + length
return utc, next
}
// utility function for UtcTime
//
fn validate_utctime(s string) !bool {
if !basic_utctime_check(s) {
return false
}
// read contents
src := s.bytes()
mut pos := 0
mut year, mut month, mut day := u16(0), u8(0), u8(0)
mut hour, mut minute, mut second := u8(0), u8(0), u8(0)
// UtcTime only encodes times prior to 2050
year, pos = read_2_digits(src, pos)!
year = u16(year)
if year >= 50 {
year = 1900 + year
} else {
year = 2000 + year
}
month, pos = read_2_digits(src, pos)!
day, pos = read_2_digits(src, pos)!
if !validate_date(year, month, day) {
return false
}
// hhmmss parts
hour, pos = read_2_digits(src, pos)!
minute, pos = read_2_digits(src, pos)!
second, pos = read_2_digits(src, pos)!
if hour > 23 || minute > 59 || second > 59 {
return false
}
// assert pos == src.len - 1
if src[pos] != 0x5A {
return false
}
return true
}
fn basic_utctime_check(s string) bool {
return s.len == 13 && valid_time_contents(s)
}
fn valid_time_contents(s string) bool {
return s.ends_with('Z') && s.contains_any('0123456789')
}
// ASN.1 UNIVERSAL CLASS OF GENERALIZEDTIME TYPE.
//
// 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.
pub struct GeneralizedTime {
pub:
value string
}
// GeneralizedTime.new creates a new GeneralizedTime from string s
pub fn GeneralizedTime.new(s string) !GeneralizedTime {
valid := validate_generalizedtime(s)!
if !valid {
return error('GeneralizedTime: failed on validate')
}
return GeneralizedTime{
value: s
}
}
// from_time creates GeneralizedTime element from standard `time.Time` (as an UTC time).
pub fn GeneralizedTime.from_time(t time.Time) !GeneralizedTime {
u := t.local_to_utc()
s := u.custom_format(default_genztime_format)
// adds support directly from time.Time
src := s.split_any('+-')
val := src[0] + 'Z'
gt := GeneralizedTime.new(val)!
return gt
}
// into_utctime turns this GeneralizedTime into corresponding UTC time.
pub fn (gt GeneralizedTime) into_utctime() !time.Time {
valid := validate_generalizedtime(gt.value)!
if !valid {
return error('GeneralizedTime: fail on validate value')
}
st := time.parse_format(gt.value, default_genztime_format)!
utime := st.local_to_utc()
return utime
}
// The tag of GeneralizedTime element.
pub fn (gt GeneralizedTime) tag() Tag {
return default_generalizedtime_tag
}
// The payload of GeneralizedTime element.
pub fn (gt GeneralizedTime) payload() ![]u8 {
valid := validate_generalizedtime(gt.value)!
if !valid {
return error('GeneralizedTime: failed on validate')
}
return gt.value.bytes()
}
fn GeneralizedTime.from_bytes(b []u8) !GeneralizedTime {
return GeneralizedTime.new(b.bytestr())!
}
// GeneralizedTime.parse tries to parse throught ongoing Parser into GeneralizedTime
fn GeneralizedTime.parse(mut p Parser) !GeneralizedTime {
tag := p.read_tag()!
if !tag.equal(default_generalizedtime_tag) {
return error('Bad GeneralizedTime tag')
}
length := p.read_length()!
bytes := p.read_bytes(length)!
res := GeneralizedTime.from_bytes(bytes)!
return res
}
fn GeneralizedTime.decode(bytes []u8) !(GeneralizedTime, int) {
return GeneralizedTime.decode_with_rule(bytes, .der)!
}
fn GeneralizedTime.decode_with_rule(bytes []u8, rule EncodingRule) !(GeneralizedTime, int) {
tag, length_pos := Tag.decode_with_rule(bytes, 0, rule)!
if !tag.equal(default_generalizedtime_tag) {
return error('Get bad GeneralizedTime tag')
}
length, content_pos := Length.decode_with_rule(bytes, length_pos, rule)!
content := if length == 0 {
[]u8{}
} else {
if content_pos >= bytes.len || content_pos + length > bytes.len {
return error('GeneralizedTime: truncated payload bytes')
}
unsafe { bytes[content_pos..content_pos + length] }
}
gtc := GeneralizedTime.from_bytes(content)!
next := content_pos + length
return gtc, next
}
// utility function for GeneralizedTime
// TODO: more clear and concise validation check
fn min_generalizedtime_length(s string) bool {
// minimum length without fractional element
return s.len >= 15
}
fn generalizedtime_contains_fraction(s string) bool {
// contains '.' part
return s.contains('.')
}
fn basic_generalizedtime_check(s string) bool {
return min_generalizedtime_length(s) && valid_time_contents(s)
}
fn validate_generalizedtime(s string) !bool {
if !basic_generalizedtime_check(s) {
return false
}
// read contents
src := s.bytes()
mut pos := 0
mut year, mut month, mut day := u16(0), u8(0), u8(0)
mut hour, mut minute, mut second := u8(0), u8(0), u8(0)
// Generalized time format was "YYYYMMDDhhmmssZ"
// TODO: support for second fractions part
year, pos = read_4_digits(src, pos)!
// year = u16(year)
month, pos = read_2_digits(src, pos)!
day, pos = read_2_digits(src, pos)!
if !validate_date(year, month, day) {
return false
}
// hhmmss parts
hour, pos = read_2_digits(src, pos)!
minute, pos = read_2_digits(src, pos)!
second, pos = read_2_digits(src, pos)!
if hour > 23 || minute > 59 || second > 59 {
return false
}
// assert pos == src.len - 1
if src[pos] != 0x5A {
return false
}
return true
}
+144
View File
@@ -0,0 +1,144 @@
// Copyright (c) 2022, 2024 blackshirt. All rights reserved.
// Use of this source code is governed by a MIT License
// that can be found in the LICENSE file.
module asn1
import os
import time
fn test_serialize_utctime_basic() ! {
inp := '191215190210Z'
exp := [u8(0x17), 0x0D, 49, 57, 49, 50, 49, 53, 49, 57, 48, 50, 49, 48, 90]
ut := UtcTime.new(inp)!
out := encode(ut)!
assert out == exp
// back
back, pos := UtcTime.decode(out)!
assert back.tag().tag_number() == int(TagType.utctime)
assert back == ut
assert back.value == inp
}
fn test_create_utctime_from_std_time() ! {
now := time.new(year: 2024, month: 11, day: 13, hour: 17, minute: 45, second: 50)
utb := UtcTime.from_time(now)!
utc := UtcTime.from_time(now)!
assert utb.value == utc.value
tt := utc.into_utctime()!
assert now == tt
inp := '191215190210Z'
t_inp := UtcTime.new(inp)!
t_inp_utc := t_inp.into_utctime()!
// time to UtcTime back
tinp_back := UtcTime.from_time(t_inp_utc)!
assert tinp_back.value == inp
}
fn test_create_utctime_from_std_time_with_negative_offset() ! {
tz := os.getenv('TZ')
os.setenv('TZ', 'utc 1', true)
defer {
os.setenv('TZ', tz, true)
}
now := time.new(year: 2024, month: 11, day: 13, hour: 17, minute: 45, second: 50)
UtcTime.from_time(now)!
}
fn test_serialize_utctime_error_without_z() ! {
// this input does not contains zulu 'Z' part
inp := '191215190210'
exp := [u8(0x17), 0x0D, 49, 57, 49, 50, 49, 53, 49, 57, 48, 50, 49, 48]
ut := UtcTime.new(inp) or {
assert err == error('UtcTime: fail on validate utctime')
return
}
}
fn test_serialize_utctime_error_month() ! {
// the month part is > 12
inp := '191815190210Z'
exp := [u8(0x17), 0x0D, 49, 57, 49, 56, 49, 53, 49, 57, 48, 50, 49, 48]
ut := UtcTime.new(inp) or {
assert err == error('UtcTime: fail on validate utctime')
return
}
}
fn test_serialize_utctime_error_day() ! {
// the day part is > 30
inp := '191235190210Z'
exp := [u8(0x17), 0x0D, 0x31, 0x39, 0x31, 0x32, 0x31, 0x32, 0x31, 0x39, 0x30, 0x32, 0x31, 0x30,
0x5A]
ut := UtcTime.new(inp) or {
assert err == error('UtcTime: fail on validate utctime')
return
}
}
fn test_serialize_decode_generalizedtime() ! {
s := '20100102030405Z'
exp := [u8(0x18), 0x0f, 50, 48, 49, 48, 48, 49, 48, 50, 48, 51, 48, 52, 48, 53, 90]
gt := GeneralizedTime.new(s)!
out := encode(gt)!
assert out == exp
// back
back, pos := GeneralizedTime.decode(out)!
assert back == gt
assert back.value == s
assert back.tag().tag_number() == int(TagType.generalizedtime)
}
fn test_create_generalizedtime_from_std_time() ! {
now := time.new(year: 2024, month: 11, day: 13, hour: 17, minute: 45, second: 50)
gtb := GeneralizedTime.from_time(now)!
gtc := GeneralizedTime.from_time(now)!
assert gtb.value == gtc.value
assert gtb.value == '20241113174550Z'
assert gtc.value == '20241113174550Z'
tt := gtc.into_utctime()!
assert now == tt
s := '20100102030405Z'
gt := GeneralizedTime.new(s)!
g_utc := gt.into_utctime()!
g_utc_back := GeneralizedTime.from_time(g_utc)!
assert g_utc_back.value == s
}
fn test_create_generalizedtime_from_std_time_with_negative_offset() ! {
tz := os.getenv('TZ')
os.setenv('TZ', 'utc 1', true)
defer {
os.setenv('TZ', tz, true)
}
now := time.new(year: 2024, month: 11, day: 13, hour: 17, minute: 45, second: 50)
GeneralizedTime.from_time(now)!
}
+103
View File
@@ -0,0 +1,103 @@
// Copyright (c) 2022, 2024 blackshirt. All rights reserved.
// Use of this source code is governed by a MIT License
// that can be found in the LICENSE file.
module asn1
import encoding.utf8
// default_utf8string_tag is the default tag of ASN.1 UTF8STRING type.
pub const default_utf8string_tag = Tag{.universal, false, int(TagType.utf8string)}
// ASN.1 UNIVERSAL CLASS OF UTF8STRING TYPE.
//
// UTF8STRING is UTF8 unicode charset
pub struct Utf8String {
pub:
value string
}
// new creates a new Utf8String element from string s.
pub fn Utf8String.new(s string) !Utf8String {
if !utf8.validate_str(s) {
return error('Utf8String: invalid UTF-8 string')
}
return Utf8String{
value: s
}
}
// from_bytes creates a new Utf8String element from bytes in src.
pub fn Utf8String.from_bytes(src []u8) !Utf8String {
if !utf8.validate_str(src.bytestr()) {
return error('Utf8String: invalid UTF-8 string')
}
return Utf8String{
value: src.bytestr()
}
}
// tag returns the tag of Utf8String element.
pub fn (uts Utf8String) tag() Tag {
return default_utf8string_tag
}
// payload returns the payload of Utf8String element.
pub fn (uts Utf8String) payload() ![]u8 {
return uts.payload_with_rule(.der)!
}
fn (uts Utf8String) str() string {
if uts.value.len == 0 {
return 'Utf8String (<empty>)'
}
return 'Utf8String (${uts.value})'
}
fn (uts Utf8String) payload_with_rule(rule EncodingRule) ![]u8 {
if rule != .der && rule != .ber {
return error('Utf8String: Unsupported rule')
}
if !utf8.validate_str(uts.value) {
return error('Utf8String: invalid UTF-8 string')
}
return uts.value.bytes()
}
fn Utf8String.parse(mut p Parser) !Utf8String {
tag := p.read_tag()!
if !tag.equal(default_utf8string_tag) {
return error('Bad Utf8String tag')
}
length := p.read_length()!
bytes := p.read_bytes(length)!
res := Utf8String.from_bytes(bytes)!
return res
}
fn Utf8String.decode(src []u8) !(Utf8String, int) {
return Utf8String.decode_with_rule(src, .der)!
}
fn Utf8String.decode_with_rule(bytes []u8, rule EncodingRule) !(Utf8String, int) {
tag, length_pos := Tag.decode_with_rule(bytes, 0, rule)!
if !tag.equal(default_utf8string_tag) {
return error('Unexpected non-utf8string tag')
}
length, content_pos := Length.decode_with_rule(bytes, length_pos, rule)!
content := if length == 0 {
[]u8{}
} else {
// non-null length should contains non-null bytes
if content_pos >= bytes.len || content_pos + length > bytes.len {
return error('Utf8String: truncated payload bytes')
}
unsafe { bytes[content_pos..content_pos + length] }
}
ust := Utf8String.from_bytes(content)!
next := content_pos + length
return ust, next
}
@@ -0,0 +1,41 @@
// Copyright (c) 2022, 2024 blackshirt. All rights reserved.
// Use of this source code is governed by a MIT License
// that can be found in the LICENSE file.
module asn1
// UTFString test
struct UTF8StringTest {
s string
out []u8
err IError
}
fn test_uf8string_handling() ! {
data := [
UTF8StringTest{'test', [u8(12), 4, 116, 101, 115, 116], none},
UTF8StringTest{'abc', '\x0c\x03abc'.bytes(), none},
// rocket := `🚀` was rune, single unicode, rocket.bytes() = [240, 159, 154, 128]
UTF8StringTest{`🚀`.str(), [u8(12), 4, 240, 159, 154, 128], none},
UTF8StringTest{')', '\x0c\x01)'.bytes(), none},
UTF8StringTest{'\x13\x03ab\x00', [u8(12), 5, 19, 3, 97, 98, 0], none},
UTF8StringTest{'Test User 1', '\x0c\x0bTest User 1'.bytes(), none},
// invalid utf8 string, emoji with removed first and fifth byte
UTF8StringTest{'🐶🐶🐶🚀'.substr(0, 5), []u8{}, error('Utf8String: invalid UTF-8 string')},
]
for c in data {
us := Utf8String.new(c.s) or {
assert err == c.err
continue
}
out := encode(us) or {
assert err == c.err
continue
}
assert out == c.out
uss, _ := Utf8String.decode(out)!
assert uss.tag().tag_number() == int(TagType.utf8string)
assert uss.value == c.s
}
}
+118
View File
@@ -0,0 +1,118 @@
// Copyright (c) 2022, 2024 blackshirt. All rights reserved.
// Use of this source code is governed by a MIT License
// that can be found in the LICENSE file.
module asn1
// ASN.1 Utility function
// read_bytes was safe version of bytes slicing, `src[pos..pos+size]`
fn read_bytes(src []u8, pos int, size int) ![]u8 {
if src.len < 1 || pos > src.len || size > src.len {
return error(' pos=${pos} or size=${size} bigger than len=${src.len}')
}
if pos + size > src.len {
return error('pos + size maybe getting overflow')
}
ret := src[pos..pos + size]
return ret
}
fn read_byte(src []u8, loc int) !(u8, int) {
if src.len == 0 || loc + 1 > src.len {
return error('invalid loc or len')
}
mut pos := loc
result := src[pos]
pos += 1
return result, pos
}
fn read_digit(src []u8, loc int) !(u8, int) {
val, pos := read_byte(src, loc)!
// check its a digit, '0'-'9',
// aka, 0x30 s/d 0x39 in hex, or 48-57 in dec
if !val.is_digit() {
return error('not digit byte')
}
digit := val - u8(0x30) // get the digit value
return digit, pos
}
fn read_2_digits(src []u8, loc int) !(u8, int) {
if loc >= src.len || src.len < 2 + loc {
return error('not enough bytes')
}
mut val, mut pos := read_digit(src, loc)!
first := val * 10
if pos < src.len {
val, pos = read_digit(src, pos)!
}
return first + val, pos
}
fn read_4_digits(src []u8, loc int) !(u16, int) {
if loc >= src.len || src.len < 4 + loc {
return error('not enough bytes')
}
mut val, mut pos := read_digit(src, loc)!
first := u16(val) * 1000
if pos < src.len {
val, pos = read_digit(src, pos)!
}
second := u16(val) * 100
if pos < src.len {
val, pos = read_digit(src, pos)!
}
third := u16(val) * 10
if pos < src.len {
val, pos = read_digit(src, pos)!
}
fourth := u16(val)
result := first + second + third + fourth
return result, pos
}
fn validate_date(year u16, month u8, day u8) bool {
if year < 0 {
return false
}
if day < 1 {
return false
}
mut dim := month
match month {
1, 3, 5, 7, 8, 10, 12 {
dim = 31
}
4, 6, 9, 11 {
dim = 30
}
2 {
// kabisat
if (year % 4 == 0 && year % 100 != 0) || year % 400 == 0 {
dim = 29
} else {
dim = 28
}
}
else {
return false
}
}
if day > dim {
return false
}
return true
}
+155
View File
@@ -0,0 +1,155 @@
// Copyright (c) 2022, 2024 blackshirt. All rights reserved.
// Use of this source code is governed by a MIT License
// that can be found in the LICENSE file.
module asn1
struct RByteTest {
inp []u8
pos int
result u8
end bool
}
fn test_simple_read_byte() {
src := [u8(0x16), 0x06, 0x44, 0x05, 0x09, 0x22]
start := 0
mut val, mut pos := read_byte(src, 0)!
assert val == src[start]
assert pos == 1
val, pos = read_byte(src, pos)!
assert val == src[1]
assert pos == 2
val, pos = read_byte(src, pos)!
assert val == src[2]
assert pos == 3
val, pos = read_byte(src, pos)!
assert val == src[3]
assert pos == 4
val, pos = read_byte(src, pos)!
assert val == src[4]
assert pos == 5
// this is the last item in the array
val, pos = read_byte(src, pos)!
assert val == src[5] // the last item, 0x22
assert pos == 6
// read next byte should error
val, pos = read_byte(src, pos) or {
assert err == error('invalid loc or len')
return
}
}
fn test_read_digit_outside_digit_iserror() ! {
src := [u8(0xff)]
_, _ := read_digit(src, 0) or {
assert err == error('not digit byte')
return
}
}
fn test_simple_read_digit() ! {
// 0x22 is not digit digit byte
src := [u8(0x32), 0x33, 0x34, 0x35, 0x39, 0x22]
start := 0
mut val, mut pos := read_digit(src, start)!
assert val == 2 // (0x32-0x30)
assert pos == 1
val, pos = read_digit(src, pos)!
assert val == 3 // 0x33-0x30
assert pos == 2
val, pos = read_digit(src, pos)!
assert val == 4 // 0x34 - 0x30
assert pos == 3
val, pos = read_digit(src, pos)!
assert val == 5 // same principe as above
assert pos == 4
val, pos = read_digit(src, pos)!
assert val == 9
assert pos == 5
// the last item was not digit
val, pos = read_digit(src, pos) or {
assert err == error('not digit byte')
return
}
}
fn test_read_2_digits_single() ! {
src := [u8(0x35)]
start := 0
read_2_digits(src, start) or {
assert err == error('not enough bytes')
return
}
}
fn test_read_2_digits_double() ! {
src := [u8(0x35), 0x33]
start := 0
val, pos := read_2_digits(src, start)!
assert val == 53 // 5*10+3
assert pos == 2
}
fn test_read_2_digits_simple() ! {
// 0x22 is not digit digit byte
src := [u8(0x30), 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x22] // 48,49,50,51,52,53,54,55,56,57, "
start := 0
mut val, mut pos := read_2_digits(src, start)!
assert val == 1 // 0*10+1
assert pos == 2
val, pos = read_2_digits(src, pos)!
assert val == 23 // 2*10+3
assert pos == 4
val, pos = read_2_digits(src, pos)!
assert val == 45 // 4*10+5
assert pos == 6
val, pos = read_2_digits(src, pos)!
assert val == 67 // 6*10+7
assert pos == 8
val, pos = read_2_digits(src, pos)!
assert val == 89 // 8*10+9
assert pos == 10
// last item was 0x22, its not digit and not enough
val, pos = read_2_digits(src, pos) or {
assert err == error('not enough bytes')
return
}
}
fn test_read_4_digits() ! {
src := [u8(0x30), 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x35] // 48,49,50,51,52,53,54,55,56,57
start := 0
mut val, mut pos := read_4_digits(src, start)!
assert val == 123 // 0*1000 + 1*100 + 2*10 + 3
assert pos == 4
val, pos = read_4_digits(src, pos)!
assert val == 4567 // 4*1000 + 5*100 + 6*10 + 7
assert pos == 8
// last read 4 digit was error
val, pos = read_4_digits(src, pos) or {
assert err == error('not enough bytes')
return
}
}
@@ -0,0 +1,104 @@
// Copyright (c) 2022, 2024 blackshirt. All rights reserved.
// Use of this source code is governed by a MIT License
// that can be found in the LICENSE file.
module asn1
// default_visiblestring_tag is the default tag of ASN.1 VISIBLESTRING type.
pub const default_visiblestring_tag = Tag{.universal, false, int(TagType.visiblestring)}
// ASN.1 UNIVERSAL CLASS OF VISIBLESTRING TYPE.
//
// The ASN.1 VisibleString type supports a subset of ASCII characters that does not include control characters.
pub struct VisibleString {
pub:
value string
}
// from_string creates a new VisibleString from string s.
pub fn VisibleString.new(s string) !VisibleString {
if contains_ctrl_chars(s.bytes()) {
return error('VisibleString: contains control chars')
}
return VisibleString{
value: s
}
}
// from_bytes creates a new VisibleString from bytes src
fn VisibleString.from_bytes(src []u8) !VisibleString {
if contains_ctrl_chars(src) {
return error('VisibleString: contains control chars')
}
return VisibleString{
value: src.bytestr()
}
}
// tag returns the tag of VisibleString type element.
pub fn (vst VisibleString) tag() Tag {
return default_visiblestring_tag
}
// payload returns the payload of VisibleString type element.
pub fn (vst VisibleString) payload() ![]u8 {
if contains_ctrl_chars(vst.value.bytes()) {
return error('VisibleString: contains control chars')
}
return vst.value.bytes()
}
fn (vst VisibleString) str() string {
if vst.value.len == 0 {
return 'VisibleString (<empty>)'
}
return 'VisibleString (${vst.value})'
}
fn VisibleString.parse(mut p Parser) !VisibleString {
tag := p.read_tag()!
if !tag.equal(default_visiblestring_tag) {
return error('Bad VisibleString tag')
}
length := p.read_length()!
bytes := p.read_bytes(length)!
res := VisibleString.from_bytes(bytes)!
return res
}
fn VisibleString.decode(src []u8) !(VisibleString, int) {
return VisibleString.decode_with_rule(src, .der)!
}
fn VisibleString.decode_with_rule(bytes []u8, rule EncodingRule) !(VisibleString, int) {
tag, length_pos := Tag.decode_with_rule(bytes, 0, rule)!
if !tag.equal(default_visiblestring_tag) {
return error('Unexpected non-visiblestring tag')
}
length, content_pos := Length.decode_with_rule(bytes, length_pos, rule)!
content := if length == 0 {
[]u8{}
} else {
if content_pos >= bytes.len || content_pos + length > bytes.len {
return error('VisibleString: truncated payload bytes')
}
unsafe { bytes[content_pos..content_pos + length] }
}
vst := VisibleString.from_bytes(content)!
next := content_pos + length
return vst, next
}
// Utility function for VisibleString
//
fn is_ctrl_char(c u8) bool {
return (c >= 0 && c <= 0x1f) || c == 0x7f
}
fn contains_ctrl_chars(src []u8) bool {
return src.any(is_ctrl_char(it))
}
@@ -0,0 +1,42 @@
// Copyright (c) 2022, 2024 blackshirt. All rights reserved.
// Use of this source code is governed by a MIT License
// that can be found in the LICENSE file.
module asn1
// VisibleString
struct VisibleTest {
inp string
out []u8
err IError
}
fn test_visible_string_handling() {
vb := [
VisibleTest{'', [u8(26), 0], none},
VisibleTest{'abc', [u8(26), 0x03, 97, 98, 99], none},
VisibleTest{'abc\x1A', [u8(26), 0x03, 97, 98, 99, 26], error('VisibleString: contains control chars')},
VisibleTest{'abc\x5A', [u8(26), 0x04, 97, 98, 99, 0x5a], none},
]
for i, c in vb {
vs := VisibleString.new(c.inp) or {
assert err == c.err
continue
}
mut out := encode(vs) or {
assert err == c.err
continue
}
assert out == c.out
// back
vsback, idx := VisibleString.decode(out) or {
assert err == c.err
continue
}
assert vsback.tag().tag_number() == int(TagType.visiblestring)
assert vsback.value == c.inp
}
}