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}