code wiki / (root) / nx_jose_es256.nx

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}