code wiki / _hdl_build / nx_authenticode_sign.nx
nx_authenticode_sign.nx source
↩ module page · 526 lines · 25100 B
1// nx_authenticode_sign.nx -- F103e RUNG 10: attach an Authenticode signature to a PE32+ EFI image.
2//
3// The last software step before enrolment. Real EDK2 with MS keys refuses our unsigned .efi
4// (Access Denied) while the SAME secboot firmware in SETUP mode runs it (debt 1786237435), so the
5// image must carry a signature and our cert must be in db.
6//
7// Assembles, bottom-up:
8// SpcIndirectDataContent ::= SEQUENCE { SpcAttributeTypeAndOptionalValue, DigestInfo }
9// PKCS#7 ContentInfo ::= SEQUENCE { OID signedData, [0] SignedData }
10// WIN_CERTIFICATE ::= dwLength || wRevision(0x0200) || wCertificateType(0x0002) || PKCS#7
11// then appends it 8-byte aligned and points data directory 4 at it.
12//
13// ★★★★★★THE TOOTH THIS RUNG WAS BUILT AROUND: **AFTER SIGNING, THE AUTHENTICODE HASH MUST BE
14// BYTE-IDENTICAL TO BEFORE.** The certificate table and its data-directory entry are two of the
15// three ranges the hash excludes, so if attaching a signature CHANGES the hash, the exclusion logic
16// in nx_pe_authhash is wrong and every signature we ever produce is invalid. Nothing else in the
17// chain tests that interaction -- the hasher alone cannot, because it never sees a signed file.
18//
19// ★Every OID is DERIVED by `der_oid_from_arcs` (validated in nx_x509_emit against the estate's own
20// shipped SHA-256 OID), never a remembered byte string.
21//
22// ⚠SCOPE, NAMED: this produces a structurally complete Authenticode blob whose signature our own
23// verifier accepts over the SpcIndirectDataContent DER. It is NOT yet proven against EDK2's
24// AuthenticodeVerify -- that proof is the enrol-and-boot rung, and until it runs this organ's
25// claim is "well-formed and self-consistent", not "accepted by firmware". Saying otherwise would be
26// the exact self-certification the third reference exists to prevent.
27//
28// Usage: nx_authenticode_sign selftest [image.efi]
29// Exit: 0 GREEN | 1 RED. Log -> knowledge/status/nishi_os.log, verdict= LAST.
30// license_tier: ORIGINAL
31import "nx_syscalls.nx"
32import "nx_sha256.nx"
33import "nx_u2048.nx"
34import "nx_u2048_mul.nx"
35import "nx_rsa2048_mod.nx"
36import "nx_rsa2048_mod_exp.nx"
37import "nx_rsa2048_mont.nx"
38import "nx_rsa2048_mod_exp_big.nx"
39import "nx_asn1.nx"
40import "nx_x509.nx"
41import "nx_rsa_pkcs1_v1_5_sha256.nx"
42import "nx_u2048_millerrabin.nx"
43import "nx_u2048_smallops.nx"
44import "nx_rsa_keygen.nx"
45import "nx_rsa_pkcs1_sign.nx"
46import "nx_pe_authhash.nx"
47import "nx_x509_emit.nx"
48const AC_MAGIC_1024: i64 = 1024
49const AC_MAGIC_113549: i64 = 113549
50const AC_MAGIC_2048: i64 = 2048
51const AC_MAGIC_4096: i64 = 4096
52const AC_MAGIC_20260808: i64 = 20260808
53const AC_MAGIC_65537: i64 = 65537
54const AC_MAGIC_4097: i64 = 4097
55
56const AC_BUF: i64 = 16384
57
58func ac_p(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 }
59func ac_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 }
60func ac_fn(fd: i64, v: i64) -> i64 {
61 let bb: *u8 = sys_mmap(28); var m: i64 = v
62 if m < 0 { m = 0 - m; bb[0] = 45 as u8; sys_write(fd, bb, 1) }
63 let t: *u8 = sys_mmap(28); var k: i64 = 0
64 if m == 0 { t[0] = 48 as u8; k = 1 }
65 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
66 var i: i64 = 0
67 while i < k { bb[i] = t[k - 1 - i]; i = i + 1 }
68 sys_write(fd, bb, k); return 0
69}
70func ac_eq_str(a: *u8, b: *u8) -> i64 {
71 var i: i64 = 0
72 while a[i] != (0 as u8) { if a[i] != b[i] { return 0 } i = i + 1 }
73 if b[i] != (0 as u8) { return 0 }
74 return 1
75}
76func ac_w32le(b: *u8, o: i64, v: i64) -> i64 {
77 b[o] = (v & 0xff) as u8; b[o+1] = ((v >> 8) & 0xff) as u8
78 b[o+2] = ((v >> 16) & 0xff) as u8; b[o+3] = ((v >> 24) & 0xff) as u8
79 return 0
80}
81func ac_r32le(b: *u8, o: i64) -> i64 {
82 return (b[o] as i64) | ((b[o+1] as i64) << 8) | ((b[o+2] as i64) << 16) | ((b[o+3] as i64) << 24)
83}
84
85// ---- SpcIndirectDataContent: what Authenticode actually signs -----------------------------------
86// SEQUENCE {
87// SEQUENCE { OID 1.3.6.1.4.1.311.2.1.15, SEQUENCE { BIT STRING(0), [0] { [2] {} } } }
88// SEQUENCE { SEQUENCE { OID sha256, NULL }, OCTET STRING hash }
89// }
90func ac_spc_indirect(out: *u8, hash32: *u8) -> i64 {
91 let peoid: *i64 = sys_mmap(128) as *i64
92 peoid[0]=1; peoid[1]=3; peoid[2]=6; peoid[3]=1; peoid[4]=4; peoid[5]=1
93 peoid[6]=311; peoid[7]=2; peoid[8]=1; peoid[9]=15
94 // SpcPeImageData ::= SEQUENCE { flags BIT STRING DEFAULT, file [0] SpcLink }
95 // Minimal well-formed link: [0] { [2] { } } (SpcLink CHOICE file, empty SpcString)
96 let link: *u8 = sys_mmap(64)
97 var lo: i64 = 0
98 link[lo] = 0xA2 as u8; link[lo+1] = 0x00 as u8 // [2] IMPLICIT, empty
99 lo = 2
100 let file0: *u8 = sys_mmap(64)
101 let flen: i64 = xe_wrap(file0, 0, 0xA0, link, lo) // [0] file
102 let peimg: *u8 = sys_mmap(128)
103 var po: i64 = 0
104 peimg[po] = ASN1_BIT_STRING as u8; peimg[po+1] = 0x01 as u8; peimg[po+2] = 0x00 as u8 // flags: 0 unused bits, empty
105 po = 3
106 var pj: i64 = 0
107 while pj < flen { peimg[po + pj] = file0[pj]; pj = pj + 1 }
108 po = po + flen
109 let peimgseq: *u8 = sys_mmap(256)
110 let pil: i64 = xe_wrap(peimgseq, 0, ASN1_SEQUENCE, peimg, po)
111 // SpcAttributeTypeAndOptionalValue
112 let atv: *u8 = sys_mmap(256)
113 var ao: i64 = der_oid_from_arcs(atv, 0, peoid, 10)
114 var aj: i64 = 0
115 while aj < pil { atv[ao + aj] = peimgseq[aj]; aj = aj + 1 }
116 ao = ao + pil
117 let atvseq: *u8 = sys_mmap(512)
118 let al: i64 = xe_wrap(atvseq, 0, ASN1_SEQUENCE, atv, ao)
119 // DigestInfo { AlgorithmIdentifier(sha256), OCTET STRING hash }
120 let shaarcs: *i64 = sys_mmap(128) as *i64
121 shaarcs[0]=2; shaarcs[1]=16; shaarcs[2]=840; shaarcs[3]=1; shaarcs[4]=101
122 shaarcs[5]=3; shaarcs[6]=4; shaarcs[7]=2; shaarcs[8]=1
123 let di: *u8 = sys_mmap(256)
124 var dio: i64 = xe_algid(di, 0, shaarcs, 9)
125 dio = xe_wrap(di, dio, ASN1_OCTET_STRING, hash32, 32)
126 let diseq: *u8 = sys_mmap(512)
127 let dl: i64 = xe_wrap(diseq, 0, ASN1_SEQUENCE, di, dio)
128 // the outer SpcIndirectDataContent
129 let body: *u8 = sys_mmap(AC_MAGIC_1024)
130 var bo: i64 = 0
131 var i: i64 = 0
132 while i < al { body[bo + i] = atvseq[i]; i = i + 1 }
133 bo = bo + al
134 var j: i64 = 0
135 while j < dl { body[bo + j] = diseq[j]; j = j + 1 }
136 bo = bo + dl
137 return xe_wrap(out, 0, ASN1_SEQUENCE, body, bo)
138}
139
140// ---- authenticatedAttributes: what Authenticode ACTUALLY signs ----------------------------------
141// MEASURED 2026-08-08: signing SpcIndirectDataContent directly produced a blob our own verifier
142// accepted and EDK2 refused with Access Denied -- while a hash-authorised db admitted the same image,
143// proving enrolment and enforcement were fine and the fault was here.
144// ★★★★★★THE SIGNATURE IS OVER THE ATTRIBUTES, NOT THE CONTENT. SignerInfo carries
145// authenticatedAttributes [0] IMPLICIT, but the bytes that get hashed are those same attributes
146// RE-TAGGED AS A SET (0x31). That re-tagging is the classic Authenticode trap: emit [0] into the
147// hash and every structure looks right while no verifier on earth agrees with you.
148// Attributes (DER SET OF is sorted; contentType's OID ends 03 and messageDigest's ends 04, so this
149// order is already canonical):
150// contentType 1.2.840.113549.1.9.3 = SpcIndirectDataContent OID
151// messageDigest 1.2.840.113549.1.9.4 = SHA-256 over the SpcIndirectDataContent DER
152func ac_authattrs(out: *u8, spc: *u8, spclen: i64) -> i64 {
153 let ctarcs: *i64 = sys_mmap(128) as *i64
154 ctarcs[0]=1; ctarcs[1]=2; ctarcs[2]=840; ctarcs[3]=AC_MAGIC_113549; ctarcs[4]=1; ctarcs[5]=9; ctarcs[6]=3
155 let mdarcs: *i64 = sys_mmap(128) as *i64
156 mdarcs[0]=1; mdarcs[1]=2; mdarcs[2]=840; mdarcs[3]=AC_MAGIC_113549; mdarcs[4]=1; mdarcs[5]=9; mdarcs[6]=4
157 let spcarcs: *i64 = sys_mmap(128) as *i64
158 spcarcs[0]=1; spcarcs[1]=3; spcarcs[2]=6; spcarcs[3]=1; spcarcs[4]=4; spcarcs[5]=1
159 spcarcs[6]=311; spcarcs[7]=2; spcarcs[8]=1; spcarcs[9]=4
160
161 // Attribute 1: contentType
162 let v1: *u8 = sys_mmap(256)
163 let v1l: i64 = der_oid_from_arcs(v1, 0, spcarcs, 10)
164 let s1: *u8 = sys_mmap(256)
165 let s1l: i64 = xe_wrap(s1, 0, ASN1_SET, v1, v1l)
166 let a1: *u8 = sys_mmap(512)
167 var a1o: i64 = der_oid_from_arcs(a1, 0, ctarcs, 7)
168 var q: i64 = 0
169 while q < s1l { a1[a1o + q] = s1[q]; q = q + 1 }
170 a1o = a1o + s1l
171 let a1w: *u8 = sys_mmap(512)
172 let a1l: i64 = xe_wrap(a1w, 0, ASN1_SEQUENCE, a1, a1o)
173
174 // Attribute 2: messageDigest.
175 // ⚠⚠MEASURED + SOURCE-DERIVED 2026-08-08: EDK2's AuthenticodeVerify locates the
176 // SpcIndirectDataContent, **SKIPS ITS SEQUENCE TAG AND LENGTH**, and hands the inner bytes to
177 // Pkcs7Verify as DETACHED content. OpenSSL then digests exactly those bytes. So messageDigest
178 // must cover the CONTENT OCTETS, not the full TLV.
179 // ★★★★★★HASHING THE WHOLE TLV WHERE THE VERIFIER HASHES ONLY ITS CONTENTS PRODUCES A SIGNATURE
180 // THAT IS INTERNALLY PERFECT AND UNIVERSALLY REJECTED — every self-check passes, because both
181 // sides of MY code agreed with each other. Only the foreign implementation disagrees.
182 var hdr: i64 = 2 // tag + short-form length
183 let lb: i64 = spc[1] as i64
184 if lb == 0x81 { hdr = 3 }
185 if lb == 0x82 { hdr = 4 }
186 if lb == 0x83 { hdr = 5 }
187 let h: *u8 = sys_mmap(64)
188 sha256_digest((spc + hdr) as *u8, spclen - hdr, h)
189 let v2: *u8 = sys_mmap(256)
190 let v2l: i64 = xe_wrap(v2, 0, ASN1_OCTET_STRING, h, 32)
191 let s2: *u8 = sys_mmap(256)
192 let s2l: i64 = xe_wrap(s2, 0, ASN1_SET, v2, v2l)
193 let a2: *u8 = sys_mmap(512)
194 var a2o: i64 = der_oid_from_arcs(a2, 0, mdarcs, 7)
195 var r: i64 = 0
196 while r < s2l { a2[a2o + r] = s2[r]; r = r + 1 }
197 a2o = a2o + s2l
198 let a2w: *u8 = sys_mmap(512)
199 let a2l: i64 = xe_wrap(a2w, 0, ASN1_SEQUENCE, a2, a2o)
200
201 var o: i64 = 0
202 var i: i64 = 0
203 while i < a1l { out[o + i] = a1w[i]; i = i + 1 }
204 o = o + a1l
205 var j: i64 = 0
206 while j < a2l { out[o + j] = a2w[j]; j = j + 1 }
207 o = o + a2l
208 return o // the BODY; caller tags it SET or [0]
209}
210
211// ---- the PKCS#7 SignedData blob ------------------------------------------------------------------
212func ac_pkcs7(out: *u8, spc: *u8, spclen: i64, sig: *u8, cert: *u8, certlen: i64,
213 cn: *u8, serial: i64, aabody: *u8, aalen: i64) -> i64 {
214 let shaarcs: *i64 = sys_mmap(128) as *i64
215 shaarcs[0]=2; shaarcs[1]=16; shaarcs[2]=840; shaarcs[3]=1; shaarcs[4]=101
216 shaarcs[5]=3; shaarcs[6]=4; shaarcs[7]=2; shaarcs[8]=1
217 let rsaarcs: *i64 = sys_mmap(128) as *i64
218 rsaarcs[0]=1; rsaarcs[1]=2; rsaarcs[2]=840; rsaarcs[3]=AC_MAGIC_113549; rsaarcs[4]=1; rsaarcs[5]=1; rsaarcs[6]=1
219 let sdoid: *i64 = sys_mmap(128) as *i64
220 sdoid[0]=1; sdoid[1]=2; sdoid[2]=840; sdoid[3]=AC_MAGIC_113549; sdoid[4]=1; sdoid[5]=7; sdoid[6]=2
221 let spcoid: *i64 = sys_mmap(128) as *i64
222 spcoid[0]=1; spcoid[1]=3; spcoid[2]=6; spcoid[3]=1; spcoid[4]=4; spcoid[5]=1
223 spcoid[6]=311; spcoid[7]=2; spcoid[8]=1; spcoid[9]=4
224
225 // digestAlgorithms SET { sha256 }
226 let da: *u8 = sys_mmap(256)
227 let dal: i64 = xe_algid(da, 0, shaarcs, 9)
228 let daset: *u8 = sys_mmap(256)
229 let dasl: i64 = xe_wrap(daset, 0, ASN1_SET, da, dal)
230
231 // contentInfo SEQUENCE { OID spcIndirect, [0] EXPLICIT SpcIndirectDataContent }
232 let ci: *u8 = sys_mmap(AC_MAGIC_2048)
233 var cio: i64 = der_oid_from_arcs(ci, 0, spcoid, 10)
234 cio = xe_wrap(ci, cio, 0xA0, spc, spclen)
235 let ciseq: *u8 = sys_mmap(AC_MAGIC_2048)
236 let cil: i64 = xe_wrap(ciseq, 0, ASN1_SEQUENCE, ci, cio)
237
238 // certificates [0] IMPLICIT
239 let certs: *u8 = sys_mmap(AC_MAGIC_4096)
240 let cl: i64 = xe_wrap(certs, 0, 0xA0, cert, certlen)
241
242 // SignerInfo
243 let iss: *u8 = sys_mmap(512)
244 let issl: i64 = xe_name(iss, 0, cn)
245 let sn: *u8 = sys_mmap(16)
246 sn[0] = ((serial >> 8) & 0xff) as u8
247 sn[1] = (serial & 0xff) as u8
248 let ias: *u8 = sys_mmap(AC_MAGIC_1024)
249 var io: i64 = 0
250 var ii: i64 = 0
251 while ii < issl { ias[io + ii] = iss[ii]; ii = ii + 1 }
252 io = io + issl
253 io = xe_wrap(ias, io, ASN1_INTEGER, sn, 2)
254 let iasseq: *u8 = sys_mmap(AC_MAGIC_1024)
255 let iasl: i64 = xe_wrap(iasseq, 0, ASN1_SEQUENCE, ias, io)
256
257 let si: *u8 = sys_mmap(AC_MAGIC_2048)
258 var so: i64 = 0
259 let v1: *u8 = sys_mmap(8)
260 v1[0] = 1 as u8
261 so = xe_wrap(si, so, ASN1_INTEGER, v1, 1)
262 var si2: i64 = 0
263 while si2 < iasl { si[so + si2] = iasseq[si2]; si2 = si2 + 1 }
264 so = so + iasl
265 so = xe_algid(si, so, shaarcs, 9)
266 so = xe_wrap(si, so, 0xA0, aabody, aalen) // authenticatedAttributes [0] IMPLICIT
267 so = xe_algid(si, so, rsaarcs, 7)
268 so = xe_wrap(si, so, ASN1_OCTET_STRING, sig, 256)
269 let siseq: *u8 = sys_mmap(AC_MAGIC_2048)
270 let sil: i64 = xe_wrap(siseq, 0, ASN1_SEQUENCE, si, so)
271 let siset: *u8 = sys_mmap(AC_MAGIC_2048)
272 let sisl: i64 = xe_wrap(siset, 0, ASN1_SET, siseq, sil)
273
274 // SignedData
275 let sd: *u8 = sys_mmap(AC_BUF)
276 var sdo: i64 = 0
277 sdo = xe_wrap(sd, sdo, ASN1_INTEGER, v1, 1)
278 var a: i64 = 0
279 while a < dasl { sd[sdo + a] = daset[a]; a = a + 1 }
280 sdo = sdo + dasl
281 var b: i64 = 0
282 while b < cil { sd[sdo + b] = ciseq[b]; b = b + 1 }
283 sdo = sdo + cil
284 var c: i64 = 0
285 while c < cl { sd[sdo + c] = certs[c]; c = c + 1 }
286 sdo = sdo + cl
287 var e: i64 = 0
288 while e < sisl { sd[sdo + e] = siset[e]; e = e + 1 }
289 sdo = sdo + sisl
290 let sdseq: *u8 = sys_mmap(AC_BUF)
291 let sdl: i64 = xe_wrap(sdseq, 0, ASN1_SEQUENCE, sd, sdo)
292
293 // ContentInfo
294 let outer: *u8 = sys_mmap(AC_BUF)
295 var oo: i64 = der_oid_from_arcs(outer, 0, sdoid, 7)
296 oo = xe_wrap(outer, oo, 0xA0, sdseq, sdl)
297 return xe_wrap(out, 0, ASN1_SEQUENCE, outer, oo)
298}
299
300// ---- attach: append WIN_CERTIFICATE, 8-byte aligned, and point data directory 4 at it ------------
301// Returns the new file length, or negative.
302func ac_attach(img: *u8, ilen: i64, p7: *u8, p7len: i64, out: *u8) -> i64 {
303 let lfa: i64 = ac_r32le(img, 0x3C)
304 let opt: i64 = lfa + 24
305 let mag: i64 = (img[opt] as i64) | ((img[opt+1] as i64) << 8)
306 var dd: i64 = 0
307 if mag == 0x20B { dd = opt + 112 } else { if mag == 0x10B { dd = opt + 96 } else { return 0 - 1 } }
308 let certdir: i64 = dd + 4 * 8
309 var i: i64 = 0
310 while i < ilen { out[i] = img[i]; i = i + 1 }
311 var base: i64 = ilen
312 while (base % 8) != 0 { out[base] = 0 as u8; base = base + 1 } // WIN_CERTIFICATE is 8-aligned
313 let total: i64 = 8 + p7len
314 var pad: i64 = 0
315 while ((total + pad) % 8) != 0 { pad = pad + 1 }
316 ac_w32le(out, base, total + pad)
317 out[base + 4] = 0x00 as u8; out[base + 5] = 0x02 as u8 // wRevision 0x0200
318 out[base + 6] = 0x02 as u8; out[base + 7] = 0x00 as u8 // wCertificateType 0x0002
319 var j: i64 = 0
320 while j < p7len { out[base + 8 + j] = p7[j]; j = j + 1 }
321 var k: i64 = 0
322 while k < pad { out[base + 8 + p7len + k] = 0 as u8; k = k + 1 }
323 ac_w32le(out, certdir, base) // RVA field = FILE OFFSET here
324 ac_w32le(out, certdir + 4, total + pad)
325 return base + total + pad
326}
327
328// =================================================================================================
329func ac_selftest(path: *u8) -> i64 {
330 var pass: i64 = 0
331 var teeth: i64 = 0
332
333 let lp: *i64 = sys_mmap(16) as *i64
334 let img: *u8 = sys_read_file(path, lp)
335 let ilen: i64 = lp[0]
336 if ilen <= 0 { ac_p("AC UNPROVEN: cannot read " as *u8); ac_p(path); ac_p("\n" as *u8); sys_exit(3); return 3 }
337
338 // hash BEFORE signing
339 let h0: *u8 = sys_mmap(64)
340 if pe_authhash(img, ilen, h0) != 0 { ac_p("AC RED: input is not a PE we can hash\n" as *u8); sys_exit(1); return 1 }
341
342 ac_p("TEST-KEY-DO-NOT-USE: generating a deterministic 2048-bit key...\n" as *u8)
343 let pp: *i64 = u2048_alloc(); let qq: *i64 = u2048_alloc()
344 let n: *i64 = u2048_alloc(); let d: *i64 = u2048_alloc()
345 let st: *i64 = sys_mmap(16) as *i64
346 st[0] = AC_MAGIC_20260808
347 if kg_keygen(AC_MAGIC_1024, pp, qq, n, d, st, 1) != 1 { ac_p("AC RED: keygen\n" as *u8); sys_exit(1); return 1 }
348 ac_p("key ready\n" as *u8)
349
350 let cn: *u8 = "Nishi Platform Key" as *u8
351 let cert: *u8 = sys_mmap(AC_BUF)
352 let toff: *i64 = sys_mmap(16) as *i64
353 let tlen: *i64 = sys_mmap(16) as *i64
354 let certlen: i64 = x509_emit_selfsigned(cert, n, d, AC_MAGIC_65537, cn, AC_MAGIC_4097, toff, tlen)
355
356 // T1 SpcIndirectDataContent must be well-formed DER carrying OUR hash, and the incumbent reader
357 // must be able to walk into it.
358 teeth = teeth + 1
359 let spc: *u8 = sys_mmap(AC_MAGIC_2048)
360 let spclen: i64 = ac_spc_indirect(spc, h0)
361 let cur: *Asn1Cursor = sys_mmap(64) as *Asn1Cursor
362 let lenp: *i64 = sys_mmap(16) as *i64
363 asn1_cursor_init(cur, spclen)
364 var t1: i64 = 0
365 if spclen > 0 { if asn1_expect_tag(spc, cur, ASN1_SEQUENCE, lenp) == 0 { t1 = 1 } }
366 // and the 32-byte hash must appear verbatim at the tail
367 var found: i64 = 1
368 var z: i64 = 0
369 while z < 32 { if spc[spclen - 32 + z] != h0[z] { found = 0 } z = z + 1 }
370 if t1 == 1 { if found == 1 { pass = pass + 1
371 ac_p("AC-T1 SpcIndirectDataContent well-formed and carries the authenticode hash GREEN\n" as *u8) }
372 else { ac_p("AC-T1 RED hash not at tail\n" as *u8) } }
373 else { ac_p("AC-T1 RED not DER, len=" as *u8); ac_fn(1, spclen); ac_p("\n" as *u8) }
374
375 // T2 build authenticatedAttributes, sign the SET-TAGGED form, and verify over exactly those bytes.
376 // The embedded copy is tagged [0]; the SIGNED copy is tagged SET. Getting that backwards is the
377 // trap this rung exists to avoid.
378 teeth = teeth + 1
379 let aab: *u8 = sys_mmap(AC_MAGIC_2048)
380 let aalen: i64 = ac_authattrs(aab, spc, spclen)
381 let aaset: *u8 = sys_mmap(AC_MAGIC_2048)
382 let aasetlen: i64 = xe_wrap(aaset, 0, ASN1_SET, aab, aalen)
383 let sig: *u8 = sys_mmap(300)
384 var t2: i64 = 0
385 if rsa_pkcs1_v1_5_sha256_sign(sig, aaset, aasetlen, d, n) == 1 {
386 if rsa_pkcs1_v1_5_sha256_verify(aaset, aasetlen, sig, n, AC_MAGIC_65537) == NX_RSA_PKCS1_V15_OK { t2 = 1 }
387 }
388 if t2 == 1 { pass = pass + 1
389 ac_p("AC-T2 signature over SET-tagged authenticatedAttributes verifies, aalen=" as *u8)
390 ac_fn(1, aalen); ac_p(" GREEN\n" as *u8) }
391 else { ac_p("AC-T2 RED\n" as *u8) }
392
393 // T2b the messageDigest attribute must carry SHA-256 of the SpcIndirectDataContent -- verified by
394 // recomputing it here, not by trusting the builder.
395 teeth = teeth + 1
396 let hchk: *u8 = sys_mmap(64)
397 var hdrc: i64 = 2
398 let lbc: i64 = spc[1] as i64
399 if lbc == 0x81 { hdrc = 3 }
400 if lbc == 0x82 { hdrc = 4 }
401 if lbc == 0x83 { hdrc = 5 }
402 sha256_digest((spc + hdrc) as *u8, spclen - hdrc, hchk) // CONTENT octets, matching EDK2
403 var t2b: i64 = 0
404 var w2: i64 = 0
405 // ⚠`w2 < (aalen - 32)` NEVER TESTS THE LAST WINDOW. With aalen=76 the digest sits at offset 44
406 // and the loop stopped at 43, so the tooth reported RED against correct code.
407 // ★★★★★★A SLIDING-WINDOW SCAN MUST RUN TO `len - width` INCLUSIVE — THE OFF-BY-ONE MAKES A TOOTH
408 // THAT CANNOT PASS, WHICH IS THE MIRROR OF A FIXTURE THAT CANNOT FAIL AND JUST AS USELESS.
409 while w2 <= (aalen - 32) {
410 var m: i64 = 1
411 var z2: i64 = 0
412 while z2 < 32 { if aab[w2 + z2] != hchk[z2] { m = 0 } z2 = z2 + 1 }
413 if m == 1 { t2b = 1 }
414 w2 = w2 + 1
415 }
416 if t2b == 1 { pass = pass + 1; ac_p("AC-T2b messageDigest == SHA-256(SpcIndirectDataContent) GREEN\n" as *u8) }
417 else { ac_p("AC-T2b RED\n" as *u8) }
418
419 // T3 the PKCS#7 blob must be well-formed DER
420 teeth = teeth + 1
421 let p7: *u8 = sys_mmap(AC_BUF)
422 let p7len: i64 = ac_pkcs7(p7, spc, spclen, sig, cert, certlen, cn, AC_MAGIC_4097, aab, aalen)
423 asn1_cursor_init(cur, p7len)
424 if p7len > 0 { if asn1_expect_tag(p7, cur, ASN1_SEQUENCE, lenp) == 0 {
425 if lenp[0] > 0 { pass = pass + 1
426 ac_p("AC-T3 PKCS#7 SignedData is well-formed DER, bytes=" as *u8); ac_fn(1, p7len); ac_p("\n" as *u8) }
427 else { ac_p("AC-T3 RED empty\n" as *u8) } }
428 else { ac_p("AC-T3 RED not a SEQUENCE\n" as *u8) } }
429 else { ac_p("AC-T3 RED len=" as *u8); ac_fn(1, p7len); ac_p("\n" as *u8) }
430
431 // T4 attach it, and check the WIN_CERTIFICATE header + 8-byte alignment + data directory
432 teeth = teeth + 1
433 let out: *u8 = sys_mmap(ilen + AC_BUF + AC_MAGIC_4096)
434 let nlen: i64 = ac_attach(img, ilen, p7, p7len, out)
435 let lfa: i64 = ac_r32le(out, 0x3C)
436 let opt: i64 = lfa + 24
437 let certdir: i64 = opt + 112 + 32
438 let coff: i64 = ac_r32le(out, certdir)
439 let csz: i64 = ac_r32le(out, certdir + 4)
440 var t4: i64 = 1
441 if nlen <= ilen { t4 = 0 }
442 if (coff % 8) != 0 { t4 = 0 }
443 if coff < ilen { t4 = 0 }
444 if (coff + csz) != nlen { t4 = 0 }
445 if ac_r32le(out, coff) != csz { t4 = 0 } // dwLength
446 if (out[coff + 4] as i64) != 0x00 { t4 = 0 }
447 if (out[coff + 5] as i64) != 0x02 { t4 = 0 } // wRevision 0x0200
448 if (out[coff + 6] as i64) != 0x02 { t4 = 0 } // wCertificateType 0x0002
449 if t4 == 1 { pass = pass + 1
450 ac_p("AC-T4 WIN_CERTIFICATE attached, 8-aligned, datadir[4] correct, newlen=" as *u8)
451 ac_fn(1, nlen); ac_p("\n" as *u8) }
452 else { ac_p("AC-T4 RED nlen=" as *u8); ac_fn(1, nlen); ac_p(" coff=" as *u8); ac_fn(1, coff)
453 ac_p(" csz=" as *u8); ac_fn(1, csz); ac_p("\n" as *u8) }
454
455 // T5 ★THE KEYSTONE: the AUTHENTICODE HASH MUST BE UNCHANGED by signing. The cert table and the
456 // data-directory entry are excluded ranges; if this moves, the exclusion logic is wrong and every
457 // signature we produce is invalid. Nothing else in the chain can test this interaction.
458 teeth = teeth + 1
459 let h1: *u8 = sys_mmap(64)
460 var t5: i64 = 0
461 if pe_authhash(out, nlen, h1) == 0 {
462 var same: i64 = 1
463 var y: i64 = 0
464 while y < 32 { if h1[y] != h0[y] { same = 0 } y = y + 1 }
465 if same == 1 { t5 = 1 }
466 }
467 if t5 == 1 { pass = pass + 1
468 ac_p("AC-T5 KEYSTONE: authenticode hash UNCHANGED after signing GREEN\n" as *u8) }
469 else { ac_p("AC-T5 RED [the exclusion ranges are wrong -- every signature would be invalid]\n" as *u8) }
470
471 // T6 NEGATIVE CONTROL for T5: a byte in the CODE must still move the hash after signing, or T5
472 // would be satisfied by a hasher that ignores the file entirely.
473 teeth = teeth + 1
474 let sv: i64 = out[600] as i64
475 out[600] = ((sv ^ 0xff) & 0xff) as u8
476 let h2: *u8 = sys_mmap(64)
477 pe_authhash(out, nlen, h2)
478 var diff: i64 = 0
479 var w: i64 = 0
480 while w < 32 { if h2[w] != h0[w] { diff = 1 } w = w + 1 }
481 out[600] = sv as u8
482 if diff == 1 { pass = pass + 1; ac_p("AC-T6 neg-control-code-byte-still-moves-the-hash GREEN\n" as *u8) }
483 else { ac_p("AC-T6 RED [VACUOUS: hash ignores the image]\n" as *u8) }
484
485 // T7 the embedded certificate must still parse with the incumbent parser after embedding
486 teeth = teeth + 1
487 let xc: *X509Cert = sys_mmap(512) as *X509Cert
488 if x509_parse(cert, certlen, xc) >= 0 { pass = pass + 1
489 ac_p("AC-T7 embedded cert parses GREEN\n" as *u8) }
490 else { ac_p("AC-T7 RED\n" as *u8) }
491
492 // T8 the signed image must still be a structurally valid PE (MZ/PE/subsystem-10 untouched)
493 teeth = teeth + 1
494 var t8: i64 = 1
495 if (out[0] as i64) != 0x4D { t8 = 0 }
496 if (out[1] as i64) != 0x5A { t8 = 0 }
497 if ac_r32le(out, lfa) != 0x00004550 { t8 = 0 }
498 if ((out[opt + 68] as i64) | ((out[opt + 69] as i64) << 8)) != 10 { t8 = 0 }
499 if t8 == 1 { pass = pass + 1; ac_p("AC-T8 signed image is still a subsystem-10 PE32+ GREEN\n" as *u8) }
500 else { ac_p("AC-T8 RED\n" as *u8) }
501
502 // write the signed image so the enrol/boot rung has a real artifact
503 let ofd: i64 = sys_openat_wr("/tmp/nishi_signed.efi" as *u8, 0x1a4)
504 if ofd >= 0 { sys_write(ofd, out, nlen); sys_close(ofd)
505 ac_p("wrote /tmp/nishi_signed.efi bytes=" as *u8); ac_fn(1, nlen); ac_p("\n" as *u8) }
506
507 ac_p("AC-SELFTEST " as *u8); ac_fn(1, pass); ac_p("/" as *u8); ac_fn(1, teeth); ac_p("\n" as *u8)
508 let lf: i64 = sys_openat_append("knowledge/status/nishi_os.log" as *u8, 0x1a4)
509 if lf >= 0 {
510 ac_fp(lf, "AUTHENTICODE selftest teeth=" as *u8); ac_fn(lf, pass)
511 ac_fp(lf, "of" as *u8); ac_fn(lf, teeth)
512 ac_fp(lf, " scope=well-formed-and-self-consistent-NOT-yet-edk2-verified verdict=" as *u8)
513 if pass == teeth { ac_fp(lf, "GREEN\n" as *u8) } else { ac_fp(lf, "RED\n" as *u8) }
514 sys_close(lf)
515 }
516 if pass == teeth { sys_exit(0); return 0 }
517 sys_exit(1); return 1
518}
519
520func main(argc: i64, argv: *i64) -> i64 {
521 var path: *u8 = "_offc/nx_boot_uefi.efi" as *u8
522 if argc >= 3 { path = argv[2] as *u8 }
523 if argc >= 2 { if ac_eq_str(argv[1] as *u8, "selftest" as *u8) == 1 { return ac_selftest(path) } }
524 ac_p("usage: nx_authenticode_sign selftest [image.efi]\n" as *u8)
525 sys_exit(2); return 2
526}