Skip to main content

purecrypto/der/
writer.rs

1//! DER encoding helpers (allocation-based).
2
3use super::tag;
4use alloc::vec::Vec;
5
6/// Encodes a definite-form DER length.
7fn encode_length(len: usize, out: &mut Vec<u8>) {
8    if len < 0x80 {
9        out.push(len as u8);
10        return;
11    }
12    let mut bytes = [0u8; core::mem::size_of::<usize>()];
13    let mut n = 0;
14    let mut l = len;
15    while l > 0 {
16        bytes[n] = (l & 0xff) as u8;
17        l >>= 8;
18        n += 1;
19    }
20    out.push(0x80 | n as u8);
21    for i in (0..n).rev() {
22        out.push(bytes[i]);
23    }
24}
25
26/// Encodes a tag-length-value with the given `tag` wrapping `content`.
27pub fn encode_tlv(tag: u8, content: &[u8]) -> Vec<u8> {
28    let mut out = Vec::with_capacity(content.len() + 4);
29    out.push(tag);
30    encode_length(content.len(), &mut out);
31    out.extend_from_slice(content);
32    out
33}
34
35/// Wraps `content` (the already-encoded elements) in a `SEQUENCE`.
36pub fn encode_sequence(content: &[u8]) -> Vec<u8> {
37    encode_tlv(tag::SEQUENCE, content)
38}
39
40/// Encodes an unsigned big-endian integer as a DER `INTEGER`, trimming leading
41/// zeros and prepending `0x00` when needed to keep the value positive. The
42/// empty slice is encoded as the canonical zero (`02 01 00`).
43pub fn encode_integer(unsigned_be: &[u8]) -> Vec<u8> {
44    if unsigned_be.is_empty() {
45        // Canonical zero encoding rather than panicking on `trimmed[0]`.
46        return encode_tlv(tag::INTEGER, &[0u8]);
47    }
48    // Strip leading zero bytes, keeping at least one byte.
49    let mut start = 0;
50    while start + 1 < unsigned_be.len() && unsigned_be[start] == 0 {
51        start += 1;
52    }
53    let trimmed = &unsigned_be[start..];
54
55    let mut content = Vec::with_capacity(trimmed.len() + 1);
56    if trimmed[0] & 0x80 != 0 {
57        content.push(0x00);
58    }
59    content.extend_from_slice(trimmed);
60    encode_tlv(tag::INTEGER, &content)
61}
62
63/// Encodes an `OCTET STRING`.
64pub fn encode_octet_string(content: &[u8]) -> Vec<u8> {
65    encode_tlv(tag::OCTET_STRING, content)
66}
67
68/// Encodes a `BIT STRING` with zero unused bits.
69pub fn encode_bit_string(bits: &[u8]) -> Vec<u8> {
70    let mut content = Vec::with_capacity(bits.len() + 1);
71    content.push(0x00); // unused-bits count
72    content.extend_from_slice(bits);
73    encode_tlv(tag::BIT_STRING, &content)
74}
75
76/// Encodes an `OBJECT IDENTIFIER` from its already-encoded body bytes.
77pub fn encode_oid(body: &[u8]) -> Vec<u8> {
78    encode_tlv(tag::OID, body)
79}
80
81/// Encodes a `NULL`.
82pub fn encode_null() -> Vec<u8> {
83    encode_tlv(tag::NULL, &[])
84}
85
86/// Encodes a `BOOLEAN` (DER uses `0xFF` for true).
87pub fn encode_boolean(value: bool) -> Vec<u8> {
88    encode_tlv(tag::BOOLEAN, &[if value { 0xff } else { 0x00 }])
89}
90
91/// Encodes a string value with the given string `tag` (e.g.
92/// [`tag::UTF8_STRING`], [`tag::PRINTABLE_STRING`], [`tag::UTC_TIME`]).
93pub fn encode_string(tag: u8, s: &str) -> Vec<u8> {
94    encode_tlv(tag, s.as_bytes())
95}
96
97/// Wraps `content` in a constructed context-specific tag `[n]`.
98pub fn encode_context(n: u8, content: &[u8]) -> Vec<u8> {
99    encode_tlv(tag::context(n), content)
100}