Files
Odin/core/encoding/asn1/cursor.odin
2026-07-12 12:14:32 -07:00

620 lines
17 KiB
Odin

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.
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).
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.
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 i64-nanosecond
// bounds (near year 2262) rather than overflowing. See the note above
// read_utc_time for why far-future dates are saturated instead of rejected.
@(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
}