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}