nx_jose_es256.nx source
↩ module page · 180 lines · 7331 B
1// nx_jose_es256.nx -- ES256 (ECDSA P-256 + SHA-256) JWS flattened
2// signing, RFC 7515/7518. The Ed25519 path (nx_jose) is sovereign but
3// Let's Encrypt's production CA requires ES256 (or RS256) account keys
4// and issues ECDSA/RSA certs -- never Ed25519 -- so the ACME client
5// needs this.
6//
7// Standalone (does NOT import nx_jose) so the whole import graph stays
8// on nx_syscalls.nx -- nx_jose pulls nx_syscalls_x86_64.nx which would
9// double-define every sys_* against the P-256 crypto stack.
10//
11// REUSABLE beyond ACME: JWT ES256, OAuth, WebPush VAPID, etc.
12//
13// ES256 JWS signature is the RAW r||s concatenation (32+32=64 bytes,
14// each big-endian), NOT ASN.1-DER -- RFC 7518 ยง3.4.
15//
16// license_tier: ORIGINAL (composes RFC 7515/7518 + the substrate P-256 stack)
17
18import "nx_syscalls.nx"
19import "nx_jwt.nx"
20import "nx_sha256.nx"
21import "nx_u256.nx"
22import "nx_p256_point.nx"
23import "nx_p256_scalar_mul.nx"
24import "nx_ecdsa_p256_sign.nx"
25import "nx_ecdsa_p256.nx"
26const NXES_MAGIC_4096: i64 = 4096
27
28const NXES_OK: i64 = 0
29const NXES_BAD_ARG: i64 = 1
30const NXES_OOM: i64 = 2
31const NXES_SIGN_ERR: i64 = 3
32
33// append cstr (NUL-terminated) into out[*off], bounded by cap.
34func nxes_put_cstr(out: *u8, off: *i64, cap: i64, s: *u8) -> i64 {
35 var i: i64 = 0
36 while s[i] != 0 {
37 if off[0] >= cap { return NXES_OOM }
38 out[off[0]] = s[i]; off[0] = off[0] + 1; i = i + 1
39 }
40 return NXES_OK
41}
42func nxes_put_bytes(out: *u8, off: *i64, cap: i64, src: *u8, n: i64) -> i64 {
43 var i: i64 = 0
44 while i < n {
45 if off[0] >= cap { return NXES_OOM }
46 out[off[0]] = src[i]; off[0] = off[0] + 1; i = i + 1
47 }
48 return NXES_OK
49}
50
51// Public key (affine x,y limbs) from a P-256 private scalar: pub = d*G.
52func nx_p256_pubkey_from_priv(priv_limbs: *i64, out_x: *i64, out_y: *i64) -> i64 {
53 let g: *P256Point = p256_point_alloc()
54 p256_point_load_g(g)
55 let pub: *P256Point = p256_point_alloc()
56 p256_scalar_mul(pub, priv_limbs, g)
57 p256_point_to_affine(pub)
58 u256_copy(out_x, pub.x)
59 u256_copy(out_y, pub.y)
60 return 0
61}
62
63// JWS signing input = b64url(protected) "." b64url(payload).
64func nxes_signature_input(protected_json: *u8, prot_n: i64,
65 payload: *u8, pay_n: i64,
66 out: *u8, cap: i64, out_n: *i64) -> i64 {
67 var off: i64 = 0
68 let pb: *u8 = sys_mmap((prot_n * 4 / 3) + 16)
69 let pbn: i64 = jwt_b64url_encode(protected_json, prot_n, pb)
70 let r1: i64 = nxes_put_bytes(out, &off, cap, pb, pbn)
71 if r1 != NXES_OK { return r1 }
72 if off >= cap { return NXES_OOM }
73 out[off] = 0x2e as u8; off = off + 1 // '.'
74 let yb: *u8 = sys_mmap((pay_n * 4 / 3) + 16)
75 let ybn: i64 = jwt_b64url_encode(payload, pay_n, yb)
76 let r2: i64 = nxes_put_bytes(out, &off, cap, yb, ybn)
77 if r2 != NXES_OK { return r2 }
78 out_n[0] = off
79 return NXES_OK
80}
81
82// Sign protected||payload with ES256, emit flattened JWS JSON.
83// Caller's protected_json MUST declare "alg":"ES256".
84func nx_jose_sign_es256_flattened(
85 protected_json: *u8, prot_n: i64,
86 payload: *u8, pay_n: i64,
87 priv_limbs: *i64,
88 out_buf: *u8, out_cap: i64, out_n: *i64) -> i64 {
89 if protected_json == (0 as *u8) { return NXES_BAD_ARG }
90 if out_buf == (0 as *u8) { return NXES_BAD_ARG }
91 if out_n == (0 as *i64) { return NXES_BAD_ARG }
92 if out_cap <= 0 { return NXES_BAD_ARG }
93
94 let si_cap: i64 = (prot_n + pay_n + 64) * 2
95 let si: *u8 = sys_mmap(si_cap)
96 let si_n: *i64 = sys_mmap(8) as *i64
97 let rs: i64 = nxes_signature_input(protected_json, prot_n, payload, pay_n, si, si_cap, si_n)
98 if rs != NXES_OK { return rs }
99
100 let hash32: *u8 = sys_mmap(32)
101 sha256_digest(si, si_n[0], hash32)
102 let hash_limbs: *i64 = u256_alloc()
103 u256_load_be(hash_limbs, hash32)
104
105 let r_limbs: *i64 = u256_alloc()
106 let s_limbs: *i64 = u256_alloc()
107 let sv: i64 = nx_ecdsa_p256_sign(priv_limbs, hash_limbs, r_limbs, s_limbs)
108 if sv != NX_ECDSA_SIGN_OK { return NXES_SIGN_ERR }
109
110 let sig64: *u8 = sys_mmap(64)
111 u256_store_be(sig64, r_limbs)
112 u256_store_be(((sig64 as i64) + 32) as *u8, s_limbs)
113
114 let hb: *u8 = sys_mmap((prot_n * 4 / 3) + 16)
115 let hbn: i64 = jwt_b64url_encode(protected_json, prot_n, hb)
116 let pb: *u8 = sys_mmap((pay_n * 4 / 3) + 16)
117 let pbn: i64 = jwt_b64url_encode(payload, pay_n, pb)
118 let sb: *u8 = sys_mmap(128)
119 let sbn: i64 = jwt_b64url_encode(sig64, 64, sb)
120
121 var off: i64 = 0
122 if nxes_put_cstr(out_buf, &off, out_cap, "{\"protected\":\"" as *u8) != NXES_OK { return NXES_OOM }
123 if nxes_put_bytes(out_buf, &off, out_cap, hb, hbn) != NXES_OK { return NXES_OOM }
124 if nxes_put_cstr(out_buf, &off, out_cap, "\",\"payload\":\"" as *u8) != NXES_OK { return NXES_OOM }
125 if nxes_put_bytes(out_buf, &off, out_cap, pb, pbn) != NXES_OK { return NXES_OOM }
126 if nxes_put_cstr(out_buf, &off, out_cap, "\",\"signature\":\"" as *u8) != NXES_OK { return NXES_OOM }
127 if nxes_put_bytes(out_buf, &off, out_cap, sb, sbn) != NXES_OK { return NXES_OOM }
128 if nxes_put_cstr(out_buf, &off, out_cap, "\"}" as *u8) != NXES_OK { return NXES_OOM }
129 out_n[0] = off
130 return NXES_OK
131}
132
133// ---- KAT: sign a fixed input, derive pub, verify r/s roundtrip ----
134func main() -> i64 {
135 let priv_be: *u8 = sys_mmap(32)
136 var i: i64 = 0
137 while i < 32 { priv_be[i] = (i + 1) as u8; i = i + 1 }
138 let priv_limbs: *i64 = u256_alloc()
139 u256_load_be(priv_limbs, priv_be)
140 sys_write(2, "T1 priv loaded\n" as *u8, 15)
141
142 // probe pubkey derivation alone
143 let tpx: *i64 = u256_alloc()
144 let tpy: *i64 = u256_alloc()
145 nx_p256_pubkey_from_priv(priv_limbs, tpx, tpy)
146 sys_write(2, "T2 pubkey ok\n" as *u8, 13)
147
148 let prot: *u8 = "{\"alg\":\"ES256\",\"nonce\":\"abc\",\"url\":\"https://acme.example/x\"}" as *u8
149 let prot_n: i64 = 56
150 let pay: *u8 = "{\"termsOfServiceAgreed\":true}" as *u8
151 let pay_n: i64 = 29
152
153 let out: *u8 = sys_mmap(NXES_MAGIC_4096)
154 let out_n: *i64 = sys_mmap(8) as *i64
155 sys_write(2, "T3 pre-sign\n" as *u8, 12)
156 if nx_jose_sign_es256_flattened(prot, prot_n, pay, pay_n, priv_limbs, out, NXES_MAGIC_4096, out_n) != NXES_OK {
157 sys_write(2, "es256 sign FAIL\n" as *u8, 16); return 1
158 }
159 sys_write(1, "es256 JWS: " as *u8, 11); sys_write(1, out, out_n[0]); sys_write(1, "\n" as *u8, 1)
160
161 // verify roundtrip
162 let si: *u8 = sys_mmap((prot_n + pay_n + 64) * 2)
163 let si_n: *i64 = sys_mmap(8) as *i64
164 nxes_signature_input(prot, prot_n, pay, pay_n, si, (prot_n + pay_n + 64) * 2, si_n)
165 let hash32: *u8 = sys_mmap(32)
166 sha256_digest(si, si_n[0], hash32)
167 let hl: *i64 = u256_alloc()
168 u256_load_be(hl, hash32)
169 let rl: *i64 = u256_alloc()
170 let sl: *i64 = u256_alloc()
171 nx_ecdsa_p256_sign(priv_limbs, hl, rl, sl)
172 let px: *i64 = u256_alloc()
173 let py: *i64 = u256_alloc()
174 nx_p256_pubkey_from_priv(priv_limbs, px, py)
175 if nx_ecdsa_p256_verify(px, py, hl, rl, sl) != NX_ECDSA_OK {
176 sys_write(2, "es256 verify FAIL\n" as *u8, 18); return 2
177 }
178 sys_write(1, "ES256 JWS KAT PASS (sign+verify roundtrip)\n" as *u8, 43)
179 return 0
180}