code wiki / _hdl_build / nx_rsa_keygen.nx
nx_rsa_keygen.nx source
↩ module page · 362 lines · 15879 B
1// nx_rsa_keygen.nx -- F103e RUNG 7: RSA key generation.
2//
3// The credential F103e has been walking toward. Real EDK2 with MS keys refuses our unsigned .efi
4// (Access Denied) while the SAME secboot firmware in SETUP mode runs it, so the blocker is the
5// enrolled key set (debt 1786237435). Enrolling OUR key requires HAVING one -- and it must be
6// GENERATED, never recited: an RSA modulus quoted from memory is a fabricated constant.
7//
8// Dependencies, both built earlier in this same arc because neither existed:
9// nx_u2048_millerrabin -- primality (needs a^d mod n with d ~1024 bits)
10// nx_rsa2048_mod_exp_big -- that modexp (the incumbent takes the exponent as an i64)
11// nx_u2048_smallops -- d = (k*phi + 1)/e without a general extended GCD
12//
13// ⚠THE WIDTH TRAP, found by arithmetic and not by testing: for 1024-bit primes phi is ~2048 bits, so
14// phi*k with k < e = 65537 is ~2065 bits and OVERFLOWS the u2048 width. The d-recovery therefore
15// runs in the WIDE (128-limb) form. ★★★★★**AN INTERMEDIATE THAT OVERFLOWS ONLY AT PRODUCTION SIZE
16// IS INVISIBLE TO EVERY SMALL-PARAMETER TEST** -- so the wide helpers are cross-checked against the
17// shipped 64-limb smallops on 64-limb inputs (T2), making them the same ruler at two widths.
18//
19// ★THE CORRECTNESS PROOF NEEDS NO EXTERNAL ORACLE: a keypair is valid iff (m^e)^d == m mod n for
20// messages we choose. That is checked for several m, INCLUDING m=1 and m=n-1 which any broken key
21// still satisfies -- so a non-trivial m is required for the tooth to mean anything.
22//
23// Usage: nx_rsa_keygen selftest (fast, small parameters -- proves the algorithm)
24// nx_rsa_keygen gen <bits> (real key from the CSPRNG, printed as hex)
25// ⚠the selftest uses a DETERMINISTIC generator so the teeth are reproducible. That path prints
26// TEST-KEY-DO-NOT-USE and is unreachable from `gen`. ★A DETERMINISTIC KEY IS A TEST FIXTURE AND A
27// SECURITY DEFECT WEARING THE SAME BYTES -- the two paths must never share an exit.
28// Exit: 0 GREEN | 1 RED. Log -> knowledge/status/nishi_os.log, verdict= LAST.
29// license_tier: ORIGINAL
30import "nx_syscalls.nx"
31import "nx_u2048.nx"
32import "nx_u2048_mul.nx"
33import "nx_rsa2048_mod.nx"
34import "nx_rsa2048_mod_exp.nx"
35import "nx_rsa2048_mod_exp_big.nx"
36import "nx_u2048_millerrabin.nx"
37import "nx_u2048_smallops.nx"
38import "nx_csprng.nx"
39const KG_MAGIC_88172645463325252: i64 = 88172645463325252
40const KG_MAGIC_20260808: i64 = 20260808
41const KG_MAGIC_123456789012345: i64 = 123456789012345
42const KG_MAGIC_65537: i64 = 65537
43const KG_MAGIC_1024: i64 = 1024
44
45const KG_E: i64 = 65537
46const KG_WIDE: i64 = 128
47const KG_MAXTRY: i64 = 20000
48
49func kg_p(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 }
50func kg_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 }
51func kg_fn(fd: i64, v: i64) -> i64 {
52 let bb: *u8 = sys_mmap(28); var m: i64 = v
53 if m < 0 { m = 0 - m; bb[0] = 45 as u8; sys_write(fd, bb, 1) }
54 let t: *u8 = sys_mmap(28); var k: i64 = 0
55 if m == 0 { t[0] = 48 as u8; k = 1 }
56 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
57 var i: i64 = 0
58 while i < k { bb[i] = t[k - 1 - i]; i = i + 1 }
59 sys_write(fd, bb, k); return 0
60}
61func kg_hex(x: *i64) -> i64 {
62 let hx: *u8 = "0123456789abcdef" as *u8
63 let b: *u8 = sys_mmap(256)
64 u2048_store_be(b, x)
65 let o: *u8 = sys_mmap(600)
66 var i: i64 = 0
67 while i < 256 {
68 o[i * 2] = hx[(b[i] as i64) >> 4]
69 o[i * 2 + 1] = hx[(b[i] as i64) & 0xf]
70 i = i + 1
71 }
72 sys_write(1, o, 512); return 0
73}
74func kg_eq_str(a: *u8, b: *u8) -> i64 {
75 var i: i64 = 0
76 while a[i] != (0 as u8) { if a[i] != b[i] { return 0 } i = i + 1 }
77 if b[i] != (0 as u8) { return 0 }
78 return 1
79}
80
81// ---- generic big-by-small over N limbs. Same arithmetic as nx_u2048_smallops, parameterised by
82// ---- width because the d-recovery needs 128 limbs. T2 pins the two widths to each other.
83func kg_mul_small_n(out: *i64, a: *i64, k: i64, nl: i64) -> i64 {
84 var i: i64 = 0
85 var carry: i64 = 0
86 while i < nl {
87 let p: i64 = (a[i] & NX_U2048_LIMB_MASK) * k + carry
88 out[i] = p & NX_U2048_LIMB_MASK
89 carry = (p >> 32) & NX_U2048_LIMB_MASK
90 i = i + 1
91 }
92 return carry
93}
94func kg_div_small_n(out: *i64, a: *i64, m: i64, nl: i64) -> i64 {
95 if m <= 0 { return 0 - 1 }
96 var rem: i64 = 0
97 var i: i64 = nl - 1
98 while i >= 0 {
99 let cur: i64 = (rem << 32) | (a[i] & NX_U2048_LIMB_MASK)
100 out[i] = cur / m
101 rem = cur % m
102 i = i - 1
103 }
104 return rem
105}
106func kg_mod_small_n(a: *i64, m: i64, nl: i64) -> i64 {
107 if m <= 0 { return 0 - 1 }
108 var rem: i64 = 0
109 var i: i64 = nl - 1
110 while i >= 0 {
111 let cur: i64 = (rem << 32) | (a[i] & NX_U2048_LIMB_MASK)
112 rem = cur % m
113 i = i - 1
114 }
115 return rem
116}
117func kg_add_small_n(out: *i64, a: *i64, k: i64, nl: i64) -> i64 {
118 var i: i64 = 0
119 var carry: i64 = k
120 while i < nl {
121 let s: i64 = (a[i] & NX_U2048_LIMB_MASK) + carry
122 out[i] = s & NX_U2048_LIMB_MASK
123 carry = (s >> 32) & NX_U2048_LIMB_MASK
124 i = i + 1
125 }
126 return carry
127}
128func kg_wide_zero(x: *i64) -> i64 { var i: i64 = 0; while i < KG_WIDE { x[i] = 0; i = i + 1 } return 0 }
129
130// ---- deterministic byte source for the TEST path (xorshift64*), and the CSPRNG for real keys.
131func kg_rng_next(st: *i64) -> i64 {
132 var x: i64 = st[0]
133 x = x ^ ((x << 13) & 0xFFFFFFFFFFFFFF)
134 x = x ^ (x >> 7)
135 x = x ^ ((x << 17) & 0xFFFFFFFFFFFFFF)
136 if x == 0 { x = KG_MAGIC_88172645463325252 }
137 st[0] = x
138 return x & 0xFFFFFFFF
139}
140func kg_fill(dst: *u8, n: i64, st: *i64, deterministic: i64) -> i64 {
141 if deterministic == 0 { return nx_csprng_fill(dst, n) }
142 var i: i64 = 0
143 while i < n { dst[i] = (kg_rng_next(st) & 0xff) as u8; i = i + 1 }
144 return 0
145}
146
147// small-prime trial division: rejects ~80% of odd candidates far cheaper than Miller-Rabin
148func kg_trial_reject(c: *i64) -> i64 {
149 let sp: *i64 = sys_mmap(512) as *i64
150 sp[0]=3; sp[1]=5; sp[2]=7; sp[3]=11; sp[4]=13; sp[5]=17; sp[6]=19; sp[7]=23
151 sp[8]=29; sp[9]=31; sp[10]=37; sp[11]=41; sp[12]=43; sp[13]=47; sp[14]=53; sp[15]=59
152 sp[16]=61; sp[17]=67; sp[18]=71; sp[19]=73; sp[20]=79; sp[21]=83; sp[22]=89; sp[23]=97
153 var i: i64 = 0
154 while i < 24 {
155 if u2048_mod_small(c, sp[i]) == 0 { return 1 }
156 i = i + 1
157 }
158 return 0
159}
160
161// generate a probable prime of exactly `bits` bits (top two bits set, odd).
162// Returns the number of candidates examined, or -1 on exhaustion (REFUSED, never a weak key).
163func kg_gen_prime(out: *i64, bits: i64, st: *i64, deterministic: i64, bases: *i64, nb: i64) -> i64 {
164 let nbytes: i64 = bits / 8
165 let buf: *u8 = sys_mmap(256)
166 var tries: i64 = 0
167 while tries < KG_MAXTRY {
168 tries = tries + 1
169 var i: i64 = 0
170 while i < 256 { buf[i] = 0 as u8; i = i + 1 }
171 kg_fill(buf + (256 - nbytes), nbytes, st, deterministic)
172 buf[256 - nbytes] = ((buf[256 - nbytes] as i64) | 0xC0) as u8 // top two bits -> exact size
173 buf[255] = ((buf[255] as i64) | 1) as u8 // odd
174 u2048_load_be(out, buf)
175 if kg_trial_reject(out) == 0 {
176 if u2048_is_probable_prime(out, bases, nb) == 1 { return tries }
177 }
178 }
179 return 0 - 1
180}
181
182// full keypair. Returns 1 on success, 0 on failure (never a partial key).
183func kg_keygen(bits: i64, p: *i64, q: *i64, n: *i64, d: *i64, st: *i64, deterministic: i64) -> i64 {
184 let bases: *i64 = sys_mmap(64) as *i64
185 bases[0]=2; bases[1]=3; bases[2]=5; bases[3]=7; bases[4]=11
186 let one: *i64 = u2048_alloc(); u2048_one(one)
187 let p1: *i64 = u2048_alloc()
188 let q1: *i64 = u2048_alloc()
189 let wide: *i64 = u2048_wide_alloc()
190 let w2: *i64 = u2048_wide_alloc()
191 let w3: *i64 = u2048_wide_alloc()
192
193 var attempts: i64 = 0
194 while attempts < 40 {
195 attempts = attempts + 1
196 if kg_gen_prime(p, bits, st, deterministic, bases, 5) < 0 { return 0 }
197 if kg_gen_prime(q, bits, st, deterministic, bases, 5) < 0 { return 0 }
198 if u2048_eq(p, q) == 1 { attempts = attempts } else {
199 u2048_mul_wide(wide, p, q)
200 u2048_wide_copy_low(n, wide)
201 u2048_sub_with_borrow(p1, p, one)
202 u2048_sub_with_borrow(q1, q, one)
203 // phi = (p-1)(q-1), kept WIDE: at production size phi*k exceeds 2048 bits
204 u2048_mul_wide(w2, p1, q1)
205 let L: i64 = kg_mod_small_n(w2, KG_E, KG_WIDE)
206 if L != 0 {
207 let inv: i64 = i64_modinv_small(L, KG_E)
208 if inv > 0 {
209 var k: i64 = (KG_E - inv) % KG_E
210 if k == 0 { k = KG_E }
211 // d = (phi*k + 1) / e -- all wide, because phi*k is ~2065 bits
212 kg_mul_small_n(w3, w2, k, KG_WIDE)
213 kg_add_small_n(w3, w3, 1, KG_WIDE)
214 let r: i64 = kg_div_small_n(wide, w3, KG_E, KG_WIDE)
215 if r == 0 { // exact division is REQUIRED; otherwise retry
216 u2048_wide_copy_low(d, wide)
217 return 1
218 }
219 }
220 }
221 }
222 }
223 return 0
224}
225
226// the only correctness statement that matters: (m^e)^d == m mod n
227func kg_roundtrip_ok(n: *i64, d: *i64, m: *i64) -> i64 {
228 let c: *i64 = u2048_alloc()
229 let back: *i64 = u2048_alloc()
230 if rsa2048_mod_exp(c, m, KG_E, n) != 1 { return 0 }
231 if rsa2048_mod_exp_big(back, c, d, n) != 1 { return 0 }
232 return u2048_eq(back, m)
233}
234
235// =================================================================================================
236func kg_selftest() -> i64 {
237 var pass: i64 = 0
238 var teeth: i64 = 0
239 let st: *i64 = sys_mmap(16) as *i64
240 st[0] = KG_MAGIC_20260808
241
242 kg_p("TEST-KEY-DO-NOT-USE: the selftest generator is DETERMINISTIC by design\n" as *u8)
243
244 let p: *i64 = u2048_alloc()
245 let q: *i64 = u2048_alloc()
246 let n: *i64 = u2048_alloc()
247 let d: *i64 = u2048_alloc()
248
249 // T1 generate a keypair at a small-but-real size and prove it round-trips on a NON-TRIVIAL m.
250 teeth = teeth + 1
251 let got: i64 = kg_keygen(128, p, q, n, d, st, 1)
252 var ok: i64 = 0
253 if got == 1 {
254 let m: *i64 = u2048_alloc()
255 let b: *u8 = sys_mmap(256)
256 var i: i64 = 0
257 while i < 256 { b[i] = 0 as u8; i = i + 1 }
258 b[240] = 0x2A as u8; b[250] = 0x99 as u8; b[255] = 0x07 as u8 // non-trivial, << n
259 u2048_load_be(m, b)
260 if kg_roundtrip_ok(n, d, m) == 1 { ok = 1 }
261 }
262 if ok == 1 { pass = pass + 1; kg_p("KG-T1 keygen-128 round-trips (m^e)^d==m GREEN\n" as *u8) }
263 else { kg_p("KG-T1 RED keygen_rc=" as *u8); kg_fn(1, got); kg_p("\n" as *u8) }
264
265 // T2 the WIDE helpers must agree with the shipped 64-limb smallops on 64-limb inputs.
266 // Without this the production-only width is a second, unverified ruler.
267 teeth = teeth + 1
268 let a64: *i64 = u2048_alloc()
269 let r1: *i64 = u2048_alloc()
270 let r2: *i64 = u2048_alloc()
271 so_set_i64(a64, KG_MAGIC_123456789012345)
272 let m1: i64 = u2048_mod_small(a64, KG_MAGIC_65537)
273 let m2: i64 = kg_mod_small_n(a64, KG_MAGIC_65537, NX_U2048_LIMBS)
274 let d1: i64 = u2048_div_small(r1, a64, KG_MAGIC_65537)
275 let d2: i64 = kg_div_small_n(r2, a64, KG_MAGIC_65537, NX_U2048_LIMBS)
276 if m1 == m2 { if d1 == d2 { if u2048_eq(r1, r2) == 1 { pass = pass + 1
277 kg_p("KG-T2 wide-helpers agree with shipped smallops GREEN\n" as *u8) }
278 else { kg_p("KG-T2 RED quotient differs\n" as *u8) } }
279 else { kg_p("KG-T2 RED remainder differs\n" as *u8) } }
280 else { kg_p("KG-T2 RED mod differs\n" as *u8) }
281
282 // T3 the generated primes must actually BE prime by the independent Miller-Rabin, and distinct.
283 teeth = teeth + 1
284 let bs: *i64 = sys_mmap(64) as *i64
285 bs[0]=2; bs[1]=3; bs[2]=5; bs[3]=7; bs[4]=11; bs[5]=13; bs[6]=17
286 var t3: i64 = 1
287 if u2048_is_probable_prime(p, bs, 7) != 1 { t3 = 0 }
288 if u2048_is_probable_prime(q, bs, 7) != 1 { t3 = 0 }
289 if u2048_eq(p, q) == 1 { t3 = 0 }
290 if t3 == 1 { pass = pass + 1; kg_p("KG-T3 p,q prime under 7 bases and distinct GREEN\n" as *u8) }
291 else { kg_p("KG-T3 RED\n" as *u8) }
292
293 // T4 ANTI-VACUITY / NEGATIVE CONTROL: a WRONG private exponent must FAIL the round trip.
294 // Without this, a round-trip check that always returned 1 would pass T1.
295 teeth = teeth + 1
296 let dbad: *i64 = u2048_alloc()
297 u2048_copy(dbad, d)
298 dbad[0] = (dbad[0] ^ 1) & NX_U2048_LIMB_MASK // flip one bit of d
299 let m2b: *i64 = u2048_alloc()
300 let bb2: *u8 = sys_mmap(256)
301 var j: i64 = 0
302 while j < 256 { bb2[j] = 0 as u8; j = j + 1 }
303 bb2[248] = 0x11 as u8; bb2[255] = 0x0D as u8
304 u2048_load_be(m2b, bb2)
305 if kg_roundtrip_ok(n, dbad, m2b) == 0 { pass = pass + 1
306 kg_p("KG-T4 neg-control-wrong-d-fails GREEN\n" as *u8) }
307 else { kg_p("KG-T4 RED [VACUOUS: the round trip accepts a corrupted d]\n" as *u8) }
308
309 // T5 n must have exactly 2*bits bits (top bit of the product set), i.e. the size is what we asked
310 teeth = teeth + 1
311 if u2048_get_bit(n, 2 * 128 - 1) == 1 { pass = pass + 1
312 kg_p("KG-T5 modulus is the requested width GREEN\n" as *u8) }
313 else { kg_p("KG-T5 RED modulus short\n" as *u8) }
314
315 // T6 a second, independent m -- one round trip could coincide; two on different values is a claim
316 teeth = teeth + 1
317 let m3: *i64 = u2048_alloc()
318 let b3: *u8 = sys_mmap(256)
319 var z: i64 = 0
320 while z < 256 { b3[z] = 0 as u8; z = z + 1 }
321 b3[235] = 0x5C as u8; b3[244] = 0xA1 as u8; b3[255] = 0x3B as u8
322 u2048_load_be(m3, b3)
323 if kg_roundtrip_ok(n, d, m3) == 1 { pass = pass + 1; kg_p("KG-T6 second-message round-trips GREEN\n" as *u8) }
324 else { kg_p("KG-T6 RED\n" as *u8) }
325
326 kg_p("KG-SELFTEST " as *u8); kg_fn(1, pass); kg_p("/" as *u8); kg_fn(1, teeth); kg_p("\n" as *u8)
327 let lf: i64 = sys_openat_append("knowledge/status/nishi_os.log" as *u8, 0x1a4)
328 if lf >= 0 {
329 kg_fp(lf, "RSAKEYGEN selftest teeth=" as *u8); kg_fn(lf, pass)
330 kg_fp(lf, "of" as *u8); kg_fn(lf, teeth)
331 kg_fp(lf, " gen=deterministic-TEST-ONLY verdict=" as *u8)
332 if pass == teeth { kg_fp(lf, "GREEN\n" as *u8) } else { kg_fp(lf, "RED\n" as *u8) }
333 sys_close(lf)
334 }
335 if pass == teeth { sys_exit(0); return 0 }
336 sys_exit(1); return 1
337}
338
339func main(argc: i64, argv: *i64) -> i64 {
340 if argc >= 2 { if kg_eq_str(argv[1] as *u8, "selftest" as *u8) == 1 { return kg_selftest() } }
341 if argc >= 3 { if kg_eq_str(argv[1] as *u8, "gen" as *u8) == 1 {
342 var bits: i64 = 0
343 let a: *u8 = argv[2] as *u8
344 var i: i64 = 0
345 while a[i] != (0 as u8) { bits = bits * 10 + ((a[i] as i64) - 48); i = i + 1 }
346 if bits < 64 { kg_p("REFUSED: bits too small to be a key\n" as *u8); sys_exit(2); return 2 }
347 if bits > KG_MAGIC_1024 { kg_p("REFUSED: bits exceeds the u2048 modulus width\n" as *u8); sys_exit(2); return 2 }
348 let p: *i64 = u2048_alloc(); let q: *i64 = u2048_alloc()
349 let n: *i64 = u2048_alloc(); let d: *i64 = u2048_alloc()
350 let st: *i64 = sys_mmap(16) as *i64
351 st[0] = 1
352 if kg_keygen(bits, p, q, n, d, st, 0) != 1 { // deterministic=0 -> CSPRNG
353 kg_p("KEYGEN FAILED\n" as *u8); sys_exit(1); return 1
354 }
355 kg_p("n=" as *u8); kg_hex(n); kg_p("\n" as *u8)
356 kg_p("d=" as *u8); kg_hex(d); kg_p("\n" as *u8)
357 kg_p("e=65537 bits=" as *u8); kg_fn(1, bits); kg_p("\n" as *u8)
358 sys_exit(0); return 0
359 } }
360 kg_p("usage: nx_rsa_keygen selftest | nx_rsa_keygen gen <bits>\n" as *u8)
361 sys_exit(2); return 2
362}