code wiki / _hdl_build / nx_rsa_pkcs1_sign.nx

nx_rsa_pkcs1_sign.nx source

↩ module page · 229 lines · 10503 B

1// nx_rsa_pkcs1_sign.nx -- F103e RUNG 8: RSASSA-PKCS1-v1_5 SIGNING with SHA-256. 2// 3// The estate shipped the VERIFY half years-equivalent ago and never the sign half, because its 4// modexp took the exponent as an i64 (fine for e = 65537, impossible for d ~2048 bits). Rung 4 5// removed that; rung 7 produces the key. This is the join. 6// 7// RFC 8017 EMSA-PKCS1-v1_5, k = 256 (RSA-2048): 8// EM = 0x00 || 0x01 || PS(0xFF * 202) || 0x00 || DigestInfo(19) || H(32) = 256 bytes 9// s = EM^d mod n 10// 11// ★THE 19-BYTE DIGESTINFO PREFIX IS COMPOSED, NOT RETYPED. `rsa_pkcs1_sha256_di_byte` already 12// encodes it in the incumbent verifier; copying those bytes into this file by hand would be a 13// recalled constant AND a second source of truth that can drift. ★★★★★★**IF THE VERIFIER AND THE 14// SIGNER DISAGREE ABOUT A CONSTANT, EVERY SIGNATURE IS INVALID AND BOTH FILES LOOK CORRECT.** 15// 16// ★THE ORACLE IS THE INCUMBENT VERIFIER. Every tooth signs with this code and verifies with 17// `rsa_pkcs1_v1_5_sha256_verify` -- two independently written halves meeting in the middle. That is 18// a far stronger statement than any self-check, and it is why no golden vector is needed for the 19// round trip. ⚠It does NOT prove byte-agreement with another implementation's signatures; PKCS#1 20// v1.5 is deterministic so that IS checkable later against a known vector, and is named as owed. 21// 22// Usage: nx_rsa_pkcs1_sign selftest (~5 min: generates a real 2048-bit modulus first) 23// Exit: 0 GREEN | 1 RED. Log -> knowledge/status/nishi_os.log, verdict= LAST. 24// license_tier: ORIGINAL 25import "nx_syscalls.nx" 26import "nx_sha256.nx" 27import "nx_u2048.nx" 28import "nx_u2048_mul.nx" 29import "nx_rsa2048_mod.nx" 30import "nx_rsa2048_mod_exp.nx" 31import "nx_rsa2048_mont.nx" 32import "nx_rsa2048_mod_exp_big.nx" 33import "nx_rsa_pkcs1_v1_5_sha256.nx" 34import "nx_u2048_millerrabin.nx" 35import "nx_u2048_smallops.nx" 36import "nx_rsa_keygen.nx" 37const PS_MAGIC_20260808: i64 = 20260808 38const PS_MAGIC_1024: i64 = 1024 39const PS_MAGIC_65537: i64 = 65537 40 41const PS_K: i64 = 256 // RSA-2048 modulus bytes 42const PS_TLEN: i64 = 51 // 19 DigestInfo + 32 SHA-256 43const PS_MINPAD: i64 = 8 // RFC 8017 floor for PS 44 45func ps_p(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 } 46func ps_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 } 47func ps_fn(fd: i64, v: i64) -> i64 { 48 let bb: *u8 = sys_mmap(28); var m: i64 = v 49 if m < 0 { m = 0 - m; bb[0] = 45 as u8; sys_write(fd, bb, 1) } 50 let t: *u8 = sys_mmap(28); var k: i64 = 0 51 if m == 0 { t[0] = 48 as u8; k = 1 } 52 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 53 var i: i64 = 0 54 while i < k { bb[i] = t[k - 1 - i]; i = i + 1 } 55 sys_write(fd, bb, k); return 0 56} 57func ps_eq_str(a: *u8, b: *u8) -> i64 { 58 var i: i64 = 0 59 while a[i] != (0 as u8) { if a[i] != b[i] { return 0 } i = i + 1 } 60 if b[i] != (0 as u8) { return 0 } 61 return 1 62} 63 64// Build EM in place (256 bytes). Returns 0 ok, negative = a NAMED refusal. 65func rsa_pkcs1_v1_5_sha256_em(em: *u8, msg: *u8, msg_len: i64) -> i64 { 66 let pslen: i64 = PS_K - 3 - PS_TLEN 67 if pslen < PS_MINPAD { return 0 - 1 } // modulus too small for this hash 68 em[0] = 0x00 as u8 69 em[1] = 0x01 as u8 70 var i: i64 = 0 71 while i < pslen { em[2 + i] = 0xFF as u8; i = i + 1 } 72 em[2 + pslen] = 0x00 as u8 73 var j: i64 = 0 74 while j < 19 { // DigestInfo, from the INCUMBENT's table 75 em[3 + pslen + j] = (rsa_pkcs1_sha256_di_byte(j) & 0xff) as u8 76 j = j + 1 77 } 78 sha256_digest(msg, msg_len, (em + 3 + pslen + 19) as *u8) 79 return 0 80} 81 82// sig_out: 256 big-endian bytes. Returns 1 on success. 83func rsa_pkcs1_v1_5_sha256_sign(sig_out: *u8, msg: *u8, msg_len: i64, d: *i64, n: *i64) -> i64 { 84 let em: *u8 = sys_mmap(PS_K + 16) 85 if rsa_pkcs1_v1_5_sha256_em(em, msg, msg_len) != 0 { return 0 } 86 let m_int: *i64 = u2048_alloc() 87 u2048_load_be(m_int, em) 88 let s_int: *i64 = u2048_alloc() 89 if rsa2048_mod_exp_big(s_int, m_int, d, n) != 1 { return 0 } 90 u2048_store_be(sig_out, s_int) 91 return 1 92} 93 94// ================================================================================================= 95func ps_selftest() -> i64 { 96 var pass: i64 = 0 97 var teeth: i64 = 0 98 99 // A REAL 2048-bit modulus. The incumbent verifier is hardcoded to k=256, so nothing smaller can 100 // be used as the oracle -- the ~5 minutes is the price of testing against the real thing rather 101 // than against a convenient toy. ★A CHEAPER FIXTURE THAT THE ORACLE CANNOT READ IS NOT CHEAPER. 102 ps_p("TEST-KEY-DO-NOT-USE: generating a deterministic 2048-bit modulus for the teeth...\n" as *u8) 103 let p: *i64 = u2048_alloc() 104 let q: *i64 = u2048_alloc() 105 let n: *i64 = u2048_alloc() 106 let d: *i64 = u2048_alloc() 107 let st: *i64 = sys_mmap(16) as *i64 108 st[0] = PS_MAGIC_20260808 109 if kg_keygen(PS_MAGIC_1024, p, q, n, d, st, 1) != 1 { 110 ps_p("PS RED: keygen failed, teeth cannot run\n" as *u8) 111 sys_exit(1); return 1 112 } 113 ps_p("key ready\n" as *u8) 114 115 let msg: *u8 = sys_mmap(64) 116 let m1: *u8 = "NISHI UEFI image, F103e signing rung" as *u8 117 var L: i64 = 0 118 while m1[L] != (0 as u8) { msg[L] = m1[L]; L = L + 1 } 119 let sig: *u8 = sys_mmap(PS_K + 16) 120 121 // T1 THE JOIN: our signer, their verifier. 122 teeth = teeth + 1 123 var ok: i64 = 0 124 if rsa_pkcs1_v1_5_sha256_sign(sig, msg, L, d, n) == 1 { 125 if rsa_pkcs1_v1_5_sha256_verify(msg, L, sig, n, PS_MAGIC_65537) == NX_RSA_PKCS1_V15_OK { ok = 1 } 126 } 127 if ok == 1 { pass = pass + 1; ps_p("PS-T1 sign->incumbent-verify GREEN\n" as *u8) } 128 else { ps_p("PS-T1 RED\n" as *u8) } 129 130 // T2 NEGATIVE CONTROL: corrupt ONE signature byte -> verify must refuse. 131 teeth = teeth + 1 132 let sv: i64 = sig[200] as i64 133 sig[200] = ((sv ^ 0xff) & 0xff) as u8 134 var moved: i64 = 0 135 if (sig[200] as i64) != sv { moved = 1 } // assert the fixture reached the condition 136 let v2: i64 = rsa_pkcs1_v1_5_sha256_verify(msg, L, sig, n, PS_MAGIC_65537) 137 if moved == 1 { if v2 != NX_RSA_PKCS1_V15_OK { pass = pass + 1 138 ps_p("PS-T2 neg-control-corrupt-signature-refused GREEN\n" as *u8) } 139 else { ps_p("PS-T2 RED [VACUOUS: verifier accepts a corrupted signature]\n" as *u8) } } 140 else { ps_p("PS-T2 RED [fixture unchanged]\n" as *u8) } 141 sig[200] = sv as u8 142 143 // T3 restore must be exact, or every later tooth measures debris 144 teeth = teeth + 1 145 if rsa_pkcs1_v1_5_sha256_verify(msg, L, sig, n, PS_MAGIC_65537) == NX_RSA_PKCS1_V15_OK { pass = pass + 1 146 ps_p("PS-T3 restore-exact GREEN\n" as *u8) } 147 else { ps_p("PS-T3 RED\n" as *u8) } 148 149 // T4 NEGATIVE CONTROL: same signature, DIFFERENT message -> refuse. 150 teeth = teeth + 1 151 msg[0] = ((msg[0] as i64) ^ 0x20) as u8 152 if rsa_pkcs1_v1_5_sha256_verify(msg, L, sig, n, PS_MAGIC_65537) != NX_RSA_PKCS1_V15_OK { pass = pass + 1 153 ps_p("PS-T4 neg-control-message-tamper-refused GREEN\n" as *u8) } 154 else { ps_p("PS-T4 RED\n" as *u8) } 155 msg[0] = ((msg[0] as i64) ^ 0x20) as u8 156 157 // T5 the EM structure itself -- a padding bug still verifies against a matching bug, so assert 158 // the bytes RFC 8017 specifies rather than only the round trip. 159 teeth = teeth + 1 160 let em: *u8 = sys_mmap(PS_K + 16) 161 rsa_pkcs1_v1_5_sha256_em(em, msg, L) 162 var sok: i64 = 1 163 if (em[0] as i64) != 0x00 { sok = 0 } 164 if (em[1] as i64) != 0x01 { sok = 0 } 165 let pslen: i64 = PS_K - 3 - PS_TLEN 166 var i: i64 = 0 167 while i < pslen { if (em[2 + i] as i64) != 0xFF { sok = 0 } i = i + 1 } 168 if (em[2 + pslen] as i64) != 0x00 { sok = 0 } 169 if (em[3 + pslen] as i64) != 0x30 { sok = 0 } // DigestInfo SEQUENCE 170 if (em[3 + pslen + 18] as i64) != 0x20 { sok = 0 } // OCTET STRING length 32 171 if pslen != 202 { sok = 0 } 172 if sok == 1 { pass = pass + 1; ps_p("PS-T5 EM structure matches RFC 8017 (PS=202) GREEN\n" as *u8) } 173 else { ps_p("PS-T5 RED\n" as *u8) } 174 175 // T6 the DigestInfo in EM must equal the INCUMBENT's table byte-for-byte -- the drift check. 176 teeth = teeth + 1 177 var dok: i64 = 1 178 var j: i64 = 0 179 while j < 19 { 180 if (em[3 + pslen + j] as i64) != (rsa_pkcs1_sha256_di_byte(j) & 0xff) { dok = 0 } 181 j = j + 1 182 } 183 if dok == 1 { pass = pass + 1; ps_p("PS-T6 digestinfo-composed-from-incumbent GREEN\n" as *u8) } 184 else { ps_p("PS-T6 RED\n" as *u8) } 185 186 // T7 a second message signs and verifies, and its signature DIFFERS from the first 187 teeth = teeth + 1 188 let msg2: *u8 = "a different payload entirely" as *u8 189 var L2: i64 = 0 190 while msg2[L2] != (0 as u8) { L2 = L2 + 1 } 191 let sig2: *u8 = sys_mmap(PS_K + 16) 192 var t7: i64 = 0 193 if rsa_pkcs1_v1_5_sha256_sign(sig2, msg2, L2, d, n) == 1 { 194 if rsa_pkcs1_v1_5_sha256_verify(msg2, L2, sig2, n, PS_MAGIC_65537) == NX_RSA_PKCS1_V15_OK { 195 var diff: i64 = 0 196 var z: i64 = 0 197 while z < PS_K { if sig2[z] != sig[z] { diff = 1 } z = z + 1 } 198 if diff == 1 { t7 = 1 } 199 } 200 } 201 if t7 == 1 { pass = pass + 1; ps_p("PS-T7 second-message signs, verifies, differs GREEN\n" as *u8) } 202 else { ps_p("PS-T7 RED\n" as *u8) } 203 204 // T8 DETERMINISM: PKCS#1 v1.5 has no randomiser, so signing twice must be byte-identical. 205 teeth = teeth + 1 206 let sig3: *u8 = sys_mmap(PS_K + 16) 207 rsa_pkcs1_v1_5_sha256_sign(sig3, msg, L, d, n) 208 var same: i64 = 1 209 var y: i64 = 0 210 while y < PS_K { if sig3[y] != sig[y] { same = 0 } y = y + 1 } 211 if same == 1 { pass = pass + 1; ps_p("PS-T8 deterministic GREEN\n" as *u8) } 212 else { ps_p("PS-T8 RED\n" as *u8) } 213 214 ps_p("PS-SELFTEST " as *u8); ps_fn(1, pass); ps_p("/" as *u8); ps_fn(1, teeth); ps_p("\n" as *u8) 215 let lf: i64 = sys_openat_append("knowledge/status/nishi_os.log" as *u8, 0x1a4) 216 if lf >= 0 { 217 ps_fp(lf, "RSAPKCS1SIGN selftest teeth=" as *u8); ps_fn(lf, pass) 218 ps_fp(lf, "of" as *u8); ps_fn(lf, teeth) 219 ps_fp(lf, " oracle=incumbent-rsa_pkcs1_v1_5_sha256_verify verdict=" as *u8) 220 if pass == teeth { ps_fp(lf, "GREEN\n" as *u8) } else { ps_fp(lf, "RED\n" as *u8) } 221 sys_close(lf) 222 } 223 if pass == teeth { sys_exit(0); return 0 } 224 sys_exit(1); return 1 225} 226 227func main(argc: i64, argv: *i64) -> i64 { 228 return ps_selftest() 229}