nx_x509_sig_alg.nx source
↩ module page · 188 lines · 7714 B
1// nx_x509_sig_alg.nx -- identify the signature algorithm of an
2// X.509 cert by inspecting its sig_alg AlgorithmIdentifier bytes.
3//
4// Phase 0b §I.4 piece 2 of the chain-walker arc. Composes the
5// X509Cert.sig_alg_off/_len fields (populated by x509_parse)
6// with a sealed-enum identifier so the chain walker can dispatch
7// to the right verifier.
8//
9// AlgorithmIdentifier ASN.1 (RFC 5280 §4.1.1.2):
10//
11// AlgorithmIdentifier ::= SEQUENCE {
12// algorithm OBJECT IDENTIFIER,
13// parameters ANY DEFINED BY algorithm OPTIONAL
14// }
15//
16// We compare the OID bytes against a small table of known
17// algorithm OIDs. Parameters are ignored for the algs we
18// support (Ed25519 and ECDSA have absent or NULL params).
19//
20// Supported algorithms (sealed enum):
21//
22// NX_X509_SIG_ALG_UNKNOWN unrecognized / unsupported
23// NX_X509_SIG_ALG_ED25519 1.3.101.112
24// DER OID: 06 03 2B 65 70
25// NX_X509_SIG_ALG_ECDSA_P256_SHA256 1.2.840.10045.4.3.2
26// DER OID: 06 08 2A 86 48 CE 3D 04 03 02
27//
28// Queued (not yet supported):
29// - ecdsa-with-SHA-384 (1.2.840.10045.4.3.3)
30// - sha256WithRSAEncryption (1.2.840.113549.1.1.11) -- needs RSA verify
31// - rsassa-pss (1.2.840.113549.1.1.10) -- needs RSA-PSS verify
32//
33// Public API:
34// nx_x509_sig_alg_identify(buf, sig_alg_off, sig_alg_len) -> alg-enum
35//
36// Returns NX_X509_SIG_ALG_* identifier for the AlgorithmIdentifier
37// starting at buf[sig_alg_off] and spanning sig_alg_len bytes.
38//
39// nx_x509_sig_alg_is_supported(alg) -> 0|1
40//
41// Per Cardinals 9 (single-responsibility -- this primitive does
42// JUST OID identification, no verify), 12 (defensive at boundaries --
43// bounded byte reads), and 23 (preamble lists every supported OID
44// + the queued ones).
45//
46// license_tier: INDEPENDENT_REDERIVE
47// genealogy_id: international-research-sources/ietf/rfc_5280 + rfc_5480 +
48// itu_t/iso_oid + ietf/rfc_8410
49// lineage_id: nishi_x509_sig_alg_q10
50
51// nx_safety_envelope:
52// intended_use: AUTO_APPLIED -- primitive-specific tuning queued
53// sil_target: SIL1
54// evidence: [bulk_applied_2026-05-19, x509-sig-alg-oid-identify]
55// verdict: NOT_YET_EVALUATED
56
57import "nx_syscalls.nx"
58import "nx_asn1.nx"
59
60const NX_X509_SIG_ALG_UNKNOWN: i64 = 0
61const NX_X509_SIG_ALG_ED25519: i64 = 1
62const NX_X509_SIG_ALG_ECDSA_P256_SHA256: i64 = 2
63// ecdsa-with-SHA384 -- the cert sig_alg field doesn't pin the
64// curve; the curve comes from the issuer's pubkey_alg. Verify
65// dispatch combines this sig_alg with the issuer's curve to
66// pick the hash + verify primitive.
67const NX_X509_SIG_ALG_ECDSA_SHA384: i64 = 3
68// sha256WithRSAEncryption -- OID 1.2.840.113549.1.1.11
69// DER inner: 06 09 2A 86 48 86 F7 0D 01 01 0B (content 9 bytes)
70const NX_X509_SIG_ALG_RSA_PKCS1_SHA256: i64 = 4
71// sha384WithRSAEncryption -- OID 1.2.840.113549.1.1.12
72// DER inner: 06 09 2A 86 48 86 F7 0D 01 01 0C (content 9 bytes; the
73// last byte is 0x0C vs 0x0B for SHA-256). Common on Sectigo / USERTrust
74// / DigiCert intermediate + root certs.
75const NX_X509_SIG_ALG_RSA_PKCS1_SHA384: i64 = 5
76const NX_X509_SIG_ALG_N: i64 = 6
77
78// Returns 1 if the algorithm enum value is one we can actually
79// verify (i.e. NOT _UNKNOWN), else 0.
80func nx_x509_sig_alg_is_supported(alg: i64) -> i64 {
81 if alg <= NX_X509_SIG_ALG_UNKNOWN { return 0 }
82 if alg >= NX_X509_SIG_ALG_N { return 0 }
83 return 1
84}
85
86// Read the AlgorithmIdentifier SEQUENCE at buf[off..off+len) and
87// identify its OID. Returns NX_X509_SIG_ALG_* enum.
88//
89// Implementation: walks the SEQUENCE header, reads the OID TLV,
90// then compares the OID bytes against a hard-coded table. Any
91// parse failure -> UNKNOWN.
92func nx_x509_sig_alg_identify(buf: *u8, sig_alg_off: i64,
93 sig_alg_len: i64) -> i64 {
94 if sig_alg_len < 4 { return NX_X509_SIG_ALG_UNKNOWN }
95
96 // The sig_alg blob is a SEQUENCE -- step inside.
97 let c_raw: *u8 = sys_mmap(32)
98 let c: *Asn1Cursor = c_raw as *Asn1Cursor
99 c.pos = sig_alg_off
100 c.end = sig_alg_off + sig_alg_len
101
102 let outer_len_p: *i64 = sys_mmap(16) as *i64
103 let rc1: i64 = asn1_expect_tag(buf, c, ASN1_SEQUENCE, outer_len_p)
104 if rc1 < 0 { return NX_X509_SIG_ALG_UNKNOWN }
105 let inner_end: i64 = c.pos + *outer_len_p
106 if inner_end > c.end { return NX_X509_SIG_ALG_UNKNOWN }
107
108 // Read the OID TLV.
109 let oid_len_p: *i64 = sys_mmap(16) as *i64
110 let rc2: i64 = asn1_expect_tag(buf, c, ASN1_OID, oid_len_p)
111 if rc2 < 0 { return NX_X509_SIG_ALG_UNKNOWN }
112 let oid_off: i64 = c.pos
113 let oid_len: i64 = *oid_len_p
114 if c.pos + oid_len > inner_end { return NX_X509_SIG_ALG_UNKNOWN }
115
116 // --- Compare against Ed25519: 06 03 2B 65 70 (content is 3 bytes 2B 65 70) ---
117 if oid_len == 3 {
118 if (buf[oid_off] & 0xff) == 0x2B {
119 if (buf[oid_off+1] & 0xff) == 0x65 {
120 if (buf[oid_off+2] & 0xff) == 0x70 {
121 return NX_X509_SIG_ALG_ED25519
122 }
123 }
124 }
125 }
126
127 // --- Compare against ecdsa-with-SHA-256 / SHA-384. Both
128 // OIDs share the first 7 bytes (1.2.840.10045.4.3.X);
129 // last byte differs: 0x02 = SHA256, 0x03 = SHA384.
130 // Content is 8 bytes: 2A 86 48 CE 3D 04 03 [02|03]
131 if oid_len == 8 {
132 if (buf[oid_off] & 0xff) == 0x2A {
133 if (buf[oid_off+1] & 0xff) == 0x86 {
134 if (buf[oid_off+2] & 0xff) == 0x48 {
135 if (buf[oid_off+3] & 0xff) == 0xCE {
136 if (buf[oid_off+4] & 0xff) == 0x3D {
137 if (buf[oid_off+5] & 0xff) == 0x04 {
138 if (buf[oid_off+6] & 0xff) == 0x03 {
139 if (buf[oid_off+7] & 0xff) == 0x02 {
140 return NX_X509_SIG_ALG_ECDSA_P256_SHA256
141 }
142 if (buf[oid_off+7] & 0xff) == 0x03 {
143 return NX_X509_SIG_ALG_ECDSA_SHA384
144 }
145 }
146 }
147 }
148 }
149 }
150 }
151 }
152 }
153
154 // --- Compare against sha256WithRSAEncryption (PKCS#1 v1.5):
155 // 1.2.840.113549.1.1.11 -- content 9 bytes:
156 // 2A 86 48 86 F7 0D 01 01 0B
157 if oid_len == 9 {
158 if (buf[oid_off] & 0xff) == 0x2A {
159 if (buf[oid_off+1] & 0xff) == 0x86 {
160 if (buf[oid_off+2] & 0xff) == 0x48 {
161 if (buf[oid_off+3] & 0xff) == 0x86 {
162 if (buf[oid_off+4] & 0xff) == 0xF7 {
163 if (buf[oid_off+5] & 0xff) == 0x0D {
164 if (buf[oid_off+6] & 0xff) == 0x01 {
165 if (buf[oid_off+7] & 0xff) == 0x01 {
166 if (buf[oid_off+8] & 0xff) == 0x0B {
167 return NX_X509_SIG_ALG_RSA_PKCS1_SHA256
168 }
169 if (buf[oid_off+8] & 0xff) == 0x0C {
170 return NX_X509_SIG_ALG_RSA_PKCS1_SHA384
171 }
172 }
173 }
174 }
175 }
176 }
177 }
178 }
179 }
180 }
181
182 return NX_X509_SIG_ALG_UNKNOWN
183}
184
185// Compile-only smoke. Real KAT in nx_x509_sig_alg_test.nx.
186func main() -> i64 {
187 return 0
188}