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