Skip to main content

gmcrypto_core/asn1/
writer.rs

1//! Strict-canonical DER writer primitives.
2//!
3//! Writers append to a caller-supplied [`Vec<u8>`]. Length encodings
4//! use the minimal form: 1-byte (`< 128`), 2-byte (`0x81 LL`),
5//! 3-byte (`0x82 HH LL`), 4-byte (`0x83 HH MM LL`). Lengths
6//! `≥ 16 MiB` panic — the documented ceiling per `CLAUDE.md`;
7//! callers chunk via SM4-CBC + outer SM2 wrap if they need more.
8//!
9//! INTEGER emit follows X.690 §8.3.2: leading zeros stripped, a
10//! disambiguating `0x00` re-prepended if the high bit of the first
11//! content byte is set.
12
13use alloc::vec::Vec;
14
15use super::reader::{
16    MAX_DER_LEN, TAG_BIT_STRING, TAG_INTEGER, TAG_NULL, TAG_OCTET_STRING, TAG_OID, TAG_SEQUENCE,
17};
18
19/// Append a minimal DER length encoding to `out`.
20///
21/// # Panics
22/// Panics if `len >= MAX_DER_LEN` (16 MiB). Callers must chunk
23/// before this boundary.
24pub fn write_length(out: &mut Vec<u8>, len: usize) {
25    #[allow(clippy::cast_possible_truncation)]
26    if len < 128 {
27        out.push(len as u8);
28    } else if len < 256 {
29        out.push(0x81);
30        out.push(len as u8);
31    } else if len < 65_536 {
32        out.push(0x82);
33        out.push((len >> 8) as u8);
34        out.push(len as u8);
35    } else if len < MAX_DER_LEN {
36        out.push(0x83);
37        out.push((len >> 16) as u8);
38        out.push((len >> 8) as u8);
39        out.push(len as u8);
40    } else {
41        panic!("DER length overflow: {} >= {} bytes", len, MAX_DER_LEN);
42    }
43}
44
45/// Append a DER INTEGER tag-length-value.
46///
47/// `value_be` is the unsigned big-endian magnitude; leading zeros
48/// are stripped, then a `0x00` is re-prepended if the first content
49/// byte has its high bit set (positive-integer disambiguation).
50///
51/// # Panics
52/// Panics if `value_be` is empty.
53pub fn write_integer(out: &mut Vec<u8>, value_be: &[u8]) {
54    assert!(!value_be.is_empty(), "INTEGER content must be non-empty");
55    let mut start = 0;
56    while start < value_be.len() - 1 && value_be[start] == 0 {
57        start += 1;
58    }
59    let trimmed = &value_be[start..];
60    let needs_pad = (trimmed[0] & 0x80) != 0;
61    let int_len = trimmed.len() + usize::from(needs_pad);
62    out.push(TAG_INTEGER);
63    write_length(out, int_len);
64    if needs_pad {
65        out.push(0x00);
66    }
67    out.extend_from_slice(trimmed);
68}
69
70/// Append a DER OCTET STRING tag-length-value.
71pub fn write_octet_string(out: &mut Vec<u8>, value: &[u8]) {
72    out.push(TAG_OCTET_STRING);
73    write_length(out, value.len());
74    out.extend_from_slice(value);
75}
76
77/// Append a DER BIT STRING tag-length-value with the given
78/// `unused_bits` count (must be `0..=7`) and content bytes.
79///
80/// # Panics
81/// Panics if `unused_bits > 7`.
82pub fn write_bit_string(out: &mut Vec<u8>, unused_bits: u8, value: &[u8]) {
83    assert!(unused_bits <= 7, "BIT STRING unused_bits must be 0..=7");
84    out.push(TAG_BIT_STRING);
85    write_length(out, value.len() + 1);
86    out.push(unused_bits);
87    out.extend_from_slice(value);
88}
89
90/// Append a DER NULL — exactly `05 00`.
91pub fn write_null(out: &mut Vec<u8>) {
92    out.push(TAG_NULL);
93    out.push(0x00);
94}
95
96/// Append a DER OBJECT IDENTIFIER. `encoded_subids` is the
97/// pre-encoded sub-identifier bytes (e.g. from the constants in
98/// [`super::oid`]).
99///
100/// # Panics
101/// Panics if `encoded_subids` is empty.
102pub fn write_oid(out: &mut Vec<u8>, encoded_subids: &[u8]) {
103    assert!(!encoded_subids.is_empty(), "OID content must be non-empty");
104    out.push(TAG_OID);
105    write_length(out, encoded_subids.len());
106    out.extend_from_slice(encoded_subids);
107}
108
109/// Append a DER SEQUENCE wrapping `body`: `30 LEN body`.
110pub fn write_sequence(out: &mut Vec<u8>, body: &[u8]) {
111    out.push(TAG_SEQUENCE);
112    write_length(out, body.len());
113    out.extend_from_slice(body);
114}
115
116/// Append a context-tagged `[n] EXPLICIT` field wrapping `inner`.
117///
118/// # Panics
119/// Panics if `n > 30` (multi-byte tag form is not supported).
120pub fn write_context_tagged_explicit(out: &mut Vec<u8>, n: u8, inner: &[u8]) {
121    assert!(n <= 30, "multi-byte context tags not supported");
122    // Class = context (0xA0); P/C = constructed (0x20).
123    out.push(0xA0 | n);
124    write_length(out, inner.len());
125    out.extend_from_slice(inner);
126}
127
128/// Append a context-tagged `[n] IMPLICIT` primitive field with
129/// `value` as the raw content.
130///
131/// # Panics
132/// Panics if `n > 30`.
133pub fn write_context_tagged_implicit(out: &mut Vec<u8>, n: u8, value: &[u8]) {
134    assert!(n <= 30, "multi-byte context tags not supported");
135    // Class = context (0x80); P/C = primitive (0x00).
136    out.push(0x80 | n);
137    write_length(out, value.len());
138    out.extend_from_slice(value);
139}
140
141#[cfg(test)]
142mod tests {
143    use super::*;
144    use crate::asn1::reader;
145
146    fn roundtrip_length(len: usize) {
147        let mut out = Vec::new();
148        write_length(&mut out, len);
149        let (decoded, rest) = reader::read_length(&out).expect("decode");
150        assert_eq!(decoded, len);
151        assert!(rest.is_empty(), "no trailing bytes");
152    }
153
154    // ---------- write_length ----------
155
156    #[test]
157    fn length_one_byte() {
158        roundtrip_length(0);
159        roundtrip_length(1);
160        roundtrip_length(127);
161    }
162
163    #[test]
164    fn length_two_byte() {
165        roundtrip_length(128);
166        roundtrip_length(255);
167    }
168
169    #[test]
170    fn length_three_byte() {
171        roundtrip_length(256);
172        roundtrip_length(65_535);
173    }
174
175    #[test]
176    fn length_four_byte() {
177        roundtrip_length(65_536);
178        roundtrip_length(MAX_DER_LEN - 1);
179    }
180
181    #[test]
182    #[should_panic(expected = "DER length overflow")]
183    fn length_overflow_panics() {
184        let mut out = Vec::new();
185        write_length(&mut out, MAX_DER_LEN);
186    }
187
188    // ---------- write_integer ----------
189
190    #[test]
191    fn integer_round_trip_small() {
192        let mut out = Vec::new();
193        write_integer(&mut out, &[0x05]);
194        let (bytes, _) = reader::read_integer(&out).unwrap();
195        assert_eq!(bytes, &[0x05]);
196    }
197
198    #[test]
199    fn integer_round_trip_high_bit_set() {
200        let mut out = Vec::new();
201        write_integer(&mut out, &[0x80, 0x01]);
202        // Encoded as 02 03 00 80 01 — disambiguating 0x00 prepended.
203        assert_eq!(out, alloc::vec![0x02, 0x03, 0x00, 0x80, 0x01]);
204        let (bytes, _) = reader::read_integer(&out).unwrap();
205        assert_eq!(bytes, &[0x80, 0x01]);
206    }
207
208    #[test]
209    fn integer_strips_leading_zeros() {
210        let mut out = Vec::new();
211        write_integer(&mut out, &[0x00, 0x00, 0x05]);
212        // Encoded as 02 01 05 — leading zeros dropped.
213        assert_eq!(out, alloc::vec![0x02, 0x01, 0x05]);
214    }
215
216    #[test]
217    fn integer_zero_round_trip() {
218        let mut out = Vec::new();
219        write_integer(&mut out, &[0x00]);
220        // Canonical zero = 02 01 00.
221        assert_eq!(out, alloc::vec![0x02, 0x01, 0x00]);
222        let (bytes, _) = reader::read_integer(&out).unwrap();
223        assert_eq!(bytes, &[0x00]);
224    }
225
226    // ---------- write_octet_string ----------
227
228    #[test]
229    fn octet_string_empty() {
230        let mut out = Vec::new();
231        write_octet_string(&mut out, &[]);
232        assert_eq!(out, alloc::vec![0x04, 0x00]);
233    }
234
235    #[test]
236    fn octet_string_round_trip() {
237        let mut out = Vec::new();
238        write_octet_string(&mut out, b"abc");
239        assert_eq!(out, alloc::vec![0x04, 0x03, b'a', b'b', b'c']);
240    }
241
242    // ---------- write_bit_string ----------
243
244    #[test]
245    fn bit_string_round_trip() {
246        let mut out = Vec::new();
247        write_bit_string(&mut out, 0, &[0xAB, 0xCD]);
248        assert_eq!(out, alloc::vec![0x03, 0x03, 0x00, 0xAB, 0xCD]);
249        let (unused, bytes, rest) = reader::read_bit_string(&out).unwrap();
250        assert_eq!(unused, 0);
251        assert_eq!(bytes, &[0xAB, 0xCD]);
252        assert!(rest.is_empty());
253    }
254
255    // ---------- write_null ----------
256
257    #[test]
258    fn null_round_trip() {
259        let mut out = Vec::new();
260        write_null(&mut out);
261        assert_eq!(out, alloc::vec![0x05, 0x00]);
262        assert_eq!(reader::read_null(&out), Some(&[][..]));
263    }
264
265    // ---------- write_oid ----------
266
267    #[test]
268    fn oid_round_trip() {
269        // 1.2.840.113549.1.5.12 sub-identifier bytes.
270        let subids: &[u8] = &[0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x05, 0x0C];
271        let mut out = Vec::new();
272        write_oid(&mut out, subids);
273        let (parsed, _) = reader::read_oid(&out).unwrap();
274        assert_eq!(parsed, subids);
275    }
276
277    // ---------- write_sequence ----------
278
279    #[test]
280    fn sequence_wrap() {
281        let mut body = Vec::new();
282        write_integer(&mut body, &[0x01]);
283        write_integer(&mut body, &[0x02]);
284        let mut out = Vec::new();
285        write_sequence(&mut out, &body);
286        assert_eq!(
287            out,
288            alloc::vec![0x30, 0x06, 0x02, 0x01, 0x01, 0x02, 0x01, 0x02]
289        );
290    }
291
292    // ---------- context tags ----------
293
294    #[test]
295    fn context_explicit_round_trip() {
296        let mut inner = Vec::new();
297        write_integer(&mut inner, &[0x01]);
298        let mut out = Vec::new();
299        write_context_tagged_explicit(&mut out, 0, &inner);
300        assert_eq!(out, alloc::vec![0xA0, 0x03, 0x02, 0x01, 0x01]);
301        let (parsed, _) = reader::read_context_tagged_explicit(&out, 0).unwrap();
302        let (v, _) = reader::read_integer(parsed).unwrap();
303        assert_eq!(v, &[0x01]);
304    }
305
306    #[test]
307    fn context_implicit_round_trip() {
308        let mut out = Vec::new();
309        write_context_tagged_implicit(&mut out, 1, b"ab");
310        assert_eq!(out, alloc::vec![0x81, 0x02, b'a', b'b']);
311        let (parsed, _) = reader::read_context_tagged_implicit(&out, 1).unwrap();
312        assert_eq!(parsed, b"ab");
313    }
314
315    #[test]
316    #[should_panic(expected = "multi-byte context tags not supported")]
317    fn context_explicit_above_30_panics() {
318        let mut out = Vec::new();
319        write_context_tagged_explicit(&mut out, 31, &[]);
320    }
321}