code wiki / (root) / nx_x509_pubkey_ec.nx

nx_x509_pubkey_ec.nx source

↩ module page · 133 lines · 4772 B

1// nx_x509_pubkey_ec.nx -- extract an uncompressed ECDSA-P256 2// public key (X, Y) from an X.509 SubjectPublicKeyInfo. 3// 4// Phase 0b §I.4 piece 3 of the chain-walker arc. Composes the 5// X509Cert.pubkey_off/_len fields (populated by x509_parse) with 6// the SEC 1 v2.0 §2.3.3 uncompressed point format: 7// 8// pubkey bytes layout (65 bytes for P-256): 9// byte 0 : 0x04 (uncompressed point indicator) 10// bytes 1..33 : X coordinate (32-byte big-endian) 11// bytes 33..65: Y coordinate (32-byte big-endian) 12// 13// x509_parse strips the BIT STRING's "unused bits" prefix byte, 14// so cert.pubkey_off points directly at the 0x04 indicator and 15// cert.pubkey_len == 65 for a P-256 key. Other formats: 16// - 0x02 / 0x03 = compressed form (Y derived from X via curve 17// equation + parity bit -- NOT supported here; rare in TLS 18// PKI which deprecated compressed forms long ago) 19// - 0x05+ = hybrid forms (deprecated) 20// 21// Public API: 22// nx_x509_pubkey_extract_ec_p256(buf, cert, out_x, out_y) -> verdict 23// nx_x509_pubkey_ec_verdict_is_valid(v) -> 0|1 24// 25// Sealed verdict: 26// NX_X509_PUBKEY_EC_OK extraction successful 27// NX_X509_PUBKEY_EC_BAD_LEN pubkey_len != 65 28// NX_X509_PUBKEY_EC_NOT_UNCOMPRESSED leading byte != 0x04 29// 30// Per Cardinals 9 (single-responsibility -- just extract; on-curve 31// check belongs to ecdsa verify), 12 (defensive at boundaries -- 32// length + format check), 23 (preamble explains the compressed- 33// form deferral). 34// 35// license_tier: INDEPENDENT_REDERIVE 36// genealogy_id: international-research-sources/sec_g/sec1_v2 + ietf/rfc_5480 37// lineage_id: nishi_x509_pubkey_ec_p256_q10 38 39// nx_safety_envelope: 40// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 41// sil_target: SIL1 42// evidence: [bulk_applied_2026-05-19, x509-ec-p256-pubkey-extract] 43// verdict: NOT_YET_EVALUATED 44 45import "nx_syscalls.nx" 46import "nx_u256.nx" 47import "nx_u384.nx" 48import "nx_x509.nx" 49 50const NX_X509_PUBKEY_EC_OK: i64 = 1 51const NX_X509_PUBKEY_EC_BAD_LEN: i64 = 2 52const NX_X509_PUBKEY_EC_NOT_UNCOMPRESSED: i64 = 3 53const NX_X509_PUBKEY_EC_VERDICT_N: i64 = 4 54 55func nx_x509_pubkey_ec_verdict_is_valid(v: i64) -> i64 { 56 if v < NX_X509_PUBKEY_EC_OK { return 0 } 57 if v >= NX_X509_PUBKEY_EC_VERDICT_N { return 0 } 58 return 1 59} 60 61// Extract the (X, Y) coordinates of an ECDSA-P256 public key from 62// a parsed cert's SubjectPublicKeyInfo bytes. Writes 8-limb LE 63// U256 to out_x and out_y. 64// 65// Caller's responsibility (NOT enforced here -- belongs to ecdsa 66// verify): 67// - The pubkey algorithm OID inside cert.pubkey_alg_off/_len 68// identifies this as an EC P-256 key (id-ecPublicKey with 69// namedCurve prime256v1). Without that check, mis-decoded 70// bytes from a non-EC pubkey will produce garbage (X, Y). 71// - On-curve check (y^2 == x^3 - 3x + b mod p) -- ecdsa_p256_verify 72// does this internally. 73func nx_x509_pubkey_extract_ec_p256(buf: *u8, cert: *X509Cert, 74 out_x: *i64, out_y: *i64) -> i64 { 75 if cert.pubkey_len != 65 { return NX_X509_PUBKEY_EC_BAD_LEN } 76 let p: *u8 = buf + cert.pubkey_off 77 if (p[0] & 0xff) != 0x04 { return NX_X509_PUBKEY_EC_NOT_UNCOMPRESSED } 78 79 // X coordinate: 32 bytes starting at offset 1 80 let x_bytes: *u8 = sys_mmap(32) 81 var i: i64 = 0 82 while i < 32 { 83 x_bytes[i] = p[1 + i] 84 i = i + 1 85 } 86 u256_load_be(out_x, x_bytes) 87 88 // Y coordinate: 32 bytes starting at offset 33 89 let y_bytes: *u8 = sys_mmap(32) 90 i = 0 91 while i < 32 { 92 y_bytes[i] = p[33 + i] 93 i = i + 1 94 } 95 u256_load_be(out_y, y_bytes) 96 97 return NX_X509_PUBKEY_EC_OK 98} 99 100// Extract (X, Y) of an ECDSA-P384 public key from cert's SPKI. 101// Writes 12-limb LE U384 to out_x and out_y. 102// SEC 1 v2.0 §2.3.3 uncompressed: 0x04 || X(48) || Y(48) = 97 bytes. 103func nx_x509_pubkey_extract_ec_p384(buf: *u8, cert: *X509Cert, 104 out_x: *i64, out_y: *i64) -> i64 { 105 if cert.pubkey_len != 97 { return NX_X509_PUBKEY_EC_BAD_LEN } 106 let p: *u8 = buf + cert.pubkey_off 107 if (p[0] & 0xff) != 0x04 { return NX_X509_PUBKEY_EC_NOT_UNCOMPRESSED } 108 109 // X coordinate: 48 bytes at offset 1 110 let x_bytes: *u8 = sys_mmap(48) 111 var i: i64 = 0 112 while i < 48 { 113 x_bytes[i] = p[1 + i] 114 i = i + 1 115 } 116 u384_load_be(out_x, x_bytes) 117 118 // Y coordinate: 48 bytes at offset 49 119 let y_bytes: *u8 = sys_mmap(48) 120 i = 0 121 while i < 48 { 122 y_bytes[i] = p[49 + i] 123 i = i + 1 124 } 125 u384_load_be(out_y, y_bytes) 126 127 return NX_X509_PUBKEY_EC_OK 128} 129 130// Compile-only smoke. Real KAT in nx_x509_pubkey_ec_test.nx. 131func main() -> i64 { 132 return 0 133}