code wiki / hub / nx_x509_build.nx

nx_x509_build.nx source

↩ module page · 603 lines · 26693 B

1// nx_x509_build.nx -- V-HOST-4b: sovereign X.509 v3 self-signed cert builder. 2// 3// Composes hub/nx_asn1_write (V-HOST-4a) + ed25519_sign_full to produce 4// a fully-conformant RFC 5280 X.509 v3 certificate signed with Ed25519 5// (RFC 8410 + RFC 8032). 6// 7// COMPOSES (per "avoid duplicate primitives"): 8// hub/nx_asn1_write (DER encoder; sibling to existing asn1.nx READ surface) 9// nx_ed25519_signature (ed25519_sign_full per RFC 8032 §5.1.6) 10// 11// COMPOSED BY: 12// bin/nx_cert_gen.nx (V-HOST-4c; CLI driver; queued) 13// future: ACME cert request flow integration 14// 15// V-HOST-4b SCOPE per RFC 5280: 16// Certificate { 17// tbsCertificate TBSCertificate, 18// signatureAlgorithm AlgorithmIdentifier, // ed25519 19// signatureValue BIT STRING // ed25519 64-byte sig 20// } 21// TBSCertificate { 22// version [0] EXPLICIT INTEGER (v3 = 2), 23// serialNumber INTEGER (operator-supplied OR random), 24// signature AlgorithmIdentifier, // ed25519 25// issuer Name, // self-signed: subject == issuer 26// validity Validity {notBefore, notAfter}, // GeneralizedTime 27// subject Name, // CN=<domain> 28// subjectPublicKeyInfo SubjectPublicKeyInfo,// ed25519 pubkey 29// extensions [3] EXPLICIT Extensions { // V3 extensions 30// subjectAltName // SAN list 31// basicConstraints // CA:FALSE 32// keyUsage // digitalSignature 33// } 34// } 35// 36// V-HOST-4b NON-SCOPE (V+1): 37// - ECDSA / RSA sig algs (only Ed25519 V1) 38// - CA certs (only end-entity / leaf V1) 39// - Cert chain (only self-signed V1) 40// - CRL distribution points / OCSP / AIA extensions 41// 42// Status: V-HOST-4b. 2026-05-27. 43 44import "nx_syscalls.nx" 45import "hub/nx_asn1_write.nx" 46import "nx_ed25519_signature.nx" 47// V-HOST-4d-1: ECDSA P-256 path additions 48import "nx_ecdsa_p256.nx" 49import "nx_ecdsa_p256_sign.nx" 50import "nx_x509_sig_alg.nx" 51import "nx_u256.nx" 52import "nx_p256_point.nx" 53import "nx_sha256.nx" 54 55// ===== Sealed verdict surface (codes 3620-3634) ================================================= 56const NX_X509_OK: i64 = 0 57const NX_X509_BAD_INPUT: i64 = 3620 58const NX_X509_BUF_OVERFLOW: i64 = 3621 59const NX_X509_TOO_MANY_SANS: i64 = 3622 60const NX_X509_BAD_TIME: i64 = 3623 61const NX_X509_SIGN_FAILED: i64 = 3624 62const NX_X509_SERIAL_TOO_LARGE: i64 = 3625 63 64// ===== Named constants (M7) ================================================= 65const NX_X509_MAX_DER_OUT: i64 = 8192 // V1 self-signed cert easily fits 66const NX_X509_MAX_TBS_BUF: i64 = 4096 // TBSCertificate scratch 67const NX_X509_MAX_NAME_BUF: i64 = 256 // CN body 68const NX_X509_MAX_SANS: i64 = 16 69const NX_X509_MAX_SAN_LEN: i64 = 253 // hostname per RFC 1035 + room 70const NX_X509_MAX_VALIDITY_BUF: i64 = 64 71const NX_X509_MAX_SPKI_BUF: i64 = 128 72const NX_X509_MAX_EXT_BUF: i64 = 512 73const NX_X509_MAX_ISSUER_BUF: i64 = 256 74const NX_X509_ED25519_SIG_LEN: i64 = 64 75const NX_X509_ED25519_PUB_LEN: i64 = 32 76const NX_X509_ED25519_PRIV_LEN: i64 = 32 77 78// ===== Sealed sig-alg enum (V-HOST-4d-1) ================================================= 79// 80// Per RFC 5280 + RFC 5480 + RFC 8410: cert can be signed with various 81// algorithms. V1 supports two sovereign options: 82// ED25519 -- RFC 8410 (Ed25519 sig; sovereign + modern) 83// ECDSA_P256_SHA256 -- RFC 5480 §2.1.1 (ECDSA with SHA-256; legacy-compat) 84 85// Signature-algorithm enum: use the ONE canonical definition from 86// nx_x509_sig_alg.nx (UNKNOWN=0, ED25519=1, ECDSA_P256_SHA256=2, 87// ECDSA_SHA384=3, RSA_PKCS1_SHA256=4, N=5). This file previously 88// redefined a SHORTER enum (NONE=0, ED25519=1, ECDSA_P256_SHA256=2, 89// N=3) -- a duplicate-const trap: when both this builder and the 90// validator (nx_x509_sig_alg) landed in one binary, the native 91// compiler silently resolved NX_X509_SIG_ALG_N to 3, breaking the 92// validator's is_supported() bound (alg>=3 -> ECDSA_SHA384 and 93// RSA_PKCS1_SHA256 wrongly rejected -> every LE chain failed). The 94// builder only ever uses ED25519 + ECDSA_P256_SHA256 (same values in 95// both enums), so importing the canonical enum is behavior-preserving. 96 97func nx_x509_sig_alg_name(a: i64) -> *u8 { 98 if a == NX_X509_SIG_ALG_ED25519 { return "ed25519" as *u8 } 99 if a == NX_X509_SIG_ALG_ECDSA_P256_SHA256 { return "ecdsa-p256-sha256" as *u8 } 100 return "UNKNOWN" as *u8 101} 102 103// ===== Sealed OID byte sequences (DER-encoded; per X.690 §8.19) ================================================= 104// 105// Each OID's body (length-prefix handled by nx_aw_put_oid). 106// Pre-computed per OID arc decomposition: 107// 1.3.101.112 (Ed25519) = 0x2B 0x65 0x70 108// 2.5.29.17 (subjectAltName) = 0x55 0x1D 0x11 109// 2.5.29.19 (basicConstraints) = 0x55 0x1D 0x13 110// 2.5.29.15 (keyUsage) = 0x55 0x1D 0x0F 111// 2.5.4.3 (CN commonName) = 0x55 0x04 0x03 112 113const NX_X509_OID_ED25519: *u8 = "\x2B\x65\x70" as *u8 114const NX_X509_OID_ED25519_N: i64 = 3 115const NX_X509_OID_SAN: *u8 = "\x55\x1D\x11" as *u8 116const NX_X509_OID_SAN_N: i64 = 3 117const NX_X509_OID_BASIC_CONSTRAINTS: *u8 = "\x55\x1D\x13" as *u8 118const NX_X509_OID_BASIC_CONSTRAINTS_N: i64 = 3 119const NX_X509_OID_KEY_USAGE: *u8 = "\x55\x1D\x0F" as *u8 120const NX_X509_OID_KEY_USAGE_N: i64 = 3 121const NX_X509_OID_CN: *u8 = "\x55\x04\x03" as *u8 122const NX_X509_OID_CN_N: i64 = 3 123 124// V-HOST-4d-1 ECDSA OIDs (per RFC 5480 + ANSI X9.62): 125// ecdsa-with-SHA256 = 1.2.840.10045.4.3.2 -> 0x2A 0x86 0x48 0xCE 0x3D 0x04 0x03 0x02 126const NX_X509_OID_ECDSA_SHA256: *u8 = "\x2A\x86\x48\xCE\x3D\x04\x03\x02" as *u8 127const NX_X509_OID_ECDSA_SHA256_N: i64 = 8 128// id-ecPublicKey = 1.2.840.10045.2.1 -> 0x2A 0x86 0x48 0xCE 0x3D 0x02 0x01 129const NX_X509_OID_EC_PUBLIC_KEY: *u8 = "\x2A\x86\x48\xCE\x3D\x02\x01" as *u8 130const NX_X509_OID_EC_PUBLIC_KEY_N: i64 = 7 131// secp256r1 (P-256 namedCurve) = 1.2.840.10045.3.1.7 -> 0x2A 0x86 0x48 0xCE 0x3D 0x03 0x01 0x07 132const NX_X509_OID_SECP256R1: *u8 = "\x2A\x86\x48\xCE\x3D\x03\x01\x07" as *u8 133const NX_X509_OID_SECP256R1_N: i64 = 8 134 135const NX_X509_ECDSA_P256_PUB_LEN: i64 = 65 // 0x04 || X(32B) || Y(32B) uncompressed 136 137// ===== Cert build inputs (caller-supplied) ================================================= 138 139struct NxX509BuildInputs { 140 // Subject (== issuer for self-signed) 141 cn: *u8 // common name (the primary domain) 142 cn_n: i64 143 // SAN list (parallel arrays; san_ptrs[i] + san_lens[i]) 144 san_ptrs: *i64 145 san_lens: *i64 146 san_count: i64 147 // Ed25519 keypair 148 priv_32: *u8 149 pub_32: *u8 150 // Validity (operator-supplied GeneralizedTime strings: YYYYMMDDhhmmssZ) 151 not_before: *u8 // 15 bytes 152 not_after: *u8 // 15 bytes 153 // Serial number (1..8 bytes; positive) 154 serial: i64 155 // V-HOST-4d-1: signature algorithm selector 156 sig_alg: i64 // NX_X509_SIG_ALG_* enum 157 // V-HOST-4d-1: ECDSA P-256 pubkey (X || Y as i64 limb arrays; 4 limbs each) 158 // Only used when sig_alg == ECDSA_P256_SHA256; ignored otherwise. 159 ecdsa_pub_x: *i64 // 4 limbs (256-bit X coordinate) 160 ecdsa_pub_y: *i64 // 4 limbs (256-bit Y coordinate) 161 // V-HOST-4d-1: ECDSA P-256 priv as 4-limb i64 162 ecdsa_priv_limbs: *i64 // 4 limbs (256-bit scalar) 163 valid: i64 164} 165 166func nx_x509_inputs_init(inp: *NxX509BuildInputs, 167 cn: *u8, cn_n: i64, 168 san_ptrs: *i64, san_lens: *i64, san_count: i64, 169 priv_32: *u8, pub_32: *u8, 170 not_before: *u8, not_after: *u8, 171 serial: i64) -> i64 { 172 if (inp as i64) == 0 { return 0 - NX_X509_BAD_INPUT } 173 if (cn as i64) == 0 { return 0 - NX_X509_BAD_INPUT } 174 if cn_n < 1 { return 0 - NX_X509_BAD_INPUT } 175 if cn_n > NX_X509_MAX_NAME_BUF { return 0 - NX_X509_BAD_INPUT } 176 if san_count < 0 { return 0 - NX_X509_BAD_INPUT } 177 if san_count > NX_X509_MAX_SANS { return 0 - NX_X509_TOO_MANY_SANS } 178 if (priv_32 as i64) == 0 { return 0 - NX_X509_BAD_INPUT } 179 if (pub_32 as i64) == 0 { return 0 - NX_X509_BAD_INPUT } 180 if (not_before as i64) == 0 { return 0 - NX_X509_BAD_INPUT } 181 if (not_after as i64) == 0 { return 0 - NX_X509_BAD_INPUT } 182 if serial < 1 { return 0 - NX_X509_BAD_INPUT } 183 184 inp.cn = cn; inp.cn_n = cn_n 185 inp.san_ptrs = san_ptrs; inp.san_lens = san_lens; inp.san_count = san_count 186 inp.priv_32 = priv_32; inp.pub_32 = pub_32 187 inp.not_before = not_before; inp.not_after = not_after 188 inp.serial = serial 189 // V-HOST-4d-1: backward-compat default = ED25519 (matches original V-HOST-4 behavior) 190 inp.sig_alg = NX_X509_SIG_ALG_ED25519 191 inp.ecdsa_pub_x = 0 as *i64 192 inp.ecdsa_pub_y = 0 as *i64 193 inp.ecdsa_priv_limbs = 0 as *i64 194 inp.valid = 1 195 return NX_X509_OK 196} 197 198// V-HOST-4d-1: ECDSA P-256 inputs init (parallel to nx_x509_inputs_init). 199// Caller supplies P-256 pub coordinates + priv as 4-limb i64 arrays. 200// priv_32 + pub_32 fields ignored for ECDSA path. 201func nx_x509_inputs_init_ecdsa_p256(inp: *NxX509BuildInputs, 202 cn: *u8, cn_n: i64, 203 san_ptrs: *i64, san_lens: *i64, san_count: i64, 204 priv_limbs: *i64, 205 pub_x_limbs: *i64, pub_y_limbs: *i64, 206 not_before: *u8, not_after: *u8, 207 serial: i64) -> i64 { 208 if (inp as i64) == 0 { return 0 - NX_X509_BAD_INPUT } 209 if (cn as i64) == 0 { return 0 - NX_X509_BAD_INPUT } 210 if cn_n < 1 { return 0 - NX_X509_BAD_INPUT } 211 if cn_n > NX_X509_MAX_NAME_BUF { return 0 - NX_X509_BAD_INPUT } 212 if san_count < 0 { return 0 - NX_X509_BAD_INPUT } 213 if san_count > NX_X509_MAX_SANS { return 0 - NX_X509_TOO_MANY_SANS } 214 if (priv_limbs as i64) == 0 { return 0 - NX_X509_BAD_INPUT } 215 if (pub_x_limbs as i64) == 0 { return 0 - NX_X509_BAD_INPUT } 216 if (pub_y_limbs as i64) == 0 { return 0 - NX_X509_BAD_INPUT } 217 if (not_before as i64) == 0 { return 0 - NX_X509_BAD_INPUT } 218 if (not_after as i64) == 0 { return 0 - NX_X509_BAD_INPUT } 219 if serial < 1 { return 0 - NX_X509_BAD_INPUT } 220 221 inp.cn = cn; inp.cn_n = cn_n 222 inp.san_ptrs = san_ptrs; inp.san_lens = san_lens; inp.san_count = san_count 223 inp.priv_32 = 0 as *u8 224 inp.pub_32 = 0 as *u8 225 inp.not_before = not_before; inp.not_after = not_after 226 inp.serial = serial 227 inp.sig_alg = NX_X509_SIG_ALG_ECDSA_P256_SHA256 228 inp.ecdsa_pub_x = pub_x_limbs 229 inp.ecdsa_pub_y = pub_y_limbs 230 inp.ecdsa_priv_limbs = priv_limbs 231 inp.valid = 1 232 return NX_X509_OK 233} 234 235// ===== Build AlgorithmIdentifier for Ed25519 ================================================= 236// 237// AlgorithmIdentifier { algorithm = OID(1.3.101.112), parameters = ABSENT } 238// per RFC 8410 §3 (Ed25519 OIDs omit parameters; not NULL). 239 240func nx_x509_build_alg_ed25519(out: *u8, cap: i64, off: i64) -> i64 { 241 let scratch: *u8 = sys_mmap(64) 242 var so: i64 = 0 243 so = nx_aw_put_oid(scratch, 64, so, NX_X509_OID_ED25519, NX_X509_OID_ED25519_N) 244 if so < 0 { return so } 245 // No parameters per RFC 8410 §3 -- wrap inp SEQUENCE 246 return nx_aw_wrap_sequence(out, cap, off, scratch, so) 247} 248 249// ===== V-HOST-4d-1: Build AlgorithmIdentifier for ECDSA-with-SHA256 ================================================= 250// 251// AlgorithmIdentifier { algorithm = OID(1.2.840.10045.4.3.2), parameters = ABSENT } 252// per RFC 5758 §3.2 (ecdsa-with-SHA256 has no parameters). 253 254func nx_x509_build_alg_ecdsa_p256_sha256(out: *u8, cap: i64, off: i64) -> i64 { 255 let scratch: *u8 = sys_mmap(64) 256 var so: i64 = 0 257 so = nx_aw_put_oid(scratch, 64, so, NX_X509_OID_ECDSA_SHA256, NX_X509_OID_ECDSA_SHA256_N) 258 if so < 0 { return so } 259 // No parameters per RFC 5758 §3.2 -- wrap inp SEQUENCE 260 return nx_aw_wrap_sequence(out, cap, off, scratch, so) 261} 262 263// ===== V-HOST-4d-1: Sig-alg-dispatched AlgorithmIdentifier ================================================= 264// 265// Switches on inp.sig_alg to emit the correct AlgorithmIdentifier. 266 267func nx_x509_build_sig_alg(out: *u8, cap: i64, off: i64, sig_alg: i64) -> i64 { 268 if sig_alg == NX_X509_SIG_ALG_ED25519 { 269 return nx_x509_build_alg_ed25519(out, cap, off) 270 } 271 if sig_alg == NX_X509_SIG_ALG_ECDSA_P256_SHA256 { 272 return nx_x509_build_alg_ecdsa_p256_sha256(out, cap, off) 273 } 274 return 0 - NX_X509_BAD_INPUT 275} 276 277// ===== V-HOST-4d-1: Build SubjectPublicKeyInfo for ECDSA P-256 ================================================= 278// 279// SubjectPublicKeyInfo { 280// algorithm = SEQUENCE { 281// algorithm = OID(id-ecPublicKey), 282// parameters = OID(secp256r1) -- named curve 283// }, 284// subjectPublicKey = BIT STRING (65 bytes: 0x04 || X(32B) || Y(32B) uncompressed) 285// } 286// per RFC 5480 §2 287 288func nx_x509_build_spki_ecdsa_p256(out: *u8, cap: i64, off: i64, 289 pub_x: *i64, pub_y: *i64) -> i64 { 290 // 1. Build inner AlgorithmIdentifier SEQUENCE {ecPublicKey OID, secp256r1 OID} 291 let alg_body: *u8 = sys_mmap(64) 292 var ao: i64 = 0 293 ao = nx_aw_put_oid(alg_body, 64, ao, NX_X509_OID_EC_PUBLIC_KEY, NX_X509_OID_EC_PUBLIC_KEY_N) 294 if ao < 0 { return ao } 295 ao = nx_aw_put_oid(alg_body, 64, ao, NX_X509_OID_SECP256R1, NX_X509_OID_SECP256R1_N) 296 if ao < 0 { return ao } 297 let alg_seq: *u8 = sys_mmap(64) 298 let alg_seq_n: i64 = nx_aw_wrap_sequence(alg_seq, 64, 0, alg_body, ao) 299 if alg_seq_n < 0 { return alg_seq_n } 300 301 // 2. Build uncompressed point: 0x04 || X(32B) || Y(32B) 302 let pub_bytes: *u8 = sys_mmap(65) 303 pub_bytes[0] = 0x04 as u8 304 let x_off: *u8 = ((pub_bytes as i64) + 1) as *u8 305 let y_off: *u8 = ((pub_bytes as i64) + 33) as *u8 306 u256_store_be(x_off, pub_x) 307 u256_store_be(y_off, pub_y) 308 309 // 3. Build SubjectPublicKeyInfo body: alg + BIT STRING(pub) 310 let spki_body: *u8 = sys_mmap(NX_X509_MAX_SPKI_BUF) 311 var sbo: i64 = 0 312 sbo = nx_aw_put_raw(spki_body, NX_X509_MAX_SPKI_BUF, sbo, alg_seq, alg_seq_n) 313 if sbo < 0 { return sbo } 314 sbo = nx_aw_put_bit_string(spki_body, NX_X509_MAX_SPKI_BUF, sbo, 0, pub_bytes, 65) 315 if sbo < 0 { return sbo } 316 317 return nx_aw_wrap_sequence(out, cap, off, spki_body, sbo) 318} 319 320// ===== V-HOST-4d-1: Sig-alg-dispatched SPKI ================================================= 321 322func nx_x509_build_spki(out: *u8, cap: i64, off: i64, inp: *NxX509BuildInputs) -> i64 { 323 if inp.sig_alg == NX_X509_SIG_ALG_ED25519 { 324 return nx_x509_build_spki_ed25519(out, cap, off, inp.pub_32) 325 } 326 if inp.sig_alg == NX_X509_SIG_ALG_ECDSA_P256_SHA256 { 327 return nx_x509_build_spki_ecdsa_p256(out, cap, off, inp.ecdsa_pub_x, inp.ecdsa_pub_y) 328 } 329 return 0 - NX_X509_BAD_INPUT 330} 331 332// ===== V-HOST-4d-1: Encode ECDSA (r, s) as DER SEQUENCE {INTEGER r, INTEGER s} ================================================= 333// 334// Per ANSI X9.62 / RFC 3279 §2.2.3: ECDSA signature value is 335// Ecdsa-Sig-Value ::= SEQUENCE {r INTEGER, s INTEGER} 336// Composes nx_asn1_write.nx_aw_put_integer_bytes (auto-prepends 0x00 337// when MSB high to keep INTEGERs unambiguously positive per DER). 338 339func nx_x509_ecdsa_sig_to_der(r: *i64, s: *i64, 340 out: *u8, cap: i64) -> i64 { 341 // 1. Convert r, s limbs to big-endian byte arrays (32 bytes each) 342 let r_be: *u8 = sys_mmap(32) 343 let s_be: *u8 = sys_mmap(32) 344 u256_store_be(r_be, r) 345 u256_store_be(s_be, s) 346 347 // 2. Strip leading zero bytes to the minimal big-endian magnitude, 348 // keeping at least one byte (an all-zero value encodes as 0x00). 349 // nx_aw_put_integer_bytes re-prepends 0x00 when the MSB is set, so 350 // we must NOT leave a spurious leading zero here. 351 var r_off: i64 = 0 352 while r_off < 31 { 353 if r_be[r_off] != (0 as u8) { break } 354 r_off = r_off + 1 355 } 356 var s_off: i64 = 0 357 while s_off < 31 { 358 if s_be[s_off] != (0 as u8) { break } 359 s_off = s_off + 1 360 } 361 let r_trimmed: *u8 = ((r_be as i64) + r_off) as *u8 362 let r_trim_n: i64 = 32 - r_off 363 let s_trimmed: *u8 = ((s_be as i64) + s_off) as *u8 364 let s_trim_n: i64 = 32 - s_off 365 366 // 3. Build SEQUENCE body: INTEGER r + INTEGER s 367 let body: *u8 = sys_mmap(96) 368 var bo: i64 = 0 369 bo = nx_aw_put_integer_bytes(body, 96, bo, r_trimmed, r_trim_n); if bo < 0 { return bo } 370 bo = nx_aw_put_integer_bytes(body, 96, bo, s_trimmed, s_trim_n); if bo < 0 { return bo } 371 372 // 4. Wrap as SEQUENCE 373 return nx_aw_wrap_sequence(out, cap, 0, body, bo) 374} 375 376// ===== Build Name (V1: just CN) ================================================= 377// 378// Name = SEQUENCE OF RelativeDistinguishedName 379// RDN = SET OF AttributeTypeAndValue 380// ATV = SEQUENCE {type OID, value UTF8String} 381 382func nx_x509_build_name_cn(out: *u8, cap: i64, off: i64, 383 cn: *u8, cn_n: i64) -> i64 { 384 let atv_buf: *u8 = sys_mmap(NX_X509_MAX_NAME_BUF) 385 var ao: i64 = 0 386 ao = nx_aw_put_oid(atv_buf, NX_X509_MAX_NAME_BUF, ao, NX_X509_OID_CN, NX_X509_OID_CN_N); if ao < 0 { return ao } 387 ao = nx_aw_put_utf8_string(atv_buf, NX_X509_MAX_NAME_BUF, ao, cn, cn_n); if ao < 0 { return ao } 388 389 // Wrap ATV inp SEQUENCE 390 let atv_seq_buf: *u8 = sys_mmap(NX_X509_MAX_NAME_BUF) 391 let atv_seq_n: i64 = nx_aw_wrap_sequence(atv_seq_buf, NX_X509_MAX_NAME_BUF, 0, atv_buf, ao) 392 if atv_seq_n < 0 { return atv_seq_n } 393 394 // Wrap inp SET (RDN) 395 let rdn_buf: *u8 = sys_mmap(NX_X509_MAX_NAME_BUF) 396 let rdn_n: i64 = nx_aw_wrap_set(rdn_buf, NX_X509_MAX_NAME_BUF, 0, atv_seq_buf, atv_seq_n) 397 if rdn_n < 0 { return rdn_n } 398 399 // Wrap RDN inp SEQUENCE (Name = SEQUENCE OF RDN) 400 return nx_aw_wrap_sequence(out, cap, off, rdn_buf, rdn_n) 401} 402 403// ===== Build Validity ================================================= 404 405func nx_x509_build_validity(out: *u8, cap: i64, off: i64, 406 not_before: *u8, not_after: *u8) -> i64 { 407 let scratch: *u8 = sys_mmap(NX_X509_MAX_VALIDITY_BUF) 408 var so: i64 = 0 409 so = nx_aw_put_generalized_time(scratch, NX_X509_MAX_VALIDITY_BUF, so, not_before) 410 if so < 0 { return so } 411 so = nx_aw_put_generalized_time(scratch, NX_X509_MAX_VALIDITY_BUF, so, not_after) 412 if so < 0 { return so } 413 return nx_aw_wrap_sequence(out, cap, off, scratch, so) 414} 415 416// ===== Build SubjectPublicKeyInfo (Ed25519) ================================================= 417// 418// SubjectPublicKeyInfo { 419// algorithm = AlgorithmIdentifier(Ed25519), 420// subjectPublicKey = BIT STRING(32-byte Ed25519 pubkey; unused_bits = 0) 421// } 422 423func nx_x509_build_spki_ed25519(out: *u8, cap: i64, off: i64, 424 pub_32: *u8) -> i64 { 425 let scratch: *u8 = sys_mmap(NX_X509_MAX_SPKI_BUF) 426 var so: i64 = 0 427 so = nx_x509_build_alg_ed25519(scratch, NX_X509_MAX_SPKI_BUF, so); if so < 0 { return so } 428 so = nx_aw_put_bit_string(scratch, NX_X509_MAX_SPKI_BUF, so, 0, pub_32, NX_X509_ED25519_PUB_LEN) 429 if so < 0 { return so } 430 return nx_aw_wrap_sequence(out, cap, off, scratch, so) 431} 432 433// ===== Build Extensions ================================================= 434 435// SAN extension: extnValue is OCTET STRING containing DER-encoded 436// SubjectAltName ::= SEQUENCE OF GeneralName. 437// GeneralName.dNSName = [2] IMPLICIT IA5String. 438 439func nx_x509_build_ext_san(out: *u8, cap: i64, off: i64, 440 san_ptrs: *i64, san_lens: *i64, san_count: i64) -> i64 { 441 // 1. Build SubjectAltName body: SEQUENCE OF [2] IMPLICIT IA5String 442 let san_body: *u8 = sys_mmap(NX_X509_MAX_EXT_BUF) 443 var sbo: i64 = 0 444 var i: i64 = 0 445 while i < san_count { 446 let san_ptr: *u8 = san_ptrs[i] as *u8 447 let san_n: i64 = san_lens[i] 448 if san_n > NX_X509_MAX_SAN_LEN { return 0 - NX_X509_BAD_INPUT } 449 sbo = nx_aw_wrap_implicit_primitive(san_body, NX_X509_MAX_EXT_BUF, sbo, 450 2, san_ptr, san_n) 451 if sbo < 0 { return sbo } 452 i = i + 1 453 } 454 // Wrap as SEQUENCE 455 let san_seq: *u8 = sys_mmap(NX_X509_MAX_EXT_BUF) 456 let san_seq_n: i64 = nx_aw_wrap_sequence(san_seq, NX_X509_MAX_EXT_BUF, 0, san_body, sbo) 457 if san_seq_n < 0 { return san_seq_n } 458 459 // 2. Wrap as OCTET STRING (extnValue) 460 let octet_buf: *u8 = sys_mmap(NX_X509_MAX_EXT_BUF) 461 let octet_n: i64 = nx_aw_put_octet_string(octet_buf, NX_X509_MAX_EXT_BUF, 0, san_seq, san_seq_n) 462 if octet_n < 0 { return octet_n } 463 464 // 3. Build Extension = SEQUENCE {extnID OID, extnValue OCTET STRING} 465 let ext_body: *u8 = sys_mmap(NX_X509_MAX_EXT_BUF) 466 var ebo: i64 = 0 467 ebo = nx_aw_put_oid(ext_body, NX_X509_MAX_EXT_BUF, ebo, NX_X509_OID_SAN, NX_X509_OID_SAN_N) 468 if ebo < 0 { return ebo } 469 ebo = nx_aw_put_raw(ext_body, NX_X509_MAX_EXT_BUF, ebo, octet_buf, octet_n) 470 if ebo < 0 { return ebo } 471 472 return nx_aw_wrap_sequence(out, cap, off, ext_body, ebo) 473} 474 475// Build full Extensions block: [3] EXPLICIT SEQUENCE OF Extension 476func nx_x509_build_extensions(out: *u8, cap: i64, off: i64, 477 inp: *NxX509BuildInputs) -> i64 { 478 let ext_seq_body: *u8 = sys_mmap(NX_X509_MAX_EXT_BUF) 479 var ebo: i64 = 0 480 481 // SAN extension (only V1 extension; basicConstraints + keyUsage queued V+1) 482 if inp.san_count > 0 { 483 ebo = nx_x509_build_ext_san(ext_seq_body, NX_X509_MAX_EXT_BUF, ebo, 484 inp.san_ptrs, inp.san_lens, inp.san_count) 485 if ebo < 0 { return ebo } 486 } 487 if ebo == 0 { return off } // no extensions; caller skips the [3] wrap 488 489 // Wrap as SEQUENCE OF Extension 490 let ext_seq: *u8 = sys_mmap(NX_X509_MAX_EXT_BUF) 491 let ext_seq_n: i64 = nx_aw_wrap_sequence(ext_seq, NX_X509_MAX_EXT_BUF, 0, ext_seq_body, ebo) 492 if ext_seq_n < 0 { return ext_seq_n } 493 494 // Wrap inp [3] EXPLICIT 495 return nx_aw_wrap_explicit(out, cap, off, 3, ext_seq, ext_seq_n) 496} 497 498// ===== Build TBSCertificate ================================================= 499 500func nx_x509_build_tbs(out: *u8, cap: i64, off: i64, 501 inp: *NxX509BuildInputs) -> i64 { 502 let tbs_body: *u8 = sys_mmap(NX_X509_MAX_TBS_BUF) 503 var bo: i64 = 0 504 505 // version [0] EXPLICIT INTEGER (v3 == 2) 506 let ver_buf: *u8 = sys_mmap(8) 507 let ver_n: i64 = nx_aw_put_integer(ver_buf, 8, 0, 2) 508 if ver_n < 0 { return ver_n } 509 bo = nx_aw_wrap_explicit(tbs_body, NX_X509_MAX_TBS_BUF, bo, 0, ver_buf, ver_n) 510 if bo < 0 { return bo } 511 512 // serialNumber INTEGER 513 bo = nx_aw_put_integer(tbs_body, NX_X509_MAX_TBS_BUF, bo, inp.serial); if bo < 0 { return bo } 514 515 // signature AlgorithmIdentifier (sig_alg-dispatched per V-HOST-4d-1) 516 bo = nx_x509_build_sig_alg(tbs_body, NX_X509_MAX_TBS_BUF, bo, inp.sig_alg); if bo < 0 { return bo } 517 518 // issuer Name (== subject for self-signed) 519 bo = nx_x509_build_name_cn(tbs_body, NX_X509_MAX_TBS_BUF, bo, inp.cn, inp.cn_n) 520 if bo < 0 { return bo } 521 522 // validity 523 bo = nx_x509_build_validity(tbs_body, NX_X509_MAX_TBS_BUF, bo, inp.not_before, inp.not_after) 524 if bo < 0 { return bo } 525 526 // subject Name 527 bo = nx_x509_build_name_cn(tbs_body, NX_X509_MAX_TBS_BUF, bo, inp.cn, inp.cn_n) 528 if bo < 0 { return bo } 529 530 // subjectPublicKeyInfo (sig_alg-dispatched per V-HOST-4d-1) 531 bo = nx_x509_build_spki(tbs_body, NX_X509_MAX_TBS_BUF, bo, inp) 532 if bo < 0 { return bo } 533 534 // extensions [3] EXPLICIT (V1: SAN only if present) 535 bo = nx_x509_build_extensions(tbs_body, NX_X509_MAX_TBS_BUF, bo, inp) 536 if bo < 0 { return bo } 537 538 // Wrap as TBSCertificate SEQUENCE 539 return nx_aw_wrap_sequence(out, cap, off, tbs_body, bo) 540} 541 542// ===== Build full self-signed Certificate ================================================= 543// 544// Certificate { tbsCertificate, signatureAlgorithm, signatureValue } 545 546func nx_x509_build_self_signed(inp: *NxX509BuildInputs, 547 out: *u8, cap: i64, out_n: *i64) -> i64 { 548 if inp.valid != 1 { return 0 - NX_X509_BAD_INPUT } 549 if (out as i64) == 0 { return 0 - NX_X509_BAD_INPUT } 550 if cap < 256 { return 0 - NX_X509_BUF_OVERFLOW } 551 if (out_n as i64) == 0 { return 0 - NX_X509_BAD_INPUT } 552 553 // 1. Build TBSCertificate to scratch 554 let tbs_buf: *u8 = sys_mmap(NX_X509_MAX_DER_OUT) 555 let tbs_n: i64 = nx_x509_build_tbs(tbs_buf, NX_X509_MAX_DER_OUT, 0, inp) 556 if tbs_n < 0 { return tbs_n } 557 558 // 2. Sign TBSCertificate (sig_alg-dispatched per V-HOST-4d-1) 559 let sig_buf: *u8 = sys_mmap(128) // Ed25519=64; ECDSA-DER up to ~72 560 let sig_n_p: *i64 = (sys_mmap(8)) as *i64 561 sig_n_p[0] = 0 562 563 if inp.sig_alg == NX_X509_SIG_ALG_ED25519 { 564 let sign_rc: i64 = ed25519_sign_full(inp.priv_32, tbs_buf, tbs_n, sig_buf) 565 if sign_rc != 0 { return 0 - NX_X509_SIGN_FAILED } 566 sig_n_p[0] = NX_X509_ED25519_SIG_LEN 567 } 568 if inp.sig_alg == NX_X509_SIG_ALG_ECDSA_P256_SHA256 { 569 // a. Hash TBS with SHA-256 570 let h: *u8 = sys_mmap(32) 571 sha256_digest(tbs_buf, tbs_n, h) 572 // b. Load hash into u256 (mod n -- nx_ecdsa_p256_sign handles reduction) 573 let h_limbs: *i64 = u256_alloc() 574 u256_load_be(h_limbs, h) 575 // c. Sign -> (r, s) u256 576 let r: *i64 = u256_alloc() 577 let s: *i64 = u256_alloc() 578 let sign_rc: i64 = nx_ecdsa_p256_sign(inp.ecdsa_priv_limbs, h_limbs, r, s) 579 if sign_rc != NX_ECDSA_OK { return 0 - NX_X509_SIGN_FAILED } 580 // d. DER-encode (r, s) into sig_buf 581 let der_n: i64 = nx_x509_ecdsa_sig_to_der(r, s, sig_buf, 128) 582 if der_n < 0 { return 0 - NX_X509_SIGN_FAILED } 583 sig_n_p[0] = der_n 584 } 585 586 // 3. Build outer Certificate SEQUENCE body: 587 // tbsCertificate (already encoded) + 588 // signatureAlgorithm (sig_alg-dispatched) + 589 // signatureValue (BIT STRING) 590 let cert_body: *u8 = sys_mmap(NX_X509_MAX_DER_OUT) 591 var co: i64 = 0 592 co = nx_aw_put_raw(cert_body, NX_X509_MAX_DER_OUT, co, tbs_buf, tbs_n); if co < 0 { return co } 593 co = nx_x509_build_sig_alg(cert_body, NX_X509_MAX_DER_OUT, co, inp.sig_alg); if co < 0 { return co } 594 co = nx_aw_put_bit_string(cert_body, NX_X509_MAX_DER_OUT, co, 0, sig_buf, sig_n_p[0]) 595 if co < 0 { return co } 596 597 // 4. Wrap as outer Certificate SEQUENCE 598 let final_n: i64 = nx_aw_wrap_sequence(out, cap, 0, cert_body, co) 599 if final_n < 0 { return final_n } 600 601 out_n[0] = final_n 602 return NX_X509_OK 603}