code wiki / _hdl_build / nx_asn1_der_emit.nx

nx_asn1_der_emit.nx source

↩ module page · 282 lines · 13533 B

1// nx_asn1_der_emit.nx -- F103e RUNG 2: the DER WRITE side. 2// 3// asn1.nx's own header has said since it was written: "We implement a reader-only subset -- write-side 4// encoders are future work." This is that work. It is the missing half of every step still ahead on 5// F103e: a self-signed X.509 certificate, PKCS#7 SignedData, SpcIndirectDataContent and the 6// EFI_SIGNATURE_LIST enrolment blob are ALL just DER structures, and none of them can be authored 7// without a correct encoder. (Why we need them: real EDK2 with MS keys refuses our unsigned .efi with 8// Access Denied, while the SAME secboot firmware in SETUP mode runs it -- so the blocker is the 9// enrolled key set. Debt 1786237435.) 10// 11// ★THE ORACLE IS THE INCUMBENT READER. Every tooth below encodes with these functions and decodes 12// with nx_asn1_lib's asn1_expect_tag / asn1_read_length -- two implementations checking each other, 13// never one implementation agreeing with itself. And T1 reproduces the EXACT byte vector nx_asn1.nx's 14// own compile-smoke builds BY HAND (30 03 02 01 2A), so the estate supplied its own golden vector. 15// 16// The two encoder bugs that matter, both tested here because both silently produce parseable output: 17// 1. NON-MINIMAL LENGTH. DER (unlike BER) requires the shortest length form. `81 05` for length 5 18// decodes fine everywhere and is INVALID DER -- it breaks signature verification because the 19// verifier re-encodes canonically and gets different bytes. 20// 2. THE INTEGER SIGN BIT. ASN.1 INTEGER is two's complement, so a positive magnitude whose top bit 21// is set needs a leading 0x00. Emit `02 01 80` and every parser reads -128 instead of 128. An 22// RSA modulus starts with a high bit set essentially always, so this bug is not an edge case 23// here -- it is the common path. 24// 25// Usage: nx_asn1_der_emit selftest (8 teeth, incl. two negative controls) 26// Exit: 0 GREEN | 1 RED. Log -> knowledge/status/nishi_os.log, verdict= LAST (positional anchor). 27// Sovereign: syscalls only, no openssl. license_tier: ORIGINAL 28import "nx_syscalls.nx" 29import "nx_asn1.nx" 30const K_MAGIC_65536: i64 = 65536 31const K_MAGIC_16777216: i64 = 16777216 32const K_MAGIC_70000: i64 = 70000 33const K_MAGIC_65535: i64 = 65535 34// ⚠2026-08-08 RETARGETED from nx_asn1_lib.nx to the INCUMBENT nx_asn1.nx. I extracted that lib on the 35// premise that a main-bearing file cannot be imported (bigint.nx's header says so) -- then built 36// nx_rsa_pkcs1_sign, which imports FOUR main-bearing files and works. The premise was stale, so the 37// lib was a SECOND COPY of the reader: the duplicate-ruler defect, created while congratulating 38// myself for composing the incumbent. ★★★★★★**A STALE WARNING IN A HEADER IS A HYPOTHESIS, NOT A 39// CONSTRAINT — TEST IT BEFORE BUILDING A WORKAROUND, BECAUSE THE WORKAROUND IS USUALLY A DUPLICATE.** 40// The teeth below still pass 9/9 against nx_asn1.nx, which is the proof the oracle did not change. 41 42func de_p(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 } 43func de_fp(fd: i64, s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(fd, s, n); return 0 } 44func de_fn(fd: i64, v: i64) -> i64 { 45 let bb: *u8 = sys_mmap(28); var m: i64 = v 46 if m < 0 { m = 0 - m } 47 let t: *u8 = sys_mmap(28); var k: i64 = 0 48 if m == 0 { t[0] = 48 as u8; k = 1 } 49 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 50 var i: i64 = 0 51 while i < k { bb[i] = t[k - 1 - i]; i = i + 1 } 52 sys_write(fd, bb, k); return 0 53} 54func de_hex(d: *u8, n: i64) -> i64 { 55 let hx: *u8 = "0123456789abcdef" as *u8 // hoisted: an inline-cast literal never indexes 56 let o: *u8 = sys_mmap(n * 3 + 8) 57 var i: i64 = 0 58 while i < n { 59 o[i * 3] = hx[(d[i] as i64) >> 4] 60 o[i * 3 + 1] = hx[(d[i] as i64) & 0xf] 61 o[i * 3 + 2] = 32 as u8 62 i = i + 1 63 } 64 sys_write(1, o, n * 3); return 0 65} 66 67// ---- length: MINIMAL form only. This is the function that makes it DER rather than BER. ---------- 68func der_len_size(n: i64) -> i64 { 69 if n < 0 { return 0 - 1 } 70 if n < 128 { return 1 } 71 if n < 256 { return 2 } 72 if n < K_MAGIC_65536 { return 3 } 73 if n < K_MAGIC_16777216 { return 4 } 74 return 5 75} 76func der_put_len(out: *u8, off: i64, n: i64) -> i64 { 77 if n < 0 { return 0 - 1 } 78 if n < 128 { out[off] = n as u8; return off + 1 } 79 if n < 256 { 80 out[off] = 0x81 as u8; out[off + 1] = n as u8; return off + 2 81 } 82 if n < K_MAGIC_65536 { 83 out[off] = 0x82 as u8 84 out[off + 1] = ((n >> 8) & 0xff) as u8 85 out[off + 2] = (n & 0xff) as u8 86 return off + 3 87 } 88 if n < K_MAGIC_16777216 { 89 out[off] = 0x83 as u8 90 out[off + 1] = ((n >> 16) & 0xff) as u8 91 out[off + 2] = ((n >> 8) & 0xff) as u8 92 out[off + 3] = (n & 0xff) as u8 93 return off + 4 94 } 95 out[off] = 0x84 as u8 96 out[off + 1] = ((n >> 24) & 0xff) as u8 97 out[off + 2] = ((n >> 16) & 0xff) as u8 98 out[off + 3] = ((n >> 8) & 0xff) as u8 99 out[off + 4] = (n & 0xff) as u8 100 return off + 5 101} 102 103// ---- a complete TLV ----------------------------------------------------------------------------- 104func der_put_tlv(out: *u8, off: i64, tag: i64, val: *u8, vlen: i64) -> i64 { 105 if vlen < 0 { return 0 - 1 } 106 out[off] = (tag & 0xff) as u8 107 var o: i64 = der_put_len(out, off + 1, vlen) 108 if o < 0 { return 0 - 1 } 109 var i: i64 = 0 110 while i < vlen { out[o + i] = val[i]; i = i + 1 } 111 return o + vlen 112} 113 114// ---- INTEGER from a big-endian MAGNITUDE (unsigned). Two rules, both silent when wrong. ---------- 115func der_put_int(out: *u8, off: i64, mag: *u8, mlen: i64) -> i64 { 116 if mlen < 0 { return 0 - 1 } 117 // strip leading zero bytes -- DER integers carry no redundant padding 118 var s: i64 = 0 119 var scanning: i64 = 1 120 while scanning == 1 { 121 if s >= mlen { scanning = 0 } else { 122 if (mag[s] as i64) == 0 { s = s + 1 } else { scanning = 0 } 123 } 124 } 125 let n: i64 = mlen - s 126 if n == 0 { // the value is zero -> exactly 02 01 00 127 out[off] = ASN1_INTEGER as u8 128 out[off + 1] = 1 as u8 129 out[off + 2] = 0 as u8 130 return off + 3 131 } 132 var pad: i64 = 0 133 if ((mag[s] as i64) & 0x80) != 0 { pad = 1 } // positive value, top bit set -> prepend 0x00 134 out[off] = ASN1_INTEGER as u8 135 var o: i64 = der_put_len(out, off + 1, n + pad) 136 if o < 0 { return 0 - 1 } 137 if pad == 1 { out[o] = 0 as u8; o = o + 1 } 138 var i: i64 = 0 139 while i < n { out[o + i] = mag[s + i]; i = i + 1 } 140 return o + n 141} 142 143// ================================================================================================= 144func de_bytes_eq(a: *u8, b: *u8, n: i64) -> i64 { 145 var i: i64 = 0 146 while i < n { if a[i] != b[i] { return 0 } i = i + 1 } 147 return 1 148} 149 150func de_selftest() -> i64 { 151 var pass: i64 = 0 152 var teeth: i64 = 0 153 let buf: *u8 = sys_mmap(K_MAGIC_70000) 154 let val: *u8 = sys_mmap(K_MAGIC_70000) 155 let lenp: *i64 = sys_mmap(16) as *i64 156 let c: *Asn1Cursor = sys_mmap(64) as *Asn1Cursor 157 158 // T1 GOLDEN VECTOR, supplied by the estate itself: nx_asn1.nx's compile-smoke hand-builds 159 // 30 03 02 01 2A = SEQUENCE { INTEGER 42 }. Our encoder must reproduce it BYTE FOR BYTE. 160 teeth = teeth + 1 161 val[0] = 42 as u8 162 var inner: i64 = der_put_int(val + 8, 0, val, 1) // -> 02 01 2A at val+8 163 var o: i64 = der_put_tlv(buf, 0, ASN1_SEQUENCE, (val + 8) as *u8, inner) 164 let want: *u8 = sys_mmap(16) 165 want[0] = 0x30 as u8; want[1] = 0x03 as u8; want[2] = 0x02 as u8; want[3] = 0x01 as u8; want[4] = 0x2A as u8 166 if o == 5 { if de_bytes_eq(buf, want, 5) == 1 { pass = pass + 1; de_p("DER-T1 golden-vector-30030201 2A GREEN\n" as *u8) } 167 else { de_p("DER-T1 RED got: " as *u8); de_hex(buf, o); de_p("\n" as *u8) } } 168 else { de_p("DER-T1 RED wrong length " as *u8); de_fn(1, o); de_p("\n" as *u8) } 169 170 // T2 ROUND TRIP THROUGH THE INCUMBENT READER -- the whole point of composing it. 171 teeth = teeth + 1 172 asn1_cursor_init(c, o) 173 var ok: i64 = 0 174 if asn1_expect_tag(buf, c, ASN1_SEQUENCE, lenp) == 0 { 175 if lenp[0] == 3 { 176 if asn1_expect_tag(buf, c, ASN1_INTEGER, lenp) == 0 { 177 if lenp[0] == 1 { if (buf[c.pos] as i64) == 42 { ok = 1 } } 178 } 179 } 180 } 181 if ok == 1 { pass = pass + 1; de_p("DER-T2 incumbent-reader-round-trip GREEN\n" as *u8) } 182 else { de_p("DER-T2 RED\n" as *u8) } 183 184 // T3 LENGTH MINIMALITY at every boundary. A non-minimal length parses fine and is invalid DER, 185 // so only an exact byte assertion catches it. 186 teeth = teeth + 1 187 var lok: i64 = 1 188 var e: i64 = der_put_len(buf, 0, 127) 189 if e != 1 { lok = 0 } 190 if (buf[0] as i64) != 127 { lok = 0 } 191 e = der_put_len(buf, 0, 128) 192 if e != 2 { lok = 0 } 193 if (buf[0] as i64) != 0x81 { lok = 0 } 194 if (buf[1] as i64) != 0x80 { lok = 0 } 195 e = der_put_len(buf, 0, 255) 196 if e != 2 { lok = 0 } 197 if (buf[1] as i64) != 0xFF { lok = 0 } 198 e = der_put_len(buf, 0, 256) 199 if e != 3 { lok = 0 } 200 if (buf[0] as i64) != 0x82 { lok = 0 } 201 if (buf[1] as i64) != 0x01 { lok = 0 } 202 if (buf[2] as i64) != 0x00 { lok = 0 } 203 e = der_put_len(buf, 0, K_MAGIC_65535) 204 if e != 3 { lok = 0 } 205 e = der_put_len(buf, 0, K_MAGIC_65536) 206 if e != 4 { lok = 0 } 207 if (buf[0] as i64) != 0x83 { lok = 0 } 208 if lok == 1 { pass = pass + 1; de_p("DER-T3 length-minimal-at-127/128/255/256/65535/65536 GREEN\n" as *u8) } 209 else { de_p("DER-T3 RED\n" as *u8) } 210 211 // T4 NEGATIVE CONTROL for minimality: length 5 must NOT be the long form. This is the tooth a 212 // BER-style encoder fails while every parser still accepts its output. 213 teeth = teeth + 1 214 e = der_put_len(buf, 0, 5) 215 if e == 1 { if (buf[0] as i64) == 5 { pass = pass + 1; de_p("DER-T4 neg-control-no-long-form-for-5 GREEN\n" as *u8) } 216 else { de_p("DER-T4 RED\n" as *u8) } } 217 else { de_p("DER-T4 RED emitted long form for a short length\n" as *u8) } 218 219 // T5 THE SIGN BIT: magnitude 0x80 is +128 and MUST get a leading 0x00 -> 02 02 00 80. 220 // Without the pad it is 02 01 80 = -128: parseable, wrong, and silent. 221 teeth = teeth + 1 222 val[0] = 0x80 as u8 223 e = der_put_int(buf, 0, val, 1) 224 if e == 4 { 225 if (buf[0] as i64) == 0x02 { if (buf[1] as i64) == 0x02 { if (buf[2] as i64) == 0x00 { if (buf[3] as i64) == 0x80 { 226 pass = pass + 1; de_p("DER-T5 integer-sign-bit-padded GREEN\n" as *u8) } } } } 227 } 228 if e != 4 { de_p("DER-T5 RED got " as *u8); de_hex(buf, e); de_p("\n" as *u8) } 229 230 // T6 zero -> exactly 02 01 00 (not 02 00, which is what a naive strip produces) 231 teeth = teeth + 1 232 val[0] = 0 as u8; val[1] = 0 as u8 233 e = der_put_int(buf, 0, val, 2) 234 if e == 3 { if (buf[1] as i64) == 1 { if (buf[2] as i64) == 0 { pass = pass + 1; de_p("DER-T6 integer-zero GREEN\n" as *u8) } } } 235 if e != 3 { de_p("DER-T6 RED got " as *u8); de_hex(buf, e); de_p("\n" as *u8) } 236 237 // T7 leading zeros stripped, and NO pad when the top bit is clear: 00 00 2A -> 02 01 2A 238 teeth = teeth + 1 239 val[0] = 0 as u8; val[1] = 0 as u8; val[2] = 0x2A as u8 240 e = der_put_int(buf, 0, val, 3) 241 if e == 3 { if (buf[1] as i64) == 1 { if (buf[2] as i64) == 0x2A { pass = pass + 1; de_p("DER-T7 leading-zeros-stripped GREEN\n" as *u8) } } } 242 if e != 3 { de_p("DER-T7 RED got " as *u8); de_hex(buf, e); de_p("\n" as *u8) } 243 244 // T8 ANTI-VACUITY on the round trip itself: if the reader accepted ANY bytes, T2 would be 245 // meaningless. Corrupt the emitted tag and the incumbent MUST refuse. 246 teeth = teeth + 1 247 val[0] = 42 as u8 248 inner = der_put_int(val + 8, 0, val, 1) 249 o = der_put_tlv(buf, 0, ASN1_SEQUENCE, (val + 8) as *u8, inner) 250 buf[0] = 0x31 as u8 // SET, not SEQUENCE 251 asn1_cursor_init(c, o) 252 if asn1_expect_tag(buf, c, ASN1_SEQUENCE, lenp) != 0 { pass = pass + 1; de_p("DER-T8 neg-control-reader-refuses-wrong-tag GREEN\n" as *u8) } 253 else { de_p("DER-T8 RED [VACUOUS: the reader accepts anything, so T2 proved nothing]\n" as *u8) } 254 255 // A large-length exercise so the 3-byte form is not merely unit-tested in isolation 256 teeth = teeth + 1 257 var i: i64 = 0 258 while i < 300 { val[i] = (i & 0xff) as u8; i = i + 1 } 259 o = der_put_tlv(buf, 0, ASN1_OCTET_STRING, val, 300) 260 asn1_cursor_init(c, o) 261 if asn1_expect_tag(buf, c, ASN1_OCTET_STRING, lenp) == 0 { 262 if lenp[0] == 300 { if o == (1 + 3 + 300) { pass = pass + 1; de_p("DER-T9 long-form-300-round-trip GREEN\n" as *u8) } 263 else { de_p("DER-T9 RED length-field size\n" as *u8) } } 264 else { de_p("DER-T9 RED len=" as *u8); de_fn(1, lenp[0]); de_p("\n" as *u8) } 265 } else { de_p("DER-T9 RED reader refused\n" as *u8) } 266 267 de_p("DER-SELFTEST " as *u8); de_fn(1, pass); de_p("/" as *u8); de_fn(1, teeth); de_p("\n" as *u8) 268 let lf: i64 = sys_openat_append("knowledge/status/nishi_os.log" as *u8, 0x1a4) 269 if lf >= 0 { 270 de_fp(lf, "DEREMIT selftest teeth=" as *u8); de_fn(lf, pass) 271 de_fp(lf, "of" as *u8); de_fn(lf, teeth) 272 de_fp(lf, " oracle=nx_asn1_lib-incumbent-reader verdict=" as *u8) 273 if pass == teeth { de_fp(lf, "GREEN\n" as *u8) } else { de_fp(lf, "RED\n" as *u8) } 274 sys_close(lf) 275 } 276 if pass == teeth { sys_exit(0); return 0 } 277 sys_exit(1); return 1 278} 279 280func main(argc: i64, argv: *i64) -> i64 { 281 return de_selftest() 282}