Skip to main content

der/
document.rs

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