nx_asn1_write.nx source
↩ module page · 350 lines · 14692 B
1// nx_asn1_write.nx -- V-HOST-4a: sovereign ASN.1 DER encoder.
2//
3// Sibling to existing asn1.nx (READ surface; cursor-based parser).
4// This module adds the WRITE surface needed for X.509 cert generation
5// (bin/nx_cert_gen V-HOST-4 closes the openssl bridge from
6// NISHI_ANDELINWEST_DEPLOY_PLAYBOOK §3).
7//
8// COMPOSES (per "avoid duplicate primitives"):
9// nx_syscalls (no allocation; caller-owned out buffer)
10// asn1.nx (READ surface; this module mirrors with WRITE)
11//
12// COMPOSED BY:
13// hub/nx_x509_build.nx (V-HOST-4b; queued)
14// bin/nx_cert_gen.nx (V-HOST-4c; queued)
15// future: nx_acme cert request (CSR builder; reusable for ACME)
16// future: nx_pkcs8_emit (sovereign priv-key DER export)
17//
18// V-HOST-4a SCOPE:
19// - DER encoding of: INTEGER, BIT STRING, OCTET STRING,
20// OBJECT IDENTIFIER, SEQUENCE, SET, NULL, UTF8 STRING,
21// IA5 STRING, GeneralizedTime, UTCTime, BOOLEAN, [n] EXPLICIT/IMPLICIT
22// - Length encoding: short form (< 128) + long form (>= 128 up to 4 bytes)
23// - Tag construction: universal + context-specific + IMPLICIT/EXPLICIT
24// - Wrapper helpers for nested SEQUENCE (DER 2-pass write OR
25// fixup-length pattern)
26//
27// V-HOST-4a NON-SCOPE (not needed for X.509 V1):
28// - BER (non-canonical) encoding
29// - REAL / EMBEDDED PDV / EXTERNAL types
30// - Streaming encode (this writes flat to caller buffer)
31//
32// Status: V-HOST-4a. 2026-05-27.
33
34import "nx_syscalls.nx"
35
36// ===== Sealed verdict surface (codes 3600-3619) =================================================
37const NX_AW_OK: i64 = 0
38const NX_AW_BAD_INPUT: i64 = 3600
39const NX_AW_BUF_OVERFLOW: i64 = 3601
40const NX_AW_LENGTH_TOO_LARGE: i64 = 3602
41const NX_AW_INTEGER_TOO_LARGE: i64 = 3603
42const NX_AW_BAD_TAG: i64 = 3604
43
44// ===== Named constants (M7) =================================================
45const NX_AW_MAX_BUF: i64 = 65536 // 64 KB; covers V1 certs comfortably
46const NX_AW_MAX_LENGTH_BYTES: i64 = 4 // long-form length up to 32 bits
47
48// ===== ASN.1 tag class + form constants =================================================
49// (per X.690 §8.1.2)
50const NX_AW_CLASS_UNIVERSAL: i64 = 0x00
51const NX_AW_CLASS_APPLICATION: i64 = 0x40
52const NX_AW_CLASS_CONTEXT: i64 = 0x80
53const NX_AW_CLASS_PRIVATE: i64 = 0xC0
54
55const NX_AW_FORM_PRIMITIVE: i64 = 0x00
56const NX_AW_FORM_CONSTRUCTED: i64 = 0x20
57
58// Universal tags (X.690 §8.1.2.2 + ASN.1 standard tag numbers)
59const NX_AW_TAG_BOOLEAN: i64 = 0x01
60const NX_AW_TAG_INTEGER: i64 = 0x02
61const NX_AW_TAG_BIT_STRING: i64 = 0x03
62const NX_AW_TAG_OCTET_STRING: i64 = 0x04
63const NX_AW_TAG_NULL: i64 = 0x05
64const NX_AW_TAG_OID: i64 = 0x06
65const NX_AW_TAG_UTF8_STRING: i64 = 0x0C
66const NX_AW_TAG_SEQUENCE: i64 = 0x10 // CONSTRUCTED implied
67const NX_AW_TAG_SET: i64 = 0x11 // CONSTRUCTED implied
68const NX_AW_TAG_PRINTABLE_STRING: i64 = 0x13
69const NX_AW_TAG_IA5_STRING: i64 = 0x16
70const NX_AW_TAG_UTC_TIME: i64 = 0x17
71const NX_AW_TAG_GENERALIZED_TIME: i64 = 0x18
72
73// ===== Low-level: write tag + length =================================================
74
75// Write a single tag byte (caller assembles class + form + tag number).
76func nx_aw_put_tag(out: *u8, cap: i64, off: i64, tag_byte: i64) -> i64 {
77 if off < 0 { return 0 - NX_AW_BAD_INPUT }
78 if off >= cap { return 0 - NX_AW_BUF_OVERFLOW }
79 out[off] = (tag_byte & 0xff) as u8
80 return off + 1
81}
82
83// Write DER length octets. Short form when length < 128; else long form
84// with leading 0x8N where N = number of length bytes (big-endian).
85func nx_aw_put_length(out: *u8, cap: i64, off: i64, length: i64) -> i64 {
86 if off < 0 { return 0 - NX_AW_BAD_INPUT }
87 if length < 0 { return 0 - NX_AW_BAD_INPUT }
88
89 if length < 128 {
90 if off + 1 > cap { return 0 - NX_AW_BUF_OVERFLOW }
91 out[off] = (length & 0xff) as u8
92 return off + 1
93 }
94
95 // Long form: count significant bytes
96 var n_bytes: i64 = 0
97 var tmp: i64 = length
98 while tmp > 0 {
99 n_bytes = n_bytes + 1
100 tmp = tmp >> 8
101 }
102 if n_bytes > NX_AW_MAX_LENGTH_BYTES { return 0 - NX_AW_LENGTH_TOO_LARGE }
103 if off + 1 + n_bytes > cap { return 0 - NX_AW_BUF_OVERFLOW }
104
105 out[off] = ((0x80 | n_bytes) & 0xff) as u8
106 var i: i64 = 0
107 while i < n_bytes {
108 let shift: i64 = (n_bytes - 1 - i) * 8
109 out[off + 1 + i] = ((length >> shift) & 0xff) as u8
110 i = i + 1
111 }
112 return off + 1 + n_bytes
113}
114
115// ===== Primitive type encoders =================================================
116
117// Write BOOLEAN (DER: 0xFF for true, 0x00 for false).
118func nx_aw_put_boolean(out: *u8, cap: i64, off: i64, val: i64) -> i64 {
119 var o: i64 = nx_aw_put_tag(out, cap, off, NX_AW_TAG_BOOLEAN); if o < 0 { return o }
120 o = nx_aw_put_length(out, cap, o, 1); if o < 0 { return o }
121 if o >= cap { return 0 - NX_AW_BUF_OVERFLOW }
122 if val == 0 { out[o] = 0x00 as u8 }
123 if val != 0 { out[o] = 0xFF as u8 }
124 return o + 1
125}
126
127// Write NULL.
128func nx_aw_put_null(out: *u8, cap: i64, off: i64) -> i64 {
129 var o: i64 = nx_aw_put_tag(out, cap, off, NX_AW_TAG_NULL); if o < 0 { return o }
130 return nx_aw_put_length(out, cap, o, 0)
131}
132
133// Write INTEGER (positive i64). Per DER X.690 §8.3.2: if MSB of first
134// byte is 1, prepend 0x00 to keep it unambiguously positive.
135func nx_aw_put_integer(out: *u8, cap: i64, off: i64, val: i64) -> i64 {
136 if val < 0 { return 0 - NX_AW_INTEGER_TOO_LARGE } // V1: positives only
137
138 var o: i64 = nx_aw_put_tag(out, cap, off, NX_AW_TAG_INTEGER); if o < 0 { return o }
139
140 // Count significant bytes
141 var n_bytes: i64 = 1
142 var tmp: i64 = val >> 8
143 while tmp > 0 {
144 n_bytes = n_bytes + 1
145 tmp = tmp >> 8
146 }
147 // If MSB of leading byte is 1, prepend 0x00
148 var leading_zero: i64 = 0
149 let msb_byte: i64 = (val >> ((n_bytes - 1) * 8)) & 0xff
150 if msb_byte >= 0x80 { leading_zero = 1 }
151
152 let total_len: i64 = n_bytes + leading_zero
153 o = nx_aw_put_length(out, cap, o, total_len); if o < 0 { return o }
154 if o + total_len > cap { return 0 - NX_AW_BUF_OVERFLOW }
155
156 if leading_zero == 1 { out[o] = 0x00 as u8; o = o + 1 }
157 var i: i64 = 0
158 while i < n_bytes {
159 let shift: i64 = (n_bytes - 1 - i) * 8
160 out[o + i] = ((val >> shift) & 0xff) as u8
161 i = i + 1
162 }
163 return o + n_bytes
164}
165
166// Write INTEGER from arbitrary-length byte buffer (big-endian; for
167// large ints like RSA modulus or arbitrary serial numbers).
168func nx_aw_put_integer_bytes(out: *u8, cap: i64, off: i64,
169 src: *u8, src_n: i64) -> i64 {
170 if src_n < 1 { return 0 - NX_AW_BAD_INPUT }
171
172 var o: i64 = nx_aw_put_tag(out, cap, off, NX_AW_TAG_INTEGER); if o < 0 { return o }
173
174 var leading_zero: i64 = 0
175 if (src[0] as i64) >= 0x80 { leading_zero = 1 }
176 let total_len: i64 = src_n + leading_zero
177
178 o = nx_aw_put_length(out, cap, o, total_len); if o < 0 { return o }
179 if o + total_len > cap { return 0 - NX_AW_BUF_OVERFLOW }
180
181 if leading_zero == 1 { out[o] = 0x00 as u8; o = o + 1 }
182 var i: i64 = 0
183 while i < src_n { out[o + i] = src[i]; i = i + 1 }
184 return o + src_n
185}
186
187// Write OCTET STRING.
188func nx_aw_put_octet_string(out: *u8, cap: i64, off: i64,
189 src: *u8, src_n: i64) -> i64 {
190 var o: i64 = nx_aw_put_tag(out, cap, off, NX_AW_TAG_OCTET_STRING); if o < 0 { return o }
191 o = nx_aw_put_length(out, cap, o, src_n); if o < 0 { return o }
192 if o + src_n > cap { return 0 - NX_AW_BUF_OVERFLOW }
193 var i: i64 = 0
194 while i < src_n { out[o + i] = src[i]; i = i + 1 }
195 return o + src_n
196}
197
198// Write BIT STRING (caller responsible for unused-bits-count byte; pass 0 for byte-aligned data).
199func nx_aw_put_bit_string(out: *u8, cap: i64, off: i64,
200 unused_bits: i64, src: *u8, src_n: i64) -> i64 {
201 if unused_bits < 0 { return 0 - NX_AW_BAD_INPUT }
202 if unused_bits > 7 { return 0 - NX_AW_BAD_INPUT }
203
204 var o: i64 = nx_aw_put_tag(out, cap, off, NX_AW_TAG_BIT_STRING); if o < 0 { return o }
205 o = nx_aw_put_length(out, cap, o, src_n + 1); if o < 0 { return o }
206 if o + 1 + src_n > cap { return 0 - NX_AW_BUF_OVERFLOW }
207 out[o] = (unused_bits & 0xff) as u8
208 o = o + 1
209 var i: i64 = 0
210 while i < src_n { out[o + i] = src[i]; i = i + 1 }
211 return o + src_n
212}
213
214// Write OBJECT IDENTIFIER from already-encoded body bytes.
215// Caller pre-encodes per X.690 §8.19 (40*arc1 + arc2 first byte;
216// base-128 with high-bit continuation for rest). Common OIDs
217// (ed25519 sig alg = 1.3.101.112 = 0x2B6570; sha256-WithRSA, etc.)
218// pre-computed by hub/nx_x509_build per use.
219func nx_aw_put_oid(out: *u8, cap: i64, off: i64,
220 oid_bytes: *u8, oid_n: i64) -> i64 {
221 if oid_n < 1 { return 0 - NX_AW_BAD_INPUT }
222 var o: i64 = nx_aw_put_tag(out, cap, off, NX_AW_TAG_OID); if o < 0 { return o }
223 o = nx_aw_put_length(out, cap, o, oid_n); if o < 0 { return o }
224 if o + oid_n > cap { return 0 - NX_AW_BUF_OVERFLOW }
225 var i: i64 = 0
226 while i < oid_n { out[o + i] = oid_bytes[i]; i = i + 1 }
227 return o + oid_n
228}
229
230// Write UTF8 STRING.
231func nx_aw_put_utf8_string(out: *u8, cap: i64, off: i64,
232 src: *u8, src_n: i64) -> i64 {
233 var o: i64 = nx_aw_put_tag(out, cap, off, NX_AW_TAG_UTF8_STRING); if o < 0 { return o }
234 o = nx_aw_put_length(out, cap, o, src_n); if o < 0 { return o }
235 if o + src_n > cap { return 0 - NX_AW_BUF_OVERFLOW }
236 var i: i64 = 0
237 while i < src_n { out[o + i] = src[i]; i = i + 1 }
238 return o + src_n
239}
240
241// Write IA5 STRING (used for email + URI; ASCII subset).
242func nx_aw_put_ia5_string(out: *u8, cap: i64, off: i64,
243 src: *u8, src_n: i64) -> i64 {
244 var o: i64 = nx_aw_put_tag(out, cap, off, NX_AW_TAG_IA5_STRING); if o < 0 { return o }
245 o = nx_aw_put_length(out, cap, o, src_n); if o < 0 { return o }
246 if o + src_n > cap { return 0 - NX_AW_BUF_OVERFLOW }
247 var i: i64 = 0
248 while i < src_n { out[o + i] = src[i]; i = i + 1 }
249 return o + src_n
250}
251
252// Write PRINTABLE STRING.
253func nx_aw_put_printable_string(out: *u8, cap: i64, off: i64,
254 src: *u8, src_n: i64) -> i64 {
255 var o: i64 = nx_aw_put_tag(out, cap, off, NX_AW_TAG_PRINTABLE_STRING); if o < 0 { return o }
256 o = nx_aw_put_length(out, cap, o, src_n); if o < 0 { return o }
257 if o + src_n > cap { return 0 - NX_AW_BUF_OVERFLOW }
258 var i: i64 = 0
259 while i < src_n { out[o + i] = src[i]; i = i + 1 }
260 return o + src_n
261}
262
263// Write UTCTime (YYMMDDhhmmssZ; 13 bytes).
264func nx_aw_put_utc_time(out: *u8, cap: i64, off: i64,
265 time_str: *u8) -> i64 {
266 var o: i64 = nx_aw_put_tag(out, cap, off, NX_AW_TAG_UTC_TIME); if o < 0 { return o }
267 o = nx_aw_put_length(out, cap, o, 13); if o < 0 { return o }
268 if o + 13 > cap { return 0 - NX_AW_BUF_OVERFLOW }
269 var i: i64 = 0
270 while i < 13 { out[o + i] = time_str[i]; i = i + 1 }
271 return o + 13
272}
273
274// Write GeneralizedTime (YYYYMMDDhhmmssZ; 15 bytes).
275func nx_aw_put_generalized_time(out: *u8, cap: i64, off: i64,
276 time_str: *u8) -> i64 {
277 var o: i64 = nx_aw_put_tag(out, cap, off, NX_AW_TAG_GENERALIZED_TIME); if o < 0 { return o }
278 o = nx_aw_put_length(out, cap, o, 15); if o < 0 { return o }
279 if o + 15 > cap { return 0 - NX_AW_BUF_OVERFLOW }
280 var i: i64 = 0
281 while i < 15 { out[o + i] = time_str[i]; i = i + 1 }
282 return o + 15
283}
284
285// ===== Constructed type wrappers (SEQUENCE / SET / [n] EXPLICIT) =================================================
286//
287// The DER 2-pass pattern: caller writes the body to a scratch buffer,
288// then we wrap it with SEQUENCE tag + length. Simpler than computing
289// length up-front for nested structures.
290
291func nx_aw_wrap_sequence(out: *u8, cap: i64, off: i64,
292 body: *u8, body_n: i64) -> i64 {
293 let tag_byte: i64 = NX_AW_CLASS_UNIVERSAL | NX_AW_FORM_CONSTRUCTED | NX_AW_TAG_SEQUENCE
294 var o: i64 = nx_aw_put_tag(out, cap, off, tag_byte); if o < 0 { return o }
295 o = nx_aw_put_length(out, cap, o, body_n); if o < 0 { return o }
296 if o + body_n > cap { return 0 - NX_AW_BUF_OVERFLOW }
297 var i: i64 = 0
298 while i < body_n { out[o + i] = body[i]; i = i + 1 }
299 return o + body_n
300}
301
302func nx_aw_wrap_set(out: *u8, cap: i64, off: i64,
303 body: *u8, body_n: i64) -> i64 {
304 let tag_byte: i64 = NX_AW_CLASS_UNIVERSAL | NX_AW_FORM_CONSTRUCTED | NX_AW_TAG_SET
305 var o: i64 = nx_aw_put_tag(out, cap, off, tag_byte); if o < 0 { return o }
306 o = nx_aw_put_length(out, cap, o, body_n); if o < 0 { return o }
307 if o + body_n > cap { return 0 - NX_AW_BUF_OVERFLOW }
308 var i: i64 = 0
309 while i < body_n { out[o + i] = body[i]; i = i + 1 }
310 return o + body_n
311}
312
313// Context-specific EXPLICIT tag: [n] WRAPPER + body. Used for X.509
314// optional fields like Extensions ([3] EXPLICIT Extensions).
315func nx_aw_wrap_explicit(out: *u8, cap: i64, off: i64,
316 tag_num: i64, body: *u8, body_n: i64) -> i64 {
317 if tag_num < 0 { return 0 - NX_AW_BAD_TAG }
318 if tag_num > 30 { return 0 - NX_AW_BAD_TAG } // V1: small tags only
319 let tag_byte: i64 = NX_AW_CLASS_CONTEXT | NX_AW_FORM_CONSTRUCTED | tag_num
320 var o: i64 = nx_aw_put_tag(out, cap, off, tag_byte); if o < 0 { return o }
321 o = nx_aw_put_length(out, cap, o, body_n); if o < 0 { return o }
322 if o + body_n > cap { return 0 - NX_AW_BUF_OVERFLOW }
323 var i: i64 = 0
324 while i < body_n { out[o + i] = body[i]; i = i + 1 }
325 return o + body_n
326}
327
328// Context-specific IMPLICIT tag (primitive): [n] body-bytes.
329func nx_aw_wrap_implicit_primitive(out: *u8, cap: i64, off: i64,
330 tag_num: i64, body: *u8, body_n: i64) -> i64 {
331 if tag_num < 0 { return 0 - NX_AW_BAD_TAG }
332 if tag_num > 30 { return 0 - NX_AW_BAD_TAG }
333 let tag_byte: i64 = NX_AW_CLASS_CONTEXT | NX_AW_FORM_PRIMITIVE | tag_num
334 var o: i64 = nx_aw_put_tag(out, cap, off, tag_byte); if o < 0 { return o }
335 o = nx_aw_put_length(out, cap, o, body_n); if o < 0 { return o }
336 if o + body_n > cap { return 0 - NX_AW_BUF_OVERFLOW }
337 var i: i64 = 0
338 while i < body_n { out[o + i] = body[i]; i = i + 1 }
339 return o + body_n
340}
341
342// ===== Convenience: raw bytes append (for already-DER-encoded sub-trees) =================================================
343
344func nx_aw_put_raw(out: *u8, cap: i64, off: i64,
345 src: *u8, src_n: i64) -> i64 {
346 if off + src_n > cap { return 0 - NX_AW_BUF_OVERFLOW }
347 var i: i64 = 0
348 while i < src_n { out[off + i] = src[i]; i = i + 1 }
349 return off + src_n
350}