Skip to main content

simoom_tls/
asn1.rs

1//! Minimal ASN.1 DER reader and writer (single-byte tags; definite lengths).
2
3use std::fmt;
4
5pub const TAG_BOOLEAN: u8 = 0x01;
6pub const TAG_INTEGER: u8 = 0x02;
7pub const TAG_BIT_STRING: u8 = 0x03;
8pub const TAG_OCTET_STRING: u8 = 0x04;
9pub const TAG_NULL: u8 = 0x05;
10pub const TAG_OID: u8 = 0x06;
11pub const TAG_UTF8_STRING: u8 = 0x0c;
12pub const TAG_PRINTABLE_STRING: u8 = 0x13;
13pub const TAG_T61_STRING: u8 = 0x14;
14pub const TAG_IA5_STRING: u8 = 0x16;
15pub const TAG_UTC_TIME: u8 = 0x17;
16pub const TAG_GENERALIZED_TIME: u8 = 0x18;
17pub const TAG_UNIVERSAL_STRING: u8 = 0x1c;
18pub const TAG_BMP_STRING: u8 = 0x1e;
19pub const TAG_SEQUENCE: u8 = 0x30;
20pub const TAG_SET: u8 = 0x31;
21
22#[derive(Debug, Clone, PartialEq, Eq)]
23pub struct DerError(pub std::borrow::Cow<'static, str>);
24
25impl fmt::Display for DerError {
26    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
27        write!(f, "DER error: {}", self.0)
28    }
29}
30
31impl std::error::Error for DerError {}
32
33pub type DerResult<T> = Result<T, DerError>;
34
35fn err<T>(msg: &'static str) -> DerResult<T> {
36    Err(DerError(msg.into()))
37}
38
39/// One decoded TLV element.
40#[derive(Clone, Copy, Debug)]
41pub struct Tlv<'a> {
42    pub tag: u8,
43    pub value: &'a [u8],
44    /// The complete encoding including tag and length.
45    pub raw: &'a [u8],
46}
47
48impl<'a> Tlv<'a> {
49    /// Reader over the contents of a constructed element.
50    pub fn reader(&self) -> Der<'a> {
51        Der::new(self.value)
52    }
53}
54
55/// Sequential reader over concatenated DER elements.
56#[derive(Clone, Debug)]
57pub struct Der<'a> {
58    data: &'a [u8],
59    pos: usize,
60}
61
62impl<'a> Der<'a> {
63    pub fn new(data: &'a [u8]) -> Der<'a> {
64        Der { data, pos: 0 }
65    }
66
67    pub fn is_empty(&self) -> bool {
68        self.pos >= self.data.len()
69    }
70
71    pub fn peek_tag(&self) -> Option<u8> {
72        self.data.get(self.pos).copied()
73    }
74
75    pub fn read(&mut self) -> DerResult<Tlv<'a>> {
76        let start = self.pos;
77        let d = self.data;
78        if start + 2 > d.len() {
79            return err("truncated element");
80        }
81        let tag = d[start];
82        if tag & 0x1f == 0x1f {
83            return err("multi-byte tags not supported");
84        }
85        let mut p = start + 1;
86        let first = d[p];
87        p += 1;
88        let len = if first < 0x80 {
89            first as usize
90        } else {
91            let n = (first & 0x7f) as usize;
92            if n == 0 || n > 4 {
93                return err("unsupported length encoding");
94            }
95            if p + n > d.len() {
96                return err("truncated length");
97            }
98            let mut l = 0usize;
99            for i in 0..n {
100                l = (l << 8) | d[p + i] as usize;
101            }
102            p += n;
103            l
104        };
105        if p + len > d.len() {
106            return err("element exceeds input");
107        }
108        self.pos = p + len;
109        Ok(Tlv { tag, value: &d[p..p + len], raw: &d[start..p + len] })
110    }
111
112    /// Reads an element and checks its tag.
113    pub fn expect(&mut self, tag: u8) -> DerResult<Tlv<'a>> {
114        let t = self.read()?;
115        if t.tag != tag {
116            return err("unexpected tag");
117        }
118        Ok(t)
119    }
120
121    /// Reads the element only if the next tag matches.
122    pub fn optional(&mut self, tag: u8) -> DerResult<Option<Tlv<'a>>> {
123        if self.peek_tag() == Some(tag) {
124            Ok(Some(self.read()?))
125        } else {
126            Ok(None)
127        }
128    }
129
130    pub fn sequence(&mut self) -> DerResult<Der<'a>> {
131        Ok(self.expect(TAG_SEQUENCE)?.reader())
132    }
133
134    pub fn oid(&mut self) -> DerResult<String> {
135        decode_oid(self.expect(TAG_OID)?.value)
136    }
137
138    /// INTEGER as unsigned big-endian bytes (leading zero stripped); rejects negatives.
139    pub fn uint(&mut self) -> DerResult<&'a [u8]> {
140        let v = self.expect(TAG_INTEGER)?.value;
141        if v.is_empty() {
142            return err("empty integer");
143        }
144        if v[0] & 0x80 != 0 {
145            return err("negative integer");
146        }
147        let mut s = v;
148        while s.len() > 1 && s[0] == 0 {
149            s = &s[1..];
150        }
151        Ok(s)
152    }
153
154    pub fn small_uint(&mut self) -> DerResult<u64> {
155        let v = self.uint()?;
156        if v.len() > 8 {
157            return err("integer too large");
158        }
159        Ok(v.iter().fold(0u64, |a, b| (a << 8) | *b as u64))
160    }
161
162    pub fn octet_string(&mut self) -> DerResult<&'a [u8]> {
163        Ok(self.expect(TAG_OCTET_STRING)?.value)
164    }
165
166    /// BIT STRING contents; requires zero unused bits.
167    pub fn bit_string(&mut self) -> DerResult<&'a [u8]> {
168        let v = self.expect(TAG_BIT_STRING)?.value;
169        if v.is_empty() || v[0] != 0 {
170            return err("bit string with unused bits");
171        }
172        Ok(&v[1..])
173    }
174}
175
176pub fn decode_oid(v: &[u8]) -> DerResult<String> {
177    if v.is_empty() {
178        return err("empty OID");
179    }
180    let mut arcs: Vec<u64> = Vec::new();
181    let mut cur: u64 = 0;
182    for (i, b) in v.iter().enumerate() {
183        if cur > (u64::MAX >> 7) {
184            return err("OID arc too large");
185        }
186        cur = (cur << 7) | (b & 0x7f) as u64;
187        if b & 0x80 == 0 {
188            if arcs.is_empty() {
189                let (a, b2) = if cur < 40 {
190                    (0, cur)
191                } else if cur < 80 {
192                    (1, cur - 40)
193                } else {
194                    (2, cur - 80)
195                };
196                arcs.push(a);
197                arcs.push(b2);
198            } else {
199                arcs.push(cur);
200            }
201            cur = 0;
202        } else if i == v.len() - 1 {
203            return err("truncated OID");
204        }
205    }
206    Ok(arcs.iter().map(|a| a.to_string()).collect::<Vec<_>>().join("."))
207}
208
209pub fn encode_oid(oid: &str) -> Vec<u8> {
210    let arcs: Vec<u64> = oid.split('.').map(|a| a.parse().expect("bad OID")).collect();
211    let mut out = Vec::new();
212    let mut push = |mut v: u64| {
213        let mut tmp = vec![(v & 0x7f) as u8];
214        v >>= 7;
215        while v > 0 {
216            tmp.push(0x80 | (v & 0x7f) as u8);
217            v >>= 7;
218        }
219        tmp.reverse();
220        out.extend(tmp);
221    };
222    push(arcs[0] * 40 + arcs[1]);
223    for a in &arcs[2..] {
224        push(*a);
225    }
226    out
227}
228
229/// Decodes an ASN.1 string type to UTF-8 (lossy for odd encodings).
230pub fn decode_string(tlv: &Tlv) -> Option<String> {
231    match tlv.tag {
232        TAG_UTF8_STRING | TAG_PRINTABLE_STRING | TAG_IA5_STRING => Some(String::from_utf8_lossy(tlv.value).into_owned()),
233        TAG_T61_STRING => Some(tlv.value.iter().map(|b| *b as char).collect()),
234        TAG_BMP_STRING => {
235            let units: Vec<u16> = tlv.value.chunks(2).map(|c| u16::from_be_bytes([c[0], *c.get(1).unwrap_or(&0)])).collect();
236            Some(String::from_utf16_lossy(&units))
237        }
238        TAG_UNIVERSAL_STRING => Some(
239            tlv.value
240                .chunks(4)
241                .filter_map(|c| if c.len() == 4 { char::from_u32(u32::from_be_bytes([c[0], c[1], c[2], c[3]])) } else { None })
242                .collect(),
243        ),
244        _ => None,
245    }
246}
247
248/// Days since 1970-01-01 for a proleptic Gregorian date.
249pub fn days_from_civil(y: i64, m: u32, d: u32) -> i64 {
250    let y = if m <= 2 { y - 1 } else { y };
251    let era = if y >= 0 { y } else { y - 399 } / 400;
252    let yoe = y - era * 400;
253    let mp = (m as i64 + 9) % 12;
254    let doy = (153 * mp + 2) / 5 + d as i64 - 1;
255    let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
256    era * 146097 + doe - 719468
257}
258
259/// (year, month, day) for days since 1970-01-01.
260pub fn civil_from_days(z: i64) -> (i64, u32, u32) {
261    let z = z + 719468;
262    let era = if z >= 0 { z } else { z - 146096 } / 146097;
263    let doe = z - era * 146097;
264    let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
265    let y = yoe + era * 400;
266    let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
267    let mp = (5 * doy + 2) / 153;
268    let d = (doy - (153 * mp + 2) / 5 + 1) as u32;
269    let m = if mp < 10 { mp + 3 } else { mp - 9 } as u32;
270    (if m <= 2 { y + 1 } else { y }, m, d)
271}
272
273/// Parses UTCTime / GeneralizedTime (Z form) to Unix seconds.
274pub fn parse_time(tlv: &Tlv) -> DerResult<i64> {
275    let s = std::str::from_utf8(tlv.value).map_err(|_| DerError("bad time".into()))?;
276    let digits = |r: std::ops::Range<usize>| -> DerResult<i64> {
277        s.get(r).and_then(|x| if x.bytes().all(|c| c.is_ascii_digit()) { x.parse().ok() } else { None }).ok_or(DerError("bad time digits".into()))
278    };
279    let (year, rest) = match tlv.tag {
280        TAG_UTC_TIME => {
281            let yy = digits(0..2)?;
282            (if yy >= 50 { 1900 + yy } else { 2000 + yy }, 2)
283        }
284        TAG_GENERALIZED_TIME => (digits(0..4)?, 4),
285        _ => return err("not a time"),
286    };
287    let mon = digits(rest..rest + 2)?;
288    let day = digits(rest + 2..rest + 4)?;
289    let hour = digits(rest + 4..rest + 6)?;
290    let min = digits(rest + 6..rest + 8)?;
291    let sec = if s.len() > rest + 8 && s.as_bytes()[rest + 8].is_ascii_digit() { digits(rest + 8..rest + 10)? } else { 0 };
292    if !s.ends_with('Z') || !(1..=12).contains(&mon) || !(1..=31).contains(&day) || hour > 23 || min > 59 || sec > 60 {
293        return err("unsupported time format");
294    }
295    Ok(days_from_civil(year, mon as u32, day as u32) * 86400 + hour * 3600 + min * 60 + sec)
296}
297
298// ------------------------------------------------------------------ writer
299
300pub fn tlv(tag: u8, content: &[u8]) -> Vec<u8> {
301    let mut out = vec![tag];
302    let len = content.len();
303    if len < 0x80 {
304        out.push(len as u8);
305    } else {
306        let bytes = len.to_be_bytes();
307        let skip = bytes.iter().take_while(|b| **b == 0).count();
308        out.push(0x80 | (bytes.len() - skip) as u8);
309        out.extend_from_slice(&bytes[skip..]);
310    }
311    out.extend_from_slice(content);
312    out
313}
314
315pub fn seq(parts: &[Vec<u8>]) -> Vec<u8> {
316    tlv(TAG_SEQUENCE, &parts.concat())
317}
318
319pub fn set(parts: &[Vec<u8>]) -> Vec<u8> {
320    tlv(TAG_SET, &parts.concat())
321}
322
323/// INTEGER from unsigned big-endian bytes.
324pub fn uint(bytes: &[u8]) -> Vec<u8> {
325    let mut b = bytes;
326    while b.len() > 1 && b[0] == 0 {
327        b = &b[1..];
328    }
329    let mut c = Vec::new();
330    if b.is_empty() || b[0] & 0x80 != 0 {
331        c.push(0);
332    }
333    c.extend_from_slice(b);
334    tlv(TAG_INTEGER, &c)
335}
336
337pub fn oid(o: &str) -> Vec<u8> {
338    tlv(TAG_OID, &encode_oid(o))
339}
340
341pub fn bit_string(bytes: &[u8]) -> Vec<u8> {
342    let mut c = vec![0u8];
343    c.extend_from_slice(bytes);
344    tlv(TAG_BIT_STRING, &c)
345}
346
347pub fn octet_string(bytes: &[u8]) -> Vec<u8> {
348    tlv(TAG_OCTET_STRING, bytes)
349}
350
351/// Context-specific constructed tag `[n]`.
352pub fn explicit(n: u8, content: &[u8]) -> Vec<u8> {
353    tlv(0xa0 | n, content)
354}
355
356/// UTCTime for years 1950..2049, GeneralizedTime otherwise.
357pub fn time(unix: i64) -> Vec<u8> {
358    let days = unix.div_euclid(86400);
359    let secs = unix.rem_euclid(86400);
360    let (y, m, d) = civil_from_days(days);
361    let (hh, mm, ss) = (secs / 3600, (secs / 60) % 60, secs % 60);
362    if (1950..2050).contains(&y) {
363        tlv(TAG_UTC_TIME, format!("{:02}{:02}{:02}{:02}{:02}{:02}Z", y % 100, m, d, hh, mm, ss).as_bytes())
364    } else {
365        tlv(TAG_GENERALIZED_TIME, format!("{:04}{:02}{:02}{:02}{:02}{:02}Z", y, m, d, hh, mm, ss).as_bytes())
366    }
367}
368
369#[cfg(test)]
370mod tests {
371    use super::*;
372
373    #[test]
374    fn oid_roundtrip() {
375        for o in ["1.2.840.113549.1.1.11", "2.5.29.17", "1.3.6.1.5.5.7.3.1", "1.2.840.10045.3.1.7", "2.999.3"] {
376            assert_eq!(decode_oid(&encode_oid(o)).unwrap(), o);
377        }
378        assert_eq!(encode_oid("1.2.840.113549"), vec![0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d]);
379    }
380
381    #[test]
382    fn dates() {
383        assert_eq!(days_from_civil(1970, 1, 1), 0);
384        assert_eq!(days_from_civil(2000, 3, 1), 11017);
385        for z in [-1000i64, 0, 11016, 11017, 19000, 47482, 2932896] {
386            let (y, m, d) = civil_from_days(z);
387            assert_eq!(days_from_civil(y, m, d), z);
388        }
389        let t = time(1_700_000_000);
390        let tl = Der::new(&t).read().unwrap();
391        assert_eq!(parse_time(&tl).unwrap(), 1_700_000_000);
392        let t = time(4_102_444_800 + 5); // 2100-01-01
393        let tl = Der::new(&t).read().unwrap();
394        assert_eq!(tl.tag, TAG_GENERALIZED_TIME);
395        assert_eq!(parse_time(&tl).unwrap(), 4_102_444_805);
396    }
397
398    #[test]
399    fn integers_and_lengths() {
400        let big = vec![0xffu8; 300];
401        let enc = uint(&big);
402        let mut r = Der::new(&enc);
403        assert_eq!(r.uint().unwrap(), &big[..]);
404        assert!(r.is_empty());
405        assert_eq!(uint(&[0x7f]), vec![2, 1, 0x7f]);
406        assert_eq!(uint(&[0x80]), vec![2, 2, 0, 0x80]);
407        assert!(Der::new(&[0x30, 0x05, 0x01]).read().is_err());
408    }
409}