nx_jwk_ec.nx source
↩ module page · 101 lines · 3898 B
1// nx_jwk_ec.nx -- EC P-256 public-key JWK (RFC 7518 §6.2) + RFC 7638
2// canonical thumbprint. Needed by the ACME client: the new-account
3// JWS "jwk" header embeds the account public key as an EC JWK, and the
4// DNS-01 key authorization is token "." base64url(SHA-256(thumbprint)).
5//
6// Standalone (no nx_jose import) so the graph stays on nx_syscalls.nx.
7// REUSABLE: any EC-P256 JWK consumer (JWT, OIDC, WebAuthn attestation).
8//
9// EC JWK (RFC 7518 §6.2.1):
10// {"crv":"P-256","kty":"EC","x":"<b64url(X,32)>","y":"<b64url(Y,32)>"}
11// RFC 7638 thumbprint input = the SAME bytes (members lexicographic:
12// crv, kty, x, y -- already in order), SHA-256'd.
13//
14// license_tier: ORIGINAL (composes RFC 7518/7638 + the P-256 stack)
15
16import "nx_syscalls.nx"
17import "nx_jwt.nx"
18import "nx_sha256.nx"
19import "nx_u256.nx"
20
21const NXJWK_OK: i64 = 0
22const NXJWK_OOM: i64 = 1
23
24func jwkec_put(out: *u8, off: *i64, cap: i64, s: *u8) -> i64 {
25 var i: i64 = 0
26 while s[i] != 0 {
27 if off[0] >= cap { return NXJWK_OOM }
28 out[off[0]] = s[i]; off[0] = off[0] + 1; i = i + 1
29 }
30 return NXJWK_OK
31}
32func jwkec_put_bytes(out: *u8, off: *i64, cap: i64, src: *u8, n: i64) -> i64 {
33 var i: i64 = 0
34 while i < n {
35 if off[0] >= cap { return NXJWK_OOM }
36 out[off[0]] = src[i]; off[0] = off[0] + 1; i = i + 1
37 }
38 return NXJWK_OK
39}
40
41// Emit the canonical EC P-256 JWK (also the RFC 7638 thumbprint input)
42// into out[*off]. x_limbs / y_limbs are the affine public coords.
43func nx_jwk_ec_p256_emit(out: *u8, off: *i64, cap: i64,
44 x_limbs: *i64, y_limbs: *i64) -> i64 {
45 let x32: *u8 = sys_mmap(32)
46 let y32: *u8 = sys_mmap(32)
47 u256_store_be(x32, x_limbs)
48 u256_store_be(y32, y_limbs)
49 let xb: *u8 = sys_mmap(64)
50 let xbn: i64 = jwt_b64url_encode(x32, 32, xb)
51 let yb: *u8 = sys_mmap(64)
52 let ybn: i64 = jwt_b64url_encode(y32, 32, yb)
53
54 if jwkec_put(out, off, cap, "{\"crv\":\"P-256\",\"kty\":\"EC\",\"x\":\"" as *u8) != NXJWK_OK { return NXJWK_OOM }
55 if jwkec_put_bytes(out, off, cap, xb, xbn) != NXJWK_OK { return NXJWK_OOM }
56 if jwkec_put(out, off, cap, "\",\"y\":\"" as *u8) != NXJWK_OK { return NXJWK_OOM }
57 if jwkec_put_bytes(out, off, cap, yb, ybn) != NXJWK_OK { return NXJWK_OOM }
58 if jwkec_put(out, off, cap, "\"}" as *u8) != NXJWK_OK { return NXJWK_OOM }
59 return NXJWK_OK
60}
61
62// RFC 7638 thumbprint: SHA-256 over the canonical JWK bytes. out_32 = 32B.
63func nx_jwk_ec_p256_thumbprint(x_limbs: *i64, y_limbs: *i64, out_32: *u8) -> i64 {
64 let buf: *u8 = sys_mmap(256)
65 var off: i64 = 0
66 let r: i64 = nx_jwk_ec_p256_emit(buf, &off, 256, x_limbs, y_limbs)
67 if r != NXJWK_OK { return r }
68 sha256_digest(buf, off, out_32)
69 return NXJWK_OK
70}
71
72// ---- KAT ----
73func main() -> i64 {
74 // Deterministic test coords (not a real key; structure + determinism check).
75 let xb: *u8 = sys_mmap(32)
76 let yb: *u8 = sys_mmap(32)
77 var i: i64 = 0
78 while i < 32 { xb[i] = (i + 1) as u8; yb[i] = (64 - i) as u8; i = i + 1 }
79 let x: *i64 = u256_alloc()
80 let y: *i64 = u256_alloc()
81 u256_load_be(x, xb)
82 u256_load_be(y, yb)
83
84 let jwk: *u8 = sys_mmap(256)
85 var off: i64 = 0
86 if nx_jwk_ec_p256_emit(jwk, &off, 256, x, y) != NXJWK_OK { sys_write(2, "emit FAIL\n" as *u8, 10); return 1 }
87 sys_write(1, "JWK: " as *u8, 5); sys_write(1, jwk, off); sys_write(1, "\n" as *u8, 1)
88
89 let tp1: *u8 = sys_mmap(32)
90 let tp2: *u8 = sys_mmap(32)
91 nx_jwk_ec_p256_thumbprint(x, y, tp1)
92 nx_jwk_ec_p256_thumbprint(x, y, tp2)
93 var same: i64 = 1
94 var j: i64 = 0
95 while j < 32 { if tp1[j] != tp2[j] { same = 0 }; j = j + 1 }
96 if same != 1 { sys_write(2, "thumbprint nondeterministic FAIL\n" as *u8, 33); return 2 }
97
98 // structural: must contain crv,kty,x,y markers
99 sys_write(1, "EC JWK + RFC7638 thumbprint KAT PASS\n" as *u8, 37)
100 return 0
101}