v2 / vlib / x / encoding / asn1 / core.v
564 lines · 519 sloc · 16.77 KB · 58fc4dead559901cad648fb695f31d0f6de9945a
Raw
1// Copyright (c) 2022, 2024 blackshirt. All rights reserved.
2// Use of this source code is governed by a MIT License
3// that can be found in the LICENSE file.
4module asn1
5
6// vfmt off
7// bit masking values for ASN.1 tag header
8const tag_class_mask = 0xc0 // 192, bits 8-7
9const constructed_mask = 0x20 // 32, bits 6
10const tag_number_mask = 0x1f // 31, bits 1-5
11// vfmt on
12
13// Maximum number of bytes to represent tag number, includes the tag byte.
14const max_tag_length = 3
15const max_tag_number = 16383
16
17// Maximum value for UNIVERSAL class tag number, see `TagType`,
18// Tag number above this number should be considered to other class,
19// PRIVATE, CONTEXT_SPECIFIC or APPLICATION class.
20const max_universal_tagnumber = 255
21
22// ASN.1 Tag identifier handling.
23//
24// Tag represents identifier of the ASN.1 element (object).
25// ASN.1 Tag number can be represented in two form, the short form and the long form.
26// The short form for tag number below <= 30 and stored enough in single byte.
27// The long form for tag number > 30, and stored in two or more bytes.
28//
29// ASN.1 imposes no limit on the tag number, but the NIST Stable Implementation Agreements (1991)
30// and its European and Asian counterparts limit the size of tags to 16383.
31// see https://www.oss.com/asn1/resources/asn1-faq.html#tag-limitation
32// We impose limit on the tag number to be in range 0..16383.
33// Its big enough to accomodate and represent different of yours own tag number.
34// Its represents 2 bytes length where maximum bytes arrays to represent tag number
35// in multibyte (long) form is `[u8(0x1f), 0xff, 0x7f]` or 16383 in base 128.
36pub struct Tag {
37mut:
38 class TagClass = .universal
39 constructed bool
40 number int
41}
42
43// `Tag.new` creates new ASN.1 tag identifier. Its accepts params of TagClass `cls`,
44// the tag form in the constructed or primitive form in `constructed` flag, and the integer tag `number`.
45pub fn Tag.new(cls TagClass, constructed bool, number int) !Tag {
46 if number < 0 || number > max_tag_number {
47 return error('Unallowed tag number, ${number} exceed limit')
48 }
49 // validates required criteria
50 if cls == .universal {
51 // UNIVERSAL tag number should below max_universal_tagnumber
52 if number > max_universal_tagnumber {
53 return error('Not a valid tag number for universal class=${number}')
54 }
55 // SEQUENCE (OF) or SET (OF) should be in constructed form
56 if number == int(TagType.sequence) || number == int(TagType.set) {
57 if !constructed {
58 return error('For SEQUENCE(OF) or SET(OF) type, should be in constructed form')
59 }
60 }
61 }
62
63 // otherwise, ok
64 tag := Tag{
65 class: cls
66 constructed: constructed
67 number: number
68 }
69 return tag
70}
71
72// `tag_class` return the ASN.1 class of this tag
73pub fn (t Tag) tag_class() TagClass {
74 return t.class
75}
76
77// `is_constructed` tells us whether this tag is in constructed form
78// or the primitive one, ie, not constructed.
79pub fn (t Tag) is_constructed() bool {
80 return t.constructed
81}
82
83// `tag_number` return the tag nunber of this tag
84pub fn (t Tag) tag_number() int {
85 return t.number
86}
87
88// `encode` serializes the tag into bytes array with default rule, ie, .der.
89// Its serializes into dst bytes buffer or return error on fails.
90pub fn (t Tag) encode(mut dst []u8) ! {
91 t.encode_with_rule(mut dst, .der)!
92}
93
94// `Tag.from_bytes` creates a new Tag from bytes. Its return newly created tag and
95// the rest of remaining bytes on success, or error on failures.
96pub fn Tag.from_bytes(bytes []u8) !(Tag, []u8) {
97 tag, next_pos := Tag.decode(bytes)!
98 if next_pos < bytes.len {
99 rest := unsafe { bytes[next_pos..] }
100 return tag, rest
101 }
102 if next_pos == bytes.len {
103 return tag, []u8{}
104 }
105 return error('Tag: too short data')
106}
107
108// `equal` checks whether this tag is equal with the other tag
109pub fn (t Tag) equal(o Tag) bool {
110 return t.class == o.class && t.constructed == o.constructed && t.number == o.number
111}
112
113// encode_with_rule serializes tag into bytes array
114fn (t Tag) encode_with_rule(mut dst []u8, rule EncodingRule) ! {
115 // we currently only support .der or (stricter) .ber
116 if rule != .der && rule != .ber {
117 return error('Tag: unsupported rule')
118 }
119 // makes sure tag number is valid
120 if t.number > max_tag_number {
121 return error('Tag: tag number exceed limit')
122 }
123
124 // get the class type and constructed bit and build the bytes tag.
125 // if the tag number > 0x1f, represented in long form required two or more bytes,
126 // otherwise, represented in short form, fit in single byte.
127 mut b := (u8(t.class) << 6) & tag_class_mask
128 if t.constructed {
129 b |= constructed_mask
130 }
131 // The tag is in long form
132 if t.number >= 0x1f {
133 b |= tag_number_mask // 0x1f
134 dst << b
135 t.to_bytes_in_base128(mut dst)!
136 } else {
137 // short form
138 b |= u8(t.number)
139 dst << b
140 }
141}
142
143// Tag.decode tries to deserializes bytes into Tag.
144// Its return error on fails.
145fn Tag.decode(bytes []u8) !(Tag, int) {
146 tag, next := Tag.decode_from_offset(bytes, 0)!
147 return tag, next
148}
149
150// Tag.decode tries to deserializes bytes into Tag. its return error on fails.
151fn Tag.decode_from_offset(bytes []u8, pos int) !(Tag, int) {
152 // default rule
153 tag, next := Tag.decode_with_rule(bytes, pos, .der)!
154 return tag, next
155}
156
157// Tag.decode_with_rule deserializes bytes back into Tag structure start from `loc` offset.
158// By default, its decodes in .der encoding rule, if you want more control, pass your `Params`.
159// Its return Tag and next offset to operate on, and return error if it fails to decode.
160fn Tag.decode_with_rule(bytes []u8, loc int, rule EncodingRule) !(Tag, int) {
161 // preliminary check
162 if bytes.len < 1 {
163 return error('Tag: bytes underflow')
164 }
165 if rule != .der && rule != .ber {
166 return error('Tag: unsupported rule')
167 }
168 // when accessing byte at ofset `loc` within bytes, ie, `b := bytes[loc]`,
169 // its maybe can lead to panic when the loc is not be checked.
170 if loc >= bytes.len {
171 return error('Tag: invalid pos')
172 }
173 mut pos := loc
174 // first byte of tag bytes
175 b := bytes[pos]
176 pos += 1
177
178 // First we get the first byte from the bytes, check and gets the class and constructed bits
179 // and the tag number marker. If this marker == 0x1f, it tells whether the tag number is represented
180 // in multibyte (long form), or short form otherwise.
181 class := int((b & tag_class_mask) >> 6)
182 constructed := b & constructed_mask == constructed_mask
183 mut number := int(b & tag_number_mask)
184
185 // check if this `number` is in long (multibyte) form, and interpretes more bytes as a tag number.
186 if number == 0x1f {
187 // we only allowed `max_tag_length` bytes following to represent tag number.
188 number, pos = Tag.read_tagnum(bytes, pos)!
189
190 // pos is the next position to read next bytes, so check tag bytes length
191 if pos >= max_tag_length + loc + 1 {
192 return error('Tag: tag bytes is too long')
193 }
194 if number < 0x1f && rule == .der {
195 // requirement for DER encoding.
196 // TODO: the other encoding may remove this restriction
197 return error('Tag: non-minimal tag')
198 }
199 }
200 // build the tag
201 tag := Tag.new(TagClass.from_int(class)!, constructed, number)!
202
203 return tag, pos
204}
205
206fn (t Tag) str() string {
207 cls := t.class.str()
208 form := if t.constructed { 'true' } else { 'false' }
209 number := t.number.str()
210 return '${cls}-${form}-${number}'
211}
212
213// bytes_len tells amount of bytes needed to store tag in base 128
214fn (t Tag) bytes_len() int {
215 if t.number == 0 {
216 return 1
217 }
218 mut n := t.number
219 mut ret := 0
220
221 for n > 0 {
222 ret += 1
223 n >>= 7
224 }
225
226 return ret
227}
228
229// `tag_size` informs us how many bytes needed to store this tag includes one byte marker if in the long form.
230pub fn (t Tag) tag_size() int {
231 // when number is greater than 31 (0x1f), its need more bytes
232 // to represent this number, includes one byte marker for long form tag
233 len := if t.number < 0x1f { 1 } else { t.bytes_len() + 1 }
234 return len
235}
236
237// to_bytes_in_base128 serializes tag number into bytes in base 128
238fn (t Tag) to_bytes_in_base128(mut dst []u8) ! {
239 n := t.bytes_len()
240 for i := n - 1; i >= 0; i-- {
241 mut o := u8(t.number >> u32(i * 7))
242 o &= 0x7f
243 if i != 0 {
244 o |= 0x80
245 }
246 dst << o
247 }
248}
249
250// Tag.read_tagnum read the tag number from bytes from offset pos in base 128.
251// Its return deserialized Tag number and next offset to process on.
252fn Tag.read_tagnum(bytes []u8, pos int) !(int, int) {
253 tnum, next := Tag.read_tagnum_with_rule(bytes, pos, .der)!
254 return tnum, next
255}
256
257// read_tagnum_with_rule is the main routine to read the tag number part in the bytes source,
258// start from offset loc in base 128. Its return the tag number and next offset to process on, or error on fails.
259fn Tag.read_tagnum_with_rule(bytes []u8, loc int, rule EncodingRule) !(int, int) {
260 if loc > bytes.len {
261 return error('Tag number: invalid pos')
262 }
263 mut pos := loc
264 mut ret := 0
265 for s := 0; pos < bytes.len; s++ {
266 ret <<= 7
267 b := bytes[pos]
268
269 if s == 0 && b == 0x80 {
270 if rule == .der {
271 // requirement for DER encoding
272 return error('Tag number: integer is not minimally encoded')
273 }
274 }
275 ret |= b & 0x7f
276 pos += 1
277
278 if b & 0x80 == 0 {
279 if ret > max_tag_number {
280 return error('Tag number: base 128 integer too large')
281 }
282 if ret < 0 {
283 return error('Negative tag number')
284 }
285
286 return ret, pos
287 }
288 }
289 return error('Tag: truncated base 128 integer')
290}
291
292fn (t Tag) valid_supported_universal_tagnum() bool {
293 return t.class == .universal && t.number < max_universal_tagnumber
294}
295
296// `universal_tag_type` turns this Tag into available UNIVERSAL class of TagType,
297// or return error if it is unknown number.
298pub fn (t Tag) universal_tag_type() !TagType {
299 // currently, only support Standard universal tag number
300 if t.number > max_universal_tagnumber {
301 return error('Tag number: unknown TagType number=${t.number}')
302 }
303 match t.class {
304 .universal {
305 match t.number {
306 // vfmt off
307 0 { return .reserved }
308 1 { return .boolean }
309 2 { return .integer }
310 3 { return .bitstring }
311 4 { return .octetstring }
312 5 { return .null }
313 6 { return .oid }
314 7 { return .objdesc }
315 8 { return .external }
316 9 { return .real }
317 10 { return .enumerated }
318 11 { return .embedded }
319 12 { return .utf8string }
320 13 { return .relativeoid }
321 14 { return .time }
322 16 { return .sequence }
323 17 { return .set }
324 18 { return .numericstring }
325 19 { return .printablestring }
326 20 { return .t61string }
327 21 { return .videotexstring }
328 22 { return .ia5string }
329 23 { return .utctime }
330 24 { return .generalizedtime }
331 25 { return .graphicstring }
332 26 { return .visiblestring }
333 27 { return .generalstring }
334 28 { return .universalstring }
335 29 { return .characterstring }
336 30 { return .bmpstring }
337 31 { return .date }
338 32 { return .time_of_day }
339 33 { return .date_time }
340 34 { return .duration }
341 35 { return .i18_oid }
342 36 { return .relative_i18_oid }
343 else {
344 return error('reserved or unknonw number')
345 }
346 // vfmt on
347 }
348 }
349 else {
350 return error('Not universal class type')
351 }
352 }
353}
354
355// TagClass is an enum of ASN.1 tag class.
356//
357// To make sure ASN.1 encodings are not ambiguous, every ASN.1 type is associated with a tag.
358// A tag consists of three parts: the tag class, tag form and the tag number.
359// The following classes are defined in the ASN.1 standard.
360pub enum TagClass {
361 universal = 0x00 // 0b00
362 application = 0x01 // 0b01
363 context_specific = 0x02 // 0b10
364 private = 0x03 // 0b11
365}
366
367// `TagClass.from_int` creates TagClass from integer v.
368// Its return a new TagClass on success or error on fails.
369fn TagClass.from_int(v int) !TagClass {
370 match v {
371 // vfmt off
372 0x00 { return .universal }
373 0x01 { return .application }
374 0x02 { return .context_specific }
375 0x03 { return .private }
376 else {
377 return error('Bad class number')
378 }
379 // vfmt on
380 }
381}
382
383// `TagClass.from_string` creates a TagClass from string s.
384// Its return a new TagClass on success or error on fails.
385fn TagClass.from_string(s string) !TagClass {
386 match s {
387 // vfmt off
388 'universal' { return .universal }
389 'private' { return .private }
390 'application' { return .application }
391 'context_specific' { return .context_specific }
392 else {
393 return error('bad class string')
394 }
395 // vfmt on
396 }
397}
398
399fn (c TagClass) str() string {
400 match c {
401 .universal { return 'universal' }
402 .application { return 'application' }
403 .context_specific { return 'context_specific' }
404 .private { return 'private' }
405 }
406}
407
408// TagType is standard UNIVERSAL tag number. Some of them was deprecated,
409// so its not going to be supported on this module.
410pub enum TagType {
411 // vfmt off
412 reserved = 0 // reserved for BER
413 boolean = 1 // BOOLEAN type
414 integer = 2 // INTEGER type
415 bitstring = 3 // BIT STRING
416 octetstring = 4 // OCTET STRING
417 null = 5 // NULL
418 oid = 6 // OBJECT IDENTIFIER
419 objdesc = 7 // OBJECT DESCRIPTOR
420 external = 8 // INSTANCE OF, EXTERNAL
421 real = 9 // REAL
422 enumerated = 10 // ENUMERATED
423 embedded = 11 // EMBEDDED PDV
424 utf8string = 12 // UTF8STRING
425 relativeoid = 13 // RELATIVE-OID
426 // deprecated
427 time = 14
428 // 0x0f is reserved
429 sequence = 16 // SEQUENCE, SEQUENCE OF, Constructed
430 set = 17 // SET, SET OF, Constructed
431 numericstring = 18 // NUMERICSTRING
432 printablestring = 19 // PRINTABLESTRING
433 t61string = 20 // TELETEXSTRING, T61STRING
434 videotexstring = 21 // VIDEOTEXSTRING
435 ia5string = 22 // IA5STRING
436 utctime = 23 // UTCTIME
437 generalizedtime = 24 // GENERALIZEDTIME
438 graphicstring = 25 // GRAPHICSTRING
439 visiblestring = 26 // VISIBLESTRING, ISO646STRING
440 generalstring = 27 // GENERALSTRING
441 universalstring = 28 // UNIVERSALSTRING
442 characterstring = 29 // CHARACTER STRING
443 bmpstring = 30 // BMPSTRING
444 // unsupported
445 date = 0x1f
446 time_of_day = 0x20
447 date_time = 0x21
448 duration = 0x22
449 // Internationalized OID
450 i18_oid = 0x23
451 // Internationalized Relative OID
452 // Reserved 0x25 and above
453 relative_i18_oid = 0x24
454 // vfmt on
455}
456
457fn (t TagType) str() string {
458 match t {
459 .boolean { return 'BOOLEAN' }
460 .integer { return 'INTEGER' }
461 .bitstring { return 'BITSTRING' }
462 .octetstring { return 'OCTETSTRING' }
463 .null { return 'NULL' }
464 .oid { return 'OID' }
465 .objdesc { return 'OBJECT_DESCRIPTOR' }
466 .external { return 'EXTERNAL' }
467 .real { return 'REAL' }
468 .enumerated { return 'ENUMERATED' }
469 .embedded { return 'EMBEDDED' }
470 .utf8string { return 'UTF8STRING' }
471 .relativeoid { return 'RELATIVEOID' }
472 .time { return 'TIME' }
473 .sequence { return 'SEQUENCE_OR_SEQUENCEOF' }
474 .set { return 'SET_OR_SET_OF' }
475 .numericstring { return 'NUMERICSTRING' }
476 .printablestring { return 'PRINTABLESTRING' }
477 .t61string { return 'T61STRING' }
478 .videotexstring { return 'VIDEOTEXSTRING' }
479 .ia5string { return 'IA5STRING' }
480 .utctime { return 'UTCTIME' }
481 .generalizedtime { return 'GENERALIZEDTIME' }
482 .graphicstring { return 'GRAPHICSTRING' }
483 .visiblestring { return 'VISIBLESTRING' }
484 .generalstring { return 'GENERALSTRING' }
485 .universalstring { return 'UNIVERSALSTRING' }
486 .characterstring { return 'CHARACTERSTRING' }
487 .bmpstring { return 'BMPSTRING' }
488 else { return 'UNSUPPORTED_TAG_TYPE' }
489 }
490}
491
492// universal_tag_from_int gets default Tag for universal class type from integer value v.
493fn universal_tag_from_int(v int) !Tag {
494 if v < 0 || v > max_universal_tagnumber {
495 return error('Get unexpected tag number for universal type')
496 }
497 match v {
498 // vfmt off
499 1 { return default_boolean_tag }
500 2 { return default_integer_tag }
501 3 { return default_bitstring_tag }
502 4 { return default_octetstring_tag }
503 5 { return default_null_tag }
504 6 { return default_oid_tag }
505 10 { return default_enumerated_tag }
506 12 { return default_utf8string_tag }
507 16 { return default_sequence_tag }
508 17 { return default_set_tag }
509 18 { return default_numericstring_tag }
510 19 { return default_printablestring_tag }
511 22 { return default_ia5string_tag }
512 23 { return default_utctime_tag }
513 24 { return default_generalizedtime_tag }
514 26 { return default_visiblestring_tag }
515 else {
516 // should in primitive form
517 return Tag{.universal, false, v}
518 }
519 // vfmt on
520 }
521}
522
523// EncodingRule is standard of rule thats drives how some ASN.1
524// element was encoded or deserialized.
525pub enum EncodingRule {
526 // Distinguished Encoding Rules (DER)
527 der = 0
528 // Basic Encoding Rules (BER)
529 ber = 1
530 // Octet Encoding Rules (OER)
531 oer = 2
532 // Packed Encoding Rules (PER)
533 per = 3
534 // XML Encoding Rules (XER)
535 xer = 4
536}
537
538// EXPLICIT and IMPLICIT MODE
539//
540// TaggedMode is way of rule how some element is being wrapped (unwrapped).
541// Explicit rule add new (outer) tag to the existing element,
542// where implicit rule replaces the tag of existing element.
543pub enum TaggedMode {
544 explicit
545 implicit
546}
547
548// `TaggedMode.from_string` creates TaggedMode from string s.
549fn TaggedMode.from_string(s string) !TaggedMode {
550 match s {
551 'explicit' { return .explicit }
552 'implicit' { return .implicit }
553 // empty string marked as .explicit
554 '' { return .explicit }
555 else { return error('Bad string for tagged mode') }
556 }
557}
558
559fn (m TaggedMode) str() string {
560 match m {
561 .explicit { return 'explicit' }
562 .implicit { return 'implicit' }
563 }
564}
565