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}