code wiki / (root) / nx_asn1_emit.nx

nx_asn1_emit.nx source

↩ module page · 221 lines · 8168 B

1// nx_asn1_emit.nx -- Encodes ASN.1 DER tagged-length-value triplets according to RFC 5280 and X.690 standards. 2const NXAE_MAGIC_65536: i64 = 65536 3const NXAE_MAGIC_16777216: i64 = 16777216 4// nx_asn1_emit.nx -- ASN.1 DER encoder helpers (RFC 5280 / X.690). 5// 6// Sibling of nx_asn1.nx (parser side). Encodes tag-length-value 7// triplets per X.690 §8 + §10. 8// 9// Length encoding (X.690 §8.1.3): 10// 0..127 : single byte (length octet = length) 11// 128..255 : 0x81, length 12// 256..65535 : 0x82, hi, lo 13// 65536..2^24 : 0x83, hi, mid, lo 14// 2^24..2^32-1 : 0x84, b3, b2, b1, b0 15// 16// Per cardinal user-owns-every-bit: caller supplies the output 17// buffer + tracks the offset. Encoder returns a sealed-enum verdict 18// or the new offset. 19// 20// nx_capability_claims: 21// needs: [sealed_enum, bounded_buffer] 22// provides: [asn1_der_encode_helpers] 23// safety: [no_unchecked_deref, no_floating_point, no_syscall, 24// bit_equal_reproducible, target_agnostic] 25// verdict: [sealed_enum_3_state] 26// license: ORIGINAL 27// kind: racing_crew_specialist 28// layer: L2 (transform: bytes -> DER bytes) 29 30const NXAE_OK: i64 = 0 31const NXAE_OOM_BUFFER: i64 = 1 32const NXAE_BAD_ARG: i64 = 2 33const NXAE_VERDICT_N: i64 = 3 34 35func nxae_verdict_is_valid(v: i64) -> i64 { 36 if v < 0 { return 0 } 37 if v >= NXAE_VERDICT_N { return 0 } 38 return 1 39} 40 41// ---- Byte put with bounds check ---------------------------------- 42 43func nxae_put(out: *u8, off: *i64, cap: i64, b: i64) -> i64 { 44 if *off >= cap { return NXAE_OOM_BUFFER } 45 out[*off] = b as u8 46 *off = *off + 1 47 return NXAE_OK 48} 49 50func nxae_put_bytes(out: *u8, off: *i64, cap: i64, src: *u8, n: i64) -> i64 { 51 var i: i64 = 0 52 while i < n { 53 if *off >= cap { return NXAE_OOM_BUFFER } 54 out[*off] = src[i] 55 *off = *off + 1 56 i = i + 1 57 } 58 return NXAE_OK 59} 60 61// ---- Length encoding (X.690 §8.1.3) ------------------------------ 62 63// Write a DER length to out[*off]; advance *off. Returns NXAE_OK 64// or NXAE_OOM_BUFFER. 65func nxae_put_length(out: *u8, off: *i64, cap: i64, n: i64) -> i64 { 66 if n < 0 { return NXAE_BAD_ARG } 67 if n < 128 { 68 return nxae_put(out, off, cap, n) 69 } 70 if n < 256 { 71 let r1: i64 = nxae_put(out, off, cap, 0x81) 72 if r1 != NXAE_OK { return r1 } 73 return nxae_put(out, off, cap, n) 74 } 75 if n < NXAE_MAGIC_65536 { 76 let r1: i64 = nxae_put(out, off, cap, 0x82) 77 if r1 != NXAE_OK { return r1 } 78 let r2: i64 = nxae_put(out, off, cap, (n >> 8) & 0xff) 79 if r2 != NXAE_OK { return r2 } 80 return nxae_put(out, off, cap, n & 0xff) 81 } 82 if n < NXAE_MAGIC_16777216 { 83 let r1: i64 = nxae_put(out, off, cap, 0x83) 84 if r1 != NXAE_OK { return r1 } 85 let r2: i64 = nxae_put(out, off, cap, (n >> 16) & 0xff) 86 if r2 != NXAE_OK { return r2 } 87 let r3: i64 = nxae_put(out, off, cap, (n >> 8) & 0xff) 88 if r3 != NXAE_OK { return r3 } 89 return nxae_put(out, off, cap, n & 0xff) 90 } 91 // 4-byte length (up to 2^32-1). 92 let r1: i64 = nxae_put(out, off, cap, 0x84) 93 if r1 != NXAE_OK { return r1 } 94 let r2: i64 = nxae_put(out, off, cap, (n >> 24) & 0xff) 95 if r2 != NXAE_OK { return r2 } 96 let r3: i64 = nxae_put(out, off, cap, (n >> 16) & 0xff) 97 if r3 != NXAE_OK { return r3 } 98 let r4: i64 = nxae_put(out, off, cap, (n >> 8) & 0xff) 99 if r4 != NXAE_OK { return r4 } 100 return nxae_put(out, off, cap, n & 0xff) 101} 102 103// Bytes needed to encode a length of n. Used for two-pass emission 104// where the caller needs to reserve space ahead of time. 105func nxae_length_size(n: i64) -> i64 { 106 if n < 128 { return 1 } 107 if n < 256 { return 2 } 108 if n < NXAE_MAGIC_65536 { return 3 } 109 if n < NXAE_MAGIC_16777216 { return 4 } 110 return 5 111} 112 113// ---- TLV emission (tag + length + value) ------------------------- 114 115// Emit a tag-length-value triplet. Caller provides value bytes. 116func nxae_put_tlv(out: *u8, off: *i64, cap: i64, 117 tag: i64, value: *u8, value_n: i64) -> i64 { 118 if value_n < 0 { return NXAE_BAD_ARG } 119 let r1: i64 = nxae_put(out, off, cap, tag) 120 if r1 != NXAE_OK { return r1 } 121 let r2: i64 = nxae_put_length(out, off, cap, value_n) 122 if r2 != NXAE_OK { return r2 } 123 return nxae_put_bytes(out, off, cap, value, value_n) 124} 125 126// ---- Common ASN.1 types ------------------------------------------ 127 128// INTEGER (tag 0x02). For small unsigned values that fit in 1 byte 129// (e.g., version=0 for CSR). 130func nxae_put_int_u8(out: *u8, off: *i64, cap: i64, v: i64) -> i64 { 131 if v < 0 { return NXAE_BAD_ARG } 132 if v > 255 { return NXAE_BAD_ARG } 133 let r1: i64 = nxae_put(out, off, cap, 0x02) 134 if r1 != NXAE_OK { return r1 } 135 let r2: i64 = nxae_put(out, off, cap, 1) 136 if r2 != NXAE_OK { return r2 } 137 return nxae_put(out, off, cap, v) 138} 139 140// NULL (tag 0x05, length 0). 141func nxae_put_null(out: *u8, off: *i64, cap: i64) -> i64 { 142 let r1: i64 = nxae_put(out, off, cap, 0x05) 143 if r1 != NXAE_OK { return r1 } 144 return nxae_put(out, off, cap, 0) 145} 146 147// OCTET STRING (tag 0x04). 148func nxae_put_octet_string(out: *u8, off: *i64, cap: i64, 149 src: *u8, n: i64) -> i64 { 150 return nxae_put_tlv(out, off, cap, 0x04, src, n) 151} 152 153// BIT STRING (tag 0x03). Caller supplies "unused-bits" (almost 154// always 0 for byte-aligned content) + the content bytes. 155func nxae_put_bit_string(out: *u8, off: *i64, cap: i64, 156 unused_bits: i64, 157 content: *u8, content_n: i64) -> i64 { 158 if unused_bits < 0 { return NXAE_BAD_ARG } 159 if unused_bits > 7 { return NXAE_BAD_ARG } 160 let r1: i64 = nxae_put(out, off, cap, 0x03) 161 if r1 != NXAE_OK { return r1 } 162 let r2: i64 = nxae_put_length(out, off, cap, 1 + content_n) 163 if r2 != NXAE_OK { return r2 } 164 let r3: i64 = nxae_put(out, off, cap, unused_bits) 165 if r3 != NXAE_OK { return r3 } 166 return nxae_put_bytes(out, off, cap, content, content_n) 167} 168 169// UTF8String (tag 0x0c). 170func nxae_put_utf8(out: *u8, off: *i64, cap: i64, 171 src: *u8, n: i64) -> i64 { 172 return nxae_put_tlv(out, off, cap, 0x0c, src, n) 173} 174 175// SEQUENCE header (tag 0x30 + length). Used by callers who emit 176// the inner content directly after. Returns NXAE_OK on success. 177// 178// Most callers should use the two-pass pattern: emit the inner 179// content into a scratch buffer + length, then call put_tlv with 180// tag=0x30. This helper is for cases where the SEQUENCE length 181// is known up front. 182func nxae_put_sequence_header(out: *u8, off: *i64, cap: i64, 183 inner_n: i64) -> i64 { 184 let r1: i64 = nxae_put(out, off, cap, 0x30) 185 if r1 != NXAE_OK { return r1 } 186 return nxae_put_length(out, off, cap, inner_n) 187} 188 189// SET header (tag 0x31 + length). 190func nxae_put_set_header(out: *u8, off: *i64, cap: i64, 191 inner_n: i64) -> i64 { 192 let r1: i64 = nxae_put(out, off, cap, 0x31) 193 if r1 != NXAE_OK { return r1 } 194 return nxae_put_length(out, off, cap, inner_n) 195} 196 197// Pre-encoded OID payload (caller supplies the raw OID bytes; 198// substrate emits tag 0x06 + length + bytes). Pre-encoded means 199// the caller has done the base-128 packing for arcs >= 128. 200// 201// Common OIDs for Ed25519 (RFC 8410): 202// 1.3.101.112 = 0x2b, 0x65, 0x70 (id-Ed25519) 203// 204// And for Subject (RFC 5280): 205// 2.5.4.3 = 0x55, 0x04, 0x03 (commonName) 206// 2.5.4.6 = 0x55, 0x04, 0x06 (countryName) 207// 2.5.4.10 = 0x55, 0x04, 0x0a (organizationName) 208func nxae_put_oid(out: *u8, off: *i64, cap: i64, 209 oid_bytes: *u8, oid_n: i64) -> i64 { 210 return nxae_put_tlv(out, off, cap, 0x06, oid_bytes, oid_n) 211} 212 213// Context-specific tag [n] EXPLICIT (constructed = 0xa0 | n). 214// Caller provides the inner DER bytes; we wrap. 215func nxae_put_context_explicit(out: *u8, off: *i64, cap: i64, 216 tag_n: i64, 217 inner: *u8, inner_n: i64) -> i64 { 218 if tag_n < 0 { return NXAE_BAD_ARG } 219 if tag_n > 30 { return NXAE_BAD_ARG } 220 return nxae_put_tlv(out, off, cap, 0xa0 | tag_n, inner, inner_n) 221}