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}