code wiki / (root) / nx_x509_sig_alg.nx

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}