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}