code wiki / (root) / nx_jwk_ec.nx

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}