der/
document.rs

1//! ASN.1 DER-encoded documents stored on the heap.
2
3use crate::{Decode, Encode, Error, FixedTag, Length, Reader, Result, SliceReader, Tag, Writer};
4use alloc::vec::Vec;
5use core::fmt::{self, Debug};
6
7#[cfg(feature = "pem")]
8use {crate::pem, alloc::string::String};
9
10#[cfg(feature = "std")]
11use std::{fs, path::Path};
12
13#[cfg(all(feature = "pem", feature = "std"))]
14use alloc::borrow::ToOwned;
15
16#[cfg(feature = "zeroize")]
17use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
18
19/// ASN.1 DER-encoded document.
20///
21/// This type wraps an encoded ASN.1 DER message. The document checked to
22/// ensure it contains a valid DER-encoded `SEQUENCE`.
23///
24/// It implements common functionality related to encoding/decoding such
25/// documents, such as PEM encapsulation as well as reading/writing documents
26/// from/to the filesystem.
27///
28/// The [`SecretDocument`] provides a wrapper for this type with additional
29/// hardening applied.
30#[derive(Clone, Eq, PartialEq)]
31pub struct Document {
32    /// ASN.1 DER encoded bytes.
33    der_bytes: Vec<u8>,
34
35    /// Length of this document.
36    length: Length,
37}
38
39impl Document {
40    /// Get the ASN.1 DER-encoded bytes of this document.
41    pub fn as_bytes(&self) -> &[u8] {
42        self.der_bytes.as_slice()
43    }
44
45    /// Convert to a [`SecretDocument`].
46    #[cfg(feature = "zeroize")]
47    pub fn into_secret(self) -> SecretDocument {
48        SecretDocument(self)
49    }
50
51    /// Convert to an ASN.1 DER-encoded byte vector.
52    pub fn into_vec(self) -> Vec<u8> {
53        self.der_bytes
54    }
55
56    /// Return an ASN.1 DER-encoded byte vector.
57    pub fn to_vec(&self) -> Vec<u8> {
58        self.der_bytes.clone()
59    }
60
61    /// Get the length of the encoded ASN.1 DER in bytes.
62    pub fn len(&self) -> Length {
63        self.length
64    }
65
66    /// Try to decode the inner ASN.1 DER message contained in this
67    /// [`Document`] as the given type.
68    pub fn decode_msg<'a, T: Decode<'a>>(&'a self) -> Result<T> {
69        T::from_der(self.as_bytes())
70    }
71
72    /// Encode the provided type as ASN.1 DER, storing the resulting encoded DER
73    /// as a [`Document`].
74    pub fn encode_msg<T: Encode>(msg: &T) -> Result<Self> {
75        msg.to_der()?.try_into()
76    }
77
78    /// Decode ASN.1 DER document from PEM.
79    ///
80    /// Returns the PEM label and decoded [`Document`] on success.
81    #[cfg(feature = "pem")]
82    pub fn from_pem(pem: &str) -> Result<(&str, Self)> {
83        let (label, der_bytes) = pem::decode_vec(pem.as_bytes())?;
84        Ok((label, der_bytes.try_into()?))
85    }
86
87    /// Encode ASN.1 DER document as a PEM string with encapsulation boundaries
88    /// containing the provided PEM type `label` (e.g. `CERTIFICATE`).
89    #[cfg(feature = "pem")]
90    pub fn to_pem(&self, label: &'static str, line_ending: pem::LineEnding) -> Result<String> {
91        Ok(pem::encode_string(label, line_ending, self.as_bytes())?)
92    }
93
94    /// Read ASN.1 DER document from a file.
95    #[cfg(feature = "std")]
96    pub fn read_der_file(path: impl AsRef<Path>) -> Result<Self> {
97        fs::read(path)?.try_into()
98    }
99
100    /// Write ASN.1 DER document to a file.
101    #[cfg(feature = "std")]
102    pub fn write_der_file(&self, path: impl AsRef<Path>) -> Result<()> {
103        Ok(fs::write(path, self.as_bytes())?)
104    }
105
106    /// Read PEM-encoded ASN.1 DER document from a file.
107    #[cfg(all(feature = "pem", feature = "std"))]
108    pub fn read_pem_file(path: impl AsRef<Path>) -> Result<(String, Self)> {
109        Self::from_pem(&fs::read_to_string(path)?).map(|(label, doc)| (label.to_owned(), doc))
110    }
111
112    /// Write PEM-encoded ASN.1 DER document to a file.
113    #[cfg(all(feature = "pem", feature = "std"))]
114    pub fn write_pem_file(
115        &self,
116        path: impl AsRef<Path>,
117        label: &'static str,
118        line_ending: pem::LineEnding,
119    ) -> Result<()> {
120        let pem = self.to_pem(label, line_ending)?;
121        Ok(fs::write(path, pem.as_bytes())?)
122    }
123}
124
125impl AsRef<[u8]> for Document {
126    fn as_ref(&self) -> &[u8] {
127        self.as_bytes()
128    }
129}
130
131impl Debug for Document {
132    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
133        f.write_str("Document(")?;
134
135        for byte in self.as_bytes() {
136            write!(f, "{:02X}", byte)?;
137        }
138
139        f.write_str(")")
140    }
141}
142
143impl<'a> Decode<'a> for Document {
144    fn decode<R: Reader<'a>>(reader: &mut R) -> Result<Document> {
145        let header = reader.peek_header()?;
146        let length = (header.encoded_len()? + header.length)?;
147        let bytes = reader.read_slice(length)?;
148
149        Ok(Self {
150            der_bytes: bytes.into(),
151            length,
152        })
153    }
154}
155
156impl Encode for Document {
157    fn encoded_len(&self) -> Result<Length> {
158        Ok(self.len())
159    }
160
161    fn encode(&self, writer: &mut impl Writer) -> Result<()> {
162        writer.write(self.as_bytes())
163    }
164}
165
166impl FixedTag for Document {
167    const TAG: Tag = Tag::Sequence;
168}
169
170impl TryFrom<&[u8]> for Document {
171    type Error = Error;
172
173    fn try_from(der_bytes: &[u8]) -> Result<Self> {
174        Self::from_der(der_bytes)
175    }
176}
177
178impl TryFrom<Vec<u8>> for Document {
179    type Error = Error;
180
181    fn try_from(der_bytes: Vec<u8>) -> Result<Self> {
182        let mut decoder = SliceReader::new(&der_bytes)?;
183        decode_sequence(&mut decoder)?;
184        decoder.finish(())?;
185
186        let length = der_bytes.len().try_into()?;
187        Ok(Self { der_bytes, length })
188    }
189}
190
191/// Secret [`Document`] type.
192///
193/// Useful for formats which represent potentially secret data, such as
194/// cryptographic keys.
195///
196/// This type provides additional hardening such as ensuring that the contents
197/// are zeroized-on-drop, and also using more restrictive file permissions when
198/// writing files to disk.
199#[cfg(feature = "zeroize")]
200#[derive(Clone)]
201pub struct SecretDocument(Document);
202
203#[cfg(feature = "zeroize")]
204impl SecretDocument {
205    /// Borrow the inner serialized bytes of this document.
206    pub fn as_bytes(&self) -> &[u8] {
207        self.0.as_bytes()
208    }
209
210    /// Return an allocated ASN.1 DER serialization as a byte vector.
211    pub fn to_bytes(&self) -> Zeroizing<Vec<u8>> {
212        Zeroizing::new(self.0.to_vec())
213    }
214
215    /// Get the length of the encoded ASN.1 DER in bytes.
216    pub fn len(&self) -> Length {
217        self.0.len()
218    }
219
220    /// Try to decode the inner ASN.1 DER message as the given type.
221    pub fn decode_msg<'a, T: Decode<'a>>(&'a self) -> Result<T> {
222        self.0.decode_msg()
223    }
224
225    /// Encode the provided type as ASN.1 DER.
226    pub fn encode_msg<T: Encode>(msg: &T) -> Result<Self> {
227        Document::encode_msg(msg).map(Self)
228    }
229
230    /// Decode ASN.1 DER document from PEM.
231    #[cfg(feature = "pem")]
232    pub fn from_pem(pem: &str) -> Result<(&str, Self)> {
233        Document::from_pem(pem).map(|(label, doc)| (label, Self(doc)))
234    }
235
236    /// Encode ASN.1 DER document as a PEM string.
237    #[cfg(feature = "pem")]
238    pub fn to_pem(
239        &self,
240        label: &'static str,
241        line_ending: pem::LineEnding,
242    ) -> Result<Zeroizing<String>> {
243        self.0.to_pem(label, line_ending).map(Zeroizing::new)
244    }
245
246    /// Read ASN.1 DER document from a file.
247    #[cfg(feature = "std")]
248    pub fn read_der_file(path: impl AsRef<Path>) -> Result<Self> {
249        Document::read_der_file(path).map(Self)
250    }
251
252    /// Write ASN.1 DER document to a file.
253    #[cfg(feature = "std")]
254    pub fn write_der_file(&self, path: impl AsRef<Path>) -> Result<()> {
255        write_secret_file(path, self.as_bytes())
256    }
257
258    /// Read PEM-encoded ASN.1 DER document from a file.
259    #[cfg(all(feature = "pem", feature = "std"))]
260    pub fn read_pem_file(path: impl AsRef<Path>) -> Result<(String, Self)> {
261        Document::read_pem_file(path).map(|(label, doc)| (label, Self(doc)))
262    }
263
264    /// Write PEM-encoded ASN.1 DER document to a file.
265    #[cfg(all(feature = "pem", feature = "std"))]
266    pub fn write_pem_file(
267        &self,
268        path: impl AsRef<Path>,
269        label: &'static str,
270        line_ending: pem::LineEnding,
271    ) -> Result<()> {
272        write_secret_file(path, self.to_pem(label, line_ending)?.as_bytes())
273    }
274}
275#[cfg(feature = "zeroize")]
276impl Debug for SecretDocument {
277    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
278        fmt.debug_struct("SecretDocument").finish_non_exhaustive()
279    }
280}
281
282#[cfg(feature = "zeroize")]
283impl Drop for SecretDocument {
284    fn drop(&mut self) {
285        self.0.der_bytes.zeroize();
286    }
287}
288
289#[cfg(feature = "zeroize")]
290impl From<Document> for SecretDocument {
291    fn from(doc: Document) -> SecretDocument {
292        SecretDocument(doc)
293    }
294}
295
296#[cfg(feature = "zeroize")]
297impl TryFrom<&[u8]> for SecretDocument {
298    type Error = Error;
299
300    fn try_from(der_bytes: &[u8]) -> Result<Self> {
301        Document::try_from(der_bytes).map(Self)
302    }
303}
304
305#[cfg(feature = "zeroize")]
306impl TryFrom<Vec<u8>> for SecretDocument {
307    type Error = Error;
308
309    fn try_from(der_bytes: Vec<u8>) -> Result<Self> {
310        Document::try_from(der_bytes).map(Self)
311    }
312}
313
314#[cfg(feature = "zeroize")]
315impl ZeroizeOnDrop for SecretDocument {}
316
317/// Attempt to decode a ASN.1 `SEQUENCE` from the given decoder, returning the
318/// entire sequence including the header.
319fn decode_sequence<'a>(decoder: &mut SliceReader<'a>) -> Result<&'a [u8]> {
320    let header = decoder.peek_header()?;
321    header.tag.assert_eq(Tag::Sequence)?;
322
323    let len = (header.encoded_len()? + header.length)?;
324    decoder.read_slice(len)
325}
326
327/// Write a file containing secret data to the filesystem, restricting the
328/// file permissions so it's only readable by the owner
329#[cfg(all(unix, feature = "std", feature = "zeroize"))]
330fn write_secret_file(path: impl AsRef<Path>, data: &[u8]) -> Result<()> {
331    use std::{io::Write, os::unix::fs::OpenOptionsExt};
332
333    /// File permissions for secret data
334    #[cfg(unix)]
335    const SECRET_FILE_PERMS: u32 = 0o600;
336
337    fs::OpenOptions::new()
338        .create(true)
339        .write(true)
340        .truncate(true)
341        .mode(SECRET_FILE_PERMS)
342        .open(path)
343        .and_then(|mut file| file.write_all(data))?;
344
345    Ok(())
346}
347
348/// Write a file containing secret data to the filesystem
349// TODO(tarcieri): permissions hardening on Windows
350#[cfg(all(not(unix), feature = "std", feature = "zeroize"))]
351fn write_secret_file(path: impl AsRef<Path>, data: &[u8]) -> Result<()> {
352    fs::write(path, data)?;
353    Ok(())
354}