nx_jose_test.nx source
↩ module page · 166 lines · 6938 B
1// nx_jose_test.nx -- smoke for nx_jose + nx_jwk.
2//
3// Covers:
4// 1. JWK enum gates + emit OKP/Ed25519 for a known public key
5// 2. JOSE enum gates + algorithm enum
6// 3. Signature input matches RFC 7515 §5.1 shape
7// 4. End-to-end sign + emit flattened-JSON for an EdDSA JWS
8// 5. Bounds-check / BAD_ARG paths
9//
10// Not yet covered (separate slice):
11// - RFC 8037 §A.4 KAT bytes (the example uses a specific keypair
12// + nonce; the substrate ed25519_sign_full is deterministic so
13// bytes match, but the §A.4 fixture isn't in this smoke).
14// - JWK thumbprint SHA-256 (queued: nx_sha256_jwk_thumbprint).
15//
16// expect_exit: 0
17//
18// NOTE 2026-05-16: nx_ed25519_signature.nx transitively imports
19// nx_syscalls.nx (RV64 number) -- composing it with
20// nx_syscalls_x86_64.nx (this test's natural import) hits the
21// F-meta-4 cross-target portability gap (duplicate sys_dup3 /
22// sys_getdents64 / blk_set_byte symbols at link). Fix path is to
23// split nx_ed25519 (and its 5-file chain) into a types-only base
24// layer + a syscall-using convenience layer, same shape as
25// nx_arena_types.nx -- queued as multi-session refactor.
26//
27// For this slice: gauntlet entry is COMPILE-clean only; runtime
28// verification awaits the refactor or an RV64-qemu harness.
29//
30// license_tier: ORIGINAL
31
32import "nx_jwk.nx"
33import "nx_jose.nx"
34
35func bytes_eq(a: *u8, b: *u8, n: i64) -> i64 {
36 var i: i64 = 0
37 while i < n {
38 if a[i] != b[i] { return 0 }
39 i = i + 1
40 }
41 return 1
42}
43
44func main() -> i64 {
45 // ---- JWK enum gate ----
46 if nxjwk_verdict_is_valid(NXJWK_OK) != 1 { return 1 }
47 if nxjwk_verdict_is_valid(NXJWK_BAD_KTY) != 1 { return 2 }
48 if nxjwk_verdict_is_valid(NXJWK_VERDICT_N) != 0 { return 3 }
49 if bytes_eq(nxjwk_verdict_name(NXJWK_OOM_BUFFER),
50 "OOM_BUFFER" as *u8, 10) != 1 { return 4 }
51 if bytes_eq(nxjwk_verdict_name(NXJWK_BAD_KTY),
52 "BAD_KTY" as *u8, 7) != 1 { return 5 }
53
54 // ---- JOSE enum gate ----
55 if nxjose_verdict_is_valid(NXJOSE_OK) != 1 { return 10 }
56 if nxjose_verdict_is_valid(NXJOSE_SIGN_ERR) != 1 { return 11 }
57 if nxjose_verdict_is_valid(NXJOSE_VERDICT_N) != 0 { return 12 }
58 if nxjose_alg_is_valid(NXJOSE_ALG_EDDSA) != 1 { return 13 }
59 if nxjose_alg_is_valid(NXJOSE_ALG_N) != 0 { return 14 }
60
61 // ---- JWK emit: canonical OKP/Ed25519 ----
62 // Use a known public key (all 32 bytes set to 0x55 for predictability).
63 let pubkey: *u8 = sys_mmap(32)
64 var i: i64 = 0
65 while i < 32 { pubkey[i] = 0x55 as u8; i = i + 1 }
66
67 let jwk_buf: *u8 = sys_mmap(256)
68 let jwk_off: *i64 = sys_mmap(8) as *i64
69 jwk_off[0] = 0
70 let rc_jwk: i64 = nx_jwk_emit_okp_ed25519(jwk_buf, jwk_off, 256, pubkey)
71 if rc_jwk != NXJWK_OK { return 20 }
72 // Expected: {"crv":"Ed25519","kty":"OKP","x":"VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVQ"}
73 // (32 bytes of 0x55 base64url-encoded = "VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVQ")
74 let exp_jwk: *u8 = "{\"crv\":\"Ed25519\",\"kty\":\"OKP\",\"x\":\"VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVQ\"}" as *u8
75 let exp_jwk_n: i64 = 79
76 if jwk_off[0] != exp_jwk_n { return 21 }
77 if bytes_eq(jwk_buf, exp_jwk, exp_jwk_n) != 1 { return 22 }
78
79 // ---- BAD_ARG paths for JWK ----
80 jwk_off[0] = 0
81 if nx_jwk_emit_okp_ed25519(0 as *u8, jwk_off, 256, pubkey) != NXJWK_BAD_ARG { return 30 }
82 if nx_jwk_emit_okp_ed25519(jwk_buf, 0 as *i64, 256, pubkey) != NXJWK_BAD_ARG { return 31 }
83 if nx_jwk_emit_okp_ed25519(jwk_buf, jwk_off, 256, 0 as *u8) != NXJWK_BAD_ARG { return 32 }
84 if nx_jwk_emit_okp_ed25519(jwk_buf, jwk_off, 0, pubkey) != NXJWK_BAD_ARG { return 33 }
85
86 // ---- JOSE signature input shape ----
87 let prot: *u8 = "{\"alg\":\"EdDSA\"}" as *u8
88 let prot_n: i64 = 15
89 let pay: *u8 = "{\"hello\":1}" as *u8
90 let pay_n: i64 = 11
91
92 let si_buf: *u8 = sys_mmap(512)
93 let si_off: *i64 = sys_mmap(8) as *i64
94 let rc_si: i64 = nx_jose_signature_input(prot, prot_n, pay, pay_n,
95 si_buf, 512, si_off)
96 if rc_si != NXJOSE_OK { return 40 }
97 // Expected = b64url(prot) || "." || b64url(pay)
98 // b64url("{\"alg\":\"EdDSA\"}") = "eyJhbGciOiJFZERTQSJ9" (20 chars)
99 // b64url("{\"hello\":1}") = "eyJoZWxsbyI6MX0" (15 chars)
100 // total = 20 + 1 + 15 = 36
101 let exp_si: *u8 = "eyJhbGciOiJFZERTQSJ9.eyJoZWxsbyI6MX0" as *u8
102 let exp_si_n: i64 = 36
103 if si_off[0] != exp_si_n { return 41 }
104 if bytes_eq(si_buf, exp_si, exp_si_n) != 1 { return 42 }
105
106 // ---- End-to-end JOSE sign + emit flattened JSON ----
107 // priv = 32 zero bytes (deterministic for test reproducibility).
108 let priv: *u8 = sys_mmap(32)
109 var j: i64 = 0
110 while j < 32 { priv[j] = 0 as u8; j = j + 1 }
111
112 let jws_buf: *u8 = sys_mmap(2048)
113 let jws_off: *i64 = sys_mmap(8) as *i64
114 jws_off[0] = 0
115 let rc_jws: i64 = nx_jose_sign_eddsa_flattened(
116 prot, prot_n, pay, pay_n, priv, jws_buf, 2048, jws_off)
117 if rc_jws != NXJOSE_OK { return 50 }
118
119 // Output shape verification: starts with {"protected":"
120 if bytes_eq(jws_buf, "{\"protected\":\"" as *u8, 14) != 1 { return 51 }
121 // Ends with "} (verify last 2 bytes)
122 let end_off: i64 = jws_off[0]
123 if end_off < 4 { return 52 }
124 if jws_buf[end_off - 2] != 0x22 as u8 { return 53 } // "
125 if jws_buf[end_off - 1] != 0x7d as u8 { return 54 } // }
126
127 // Verify the protected + payload b64url substrings appear in the
128 // emitted JSON. Scan for "eyJhbGciOiJFZERTQSJ9" (protected b64url).
129 var found_prot: i64 = 0
130 var k: i64 = 0
131 while k <= end_off - 20 {
132 if bytes_eq((jws_buf as i64 + k) as *u8,
133 "eyJhbGciOiJFZERTQSJ9" as *u8, 20) == 1 {
134 found_prot = 1
135 k = end_off // break
136 }
137 k = k + 1
138 }
139 if found_prot != 1 { return 60 }
140
141 // Scan for payload b64url
142 var found_pay: i64 = 0
143 var m: i64 = 0
144 while m <= end_off - 15 {
145 if bytes_eq((jws_buf as i64 + m) as *u8,
146 "eyJoZWxsbyI6MX0" as *u8, 15) == 1 {
147 found_pay = 1
148 m = end_off // break
149 }
150 m = m + 1
151 }
152 if found_pay != 1 { return 61 }
153
154 // ---- BAD_ARG paths for JOSE sign ----
155 jws_off[0] = 0
156 if nx_jose_sign_eddsa_flattened(0 as *u8, 5, pay, pay_n, priv,
157 jws_buf, 2048, jws_off) != NXJOSE_BAD_ARG { return 70 }
158 if nx_jose_sign_eddsa_flattened(prot, prot_n, 0 as *u8, 5, priv,
159 jws_buf, 2048, jws_off) != NXJOSE_BAD_ARG { return 71 }
160 if nx_jose_sign_eddsa_flattened(prot, prot_n, pay, pay_n, 0 as *u8,
161 jws_buf, 2048, jws_off) != NXJOSE_BAD_ARG { return 72 }
162 if nx_jose_sign_eddsa_flattened(prot, prot_n, pay, pay_n, priv,
163 0 as *u8, 2048, jws_off) != NXJOSE_BAD_ARG { return 73 }
164
165 return 0
166}