Initial project import
This commit is contained in:
commit
23f2d29e32
11957 files changed
+1754577
No files matched your search
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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()!
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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')
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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)'
|
||||
}
|
||||
@@ -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()!
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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' }
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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?'
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)!
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user