code wiki / hub / nx_asn1_write.nx

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}