nx_x509_pubkey_alg.nx source
↩ module page · 187 lines · 7534 B
1// nx_x509_pubkey_alg.nx -- identify the public-key algorithm of a
2// SubjectPublicKeyInfo by inspecting its AlgorithmIdentifier bytes.
3//
4// Phase 0b §I.4 piece 4 of the chain-walker arc. Parallel to
5// nx_x509_sig_alg.nx (046d5dae) but reads from cert.pubkey_alg_off/_len
6// instead of cert.sig_alg_off/_len.
7//
8// Sealed enum:
9// NX_X509_PUBKEY_ALG_UNKNOWN (0) unrecognized / unsupported
10// NX_X509_PUBKEY_ALG_ED25519 (1) OID 1.3.101.112 (RFC 8410)
11// DER inner: 06 03 2B 65 70
12// NX_X509_PUBKEY_ALG_EC_P256 (2) OID 1.2.840.10045.2.1 (id-ecPublicKey)
13// with namedCurve OID 1.2.840.10045.3.1.7
14// (prime256v1)
15//
16// For EC pubkeys we MUST peek at the second OID (the namedCurve)
17// to distinguish P-256 from P-384 etc. Just checking
18// id-ecPublicKey would let a P-384 pubkey be mis-identified as
19// P-256, causing extract_ec_p256 to fail later with confusing
20// "BAD_LEN" rather than the precise "wrong curve" diagnosis.
21//
22// Per Cardinals 9 (single-responsibility -- identify, not extract),
23// 12 (defensive at boundaries -- bounded reads), and 23 (preamble
24// explains the namedCurve peek + why it matters).
25//
26// Public API:
27// nx_x509_pubkey_alg_identify(buf, off, len) -> alg-enum
28// nx_x509_pubkey_alg_is_supported(alg) -> 0|1
29//
30// license_tier: INDEPENDENT_REDERIVE
31// genealogy_id: international-research-sources/ietf/rfc_5280 + rfc_5480 +
32// ietf/rfc_8410 + sec_g/sec2_v1
33// lineage_id: nishi_x509_pubkey_alg_q10
34
35// nx_safety_envelope:
36// intended_use: AUTO_APPLIED -- primitive-specific tuning queued
37// sil_target: SIL1
38// evidence: [bulk_applied_2026-05-19, x509-pubkey-alg-identify]
39// verdict: NOT_YET_EVALUATED
40
41import "nx_syscalls.nx"
42import "nx_asn1.nx"
43
44const NX_X509_PUBKEY_ALG_UNKNOWN: i64 = 0
45const NX_X509_PUBKEY_ALG_ED25519: i64 = 1
46const NX_X509_PUBKEY_ALG_EC_P256: i64 = 2
47const NX_X509_PUBKEY_ALG_EC_P384: i64 = 3
48// rsaEncryption -- OID 1.2.840.113549.1.1.1 (RFC 8017).
49// Used by RSA-2048+ pubkeys. Distinguished from the SIGNATURE
50// algorithms 1.2.840.113549.1.1.{5,11,12,13,14} -- here we only
51// recognise the encryption (key) algorithm at SPKI position.
52const NX_X509_PUBKEY_ALG_RSA: i64 = 4
53const NX_X509_PUBKEY_ALG_N: i64 = 5
54
55func nx_x509_pubkey_alg_is_supported(alg: i64) -> i64 {
56 if alg <= NX_X509_PUBKEY_ALG_UNKNOWN { return 0 }
57 if alg >= NX_X509_PUBKEY_ALG_N { return 0 }
58 return 1
59}
60
61// Returns 1 if (buf+oid_off) for oid_len bytes matches id-ecPublicKey
62// (1.2.840.10045.2.1). Content bytes: 2A 86 48 CE 3D 02 01 (7 bytes).
63func oid_is_id_ec_public_key(buf: *u8, off: i64, len: i64) -> i64 {
64 if len != 7 { return 0 }
65 if (buf[off] & 0xff) != 0x2A { return 0 }
66 if (buf[off+1] & 0xff) != 0x86 { return 0 }
67 if (buf[off+2] & 0xff) != 0x48 { return 0 }
68 if (buf[off+3] & 0xff) != 0xCE { return 0 }
69 if (buf[off+4] & 0xff) != 0x3D { return 0 }
70 if (buf[off+5] & 0xff) != 0x02 { return 0 }
71 if (buf[off+6] & 0xff) != 0x01 { return 0 }
72 return 1
73}
74
75// Returns 1 if (buf+oid_off) for oid_len bytes matches prime256v1
76// (1.2.840.10045.3.1.7). Content bytes: 2A 86 48 CE 3D 03 01 07 (8 bytes).
77func oid_is_prime256v1(buf: *u8, off: i64, len: i64) -> i64 {
78 if len != 8 { return 0 }
79 if (buf[off] & 0xff) != 0x2A { return 0 }
80 if (buf[off+1] & 0xff) != 0x86 { return 0 }
81 if (buf[off+2] & 0xff) != 0x48 { return 0 }
82 if (buf[off+3] & 0xff) != 0xCE { return 0 }
83 if (buf[off+4] & 0xff) != 0x3D { return 0 }
84 if (buf[off+5] & 0xff) != 0x03 { return 0 }
85 if (buf[off+6] & 0xff) != 0x01 { return 0 }
86 if (buf[off+7] & 0xff) != 0x07 { return 0 }
87 return 1
88}
89
90// Returns 1 if (buf+oid_off) for oid_len bytes matches secp384r1
91// (1.3.132.0.34). Content bytes: 2B 81 04 00 22 (5 bytes).
92func oid_is_secp384r1(buf: *u8, off: i64, len: i64) -> i64 {
93 if len != 5 { return 0 }
94 if (buf[off] & 0xff) != 0x2B { return 0 }
95 if (buf[off+1] & 0xff) != 0x81 { return 0 }
96 if (buf[off+2] & 0xff) != 0x04 { return 0 }
97 if (buf[off+3] & 0xff) != 0x00 { return 0 }
98 if (buf[off+4] & 0xff) != 0x22 { return 0 }
99 return 1
100}
101
102// Returns 1 if (buf+oid_off) for oid_len bytes matches rsaEncryption
103// (1.2.840.113549.1.1.1). Content bytes: 2A 86 48 86 F7 0D 01 01 01 (9 bytes).
104func oid_is_rsa_encryption(buf: *u8, off: i64, len: i64) -> i64 {
105 if len != 9 { return 0 }
106 if (buf[off] & 0xff) != 0x2A { return 0 }
107 if (buf[off+1] & 0xff) != 0x86 { return 0 }
108 if (buf[off+2] & 0xff) != 0x48 { return 0 }
109 if (buf[off+3] & 0xff) != 0x86 { return 0 }
110 if (buf[off+4] & 0xff) != 0xF7 { return 0 }
111 if (buf[off+5] & 0xff) != 0x0D { return 0 }
112 if (buf[off+6] & 0xff) != 0x01 { return 0 }
113 if (buf[off+7] & 0xff) != 0x01 { return 0 }
114 if (buf[off+8] & 0xff) != 0x01 { return 0 }
115 return 1
116}
117
118// Returns 1 if (buf+oid_off) for oid_len bytes matches Ed25519
119// (1.3.101.112). Content bytes: 2B 65 70 (3 bytes).
120func oid_is_ed25519(buf: *u8, off: i64, len: i64) -> i64 {
121 if len != 3 { return 0 }
122 if (buf[off] & 0xff) != 0x2B { return 0 }
123 if (buf[off+1] & 0xff) != 0x65 { return 0 }
124 if (buf[off+2] & 0xff) != 0x70 { return 0 }
125 return 1
126}
127
128// Identify the public-key algorithm at buf[off..off+len), where
129// the bytes are a SubjectPublicKeyInfo.algorithm AlgorithmIdentifier
130// SEQUENCE. Returns NX_X509_PUBKEY_ALG_* enum.
131func nx_x509_pubkey_alg_identify(buf: *u8, off: i64, len: i64) -> i64 {
132 if len < 4 { return NX_X509_PUBKEY_ALG_UNKNOWN }
133
134 let c_raw: *u8 = sys_mmap(32)
135 let c: *Asn1Cursor = c_raw as *Asn1Cursor
136 c.pos = off
137 c.end = off + len
138
139 let outer_len_p: *i64 = sys_mmap(16) as *i64
140 let rc1: i64 = asn1_expect_tag(buf, c, ASN1_SEQUENCE, outer_len_p)
141 if rc1 < 0 { return NX_X509_PUBKEY_ALG_UNKNOWN }
142 let inner_end: i64 = c.pos + *outer_len_p
143 if inner_end > c.end { return NX_X509_PUBKEY_ALG_UNKNOWN }
144
145 // First OID (the algorithm).
146 let oid_len_p: *i64 = sys_mmap(16) as *i64
147 let rc2: i64 = asn1_expect_tag(buf, c, ASN1_OID, oid_len_p)
148 if rc2 < 0 { return NX_X509_PUBKEY_ALG_UNKNOWN }
149 let oid_off: i64 = c.pos
150 let oid_len: i64 = *oid_len_p
151 if c.pos + oid_len > inner_end { return NX_X509_PUBKEY_ALG_UNKNOWN }
152 c.pos = c.pos + oid_len
153
154 // Case 1: Ed25519 (no parameters).
155 if oid_is_ed25519(buf, oid_off, oid_len) == 1 {
156 return NX_X509_PUBKEY_ALG_ED25519
157 }
158
159 // Case 3: rsaEncryption (parameters are NULL or absent; we don't
160 // probe them here).
161 if oid_is_rsa_encryption(buf, oid_off, oid_len) == 1 {
162 return NX_X509_PUBKEY_ALG_RSA
163 }
164
165 // Case 2: id-ecPublicKey -- requires namedCurve OID parameter to
166 // distinguish P-256 from P-384.
167 if oid_is_id_ec_public_key(buf, oid_off, oid_len) == 1 {
168 // Read the parameters: must be a namedCurve OID TLV
169 let curve_len_p: *i64 = sys_mmap(16) as *i64
170 let rc3: i64 = asn1_expect_tag(buf, c, ASN1_OID, curve_len_p)
171 if rc3 < 0 { return NX_X509_PUBKEY_ALG_UNKNOWN }
172 if oid_is_prime256v1(buf, c.pos, *curve_len_p) == 1 {
173 return NX_X509_PUBKEY_ALG_EC_P256
174 }
175 if oid_is_secp384r1(buf, c.pos, *curve_len_p) == 1 {
176 return NX_X509_PUBKEY_ALG_EC_P384
177 }
178 return NX_X509_PUBKEY_ALG_UNKNOWN
179 }
180
181 return NX_X509_PUBKEY_ALG_UNKNOWN
182}
183
184// Compile-only smoke. Real KAT in nx_x509_pubkey_alg_test.nx.
185func main() -> i64 {
186 return 0
187}