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}