code wiki / (root) / nx_acme_csr.nx

nx_acme_csr.nx source

↩ module page · 178 lines · 8200 B

1// nx_acme_csr.nx -- PKCS#10 CertificationRequest (CSR) for ECDSA P-256, 2// ECDSA-with-SHA256 self-signed. The ACME finalize step (RFC 8555 ยง7.4) 3// POSTs a DER CSR; Let's Encrypt issues ECDSA certs, so the in-tree 4// Ed25519 CSR path (nx_x509_csr_emit) is unusable -- this is its ECDSA 5// sibling. REUSABLE for any ECDSA P-256 CSR (not just ACME). 6// 7// PKCS#10 (RFC 2986): 8// CertificationRequest ::= SEQUENCE { 9// certificationRequestInfo CertificationRequestInfo, 10// signatureAlgorithm AlgorithmIdentifier, 11// signature BIT STRING } 12// CertificationRequestInfo ::= SEQUENCE { 13// version INTEGER (0), 14// subject Name, 15// subjectPKInfo SubjectPublicKeyInfo, 16// attributes [0] IMPLICIT SET OF Attribute } 17// Attribute (extensionRequest, OID 1.2.840.113549.1.9.14) ::= SEQUENCE { 18// type OID, 19// values SET OF { Extensions } } -- Extensions = SEQUENCE OF Extension 20// 21// The signature is ECDSA over SHA-256(CertificationRequestInfo DER bytes), 22// emitted as DER SEQUENCE{INTEGER r, INTEGER s} inside the BIT STRING. 23// 24// Composes the shipped nx_x509_build hub (nx_aw_* writers, name_cn, SPKI, 25// SAN, alg-ecdsa, sig-to-der) + nx_ecdsa_p256_sign + nx_sha256. Single 26// import of the hub pulls the whole closure (path-deduped). 27// 28// license_tier: ORIGINAL (composes RFC 2986 + RFC 5280 SAN + the P-256 stack) 29 30import "hub/nx_x509_build.nx" 31const NX_MAGIC_1024: i64 = 1024 32const NX_MAGIC_2048: i64 = 2048 33 34const NX_ACME_CSR_OK: i64 = 0 35const NX_ACME_CSR_BAD_ARG: i64 = 1 36const NX_ACME_CSR_BUILD_ERR: i64 = 2 37const NX_ACME_CSR_SIGN_ERR: i64 = 3 38 39// extensionRequest OID 1.2.840.113549.1.9.14 40const NX_ACME_OID_EXT_REQUEST: *u8 = "\x2A\x86\x48\x86\xF7\x0D\x01\x09\x0E" as *u8 41const NX_ACME_OID_EXT_REQUEST_N: i64 = 9 42 43// Build the attributes [0] IMPLICIT block carrying one extensionRequest 44// attribute whose single value is Extensions { subjectAltName }. 45// Writes at out[off]; returns new offset (or negative nx_aw error). 46func acme_csr_attributes(out: *u8, cap: i64, off: i64, 47 san_ptrs: *i64, san_lens: *i64, san_count: i64) -> i64 { 48 // 1. one Extension (SEQUENCE { SAN-OID, OCTET STRING }) 49 let ext1: *u8 = sys_mmap(NX_MAGIC_1024) 50 let ext1_n: i64 = nx_x509_build_ext_san(ext1, NX_MAGIC_1024, 0, san_ptrs, san_lens, san_count) 51 if ext1_n < 0 { return ext1_n } 52 53 // 2. Extensions ::= SEQUENCE OF Extension (here exactly one) 54 let exts: *u8 = sys_mmap(NX_MAGIC_1024) 55 let exts_n: i64 = nx_aw_wrap_sequence(exts, NX_MAGIC_1024, 0, ext1, ext1_n) 56 if exts_n < 0 { return exts_n } 57 58 // 3. values ::= SET OF { Extensions } 59 let vals: *u8 = sys_mmap(NX_MAGIC_1024) 60 let vals_n: i64 = nx_aw_wrap_set(vals, NX_MAGIC_1024, 0, exts, exts_n) 61 if vals_n < 0 { return vals_n } 62 63 // 4. Attribute ::= SEQUENCE { extensionRequest OID, values } 64 let attr_body: *u8 = sys_mmap(NX_MAGIC_1024) 65 var abo: i64 = 0 66 abo = nx_aw_put_oid(attr_body, NX_MAGIC_1024, abo, NX_ACME_OID_EXT_REQUEST, NX_ACME_OID_EXT_REQUEST_N) 67 if abo < 0 { return abo } 68 abo = nx_aw_put_raw(attr_body, NX_MAGIC_1024, abo, vals, vals_n) 69 if abo < 0 { return abo } 70 let attr: *u8 = sys_mmap(NX_MAGIC_1024) 71 let attr_n: i64 = nx_aw_wrap_sequence(attr, NX_MAGIC_1024, 0, attr_body, abo) 72 if attr_n < 0 { return attr_n } 73 74 // 5. attributes [0] IMPLICIT SET OF Attribute. The IMPLICIT [0] tag 75 // (0xA0, constructed-context) replaces the SET's universal tag; its 76 // content is the Attribute sequence(s) directly -- byte-identical to 77 // a constructed-context wrap of the attribute content. 78 return nx_aw_wrap_explicit(out, cap, off, 0, attr, attr_n) 79} 80 81// Build a complete DER PKCS#10 CSR for an ECDSA P-256 key. 82// cn primary domain (CN of subject + first SAN) 83// san_* parallel arrays of dNSName strings (>=1; include the CN) 84// priv/pub P-256 key: priv as 4-limb scalar, pub as affine x/y limbs 85// out DER output buffer; out_n receives the length 86func nx_acme_build_csr_ecdsa_p256(cn: *u8, cn_n: i64, 87 san_ptrs: *i64, san_lens: *i64, san_count: i64, 88 priv_limbs: *i64, pub_x: *i64, pub_y: *i64, 89 out: *u8, cap: i64, out_n: *i64) -> i64 { 90 if (cn as i64) == 0 { return NX_ACME_CSR_BAD_ARG } 91 if cn_n < 1 { return NX_ACME_CSR_BAD_ARG } 92 if san_count < 1 { return NX_ACME_CSR_BAD_ARG } 93 if (out as i64) == 0 { return NX_ACME_CSR_BAD_ARG } 94 95 // ---- CertificationRequestInfo body ---- 96 let cri: *u8 = sys_mmap(NX_MAGIC_2048) 97 var bo: i64 = 0 98 bo = nx_aw_put_integer(cri, NX_MAGIC_2048, bo, 0) // version 0 99 if bo < 0 { return NX_ACME_CSR_BUILD_ERR } 100 bo = nx_x509_build_name_cn(cri, NX_MAGIC_2048, bo, cn, cn_n) // subject 101 if bo < 0 { return NX_ACME_CSR_BUILD_ERR } 102 bo = nx_x509_build_spki_ecdsa_p256(cri, NX_MAGIC_2048, bo, pub_x, pub_y) // subjectPKInfo 103 if bo < 0 { return NX_ACME_CSR_BUILD_ERR } 104 bo = acme_csr_attributes(cri, NX_MAGIC_2048, bo, san_ptrs, san_lens, san_count) // attributes [0] 105 if bo < 0 { return NX_ACME_CSR_BUILD_ERR } 106 107 // CRI TLV = the exact bytes the signature covers. 108 let cri_tlv: *u8 = sys_mmap(NX_MAGIC_2048) 109 let cri_tlv_n: i64 = nx_aw_wrap_sequence(cri_tlv, NX_MAGIC_2048, 0, cri, bo) 110 if cri_tlv_n < 0 { return NX_ACME_CSR_BUILD_ERR } 111 112 // ---- Sign SHA-256(CRI) with ECDSA P-256 ---- 113 let hash32: *u8 = sys_mmap(32) 114 sha256_digest(cri_tlv, cri_tlv_n, hash32) 115 let hl: *i64 = u256_alloc() 116 u256_load_be(hl, hash32) 117 let r: *i64 = u256_alloc() 118 let s: *i64 = u256_alloc() 119 if nx_ecdsa_p256_sign(priv_limbs, hl, r, s) != NX_ECDSA_SIGN_OK { 120 return NX_ACME_CSR_SIGN_ERR 121 } 122 let sig_der: *u8 = sys_mmap(128) 123 let sig_n: i64 = nx_x509_ecdsa_sig_to_der(r, s, sig_der, 128) 124 if sig_n < 0 { return NX_ACME_CSR_BUILD_ERR } 125 126 // ---- CSR body = CRI + sigAlg + signature BIT STRING ---- 127 let csr_body: *u8 = sys_mmap(NX_MAGIC_2048) 128 var cbo: i64 = 0 129 cbo = nx_aw_put_raw(csr_body, NX_MAGIC_2048, cbo, cri_tlv, cri_tlv_n) 130 if cbo < 0 { return NX_ACME_CSR_BUILD_ERR } 131 cbo = nx_x509_build_alg_ecdsa_p256_sha256(csr_body, NX_MAGIC_2048, cbo) 132 if cbo < 0 { return NX_ACME_CSR_BUILD_ERR } 133 cbo = nx_aw_put_bit_string(csr_body, NX_MAGIC_2048, cbo, 0, sig_der, sig_n) 134 if cbo < 0 { return NX_ACME_CSR_BUILD_ERR } 135 136 let csr_n: i64 = nx_aw_wrap_sequence(out, cap, 0, csr_body, cbo) 137 if csr_n < 0 { return NX_ACME_CSR_BUILD_ERR } 138 out_n[0] = csr_n 139 return NX_ACME_CSR_OK 140} 141 142// ---- KAT: emit a real andelinwest.com CSR (DER) to stdout for openssl 143// to structurally verify + check the ECDSA self-signature. Deterministic 144// private key (1..32) -> pub via d*G. stderr carries status text only. 145func main() -> i64 { 146 let priv_be: *u8 = sys_mmap(32) 147 var i: i64 = 0 148 while i < 32 { priv_be[i] = (i + 1) as u8; i = i + 1 } 149 let priv_limbs: *i64 = u256_alloc() 150 u256_load_be(priv_limbs, priv_be) 151 152 // pub = d*G (affine) 153 let g: *P256Point = p256_point_alloc() 154 p256_point_load_g(g) 155 let pub: *P256Point = p256_point_alloc() 156 p256_scalar_mul(pub, priv_limbs, g) 157 p256_point_to_affine(pub) 158 159 let cn: *u8 = "andelinwest.com" as *u8 160 let cn_n: i64 = 15 161 let san_ptrs: *i64 = sys_mmap(16) as *i64 162 let san_lens: *i64 = sys_mmap(16) as *i64 163 san_ptrs[0] = ("andelinwest.com" as *u8) as i64; san_lens[0] = 15 164 san_ptrs[1] = ("www.andelinwest.com" as *u8) as i64; san_lens[1] = 19 165 166 let out: *u8 = sys_mmap(NX_MAGIC_2048) 167 let out_n: *i64 = sys_mmap(8) as *i64 168 let rc: i64 = nx_acme_build_csr_ecdsa_p256(cn, cn_n, san_ptrs, san_lens, 2, 169 priv_limbs, pub.x, pub.y, 170 out, NX_MAGIC_2048, out_n) 171 if rc != NX_ACME_CSR_OK { 172 sys_write(2, "CSR build FAIL\n" as *u8, 15) 173 return 1 174 } 175 sys_write(2, "CSR built OK (DER on stdout)\n" as *u8, 29) 176 sys_write(1, out, out_n[0]) // raw DER -> stdout for openssl 177 return 0 178}