Skip to main content

arcana/encoding/
der.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 Cédric Mesnil <cslashm@pm.me>
3
4//! Minimal ASN.1 DER encoder/decoder.
5//!
6//! Distinguished Encoding Rules per **ITU-T X.690** (ISO/IEC 8825-1) — the
7//! canonical DER subset of BER. Supports only the tags needed by PKCS#1,
8//! PKCS#8, SEC1 and SPKI: SEQUENCE, INTEGER, OCTET STRING, BIT STRING, OID,
9//! context-tagged (explicit), and NULL. Non-canonical BER inputs (indefinite
10//! length, redundant encodings) are rejected by design.
11
12// ====================================================================
13// Tag constants
14// ====================================================================
15
16/// ASN.1 tag bytes.
17pub const TAG_INTEGER: u8 = 0x02;
18/// BIT STRING tag.
19pub const TAG_BIT_STRING: u8 = 0x03;
20/// OCTET STRING tag.
21pub const TAG_OCTET_STRING: u8 = 0x04;
22/// NULL tag.
23pub const TAG_NULL: u8 = 0x05;
24/// OBJECT IDENTIFIER tag.
25pub const TAG_OID: u8 = 0x06;
26/// SEQUENCE (constructed) tag.
27pub const TAG_SEQUENCE: u8 = 0x30;
28
29// ====================================================================
30// Encoder
31// ====================================================================
32
33/// DER encoder: builds a byte vector by appending TLV items.
34pub struct DerEncoder {
35    buf: Vec<u8>,
36}
37
38impl Default for DerEncoder {
39    fn default() -> Self {
40        Self::new()
41    }
42}
43
44impl DerEncoder {
45    /// Create a new empty encoder.
46    pub fn new() -> Self {
47        Self { buf: Vec::new() }
48    }
49
50    /// Return the encoded bytes.
51    pub fn finish(self) -> Vec<u8> {
52        self.buf
53    }
54
55    /// Append a raw DER length.
56    pub fn write_length(&mut self, len: usize) {
57        if len < 0x80 {
58            self.buf.push(len as u8);
59        } else if len < 0x100 {
60            self.buf.push(0x81);
61            self.buf.push(len as u8);
62        } else if len < 0x10000 {
63            self.buf.push(0x82);
64            self.buf.push((len >> 8) as u8);
65            self.buf.push((len & 0xFF) as u8);
66        } else {
67            self.buf.push(0x83);
68            self.buf.push((len >> 16) as u8);
69            self.buf.push(((len >> 8) & 0xFF) as u8);
70            self.buf.push((len & 0xFF) as u8);
71        }
72    }
73
74    /// Append a SEQUENCE wrapping the given content.
75    pub fn sequence(&mut self, content: &[u8]) {
76        self.buf.push(TAG_SEQUENCE);
77        self.write_length(content.len());
78        self.buf.extend_from_slice(content);
79    }
80
81    /// Append an INTEGER from big-endian unsigned bytes.
82    /// Adds a leading 0x00 if the MSB is set (to keep it positive).
83    pub fn integer(&mut self, value: &[u8]) {
84        // Strip leading zeros but keep at least one byte.
85        let mut start = 0;
86        while start < value.len() - 1 && value[start] == 0 {
87            start += 1;
88        }
89        let val = &value[start..];
90        let needs_pad = val[0] & 0x80 != 0;
91        let total = val.len() + if needs_pad { 1 } else { 0 };
92
93        self.buf.push(TAG_INTEGER);
94        self.write_length(total);
95        if needs_pad {
96            self.buf.push(0x00);
97        }
98        self.buf.extend_from_slice(val);
99    }
100
101    /// Append a small non-negative INTEGER from a u64 (for version fields).
102    pub fn integer_u64(&mut self, v: u64) {
103        if v == 0 {
104            self.buf.extend_from_slice(&[TAG_INTEGER, 1, 0]);
105            return;
106        }
107        let be = v.to_be_bytes();
108        let start = be.iter().position(|&b| b != 0).unwrap_or(7);
109        self.integer(&be[start..]);
110    }
111
112    /// Append an OCTET STRING.
113    pub fn octet_string(&mut self, data: &[u8]) {
114        self.buf.push(TAG_OCTET_STRING);
115        self.write_length(data.len());
116        self.buf.extend_from_slice(data);
117    }
118
119    /// Append a BIT STRING (with zero unused-bits prefix).
120    pub fn bit_string(&mut self, data: &[u8]) {
121        self.buf.push(TAG_BIT_STRING);
122        self.write_length(data.len() + 1);
123        self.buf.push(0x00); // unused bits
124        self.buf.extend_from_slice(data);
125    }
126
127    /// Append an OBJECT IDENTIFIER from its encoded byte form.
128    pub fn oid(&mut self, encoded: &[u8]) {
129        self.buf.push(TAG_OID);
130        self.write_length(encoded.len());
131        self.buf.extend_from_slice(encoded);
132    }
133
134    /// Append a NULL.
135    pub fn null(&mut self) {
136        self.buf.extend_from_slice(&[TAG_NULL, 0x00]);
137    }
138
139    /// Append an explicit context-tagged \[N\] CONSTRUCTED wrapping.
140    pub fn context_explicit(&mut self, tag_num: u8, content: &[u8]) {
141        self.buf.push(0xA0 | tag_num);
142        self.write_length(content.len());
143        self.buf.extend_from_slice(content);
144    }
145
146    /// Append raw bytes (for building inner content).
147    pub fn raw(&mut self, data: &[u8]) {
148        self.buf.extend_from_slice(data);
149    }
150}
151
152// ====================================================================
153// Decoder
154// ====================================================================
155
156/// DER decoder: reads TLV items from a byte slice.
157pub struct DerDecoder<'a> {
158    data: &'a [u8],
159    pos: usize,
160}
161
162impl<'a> DerDecoder<'a> {
163    /// Create a decoder over a byte slice.
164    pub fn new(data: &'a [u8]) -> Self {
165        Self { data, pos: 0 }
166    }
167
168    /// Bytes remaining.
169    pub fn remaining(&self) -> usize {
170        self.data.len() - self.pos
171    }
172
173    /// True if all bytes consumed.
174    pub fn is_empty(&self) -> bool {
175        self.pos >= self.data.len()
176    }
177
178    /// Peek at the next tag byte without advancing.
179    pub fn peek_tag(&self) -> Option<u8> {
180        self.data.get(self.pos).copied()
181    }
182
183    /// Read tag + length, return (tag, content_slice).
184    pub fn read_tlv(&mut self) -> Option<(u8, &'a [u8])> {
185        let tag = *self.data.get(self.pos)?;
186        self.pos += 1;
187        let len = self.read_length()?;
188        if self.pos + len > self.data.len() {
189            return None;
190        }
191        let content = &self.data[self.pos..self.pos + len];
192        self.pos += len;
193        Some((tag, content))
194    }
195
196    fn read_length(&mut self) -> Option<usize> {
197        let first = *self.data.get(self.pos)?;
198        self.pos += 1;
199        if first < 0x80 {
200            Some(first as usize)
201        } else if first == 0x81 {
202            let b = *self.data.get(self.pos)? as usize;
203            self.pos += 1;
204            Some(b)
205        } else if first == 0x82 {
206            let hi = *self.data.get(self.pos)? as usize;
207            let lo = *self.data.get(self.pos + 1)? as usize;
208            self.pos += 2;
209            Some((hi << 8) | lo)
210        } else if first == 0x83 {
211            let a = *self.data.get(self.pos)? as usize;
212            let b = *self.data.get(self.pos + 1)? as usize;
213            let c = *self.data.get(self.pos + 2)? as usize;
214            self.pos += 3;
215            Some((a << 16) | (b << 8) | c)
216        } else {
217            None
218        }
219    }
220
221    /// Read a SEQUENCE, return a sub-decoder over its content.
222    pub fn read_sequence(&mut self) -> Option<DerDecoder<'a>> {
223        let (tag, content) = self.read_tlv()?;
224        if tag != TAG_SEQUENCE {
225            return None;
226        }
227        Some(DerDecoder::new(content))
228    }
229
230    /// Read an INTEGER, return the big-endian unsigned bytes
231    /// (leading zero stripped).
232    pub fn read_integer(&mut self) -> Option<&'a [u8]> {
233        let (tag, content) = self.read_tlv()?;
234        if tag != TAG_INTEGER || content.is_empty() {
235            return None;
236        }
237        // Strip leading zero used for sign padding.
238        if content[0] == 0x00 && content.len() > 1 {
239            Some(&content[1..])
240        } else {
241            Some(content)
242        }
243    }
244
245    /// Read an INTEGER as u64 (small values like version fields).
246    pub fn read_integer_u64(&mut self) -> Option<u64> {
247        let bytes = self.read_integer()?;
248        let mut v = 0u64;
249        for &b in bytes {
250            v = (v << 8) | b as u64;
251        }
252        Some(v)
253    }
254
255    /// Read an OCTET STRING.
256    pub fn read_octet_string(&mut self) -> Option<&'a [u8]> {
257        let (tag, content) = self.read_tlv()?;
258        if tag != TAG_OCTET_STRING {
259            return None;
260        }
261        Some(content)
262    }
263
264    /// Read a BIT STRING, return the data bytes (skip unused-bits byte).
265    pub fn read_bit_string(&mut self) -> Option<&'a [u8]> {
266        let (tag, content) = self.read_tlv()?;
267        if tag != TAG_BIT_STRING || content.is_empty() {
268            return None;
269        }
270        // First byte = number of unused bits in the last byte.
271        Some(&content[1..])
272    }
273
274    /// Read an OID, return the encoded bytes.
275    pub fn read_oid(&mut self) -> Option<&'a [u8]> {
276        let (tag, content) = self.read_tlv()?;
277        if tag != TAG_OID {
278            return None;
279        }
280        Some(content)
281    }
282
283    /// Read a NULL.
284    pub fn read_null(&mut self) -> Option<()> {
285        let (tag, content) = self.read_tlv()?;
286        if tag != TAG_NULL || !content.is_empty() {
287            return None;
288        }
289        Some(())
290    }
291
292    /// Read an explicit context-tagged \[N\] CONSTRUCTED, return a
293    /// sub-decoder over its content. Returns None if the tag number
294    /// doesn't match or the tag is absent.
295    pub fn read_context_explicit(&mut self, tag_num: u8) -> Option<DerDecoder<'a>> {
296        let expected = 0xA0 | tag_num;
297        if self.peek_tag() != Some(expected) {
298            return None;
299        }
300        let (_, content) = self.read_tlv()?;
301        Some(DerDecoder::new(content))
302    }
303
304    /// Skip the next TLV element (any tag).
305    pub fn skip(&mut self) -> Option<()> {
306        self.read_tlv().map(|_| ())
307    }
308}
309
310// ====================================================================
311// Well-known OIDs (encoded form, without tag+length)
312// ====================================================================
313
314/// rsaEncryption (1.2.840.113549.1.1.1)
315pub const OID_RSA: &[u8] = &[0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01];
316
317/// id-ecPublicKey (1.2.840.10045.2.1)
318pub const OID_EC_PUBLIC_KEY: &[u8] = &[0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01];
319
320/// secp256r1 / P-256 (1.2.840.10045.3.1.7)
321pub const OID_SECP256R1: &[u8] = &[0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07];
322
323/// secp384r1 / P-384 (1.3.132.0.34)
324pub const OID_SECP384R1: &[u8] = &[0x2B, 0x81, 0x04, 0x00, 0x22];
325
326/// secp521r1 / P-521 (1.3.132.0.35)
327pub const OID_SECP521R1: &[u8] = &[0x2B, 0x81, 0x04, 0x00, 0x23];
328
329/// secp256k1 (1.3.132.0.10)
330pub const OID_SECP256K1: &[u8] = &[0x2B, 0x81, 0x04, 0x00, 0x0A];
331
332/// brainpoolP256r1 (1.3.36.3.3.2.8.1.1.7)
333pub const OID_BRAINPOOL_P256R1: &[u8] = &[0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x07];
334
335/// brainpoolP384r1 (1.3.36.3.3.2.8.1.1.11)
336pub const OID_BRAINPOOL_P384R1: &[u8] = &[0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x0B];
337
338/// brainpoolP512r1 (1.3.36.3.3.2.8.1.1.13)
339pub const OID_BRAINPOOL_P512R1: &[u8] = &[0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x0D];
340
341/// id-Ed25519 (1.3.101.112)
342pub const OID_ED25519: &[u8] = &[0x2B, 0x65, 0x70];
343
344/// id-X25519 (1.3.101.110)
345pub const OID_X25519: &[u8] = &[0x2B, 0x65, 0x6E];
346
347/// id-X448 (1.3.101.111)
348pub const OID_X448: &[u8] = &[0x2B, 0x65, 0x6F];
349
350// ====================================================================
351// Tests
352// ====================================================================
353
354#[cfg(test)]
355mod tests {
356    use super::*;
357
358    #[test]
359    fn roundtrip_integer() {
360        let val = [0x00, 0x80, 0xFF]; // needs leading-zero strip then pad
361        let mut enc = DerEncoder::new();
362        enc.integer(&val);
363        let der = enc.finish();
364        let mut dec = DerDecoder::new(&der);
365        let got = dec.read_integer().unwrap();
366        assert_eq!(got, &[0x80, 0xFF]);
367    }
368
369    #[test]
370    fn roundtrip_integer_u64() {
371        for v in [0u64, 1, 127, 128, 255, 256, 65535, 0xDEADBEEF] {
372            let mut enc = DerEncoder::new();
373            enc.integer_u64(v);
374            let der = enc.finish();
375            let mut dec = DerDecoder::new(&der);
376            assert_eq!(dec.read_integer_u64().unwrap(), v, "v={}", v);
377        }
378    }
379
380    #[test]
381    fn roundtrip_sequence() {
382        let mut inner = DerEncoder::new();
383        inner.integer_u64(42);
384        inner.octet_string(b"hello");
385        let content = inner.finish();
386
387        let mut outer = DerEncoder::new();
388        outer.sequence(&content);
389        let der = outer.finish();
390
391        let mut dec = DerDecoder::new(&der);
392        let mut seq = dec.read_sequence().unwrap();
393        assert_eq!(seq.read_integer_u64().unwrap(), 42);
394        assert_eq!(seq.read_octet_string().unwrap(), b"hello");
395        assert!(seq.is_empty());
396    }
397
398    #[test]
399    fn roundtrip_bit_string() {
400        let data = [0x04, 0x01, 0x02]; // uncompressed EC point prefix
401        let mut enc = DerEncoder::new();
402        enc.bit_string(&data);
403        let der = enc.finish();
404
405        let mut dec = DerDecoder::new(&der);
406        let got = dec.read_bit_string().unwrap();
407        assert_eq!(got, &data);
408    }
409
410    #[test]
411    fn roundtrip_oid() {
412        let mut enc = DerEncoder::new();
413        enc.oid(OID_RSA);
414        let der = enc.finish();
415
416        let mut dec = DerDecoder::new(&der);
417        assert_eq!(dec.read_oid().unwrap(), OID_RSA);
418    }
419
420    #[test]
421    fn context_explicit_tag() {
422        let mut inner = DerEncoder::new();
423        inner.integer_u64(1);
424        let content = inner.finish();
425
426        let mut enc = DerEncoder::new();
427        enc.context_explicit(0, &content);
428        let der = enc.finish();
429
430        let mut dec = DerDecoder::new(&der);
431        let mut ctx = dec.read_context_explicit(0).unwrap();
432        assert_eq!(ctx.read_integer_u64().unwrap(), 1);
433    }
434
435    #[test]
436    fn large_length_encoding() {
437        // 256 bytes → 0x82 0x01 0x00 encoding
438        let data = vec![0xABu8; 256];
439        let mut enc = DerEncoder::new();
440        enc.octet_string(&data);
441        let der = enc.finish();
442
443        let mut dec = DerDecoder::new(&der);
444        let got = dec.read_octet_string().unwrap();
445        assert_eq!(got, &data[..]);
446    }
447}