mirror of
https://github.com/odin-lang/Odin.git
synced 2026-07-27 09:36:21 +00:00
asn1 DER reader/writer and X.509 reader
This commit is contained in:
72
core/encoding/asn1/asn1.odin
Normal file
72
core/encoding/asn1/asn1.odin
Normal file
@@ -0,0 +1,72 @@
|
||||
package asn1
|
||||
|
||||
// Tag class from the identifier octet (X.690 section 8.1.2.2).
|
||||
Class :: enum u8 {
|
||||
Universal = 0,
|
||||
Application = 1,
|
||||
Context_Specific = 2,
|
||||
Private = 3,
|
||||
}
|
||||
|
||||
// Tag_Number enumerates the UNIVERSAL tag numbers relevant to DER as
|
||||
// used by PKIX. Context-specific tags carry their number directly in
|
||||
// Tag.number.
|
||||
Tag_Number :: enum u32 {
|
||||
Boolean = 1,
|
||||
Integer = 2,
|
||||
Bit_String = 3,
|
||||
Octet_String = 4,
|
||||
Null = 5,
|
||||
Object_Identifier = 6,
|
||||
Enumerated = 10,
|
||||
UTF8_String = 12,
|
||||
Sequence = 16,
|
||||
Set = 17,
|
||||
Numeric_String = 18,
|
||||
Printable_String = 19,
|
||||
Teletex_String = 20,
|
||||
IA5_String = 22,
|
||||
UTC_Time = 23,
|
||||
Generalized_Time = 24,
|
||||
Visible_String = 26,
|
||||
BMP_String = 30,
|
||||
}
|
||||
|
||||
// Tag is a decoded identifier octet (plus high-tag-number form).
|
||||
Tag :: struct {
|
||||
class: Class,
|
||||
constructed: bool,
|
||||
number: u32,
|
||||
}
|
||||
|
||||
Error :: enum {
|
||||
None,
|
||||
Truncated, // The element (or its header) extends past the end of the input.
|
||||
Invalid_Tag, // Malformed identifier octets (non-minimal high-tag-number form, or a tag number that overflows u32).
|
||||
Invalid_Length, // Indefinite length, non-minimal length encoding, or a length beyond what this implementation supports.
|
||||
Unexpected_Tag, // An expect_*/read_* procedure found a different tag than required.
|
||||
Invalid_Boolean, // BOOLEAN content was not exactly one octet of 0x00 or 0xFF.
|
||||
Invalid_Integer, // INTEGER content was empty or not minimally encoded.
|
||||
Integer_Overflow, // INTEGER does not fit the requested machine type.
|
||||
Negative_Integer, // INTEGER was negative where an unsigned value is required.
|
||||
Invalid_Bit_String, // BIT STRING content violated X.690 sections 8.6/11.2 (bad unused-bit count, non-zero padding bits, or unused bits where none are permitted).
|
||||
Invalid_Null, // NULL with non-empty content.
|
||||
Invalid_Object_Identifier, // OBJECT IDENTIFIER content was empty or not minimally encoded.
|
||||
Invalid_Time, // UTCTime/GeneralizedTime outside the RFC 5280 DER profile (YYMMDDHHMMSSZ / YYYYMMDDHHMMSSZ, Zulu only, seconds present, no fractional seconds), or an impossible date.
|
||||
Leftover_Bytes, // done() was called with input remaining.
|
||||
// An OBJECT IDENTIFIER arc exceeds u64. Arc magnitude is unbounded per X.660 (e.g. 2.25 UUID-derived OIDs carry a 128-bit arc), so
|
||||
// this is a representation limit of oid_components/oid_to_string, not a malformed input; compare such OIDs by their raw bytes.
|
||||
Arc_Overflow,
|
||||
Allocation_Failed, // OOM
|
||||
Buffer_Too_Small, // encode: the destination slice is shorter than encoded_len.
|
||||
}
|
||||
|
||||
// universal builds a UNIVERSAL-class tag.
|
||||
universal :: proc "contextless" (number: Tag_Number, constructed := false) -> Tag {
|
||||
return Tag{class = .Universal, constructed = constructed, number = u32(number)}
|
||||
}
|
||||
|
||||
// context_specific builds a CONTEXT-SPECIFIC-class tag ("[n]").
|
||||
context_specific :: proc "contextless" (number: u32, constructed := true) -> Tag {
|
||||
return Tag{class = .Context_Specific, constructed = constructed, number = number}
|
||||
}
|
||||
624
core/encoding/asn1/cursor.odin
Normal file
624
core/encoding/asn1/cursor.odin
Normal file
@@ -0,0 +1,624 @@
|
||||
package asn1
|
||||
|
||||
import "core:strings"
|
||||
import dt "core:time"
|
||||
|
||||
// Cursor is a position into DER input, advanced by the read_* procs.
|
||||
Cursor :: struct {
|
||||
data: []byte,
|
||||
pos: int,
|
||||
}
|
||||
|
||||
// Points a Cursor at `data` and rewinds it to the start.
|
||||
cursor_init :: proc "contextless" (r: ^Cursor, data: []byte) {
|
||||
r.data = data
|
||||
r.pos = 0
|
||||
}
|
||||
|
||||
// Returns the number of unconsumed bytes.
|
||||
remaining :: proc "contextless" (r: ^Cursor) -> int {
|
||||
return len(r.data) - r.pos
|
||||
}
|
||||
|
||||
// Reports whether the Cursor has been fully consumed.
|
||||
is_empty :: proc "contextless" (r: ^Cursor) -> bool {
|
||||
return r.pos >= len(r.data)
|
||||
}
|
||||
|
||||
// Returns Leftover_Bytes if input remains. DER structures are exact: every SEQUENCE walk should end with done() on its sub-cursor.
|
||||
done :: proc "contextless" (r: ^Cursor) -> Error {
|
||||
if r.pos < len(r.data) {
|
||||
return .Leftover_Bytes
|
||||
}
|
||||
return .None
|
||||
}
|
||||
|
||||
// Reads one complete TLV element of any tag, returning the tag and a view of the content octets.
|
||||
read_any :: proc "contextless" (r: ^Cursor) -> (tag: Tag, content: []byte, err: Error) {
|
||||
tag, err = _read_tag(r)
|
||||
if err != .None {
|
||||
return
|
||||
}
|
||||
length: int
|
||||
length, err = _read_length(r)
|
||||
if err != .None {
|
||||
return
|
||||
}
|
||||
if length > remaining(r) {
|
||||
err = .Truncated
|
||||
return
|
||||
}
|
||||
content = r.data[r.pos:r.pos + length]
|
||||
r.pos += length
|
||||
return
|
||||
}
|
||||
|
||||
// Decodes the next element's tag without consuming anything.
|
||||
peek_tag :: proc "contextless" (r: ^Cursor) -> (tag: Tag, err: Error) {
|
||||
tmp := r^
|
||||
return _read_tag(&tmp)
|
||||
}
|
||||
|
||||
// Consumes one complete element of any tag.
|
||||
skip :: proc "contextless" (r: ^Cursor) -> Error {
|
||||
_, _, err := read_any(r)
|
||||
return err
|
||||
}
|
||||
|
||||
// Reads one element and requires its tag to match exactly.
|
||||
expect :: proc "contextless" (r: ^Cursor, tag: Tag) -> (content: []byte, err: Error) {
|
||||
got: Tag
|
||||
got, content, err = read_any(r)
|
||||
if err != .None {
|
||||
return
|
||||
}
|
||||
if got != tag {
|
||||
err = .Unexpected_Tag
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Enters a SEQUENCE, returning a sub-cursor over its content.
|
||||
read_sequence :: proc "contextless" (r: ^Cursor) -> (seq: Cursor, err: Error) {
|
||||
content, eerr := expect(r, universal(.Sequence, true))
|
||||
if eerr != .None {
|
||||
return {}, eerr
|
||||
}
|
||||
return Cursor{data = content}, .None
|
||||
}
|
||||
|
||||
// Enters a SET, returning a sub-cursor over its content.
|
||||
// NOTE: DER requires SET OF contents to be sorted; this cursor does
|
||||
// not verify ordering, consumers that care (none in PKIX cert parsing) must check.
|
||||
read_set :: proc "contextless" (r: ^Cursor) -> (set: Cursor, err: Error) {
|
||||
content, eerr := expect(r, universal(.Set, true))
|
||||
if eerr != .None {
|
||||
return {}, eerr
|
||||
}
|
||||
return Cursor{data = content}, .None
|
||||
}
|
||||
|
||||
// Handles `[number] EXPLICIT ... OPTIONAL`: if the next element is the given
|
||||
// constructed context-specific tag, it is consumed and a sub-cursor over its
|
||||
// content returned with present=true. Otherwise nothing is consumed.
|
||||
read_explicit :: proc "contextless" (r: ^Cursor, number: u32) -> (inner: Cursor, present: bool, err: Error) {
|
||||
if is_empty(r) {
|
||||
return {}, false, .None
|
||||
}
|
||||
tag, perr := peek_tag(r)
|
||||
if perr != .None {
|
||||
return {}, false, perr
|
||||
}
|
||||
if tag != context_specific(number, true) {
|
||||
return {}, false, .None
|
||||
}
|
||||
content, eerr := expect(r, tag)
|
||||
if eerr != .None {
|
||||
return {}, false, eerr
|
||||
}
|
||||
return Cursor{data = content}, true, .None
|
||||
}
|
||||
|
||||
// Reads a BOOLEAN. DER: exactly one octet, 0x00 or 0xFF.
|
||||
read_boolean :: proc "contextless" (r: ^Cursor) -> (value: bool, err: Error) {
|
||||
content, eerr := expect(r, universal(.Boolean))
|
||||
if eerr != .None {
|
||||
return false, eerr
|
||||
}
|
||||
if len(content) != 1 {
|
||||
return false, .Invalid_Boolean
|
||||
}
|
||||
switch content[0] {
|
||||
case 0x00:
|
||||
return false, .None
|
||||
case 0xFF:
|
||||
return true, .None
|
||||
}
|
||||
return false, .Invalid_Boolean
|
||||
}
|
||||
|
||||
// Reads an INTEGER and returns the validated, minimally-encoded two's-complement content octets.
|
||||
read_integer_bytes :: proc "contextless" (r: ^Cursor) -> (content: []byte, err: Error) {
|
||||
content, err = expect(r, universal(.Integer))
|
||||
if err != .None {
|
||||
return
|
||||
}
|
||||
err = _check_integer(content)
|
||||
return
|
||||
}
|
||||
|
||||
// Reads an INTEGER that must fit in an i64.
|
||||
read_i64 :: proc "contextless" (r: ^Cursor) -> (value: i64, err: Error) {
|
||||
content, ierr := read_integer_bytes(r)
|
||||
if ierr != .None {
|
||||
return 0, ierr
|
||||
}
|
||||
if len(content) > 8 {
|
||||
return 0, .Integer_Overflow
|
||||
}
|
||||
if content[0] & 0x80 != 0 {
|
||||
value = -1 // sign-extend
|
||||
}
|
||||
for b in content {
|
||||
value = value << 8 | i64(b)
|
||||
}
|
||||
return value, .None
|
||||
}
|
||||
|
||||
// Reads a non-negative INTEGER and returns its magnitude octets with any leading 0x00
|
||||
// sign octet stripped, the shape RSA moduli, public exponents, and certificate serials
|
||||
// are consumed in.
|
||||
read_unsigned_integer_bytes :: proc "contextless" (r: ^Cursor) -> (magnitude: []byte, err: Error) {
|
||||
content, ierr := read_integer_bytes(r)
|
||||
if ierr != .None {
|
||||
return nil, ierr
|
||||
}
|
||||
if content[0] & 0x80 != 0 {
|
||||
return nil, .Negative_Integer
|
||||
}
|
||||
if len(content) > 1 && content[0] == 0x00 {
|
||||
content = content[1:]
|
||||
}
|
||||
return content, .None
|
||||
}
|
||||
|
||||
// Reads a BIT STRING, returning the payload octets and the count of unused trailing bits
|
||||
// in the final octet. DER: primitive form only, unused count 0..7 (0 if the payload is
|
||||
// empty), and the unused bits themselves must be zero.
|
||||
read_bit_string :: proc "contextless" (r: ^Cursor) -> (bits: []byte, unused: int, err: Error) {
|
||||
content, eerr := expect(r, universal(.Bit_String))
|
||||
if eerr != .None {
|
||||
return nil, 0, eerr
|
||||
}
|
||||
if len(content) < 1 {
|
||||
return nil, 0, .Invalid_Bit_String
|
||||
}
|
||||
unused = int(content[0])
|
||||
bits = content[1:]
|
||||
if unused > 7 {
|
||||
return nil, 0, .Invalid_Bit_String
|
||||
}
|
||||
if len(bits) == 0 && unused != 0 {
|
||||
return nil, 0, .Invalid_Bit_String
|
||||
}
|
||||
if unused > 0 {
|
||||
mask := byte(1 << uint(unused)) - 1
|
||||
if bits[len(bits) - 1] & mask != 0 {
|
||||
return nil, 0, .Invalid_Bit_String
|
||||
}
|
||||
}
|
||||
return bits, unused, .None
|
||||
}
|
||||
|
||||
// Reads a BIT STRING that must be a whole number of octets (unused == 0),
|
||||
// the only form PKIX uses for SubjectPublicKeyInfo keys and signature values.
|
||||
read_bit_string_octets :: proc "contextless" (r: ^Cursor) -> (octets: []byte, err: Error) {
|
||||
bits, unused, berr := read_bit_string(r)
|
||||
if berr != .None {
|
||||
return nil, berr
|
||||
}
|
||||
if unused != 0 {
|
||||
return nil, .Invalid_Bit_String
|
||||
}
|
||||
return bits, .None
|
||||
}
|
||||
|
||||
// Reads an OCTET STRING (primitive form only).
|
||||
read_octet_string :: proc "contextless" (r: ^Cursor) -> (octets: []byte, err: Error) {
|
||||
return expect(r, universal(.Octet_String))
|
||||
}
|
||||
|
||||
// Reads a NULL (content must be empty).
|
||||
read_null :: proc "contextless" (r: ^Cursor) -> Error {
|
||||
content, err := expect(r, universal(.Null))
|
||||
if err != .None {
|
||||
return err
|
||||
}
|
||||
if len(content) != 0 {
|
||||
return .Invalid_Null
|
||||
}
|
||||
return .None
|
||||
}
|
||||
|
||||
// Reads an OBJECT IDENTIFIER and returns a view of its content octets,
|
||||
// validated for minimal base-128 encoding. The validation is structural
|
||||
// only: arc magnitude is unbounded per X.660, so PKIX consumers should
|
||||
// compare these bytes directly against known-OID constants.
|
||||
// oid_components/oid_to_string decode arcs when needed, reporting
|
||||
// Arc_Overflow for arcs beyond u64.
|
||||
read_oid :: proc "contextless" (r: ^Cursor) -> (raw: []byte, err: Error) {
|
||||
raw, err = expect(r, universal(.Object_Identifier))
|
||||
if err != .None {
|
||||
return
|
||||
}
|
||||
if len(raw) == 0 {
|
||||
return nil, .Invalid_Object_Identifier
|
||||
}
|
||||
// Validate: each subidentifier is base-128 with minimal encoding
|
||||
// (no 0x80 lead octet) and terminates (last octet has bit 8 clear).
|
||||
expect_start := true
|
||||
for b in raw {
|
||||
if expect_start && b == 0x80 {
|
||||
return nil, .Invalid_Object_Identifier
|
||||
}
|
||||
expect_start = b & 0x80 == 0
|
||||
}
|
||||
if !expect_start {
|
||||
return nil, .Invalid_Object_Identifier
|
||||
}
|
||||
return raw, .None
|
||||
}
|
||||
|
||||
// Times are returned as core:time.Time. time.Time is i64 nanoseconds
|
||||
// and so tops out near year 2262, while UTCTime/GeneralizedTime reach
|
||||
// year 9999; dates beyond what time.Time can hold (notably RFC 5280's
|
||||
// "99991231235959Z" no-well-defined-expiration sentinel) saturate to
|
||||
// time.Time's bound rather than erroring, so a far-future cert still
|
||||
// parses and reads as "effectively never expires". See _time_from_unix.
|
||||
|
||||
// read_utc_time reads a UTCTime in the RFC 5280 DER profile:
|
||||
// "YYMMDDHHMMSSZ", with the sliding century window (00-49 → 20xx,
|
||||
// 50-99 → 19xx).
|
||||
read_utc_time :: proc "contextless" (r: ^Cursor) -> (value: dt.Time, err: Error) {
|
||||
content, eerr := expect(r, universal(.UTC_Time))
|
||||
if eerr != .None {
|
||||
return {}, eerr
|
||||
}
|
||||
if len(content) != 13 || content[12] != 'Z' {
|
||||
return {}, .Invalid_Time
|
||||
}
|
||||
yy, ok := _two_digits(content[0:2])
|
||||
if !ok {
|
||||
return {}, .Invalid_Time
|
||||
}
|
||||
year := 2000 + yy
|
||||
if yy >= 50 {
|
||||
year = 1900 + yy
|
||||
}
|
||||
secs := _unix_from_fields(year, content[2:12]) or_return
|
||||
return _time_from_unix(secs), .None
|
||||
}
|
||||
|
||||
// Reads a GeneralizedTime in the RFC 5280 DER profile: "YYYYMMDDHHMMSSZ", Zulu only, no fractional seconds.
|
||||
read_generalized_time :: proc "contextless" (r: ^Cursor) -> (value: dt.Time, err: Error) {
|
||||
content, eerr := expect(r, universal(.Generalized_Time))
|
||||
if eerr != .None {
|
||||
return {}, eerr
|
||||
}
|
||||
if len(content) != 15 || content[14] != 'Z' {
|
||||
return {}, .Invalid_Time
|
||||
}
|
||||
hi, ok1 := _two_digits(content[0:2])
|
||||
lo, ok2 := _two_digits(content[2:4])
|
||||
if !ok1 || !ok2 {
|
||||
return {}, .Invalid_Time
|
||||
}
|
||||
secs := _unix_from_fields(hi * 100 + lo, content[4:14]) or_return
|
||||
return _time_from_unix(secs), .None
|
||||
}
|
||||
|
||||
// Reads either time form, PKIX Validity uses UTCTime for dates through 2049 and GeneralizedTime from 2050 on.
|
||||
read_time :: proc "contextless" (r: ^Cursor) -> (value: dt.Time, err: Error) {
|
||||
tag, perr := peek_tag(r)
|
||||
if perr != .None {
|
||||
return {}, perr
|
||||
}
|
||||
if tag == universal(.Generalized_Time) {
|
||||
return read_generalized_time(r)
|
||||
}
|
||||
return read_utc_time(r)
|
||||
}
|
||||
|
||||
// OBJECT IDENTIFIER helpers (allocating).
|
||||
|
||||
// Decodes validated OID content octets (from read_oid) into their integer arcs,
|
||||
// e.g. {1, 2, 840, 113549, 1, 1, 1}. Arcs beyond u64 (legal per X.660, see
|
||||
// Arc_Overflow) are not representable; compare such OIDs by their raw bytes instead.
|
||||
oid_components :: proc(raw: []byte, allocator := context.allocator) -> (arcs: []u64, err: Error) {
|
||||
if len(raw) == 0 {
|
||||
return nil, .Invalid_Object_Identifier
|
||||
}
|
||||
count := 1 // the first octet encodes two arcs
|
||||
for b in raw {
|
||||
if b & 0x80 == 0 {
|
||||
count += 1
|
||||
}
|
||||
}
|
||||
|
||||
out, merr := make([]u64, count, allocator)
|
||||
if merr != nil {
|
||||
return nil, .Allocation_Failed
|
||||
}
|
||||
idx := 0
|
||||
acc: u64 = 0
|
||||
first := true
|
||||
for b in raw {
|
||||
if acc > max(u64) >> 7 {
|
||||
delete(out, allocator)
|
||||
return nil, .Arc_Overflow
|
||||
}
|
||||
acc = acc << 7 | u64(b & 0x7F)
|
||||
if b & 0x80 != 0 {
|
||||
continue
|
||||
}
|
||||
if first {
|
||||
// X.690 section 8.19.4: the first subidentifier encodes the first
|
||||
// two arcs as arc1*40 + arc2 (arc1 limited to 0..2; arc2
|
||||
// unbounded only when arc1 == 2).
|
||||
switch {
|
||||
case acc < 40:
|
||||
out[idx] = 0
|
||||
out[idx + 1] = acc
|
||||
case acc < 80:
|
||||
out[idx] = 1
|
||||
out[idx + 1] = acc - 40
|
||||
case:
|
||||
out[idx] = 2
|
||||
out[idx + 1] = acc - 80
|
||||
}
|
||||
idx += 2
|
||||
first = false
|
||||
} else {
|
||||
out[idx] = acc
|
||||
idx += 1
|
||||
}
|
||||
acc = 0
|
||||
}
|
||||
return out, .None
|
||||
}
|
||||
|
||||
// Renders OID content octets in dotted-decimal form ("1.2.840.113549.1.1.1") for diagnostics.
|
||||
// The arcs are streamed directly into the result; the only allocation is the returned string.
|
||||
oid_to_string :: proc(raw: []byte, allocator := context.allocator) -> (str: string, err: Error) {
|
||||
if len(raw) == 0 {
|
||||
return "", .Invalid_Object_Identifier
|
||||
}
|
||||
|
||||
sb: strings.Builder
|
||||
if _, berr := strings.builder_init(&sb, allocator); berr != nil {
|
||||
return "", .Allocation_Failed
|
||||
}
|
||||
defer if err != .None {
|
||||
strings.builder_destroy(&sb)
|
||||
}
|
||||
|
||||
// Builder writes swallow allocator failures, so tally the written
|
||||
// vs expected lengths and treat any shortfall as an allocation
|
||||
// failure (the same defense pem.encode uses).
|
||||
written, expected := 0, 0
|
||||
acc: u64 = 0
|
||||
first := true
|
||||
for b in raw {
|
||||
if acc > max(u64) >> 7 {
|
||||
err = .Arc_Overflow
|
||||
return "", err
|
||||
}
|
||||
acc = acc << 7 | u64(b & 0x7F)
|
||||
if b & 0x80 != 0 {
|
||||
continue
|
||||
}
|
||||
if first {
|
||||
// See oid_components for the X.690 section 8.19.4 split of the first subidentifier.
|
||||
arc1, arc2: u64
|
||||
switch {
|
||||
case acc < 40:
|
||||
arc1, arc2 = 0, acc
|
||||
case acc < 80:
|
||||
arc1, arc2 = 1, acc - 40
|
||||
case:
|
||||
arc1, arc2 = 2, acc - 80
|
||||
}
|
||||
written += strings.write_u64(&sb, arc1)
|
||||
written += strings.write_byte(&sb, '.')
|
||||
written += strings.write_u64(&sb, arc2)
|
||||
expected += _decimal_len(arc1) + 1 + _decimal_len(arc2)
|
||||
first = false
|
||||
} else {
|
||||
written += strings.write_byte(&sb, '.')
|
||||
written += strings.write_u64(&sb, acc)
|
||||
expected += 1 + _decimal_len(acc)
|
||||
}
|
||||
acc = 0
|
||||
}
|
||||
if written != expected {
|
||||
err = .Allocation_Failed
|
||||
return "", err
|
||||
}
|
||||
return strings.to_string(sb), .None
|
||||
}
|
||||
|
||||
@(private)
|
||||
_decimal_len :: proc "contextless" (v: u64) -> (n: int) {
|
||||
n = 1
|
||||
x := v
|
||||
for x >= 10 {
|
||||
x /= 10
|
||||
n += 1
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
@(private)
|
||||
_read_tag :: proc "contextless" (r: ^Cursor) -> (tag: Tag, err: Error) {
|
||||
if is_empty(r) {
|
||||
return {}, .Truncated
|
||||
}
|
||||
b := r.data[r.pos]
|
||||
r.pos += 1
|
||||
|
||||
tag.class = Class(b >> 6)
|
||||
tag.constructed = b & 0x20 != 0
|
||||
number := u32(b & 0x1F)
|
||||
|
||||
if number != 0x1F {
|
||||
tag.number = number
|
||||
return tag, .None
|
||||
}
|
||||
|
||||
// High-tag-number form (X.690 section 8.1.2.4): base-128, minimal (first
|
||||
// octet may not be 0x80), and the resulting number must be >= 31.
|
||||
number = 0
|
||||
for i := 0; ; i += 1 {
|
||||
if is_empty(r) {
|
||||
return {}, .Truncated
|
||||
}
|
||||
nb := r.data[r.pos]
|
||||
r.pos += 1
|
||||
if i == 0 && nb == 0x80 {
|
||||
return {}, .Invalid_Tag
|
||||
}
|
||||
if number > (max(u32) >> 7) {
|
||||
return {}, .Invalid_Tag
|
||||
}
|
||||
number = number << 7 | u32(nb & 0x7F)
|
||||
if nb & 0x80 == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if number < 0x1F {
|
||||
return {}, .Invalid_Tag
|
||||
}
|
||||
tag.number = number
|
||||
return tag, .None
|
||||
}
|
||||
|
||||
@(private)
|
||||
_read_length :: proc "contextless" (r: ^Cursor) -> (length: int, err: Error) {
|
||||
if is_empty(r) {
|
||||
return 0, .Truncated
|
||||
}
|
||||
b := r.data[r.pos]
|
||||
r.pos += 1
|
||||
|
||||
if b & 0x80 == 0 {
|
||||
return int(b), .None
|
||||
}
|
||||
|
||||
n := int(b & 0x7F)
|
||||
if n == 0 {
|
||||
// 0x80: indefinite length, BER only.
|
||||
return 0, .Invalid_Length
|
||||
}
|
||||
if n > 4 {
|
||||
// Lengths beyond 2^31 are not plausible inputs here.
|
||||
return 0, .Invalid_Length
|
||||
}
|
||||
if remaining(r) < n {
|
||||
return 0, .Truncated
|
||||
}
|
||||
|
||||
value := 0
|
||||
for i in 0 ..< n {
|
||||
value = value << 8 | int(r.data[r.pos + i])
|
||||
}
|
||||
r.pos += n
|
||||
|
||||
// DER minimality: no leading zero octet, and the long form may only be used for lengths >= 128.
|
||||
if r.data[r.pos - n] == 0 || value < 0x80 {
|
||||
return 0, .Invalid_Length
|
||||
}
|
||||
if value < 0 {
|
||||
return 0, .Invalid_Length
|
||||
}
|
||||
return value, .None
|
||||
}
|
||||
|
||||
// Enforces X.690 section 8.3: at least one octet, and minimal (the first nine bits may not be all-zero or all-one).
|
||||
@(private)
|
||||
_check_integer :: proc "contextless" (content: []byte) -> Error {
|
||||
switch len(content) {
|
||||
case 0:
|
||||
return .Invalid_Integer
|
||||
case 1:
|
||||
return .None
|
||||
}
|
||||
if content[0] == 0x00 && content[1] & 0x80 == 0 {
|
||||
return .Invalid_Integer
|
||||
}
|
||||
if content[0] == 0xFF && content[1] & 0x80 != 0 {
|
||||
return .Invalid_Integer
|
||||
}
|
||||
return .None
|
||||
}
|
||||
|
||||
@(private)
|
||||
_two_digits :: proc "contextless" (b: []byte) -> (value: int, ok: bool) {
|
||||
if b[0] < '0' || b[0] > '9' || b[1] < '0' || b[1] > '9' {
|
||||
return 0, false
|
||||
}
|
||||
return int(b[0] - '0') * 10 + int(b[1] - '0'), true
|
||||
}
|
||||
|
||||
// Converts Unix seconds to a time.Time, saturating at time.Time's
|
||||
// representable bounds. time.Time counts i64 nanoseconds, so it tops
|
||||
// out near year 2262; a far-future X.509 date (notably RFC 5280's
|
||||
// "99991231235959Z" no-expiration sentinel) saturates to that bound
|
||||
// rather than overflowing, and so reads as "effectively never".
|
||||
@(private)
|
||||
_time_from_unix :: proc "contextless" (secs: i64) -> dt.Time {
|
||||
NS_PER_SEC :: i64(1_000_000_000)
|
||||
if secs > max(i64) / NS_PER_SEC {
|
||||
return dt.Time{_nsec = max(i64)}
|
||||
}
|
||||
if secs < min(i64) / NS_PER_SEC {
|
||||
return dt.Time{_nsec = min(i64)}
|
||||
}
|
||||
return dt.Time{_nsec = secs * NS_PER_SEC}
|
||||
}
|
||||
|
||||
// Converts a year plus "MMDDHHMMSS" into seconds since the Unix epoch,
|
||||
// validating field ranges. Computed directly (via the civil-date algorithm
|
||||
// below) rather than through time.Time so the whole year 1..9999 range is
|
||||
// computable before _time_from_unix decides how to represent it.
|
||||
@(private)
|
||||
_unix_from_fields :: proc "contextless" (year: int, fields: []byte) -> (unix_seconds: i64, err: Error) {
|
||||
month, mo_ok := _two_digits(fields[0:2])
|
||||
day, d_ok := _two_digits(fields[2:4])
|
||||
hour, h_ok := _two_digits(fields[4:6])
|
||||
minute, min_ok := _two_digits(fields[6:8])
|
||||
second, s_ok := _two_digits(fields[8:10])
|
||||
if !mo_ok || !d_ok || !h_ok || !min_ok || !s_ok {
|
||||
return 0, .Invalid_Time
|
||||
}
|
||||
if month < 1 || month > 12 || day < 1 || day > 31 {
|
||||
return 0, .Invalid_Time
|
||||
}
|
||||
if hour > 23 || minute > 59 || second > 59 {
|
||||
return 0, .Invalid_Time
|
||||
}
|
||||
days := _days_from_civil(i64(year), month, day)
|
||||
return days * 86400 + i64(hour) * 3600 + i64(minute) * 60 + i64(second), .None
|
||||
}
|
||||
|
||||
// Returns the number of days since 1970-01-01 for a proleptic-Gregorian date
|
||||
// (Ref: http://howardhinnant.github.io/date_algorithms.html#days_from_civil).
|
||||
// Exact for any representable year; no epoch-range limit.
|
||||
@(private)
|
||||
_days_from_civil :: proc "contextless" (y: i64, m, d: int) -> i64 {
|
||||
yy := y - (m <= 2 ? 1 : 0)
|
||||
era := (yy >= 0 ? yy : yy - 399) / 400
|
||||
yoe := yy - era * 400 // [0, 399]
|
||||
doy := i64((153 * (m + (m > 2 ? -3 : 9)) + 2) / 5 + d - 1) // [0, 365]
|
||||
doe := yoe * 365 + yoe / 4 - yoe / 100 + doy // [0, 146096]
|
||||
return era * 146097 + doe - 719468
|
||||
}
|
||||
29
core/encoding/asn1/doc.odin
Normal file
29
core/encoding/asn1/doc.odin
Normal file
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
Strict DER (Distinguished Encoding Rules) reader and writer for the PKIX
|
||||
subset of ASN.1, the substrate for X.509 certificates and related structures.
|
||||
|
||||
Reader: a `Cursor` over the input; `read_*` procs return VIEWS into it (only
|
||||
`oid_components` / `oid_to_string` take an allocator). The input must outlive
|
||||
the results.
|
||||
|
||||
Writer: build a declarative tree of `Value` nodes with the constructors, then
|
||||
`encoded_len` + `encode` (no allocation, into a caller buffer) or `marshal`
|
||||
(one allocation). Constructors BORROW their inputs, so build and encode
|
||||
within one expression (or back children with a slice that outlives the call);
|
||||
the encoded output is self-contained and aliases nothing.
|
||||
|
||||
Scope & limitations:
|
||||
|
||||
- DER only (no BER/CER); strict (minimal lengths, minimal integers, ...).
|
||||
- PKIX subset: no typed readers for STRING/REAL/... (walk with `read_any`);
|
||||
the writer emits low-tag-number identifiers only (tag number <= 30).
|
||||
|
||||
Times use core:time.Time per the RFC 5280 DER profile (Zulu, seconds present,
|
||||
no fractional seconds). time.Time is i64 nanoseconds (tops out near year 2262)
|
||||
while UTCTime/GeneralizedTime reach 9999; on read, dates beyond that saturate.
|
||||
|
||||
See:
|
||||
- [[ https://www.itu.int/rec/T-REC-X.690 ]]
|
||||
- [[ https://www.rfc-editor.org/rfc/rfc5280 ]]
|
||||
*/
|
||||
package asn1
|
||||
498
core/encoding/asn1/writer.odin
Normal file
498
core/encoding/asn1/writer.odin
Normal file
@@ -0,0 +1,498 @@
|
||||
package asn1
|
||||
|
||||
/*
|
||||
DER (Distinguished Encoding Rules) writer, the inverse of the cursor
|
||||
reader, and the encoding substrate for signatures, keys, and certificates.
|
||||
|
||||
The model is declarative: build a tree of `Value` nodes with the
|
||||
constructors below, then turn it into bytes with `encoded_len` + `encode`
|
||||
(no allocation, into a caller buffer) or `marshal` (one allocation, owned
|
||||
slice). A SEQUENCE/SET simply holds its children, so length is discovered
|
||||
by a measure pass rather than back-patched, and `set` can sort its
|
||||
children (DER SET OF ordering) without disturbing this surface, both are
|
||||
additive.
|
||||
|
||||
Zero-copy, with a lifetime caveat: the constructors BORROW their byte and
|
||||
child inputs (no copies), so a Value tree is only valid while those inputs
|
||||
live. In practice build and encode the tree within one expression, or back
|
||||
its children with a slice/array that outlives the encode call:
|
||||
|
||||
// one expression (inputs r, s outlive the call):
|
||||
out := marshal(sequence({integer_unsigned(r), integer_unsigned(s)})) or_return
|
||||
|
||||
DER is canonical by construction: definite minimal-length headers, minimal
|
||||
INTEGER magnitudes with the sign octet inserted only when required. The
|
||||
writer emits low-tag-number identifiers only (tag number <= 30), which
|
||||
covers all of PKIX; high-tag-number form is a future addition.
|
||||
|
||||
See:
|
||||
- [[ https://www.itu.int/rec/T-REC-X.690 ]]
|
||||
*/
|
||||
|
||||
import dt "core:time"
|
||||
|
||||
// Selects how a Value's content octets are produced when encoding.
|
||||
@(private)
|
||||
_Form :: enum u8 {
|
||||
Primitive, // content holds the exact content octets, emitted verbatim.
|
||||
Constructed, // children holds the sub-values, emitted in order.
|
||||
Integer_Magnitude, // content holds an unsigned big-endian magnitude; DER INTEGER rules are applied on emit.
|
||||
Bit_String_Octets, // content holds whole-octet payload; a leading 0x00 unused-bits count is added on emit.
|
||||
Bit_String_Wrapped, // children's DER becomes the payload, behind the 0x00 unused-bits count.
|
||||
Time, // _when is formatted to UTCTime/GeneralizedTime per the tag on emit.
|
||||
Raw, // content is a complete pre-encoded element, emitted verbatim (no added tag/length).
|
||||
}
|
||||
|
||||
// Value is a node in a to-be-encoded DER tree. Construct it with the
|
||||
// helpers below rather than by hand; the fields are an implementation
|
||||
// detail. The byte/child inputs are borrowed (see the package lifetime note).
|
||||
Value :: struct {
|
||||
tag: Tag,
|
||||
form: _Form,
|
||||
content: []byte,
|
||||
children: []Value,
|
||||
_when: dt.Time, // meaningful only when form == .Time: the instant to format on emit
|
||||
}
|
||||
|
||||
@(rodata, private)
|
||||
_BOOL_FALSE := []byte{0x00}
|
||||
@(rodata, private)
|
||||
_BOOL_TRUE := []byte{0xFF}
|
||||
|
||||
// Builds a primitive value with `tag` and the exact `content`
|
||||
// octets (emitted verbatim). The caller owns canonical-form correctness.
|
||||
primitive :: proc "contextless" (tag: Tag, content: []byte) -> Value {
|
||||
return Value{tag = tag, form = .Primitive, content = content}
|
||||
}
|
||||
|
||||
// Builds a value whose encoding IS `encoded` verbatim, a complete
|
||||
// already-DER-encoded element spliced in as-is (no tag/length added). The
|
||||
// composition primitive for nesting an independently-marshalled structure
|
||||
// (a signed CertificationRequestInfo, a pre-built TBSCertificate) inside a
|
||||
// parent without re-encoding it.
|
||||
raw :: proc "contextless" (encoded: []byte) -> Value {
|
||||
return Value{form = .Raw, content = encoded}
|
||||
}
|
||||
|
||||
// Builds a BOOLEAN (DER: 0x00 / 0xFF).
|
||||
boolean :: proc "contextless" (v: bool) -> Value {
|
||||
return Value{tag = universal(.Boolean), form = .Primitive, content = v ? _BOOL_TRUE : _BOOL_FALSE}
|
||||
}
|
||||
|
||||
// Builds an INTEGER from an unsigned big-endian magnitude: leading zero
|
||||
// octets are dropped (minimal encoding) and a single 0x00 sign octet is
|
||||
// inserted when the top bit would otherwise read as negative. An empty
|
||||
// or all-zero magnitude encodes as 0. This is the shape RSA moduli /
|
||||
// exponents and certificate serials are written in, and the inverse of
|
||||
// read_unsigned_integer_bytes.
|
||||
integer_unsigned :: proc "contextless" (magnitude: []byte) -> Value {
|
||||
return Value{tag = universal(.Integer), form = .Integer_Magnitude, content = magnitude}
|
||||
}
|
||||
|
||||
// Builds an INTEGER from content octets that are ALREADY a minimal
|
||||
// two's-complement encoding (e.g. a serial preserved verbatim from
|
||||
// read_integer_bytes). No normalization is applied.
|
||||
integer_raw :: proc "contextless" (content: []byte) -> Value {
|
||||
return Value{tag = universal(.Integer), form = .Primitive, content = content}
|
||||
}
|
||||
|
||||
// Builds an OCTET STRING wrapping `content`.
|
||||
octet_string :: proc "contextless" (content: []byte) -> Value {
|
||||
return Value{tag = universal(.Octet_String), form = .Primitive, content = content}
|
||||
}
|
||||
|
||||
// Builds an OCTET STRING whose content is the DER encoding of `children`,
|
||||
// the form X.509 Extension.extnValue uses to carry an extension's value. The
|
||||
// OCTET STRING stays primitive (0x04); its content is just the children's
|
||||
// concatenated DER, so this reuses the constructed-content machinery without
|
||||
// a wrapper octet.
|
||||
octet_string_wrap :: proc "contextless" (children: []Value) -> Value {
|
||||
return Value{tag = universal(.Octet_String), form = .Constructed, children = children}
|
||||
}
|
||||
|
||||
// Builds a SEQUENCE from its sub-values (borrowed).
|
||||
sequence :: proc "contextless" (children: []Value) -> Value {
|
||||
return Value{tag = universal(.Sequence, true), form = .Constructed, children = children}
|
||||
}
|
||||
|
||||
// Builds a SET from its sub-values, emitted in the given order. DER SET OF
|
||||
// requires components sorted by their encoding (X.690 section 11.6); this
|
||||
// constructor does NOT sort, so a SET OF with more than one element must be
|
||||
// given pre-sorted (a single-element RDN, the common PKIX case, is trivially
|
||||
// ordered).
|
||||
set :: proc "contextless" (children: []Value) -> Value {
|
||||
return Value{tag = universal(.Set, true), form = .Constructed, children = children}
|
||||
}
|
||||
|
||||
// Builds a SET OF from its sub-values, sorted into the DER canonical order
|
||||
// (X.690 section 11.6: component encodings ascending, shorter padded with
|
||||
// trailing 0-octets). Unlike the other constructors this one ALLOCATES, it
|
||||
// must encode each child to compare them, and it sorts `children` in place;
|
||||
// the returned Value then borrows that reordered slice as usual. Scratch is
|
||||
// taken from and released to `allocator`. 0/1-element sets need no work.
|
||||
@(require_results)
|
||||
set_of :: proc(children: []Value, allocator := context.allocator) -> (value: Value, err: Error) {
|
||||
n := len(children)
|
||||
if n <= 1 {
|
||||
return set(children), .None
|
||||
}
|
||||
encs, merr := make([][]byte, n, allocator)
|
||||
if merr != nil {
|
||||
return {}, .Allocation_Failed
|
||||
}
|
||||
defer {
|
||||
for e in encs {
|
||||
delete(e, allocator)
|
||||
}
|
||||
delete(encs, allocator)
|
||||
}
|
||||
for i in 0 ..< n {
|
||||
encs[i] = marshal(children[i], allocator) or_return
|
||||
}
|
||||
// Insertion sort (n is small for a SET OF) keying children on their encodings.
|
||||
for i in 1 ..< n {
|
||||
for j := i; j > 0 && _der_less(encs[j], encs[j - 1]); j -= 1 {
|
||||
encs[j], encs[j - 1] = encs[j - 1], encs[j]
|
||||
children[j], children[j - 1] = children[j - 1], children[j]
|
||||
}
|
||||
}
|
||||
return set(children), .None
|
||||
}
|
||||
|
||||
// Compares two encodings as octet strings with the shorter padded at its
|
||||
// trailing end with 0-octets (X.690 section 11.6 SET OF ordering).
|
||||
@(private)
|
||||
_der_less :: proc "contextless" (a, b: []byte) -> bool {
|
||||
n := min(len(a), len(b))
|
||||
for i in 0 ..< n {
|
||||
if a[i] != b[i] {
|
||||
return a[i] < b[i]
|
||||
}
|
||||
}
|
||||
if len(a) < len(b) {
|
||||
for i in n ..< len(b) {
|
||||
if b[i] != 0x00 {
|
||||
return true // a's zero padding is below b's non-zero tail
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
for i in n ..< len(a) {
|
||||
if a[i] != 0x00 {
|
||||
return false // a's non-zero tail is above b's zero padding
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Builds a NULL (empty content).
|
||||
null :: proc "contextless" () -> Value {
|
||||
return Value{tag = universal(.Null), form = .Primitive, content = nil}
|
||||
}
|
||||
|
||||
// Builds an OBJECT IDENTIFIER from already-encoded content octets (the form
|
||||
// PKIX OIDs are held and compared in, e.g. the package's known-OID
|
||||
// constants). The content is emitted verbatim; the caller owns its validity.
|
||||
object_identifier :: proc "contextless" (content: []byte) -> Value {
|
||||
return Value{tag = universal(.Object_Identifier), form = .Primitive, content = content}
|
||||
}
|
||||
|
||||
// Builds a BIT STRING from a whole-octet payload (unused-bits count 0), the
|
||||
// only form PKIX uses for SubjectPublicKeyInfo keys and signature values.
|
||||
bit_string_octets :: proc "contextless" (payload: []byte) -> Value {
|
||||
return Value{tag = universal(.Bit_String), form = .Bit_String_Octets, content = payload}
|
||||
}
|
||||
|
||||
// Builds a BIT STRING (whole octets) whose payload is the DER encoding of
|
||||
// `children`, the form SubjectPublicKeyInfo uses to carry an RSAPublicKey
|
||||
// SEQUENCE inside the subjectPublicKey bit string.
|
||||
bit_string_wrap :: proc "contextless" (children: []Value) -> Value {
|
||||
return Value{tag = universal(.Bit_String), form = .Bit_String_Wrapped, children = children}
|
||||
}
|
||||
|
||||
// Builds a UTCTime ("YYMMDDHHMMSSZ", RFC 5280 DER profile). Appropriate for
|
||||
// instants in 1950..=2049; the sliding-window century is what the reader
|
||||
// (read_utc_time) decodes, so use generalized_time outside that range.
|
||||
utc_time :: proc "contextless" (at: dt.Time) -> Value {
|
||||
return Value{tag = universal(.UTC_Time), form = .Time, _when = at}
|
||||
}
|
||||
|
||||
// Builds a GeneralizedTime ("YYYYMMDDHHMMSSZ", RFC 5280 DER profile: Zulu,
|
||||
// seconds present, no fractional part). The inverse of read_generalized_time.
|
||||
generalized_time :: proc "contextless" (at: dt.Time) -> Value {
|
||||
return Value{tag = universal(.Generalized_Time), form = .Time, _when = at}
|
||||
}
|
||||
|
||||
// 2050-01-01T00:00:00Z: the RFC 5280 boundary between the two time forms.
|
||||
@(private)
|
||||
_UNIX_2050 :: i64(2_524_608_000)
|
||||
|
||||
// Builds a UTCTime or GeneralizedTime, auto-selecting the form per the RFC
|
||||
// 5280 profile: UTCTime for instants in 1950..=2049, GeneralizedTime from
|
||||
// 2050 on. (X.509 validity dates are written this way.)
|
||||
time :: proc "contextless" (at: dt.Time) -> Value {
|
||||
if dt.to_unix_seconds(at) < _UNIX_2050 {
|
||||
return utc_time(at)
|
||||
}
|
||||
return generalized_time(at)
|
||||
}
|
||||
|
||||
// Builds a primitive [number] IMPLICIT value carrying raw content octets,
|
||||
// e.g. AuthorityKeyIdentifier's keyIdentifier [0] IMPLICIT OCTET STRING.
|
||||
context_primitive :: proc "contextless" (number: u32, content: []byte) -> Value {
|
||||
return Value{tag = context_specific(number, false), form = .Primitive, content = content}
|
||||
}
|
||||
|
||||
// Builds a constructed [number] EXPLICIT wrapper around the given sub-values,
|
||||
// e.g. TBSCertificate's version [0] EXPLICIT INTEGER.
|
||||
context_explicit :: proc "contextless" (number: u32, children: []Value) -> Value {
|
||||
return Value{tag = context_specific(number, true), form = .Constructed, children = children}
|
||||
}
|
||||
|
||||
// Returns the exact number of bytes encode/marshal will write
|
||||
// for `v`, including its identifier and length octets.
|
||||
encoded_len :: proc(v: Value) -> int {
|
||||
if v.form == .Raw {
|
||||
return len(v.content) // already a complete element
|
||||
}
|
||||
clen := _content_len(v)
|
||||
return _tag_len(v.tag) + _length_len(clen) + clen
|
||||
}
|
||||
|
||||
// Writes the DER encoding of `v` into `dst` and returns the number
|
||||
// of bytes written. `dst` must be at least encoded_len(v) bytes; if it is
|
||||
// shorter, nothing is written and Buffer_Too_Small is returned.
|
||||
encode :: proc(v: Value, dst: []byte) -> (n: int, err: Error) {
|
||||
need := encoded_len(v)
|
||||
if len(dst) < need {
|
||||
return 0, .Buffer_Too_Small
|
||||
}
|
||||
_emit(v, dst[:need])
|
||||
return need, .None
|
||||
}
|
||||
|
||||
// Encodes `v` into a freshly allocated slice the caller owns.
|
||||
marshal :: proc(v: Value, allocator := context.allocator) -> (out: []byte, err: Error) {
|
||||
n := encoded_len(v)
|
||||
buf, merr := make([]byte, n, allocator)
|
||||
if merr != nil {
|
||||
return nil, .Allocation_Failed
|
||||
}
|
||||
_emit(v, buf)
|
||||
return buf, .None
|
||||
}
|
||||
|
||||
@(private)
|
||||
_content_len :: proc(v: Value) -> int {
|
||||
switch v.form {
|
||||
case .Primitive:
|
||||
return len(v.content)
|
||||
case .Integer_Magnitude:
|
||||
start, pad := _int_shape(v.content)
|
||||
if start == len(v.content) {
|
||||
return 1 // zero encodes as a single 0x00 octet
|
||||
}
|
||||
return (len(v.content) - start) + (pad ? 1 : 0)
|
||||
case .Bit_String_Octets:
|
||||
return 1 + len(v.content) // leading unused-bits octet (0x00)
|
||||
case .Bit_String_Wrapped:
|
||||
total := 1 // leading unused-bits octet (0x00)
|
||||
for child in v.children {
|
||||
total += encoded_len(child)
|
||||
}
|
||||
return total
|
||||
case .Time:
|
||||
return _time_content_len(v.tag)
|
||||
case .Constructed:
|
||||
total := 0
|
||||
for child in v.children {
|
||||
total += encoded_len(child)
|
||||
}
|
||||
return total
|
||||
case .Raw:
|
||||
return len(v.content) // handled in encoded_len; here for exhaustiveness
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Writes v's complete encoding so that it ENDS at dst[len(dst)], i.e. into
|
||||
// the tail of dst, and returns the bytes written. `dst` is the exactly-sized
|
||||
// region this node may occupy (encoded_len(v) == len(dst) at the top call).
|
||||
//
|
||||
// Emitting back-to-front means a constructed node's content length falls out
|
||||
// of where its children landed (no second measure pass): encoded_len does the
|
||||
// single O(n) sizing pass, this does the single O(n) write pass. DER wants to
|
||||
// be written this way — it is the tree generalization of the fixed-buffer
|
||||
// trick in crypto/ecdsa's hand-rolled encoder.
|
||||
@(private)
|
||||
_emit :: proc(v: Value, dst: []byte) -> int {
|
||||
if v.form == .Raw {
|
||||
n := len(v.content)
|
||||
copy(dst[len(dst) - n:], v.content)
|
||||
return n
|
||||
}
|
||||
end := len(dst)
|
||||
switch v.form {
|
||||
case .Raw: // handled by the early return above
|
||||
case .Primitive:
|
||||
end -= len(v.content)
|
||||
copy(dst[end:], v.content)
|
||||
case .Integer_Magnitude:
|
||||
start, pad := _int_shape(v.content)
|
||||
if start == len(v.content) {
|
||||
end -= 1
|
||||
dst[end] = 0x00 // zero -> single 0x00 octet
|
||||
} else {
|
||||
body := v.content[start:]
|
||||
end -= len(body)
|
||||
copy(dst[end:], body)
|
||||
if pad {
|
||||
end -= 1
|
||||
dst[end] = 0x00 // sign octet so the value reads as non-negative
|
||||
}
|
||||
}
|
||||
case .Bit_String_Octets:
|
||||
end -= len(v.content)
|
||||
copy(dst[end:], v.content)
|
||||
end -= 1
|
||||
dst[end] = 0x00 // unused-bits count: whole octets
|
||||
case .Bit_String_Wrapped:
|
||||
for i := len(v.children) - 1; i >= 0; i -= 1 {
|
||||
end -= _emit(v.children[i], dst[:end])
|
||||
}
|
||||
end -= 1
|
||||
dst[end] = 0x00 // unused-bits count: whole octets
|
||||
case .Time:
|
||||
tmp: [15]byte // GeneralizedTime is the longest form
|
||||
n := _format_time(tmp[:], v._when, v.tag.number == u32(Tag_Number.Generalized_Time))
|
||||
end -= n
|
||||
copy(dst[end:], tmp[:n])
|
||||
case .Constructed:
|
||||
for i := len(v.children) - 1; i >= 0; i -= 1 {
|
||||
end -= _emit(v.children[i], dst[:end])
|
||||
}
|
||||
}
|
||||
clen := len(dst) - end // content length, read off the cursor — never recomputed
|
||||
|
||||
tmp: [9]byte // identifier byte + up to 8 length octets covers any int length
|
||||
lw := _write_length(tmp[:], clen)
|
||||
end -= lw
|
||||
copy(dst[end:], tmp[:lw])
|
||||
|
||||
end -= 1
|
||||
dst[end] = _tag_byte(v.tag)
|
||||
|
||||
return len(dst) - end
|
||||
}
|
||||
|
||||
// UTCTime content is "YYMMDDHHMMSSZ" (13 octets); GeneralizedTime is
|
||||
// "YYYYMMDDHHMMSSZ" (15). The content length is fixed by the tag, so it is
|
||||
// known for the measure pass without formatting.
|
||||
@(private)
|
||||
_time_content_len :: proc "contextless" (tag: Tag) -> int {
|
||||
return tag.number == u32(Tag_Number.Generalized_Time) ? 15 : 13
|
||||
}
|
||||
|
||||
// Formats `at` into dst as the RFC 5280 DER time profile (Zulu, seconds
|
||||
// present, no fractional part) and returns the bytes written: 15 for
|
||||
// GeneralizedTime ("YYYYMMDDHHMMSSZ"), 13 for UTCTime ("YYMMDDHHMMSSZ", with
|
||||
// the low two year digits). dst must hold the full width.
|
||||
@(private)
|
||||
_format_time :: proc "contextless" (dst: []byte, at: dt.Time, generalized: bool) -> int {
|
||||
// time_to_datetime decomposes the instant into UTC calendar fields; it only
|
||||
// fails outside time.Time's range, which an in-range time.Time can't reach.
|
||||
c, _ := dt.time_to_datetime(at)
|
||||
p := 0
|
||||
if generalized {
|
||||
p += _write_digits(dst[p:], int(c.year), 4)
|
||||
} else {
|
||||
p += _write_digits(dst[p:], int(c.year % 100), 2)
|
||||
}
|
||||
p += _write_digits(dst[p:], int(c.month), 2)
|
||||
p += _write_digits(dst[p:], int(c.day), 2)
|
||||
p += _write_digits(dst[p:], int(c.hour), 2)
|
||||
p += _write_digits(dst[p:], int(c.minute), 2)
|
||||
p += _write_digits(dst[p:], int(c.second), 2)
|
||||
dst[p] = 'Z'
|
||||
p += 1
|
||||
return p
|
||||
}
|
||||
|
||||
// Writes `value` as exactly `width` zero-padded decimal digits.
|
||||
@(private)
|
||||
_write_digits :: proc "contextless" (dst: []byte, value, width: int) -> int {
|
||||
x := value
|
||||
for i := width - 1; i >= 0; i -= 1 {
|
||||
dst[i] = byte('0' + x % 10)
|
||||
x /= 10
|
||||
}
|
||||
return width
|
||||
}
|
||||
|
||||
// Reports the index of the first significant magnitude octet
|
||||
// (== len(mag) when the value is zero) and whether a 0x00 sign octet must
|
||||
// be prepended so the result reads as non-negative.
|
||||
@(private)
|
||||
_int_shape :: proc "contextless" (mag: []byte) -> (start: int, pad: bool) {
|
||||
start = 0
|
||||
for start < len(mag) && mag[start] == 0x00 {
|
||||
start += 1
|
||||
}
|
||||
if start == len(mag) {
|
||||
return start, false
|
||||
}
|
||||
return start, mag[start] & 0x80 != 0
|
||||
}
|
||||
|
||||
// _length_len / _write_length are the inverse of _read_length: definite
|
||||
// form, minimal (short form below 128, otherwise the fewest octets).
|
||||
@(private)
|
||||
_length_len :: proc "contextless" (length: int) -> int {
|
||||
if length < 0x80 {
|
||||
return 1
|
||||
}
|
||||
n := 1
|
||||
v := length
|
||||
for v > 0 {
|
||||
v >>= 8
|
||||
n += 1
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
@(private)
|
||||
_write_length :: proc "contextless" (dst: []byte, length: int) -> int {
|
||||
if length < 0x80 {
|
||||
dst[0] = byte(length)
|
||||
return 1
|
||||
}
|
||||
nbytes := 0
|
||||
v := length
|
||||
for v > 0 {
|
||||
v >>= 8
|
||||
nbytes += 1
|
||||
}
|
||||
dst[0] = 0x80 | byte(nbytes)
|
||||
for i in 0 ..< nbytes {
|
||||
dst[1 + i] = byte(length >> uint(8 * (nbytes - 1 - i)))
|
||||
}
|
||||
return 1 + nbytes
|
||||
}
|
||||
|
||||
// _tag_len / _tag_byte are the inverse of _read_tag, low-tag-number form
|
||||
// only (number <= 30); PKIX never needs high-tag-number identifiers.
|
||||
@(private)
|
||||
_tag_len :: proc "contextless" (tag: Tag) -> int {
|
||||
return 1
|
||||
}
|
||||
|
||||
@(private)
|
||||
_tag_byte :: proc "contextless" (tag: Tag) -> byte {
|
||||
assert_contextless(tag.number <= 30, "asn1: high-tag-number form is not supported by the writer")
|
||||
b := byte(tag.class) << 6
|
||||
if tag.constructed {
|
||||
b |= 0x20
|
||||
}
|
||||
b |= byte(tag.number) & 0x1F
|
||||
return b
|
||||
}
|
||||
Reference in New Issue
Block a user