Coverage Report

Created: 2025-07-11 06:15

/rust/registry/src/index.crates.io-6f17d22bba15001f/der-0.7.10/src/document.rs
Line
Count
Source (jump to first uncovered line)
1
//! ASN.1 DER-encoded documents stored on the heap.
2
3
use crate::{Decode, Encode, Error, FixedTag, Length, Reader, Result, SliceReader, Tag, Writer};
4
use alloc::vec::Vec;
5
use core::fmt::{self, Debug};
6
7
#[cfg(feature = "pem")]
8
use {crate::pem, alloc::string::String};
9
10
#[cfg(feature = "std")]
11
use std::{fs, path::Path};
12
13
#[cfg(all(feature = "pem", feature = "std"))]
14
use alloc::borrow::ToOwned;
15
16
#[cfg(feature = "zeroize")]
17
use 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)]
31
pub struct Document {
32
    /// ASN.1 DER encoded bytes.
33
    der_bytes: Vec<u8>,
34
35
    /// Length of this document.
36
    length: Length,
37
}
38
39
impl Document {
40
    /// Get the ASN.1 DER-encoded bytes of this document.
41
0
    pub fn as_bytes(&self) -> &[u8] {
42
0
        self.der_bytes.as_slice()
43
0
    }
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
0
    pub fn into_vec(self) -> Vec<u8> {
53
0
        self.der_bytes
54
0
    }
55
56
    /// Return an ASN.1 DER-encoded byte vector.
57
0
    pub fn to_vec(&self) -> Vec<u8> {
58
0
        self.der_bytes.clone()
59
0
    }
60
61
    /// Get the length of the encoded ASN.1 DER in bytes.
62
0
    pub fn len(&self) -> Length {
63
0
        self.length
64
0
    }
65
66
    /// Try to decode the inner ASN.1 DER message contained in this
67
    /// [`Document`] as the given type.
68
0
    pub fn decode_msg<'a, T: Decode<'a>>(&'a self) -> Result<T> {
69
0
        T::from_der(self.as_bytes())
70
0
    }
71
72
    /// Encode the provided type as ASN.1 DER, storing the resulting encoded DER
73
    /// as a [`Document`].
74
0
    pub fn encode_msg<T: Encode>(msg: &T) -> Result<Self> {
75
0
        msg.to_der()?.try_into()
76
0
    }
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
125
impl AsRef<[u8]> for Document {
126
0
    fn as_ref(&self) -> &[u8] {
127
0
        self.as_bytes()
128
0
    }
129
}
130
131
impl Debug for Document {
132
0
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
133
0
        f.write_str("Document(")?;
134
135
0
        for byte in self.as_bytes() {
136
0
            write!(f, "{:02X}", byte)?;
137
        }
138
139
0
        f.write_str(")")
140
0
    }
141
}
142
143
impl<'a> Decode<'a> for Document {
144
0
    fn decode<R: Reader<'a>>(reader: &mut R) -> Result<Document> {
145
0
        let header = reader.peek_header()?;
146
0
        let length = (header.encoded_len()? + header.length)?;
147
0
        let bytes = reader.read_slice(length)?;
148
149
0
        Ok(Self {
150
0
            der_bytes: bytes.into(),
151
0
            length,
152
0
        })
153
0
    }
154
}
155
156
impl Encode for Document {
157
0
    fn encoded_len(&self) -> Result<Length> {
158
0
        Ok(self.len())
159
0
    }
160
161
0
    fn encode(&self, writer: &mut impl Writer) -> Result<()> {
162
0
        writer.write(self.as_bytes())
163
0
    }
164
}
165
166
impl FixedTag for Document {
167
    const TAG: Tag = Tag::Sequence;
168
}
169
170
impl TryFrom<&[u8]> for Document {
171
    type Error = Error;
172
173
0
    fn try_from(der_bytes: &[u8]) -> Result<Self> {
174
0
        Self::from_der(der_bytes)
175
0
    }
176
}
177
178
impl TryFrom<Vec<u8>> for Document {
179
    type Error = Error;
180
181
0
    fn try_from(der_bytes: Vec<u8>) -> Result<Self> {
182
0
        let mut decoder = SliceReader::new(&der_bytes)?;
183
0
        decode_sequence(&mut decoder)?;
184
0
        decoder.finish(())?;
185
186
0
        let length = der_bytes.len().try_into()?;
187
0
        Ok(Self { der_bytes, length })
188
0
    }
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)]
201
pub struct SecretDocument(Document);
202
203
#[cfg(feature = "zeroize")]
204
impl 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")]
276
impl 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")]
283
impl Drop for SecretDocument {
284
    fn drop(&mut self) {
285
        self.0.der_bytes.zeroize();
286
    }
287
}
288
289
#[cfg(feature = "zeroize")]
290
impl From<Document> for SecretDocument {
291
    fn from(doc: Document) -> SecretDocument {
292
        SecretDocument(doc)
293
    }
294
}
295
296
#[cfg(feature = "zeroize")]
297
impl 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")]
306
impl 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")]
315
impl ZeroizeOnDrop for SecretDocument {}
316
317
/// Attempt to decode a ASN.1 `SEQUENCE` from the given decoder, returning the
318
/// entire sequence including the header.
319
0
fn decode_sequence<'a>(decoder: &mut SliceReader<'a>) -> Result<&'a [u8]> {
320
0
    let header = decoder.peek_header()?;
321
0
    header.tag.assert_eq(Tag::Sequence)?;
322
323
0
    let len = (header.encoded_len()? + header.length)?;
324
0
    decoder.read_slice(len)
325
0
}
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"))]
330
fn 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"))]
351
fn write_secret_file(path: impl AsRef<Path>, data: &[u8]) -> Result<()> {
352
    fs::write(path, data)?;
353
    Ok(())
354
}