code wiki / (root) / asn1.nx

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}