asn1.nx source
↩ module page · 193 lines · 7177 B
1// asn1.nx -- ASN.1 DER TLV (Tag-Length-Value) primitives.
2//
3// Distinguished Encoding Rules (ITU-T X.690) reader. DER is the
4// canonical form every X.509 cert uses, every TLS signature, every
5// PKCS#8 key, every CMS message. We implement a reader-only
6// subset -- write-side encoders are future work.
7//
8// DER structure:
9// TLV byte sequence:
10// - Tag: 1-N bytes. Low-tag-number form: 1 byte encoding
11// class (bits 7-6), constructed flag (bit 5), tag (bits 4-0).
12// High-tag-number form (tag >= 31): bits 4-0 = 0x1F, then
13// tag in base-128 continuation bytes. X.509 uses low form
14// exclusively in practice; we support both.
15// - Length: 1-N bytes. Short form (0-127): single byte.
16// Long form: byte 0x80+n indicates n bytes of length follow,
17// big-endian. n <= 126; indefinite-length form (0x80) is
18// explicitly forbidden in DER.
19// - Value: length bytes, content depends on tag.
20//
21// Tags relevant to X.509:
22// 0x02 INTEGER
23// 0x03 BIT STRING
24// 0x04 OCTET STRING
25// 0x05 NULL
26// 0x06 OBJECT IDENTIFIER
27// 0x0C UTF8String
28// 0x13 PrintableString
29// 0x17 UTCTime
30// 0x18 GeneralizedTime
31// 0x30 SEQUENCE (constructed)
32// 0x31 SET (constructed)
33// 0xA0-0xA7 context-specific [0]..[7]
34//
35// Invariants:
36// A1 All reads bounds-check against the caller-supplied length.
37// Negative return signals either "invalid input" (malformed
38// DER) or "out of bounds" (read would exceed buffer).
39// A2 Length field rejects indefinite-length form (DER forbids).
40// A3 No recursion; callers drive descent explicitly.
41// A4 No mallocs inside the reader. All state lives on the stack
42// (or in an Asn1Cursor struct the caller owns).
43
44import "syscalls.nx"
45
46// Common tag constants. Named so callers don't sprinkle magic hex.
47const ASN1_INTEGER: i64 = 0x02
48const ASN1_BIT_STRING: i64 = 0x03
49const ASN1_OCTET_STRING: i64 = 0x04
50const ASN1_NULL: i64 = 0x05
51const ASN1_OID: i64 = 0x06
52const ASN1_UTF8: i64 = 0x0C
53const ASN1_PRINTABLE: i64 = 0x13
54const ASN1_UTC_TIME: i64 = 0x17
55const ASN1_GENERALIZED: i64 = 0x18
56const ASN1_SEQUENCE: i64 = 0x30
57const ASN1_SET: i64 = 0x31
58
59// Error codes (negative).
60const ASN1_ERR_OOB: i64 = -1
61const ASN1_ERR_INDEFINITE: i64 = -2
62const ASN1_ERR_TAG_OVERFLOW:i64 = -3
63const ASN1_ERR_LENGTH_BAD: i64 = -4
64
65// Cursor tracks a position within a buffer; callers step it
66// forward as they read TLVs. The cursor and buffer are passed
67// explicitly to every reader -- no hidden state.
68struct Asn1Cursor {
69 pos: i64,
70 end: i64,
71}
72
73// Initialise a cursor over `[buf, buf+len)`.
74func asn1_cursor_init(c: *Asn1Cursor, len: i64) -> i64 {
75 c.pos = 0
76 c.end = len
77 return 0
78}
79
80// Read the tag byte(s). Returns the tag value (low form: just
81// the first byte; high form: accumulated from continuation
82// bytes). Caller bounds-checks via cursor.pos update.
83func asn1_read_tag(buf: *u8, c: *Asn1Cursor) -> i64 {
84 if c.pos >= c.end { return ASN1_ERR_OOB }
85 let b: i64 = buf[c.pos]
86 c.pos = c.pos + 1
87 // Low tag number (0-30) fits in bits 4-0. Mask 0x1F.
88 let low_bits: i64 = b & 0x1F
89 if low_bits != 0x1F {
90 return b // full first byte = class + constructed + tag
91 }
92 // High-tag-number: read base-128 continuation bytes until MSB
93 // is clear. Accumulate into tag (preserving class + constructed
94 // flags from the first byte in high bits).
95 let class_ctor: i64 = b & 0xE0 // bits 7-5
96 var tag: i64 = 0
97 while c.pos < c.end {
98 let cb: i64 = buf[c.pos]
99 c.pos = c.pos + 1
100 tag = (tag << 7) | (cb & 0x7F)
101 if (cb & 0x80) == 0 { return class_ctor | tag }
102 if tag > 0x10000000 { return ASN1_ERR_TAG_OVERFLOW }
103 }
104 return ASN1_ERR_OOB
105}
106
107// Read the length field. Returns length value on success, negative
108// on error. Rejects indefinite-length form (A2).
109func asn1_read_length(buf: *u8, c: *Asn1Cursor) -> i64 {
110 if c.pos >= c.end { return ASN1_ERR_OOB }
111 let b: i64 = buf[c.pos]
112 c.pos = c.pos + 1
113 if (b & 0x80) == 0 {
114 return b & 0x7F // short form: 0-127
115 }
116 let nbytes: i64 = b & 0x7F
117 if nbytes == 0 { return ASN1_ERR_INDEFINITE } // 0x80 forbidden in DER
118 if nbytes > 8 { return ASN1_ERR_LENGTH_BAD } // no 2^64+ messages
119 if c.pos + nbytes > c.end { return ASN1_ERR_OOB }
120 var len: i64 = 0
121 var i: i64 = 0
122 while i < nbytes {
123 len = (len << 8) | buf[c.pos + i]
124 i = i + 1
125 }
126 c.pos = c.pos + nbytes
127 return len
128}
129
130// Read one TLV element's tag + length. Writes tag to *out_tag
131// and length to *out_len; on exit cursor.pos points at the value's
132// first byte. Returns 0 on success, negative on error.
133func asn1_read_tlv_header(buf: *u8, c: *Asn1Cursor,
134 out_tag: *i64, out_len: *i64) -> i64 {
135 let tag: i64 = asn1_read_tag(buf, c)
136 if tag < 0 { return tag }
137 let len: i64 = asn1_read_length(buf, c)
138 if len < 0 { return len }
139 if c.pos + len > c.end { return ASN1_ERR_OOB }
140 *out_tag = tag
141 *out_len = len
142 return 0
143}
144
145// Skip the current TLV (value bytes). Caller has already read
146// the header via asn1_read_tlv_header; cursor is at value start.
147// `len` is the length returned from that header.
148func asn1_skip_value(c: *Asn1Cursor, len: i64) -> i64 {
149 if c.pos + len > c.end { return ASN1_ERR_OOB }
150 c.pos = c.pos + len
151 return 0
152}
153
154// Expect a specific tag; fails with ASN1_ERR_LENGTH_BAD on mismatch.
155// On success, writes value length to *out_len and leaves cursor at
156// value start. Useful shorthand for the common "navigate to this
157// specific field" pattern.
158func asn1_expect_tag(buf: *u8, c: *Asn1Cursor,
159 expected_tag: i64, out_len: *i64) -> i64 {
160 var tag_slot: i64 = 0
161 let tag_ptr: *i64 = (tag_slot as i64) as *i64
162 // We need a stable i64 slot; allocate scratch.
163 let scratch: *u8 = sys_mmap(16)
164 let t_p: *i64 = scratch as *i64
165 let rc: i64 = asn1_read_tlv_header(buf, c, t_p, out_len)
166 if rc < 0 { return rc }
167 if *t_p != expected_tag { return ASN1_ERR_LENGTH_BAD }
168 return 0
169}
170
171// Compile-only smoke: build a tiny SEQUENCE { INTEGER 42 } in
172// memory and parse it back.
173// 30 03 SEQUENCE, length 3
174// 02 01 2A INTEGER, length 1, value 42
175func main() -> i64 {
176 let buf: *u8 = sys_mmap(16)
177 buf[0] = 0x30; buf[1] = 0x03; buf[2] = 0x02; buf[3] = 0x01; buf[4] = 0x2A
178
179 let c_raw: *u8 = sys_mmap(32)
180 let c: *Asn1Cursor = c_raw as *Asn1Cursor
181 asn1_cursor_init(c, 5)
182
183 let len_p: *i64 = sys_mmap(16) as *i64
184 let rc1: i64 = asn1_expect_tag(buf, c, ASN1_SEQUENCE, len_p)
185 if rc1 != 0 { return 1 }
186 if *len_p != 3 { return 2 }
187
188 let rc2: i64 = asn1_expect_tag(buf, c, ASN1_INTEGER, len_p)
189 if rc2 != 0 { return 3 }
190 if *len_p != 1 { return 4 }
191 if buf[c.pos] != 42 { return 5 }
192 return 0
193}