|
1 |
| -//! PKCS#8 documents: serialized PKCS#8 private keys and SPKI public keys |
| 1 | +//! Serialized DER-encoded documents stored in heap-backed buffers. |
2 | 2 | // TODO(tarcieri): heapless support?
|
3 | 3 |
|
4 |
| -use crate::{error, Error, PrivateKeyInfo, Result, SubjectPublicKeyInfo}; |
5 |
| -use alloc::{borrow::ToOwned, vec::Vec}; |
6 |
| -use core::{ |
7 |
| - convert::{TryFrom, TryInto}, |
8 |
| - fmt, |
9 |
| -}; |
10 |
| -use der::Encodable; |
11 |
| -use zeroize::{Zeroize, Zeroizing}; |
12 |
| - |
13 |
| -#[cfg(feature = "std")] |
14 |
| -use std::{fs, path::Path, str}; |
15 |
| - |
16 |
| -#[cfg(feature = "pem")] |
17 |
| -use {crate::pem, alloc::string::String, core::str::FromStr}; |
18 |
| - |
19 |
| -/// PKCS#8 private key document. |
20 |
| -/// |
21 |
| -/// This type provides storage for a PKCS#8 private key encoded as ASN.1 DER |
22 |
| -/// with the invariant that the contained-document is "well-formed", i.e. it |
23 |
| -/// will parse successfully according to this crate's parsing rules. |
24 |
| -#[derive(Clone)] |
25 |
| -#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] |
26 |
| -pub struct PrivateKeyDocument(Zeroizing<Vec<u8>>); |
27 |
| - |
28 |
| -impl PrivateKeyDocument { |
29 |
| - /// Parse [`PrivateKeyDocument`] from ASN.1 DER-encoded PKCS#8 |
30 |
| - pub fn from_der(bytes: &[u8]) -> Result<Self> { |
31 |
| - bytes.try_into() |
32 |
| - } |
33 |
| - |
34 |
| - /// Parse [`PrivateKeyDocument`] from PEM-encoded PKCS#8. |
35 |
| - /// |
36 |
| - /// PEM-encoded private keys can be identified by the leading delimiter: |
37 |
| - /// |
38 |
| - /// ```text |
39 |
| - /// -----BEGIN PRIVATE KEY----- |
40 |
| - /// ``` |
41 |
| - #[cfg(feature = "pem")] |
42 |
| - #[cfg_attr(docsrs, doc(cfg(feature = "pem")))] |
43 |
| - pub fn from_pem(s: &str) -> Result<Self> { |
44 |
| - let der_bytes = pem::decode(s, pem::PRIVATE_KEY_BOUNDARY)?; |
45 |
| - Self::from_der(&*der_bytes) |
46 |
| - } |
47 |
| - |
48 |
| - /// Serialize [`PrivateKeyDocument`] as self-zeroizing PEM-encoded PKCS#8 string. |
49 |
| - #[cfg(feature = "pem")] |
50 |
| - #[cfg_attr(docsrs, doc(cfg(feature = "pem")))] |
51 |
| - pub fn to_pem(&self) -> Zeroizing<String> { |
52 |
| - Zeroizing::new(pem::encode(&self.0, pem::PRIVATE_KEY_BOUNDARY)) |
53 |
| - } |
54 |
| - |
55 |
| - /// Load [`PrivateKeyDocument`] from an ASN.1 DER-encoded file on the local |
56 |
| - /// filesystem (binary format). |
57 |
| - #[cfg(feature = "std")] |
58 |
| - #[cfg_attr(docsrs, doc(cfg(feature = "std")))] |
59 |
| - pub fn read_der_file(path: impl AsRef<Path>) -> Result<Self> { |
60 |
| - fs::read(path)?.try_into() |
61 |
| - } |
62 |
| - |
63 |
| - /// Load [`PrivateKeyDocument`] from a PEM-encoded file on the local filesystem. |
64 |
| - #[cfg(all(feature = "pem", feature = "std"))] |
65 |
| - #[cfg_attr(docsrs, doc(cfg(feature = "pem")))] |
66 |
| - #[cfg_attr(docsrs, doc(cfg(feature = "std")))] |
67 |
| - pub fn read_pem_file(path: impl AsRef<Path>) -> Result<Self> { |
68 |
| - let bytes = Zeroizing::new(fs::read(path)?); |
69 |
| - let pem = str::from_utf8(&*bytes).map_err(|_| Error::Decode)?; |
70 |
| - Self::from_pem(pem) |
71 |
| - } |
72 |
| - |
73 |
| - /// Write ASN.1 DER-encoded PKCS#8 private key to the given path |
74 |
| - #[cfg(feature = "std")] |
75 |
| - #[cfg_attr(docsrs, doc(cfg(feature = "std")))] |
76 |
| - pub fn write_der_file(&self, path: impl AsRef<Path>) -> Result<()> { |
77 |
| - write_secret_file(path, self.as_ref()) |
78 |
| - } |
79 |
| - |
80 |
| - /// Write PEM-encoded PKCS#8 private key to the given path |
81 |
| - #[cfg(all(feature = "pem", feature = "std"))] |
82 |
| - #[cfg_attr(docsrs, doc(cfg(feature = "pem")))] |
83 |
| - #[cfg_attr(docsrs, doc(cfg(feature = "std")))] |
84 |
| - pub fn write_pem_file(&self, path: impl AsRef<Path>) -> Result<()> { |
85 |
| - write_secret_file(path, self.to_pem().as_bytes()) |
86 |
| - } |
87 |
| - |
88 |
| - /// Parse the [`PrivateKeyInfo`] contained in this [`PrivateKeyDocument`] |
89 |
| - pub fn private_key_info(&self) -> PrivateKeyInfo<'_> { |
90 |
| - PrivateKeyInfo::try_from(self.0.as_ref()).expect("constructor failed to validate document") |
91 |
| - } |
92 |
| -} |
93 |
| - |
94 |
| -impl AsRef<[u8]> for PrivateKeyDocument { |
95 |
| - fn as_ref(&self) -> &[u8] { |
96 |
| - self.0.as_ref() |
97 |
| - } |
98 |
| -} |
99 |
| - |
100 |
| -impl From<PrivateKeyInfo<'_>> for PrivateKeyDocument { |
101 |
| - fn from(private_key_info: PrivateKeyInfo<'_>) -> PrivateKeyDocument { |
102 |
| - PrivateKeyDocument::from(&private_key_info) |
103 |
| - } |
104 |
| -} |
105 |
| - |
106 |
| -impl From<&PrivateKeyInfo<'_>> for PrivateKeyDocument { |
107 |
| - fn from(private_key_info: &PrivateKeyInfo<'_>) -> PrivateKeyDocument { |
108 |
| - private_key_info |
109 |
| - .to_vec() |
110 |
| - .ok() |
111 |
| - .and_then(|buf| buf.try_into().ok()) |
112 |
| - .expect(error::DER_ENCODING_MSG) |
113 |
| - } |
114 |
| -} |
115 |
| - |
116 |
| -impl TryFrom<&[u8]> for PrivateKeyDocument { |
117 |
| - type Error = Error; |
118 |
| - |
119 |
| - fn try_from(bytes: &[u8]) -> Result<Self> { |
120 |
| - // Ensure document is well-formed |
121 |
| - PrivateKeyInfo::try_from(bytes)?; |
122 |
| - Ok(Self(Zeroizing::new(bytes.to_owned()))) |
123 |
| - } |
124 |
| -} |
125 |
| - |
126 |
| -impl TryFrom<Vec<u8>> for PrivateKeyDocument { |
127 |
| - type Error = Error; |
128 |
| - |
129 |
| - fn try_from(mut bytes: Vec<u8>) -> Result<Self> { |
130 |
| - // Ensure document is well-formed |
131 |
| - if PrivateKeyInfo::try_from(bytes.as_slice()).is_ok() { |
132 |
| - Ok(Self(Zeroizing::new(bytes))) |
133 |
| - } else { |
134 |
| - bytes.zeroize(); |
135 |
| - Err(Error::Decode) |
136 |
| - } |
137 |
| - } |
138 |
| -} |
139 |
| - |
140 |
| -impl fmt::Debug for PrivateKeyDocument { |
141 |
| - fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { |
142 |
| - fmt.debug_tuple("PrivateKeyDocument") |
143 |
| - .field(&self.private_key_info()) |
144 |
| - .finish() |
145 |
| - } |
146 |
| -} |
147 |
| - |
148 |
| -#[cfg(feature = "pem")] |
149 |
| -#[cfg_attr(docsrs, doc(cfg(feature = "pem")))] |
150 |
| -impl FromStr for PrivateKeyDocument { |
151 |
| - type Err = Error; |
152 |
| - |
153 |
| - fn from_str(s: &str) -> Result<Self> { |
154 |
| - Self::from_pem(s) |
155 |
| - } |
156 |
| -} |
157 |
| - |
158 |
| -/// SPKI public key document. |
159 |
| -/// |
160 |
| -/// This type provides storage for a SPKI public key encoded as ASN.1 DER with |
161 |
| -/// the invariant that the contained-document is "well-formed", i.e. it will |
162 |
| -/// parse successfully according to this crate's parsing rules. |
163 |
| -#[derive(Clone)] |
164 |
| -#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] |
165 |
| -pub struct PublicKeyDocument(Vec<u8>); |
166 |
| - |
167 |
| -impl PublicKeyDocument { |
168 |
| - /// Parse [`PublicKeyDocument`] from ASN.1 DER |
169 |
| - pub fn from_der(bytes: &[u8]) -> Result<Self> { |
170 |
| - bytes.try_into() |
171 |
| - } |
172 |
| - |
173 |
| - /// Parse [`PublicKeyDocument`] from PEM |
174 |
| - /// |
175 |
| - /// PEM-encoded public keys can be identified by the leading delimiter: |
176 |
| - /// |
177 |
| - /// ```text |
178 |
| - /// -----BEGIN PUBLIC KEY----- |
179 |
| - /// ``` |
180 |
| - #[cfg(feature = "pem")] |
181 |
| - #[cfg_attr(docsrs, doc(cfg(feature = "pem")))] |
182 |
| - pub fn from_pem(s: &str) -> Result<Self> { |
183 |
| - let der_bytes = pem::decode(s, pem::PUBLIC_KEY_BOUNDARY)?; |
184 |
| - Self::from_der(&*der_bytes) |
185 |
| - } |
186 |
| - |
187 |
| - /// Serialize [`PublicKeyDocument`] as PEM-encoded PKCS#8 string. |
188 |
| - #[cfg(feature = "pem")] |
189 |
| - #[cfg_attr(docsrs, doc(cfg(feature = "pem")))] |
190 |
| - pub fn to_pem(&self) -> String { |
191 |
| - pem::encode(&self.0, pem::PUBLIC_KEY_BOUNDARY) |
192 |
| - } |
193 |
| - |
194 |
| - /// Load [`PublicKeyDocument`] from an ASN.1 DER-encoded file on the local |
195 |
| - /// filesystem (binary format). |
196 |
| - #[cfg(feature = "std")] |
197 |
| - #[cfg_attr(docsrs, doc(cfg(feature = "std")))] |
198 |
| - pub fn read_der_file(path: impl AsRef<Path>) -> Result<Self> { |
199 |
| - fs::read(path)?.try_into() |
200 |
| - } |
201 |
| - |
202 |
| - /// Load [`PublicKeyDocument`] from a PEM-encoded file on the local filesystem. |
203 |
| - #[cfg(all(feature = "pem", feature = "std"))] |
204 |
| - #[cfg_attr(docsrs, doc(cfg(feature = "pem")))] |
205 |
| - #[cfg_attr(docsrs, doc(cfg(feature = "std")))] |
206 |
| - pub fn read_pem_file(path: impl AsRef<Path>) -> Result<Self> { |
207 |
| - let pem = fs::read_to_string(path)?; |
208 |
| - Self::from_pem(&pem) |
209 |
| - } |
210 |
| - |
211 |
| - /// Write ASN.1 DER-encoded public key to the given path |
212 |
| - #[cfg(feature = "std")] |
213 |
| - #[cfg_attr(docsrs, doc(cfg(feature = "std")))] |
214 |
| - pub fn write_der_file(&self, path: impl AsRef<Path>) -> Result<()> { |
215 |
| - fs::write(path, self.as_ref())?; |
216 |
| - Ok(()) |
217 |
| - } |
218 |
| - |
219 |
| - /// Write PEM-encoded public key to the given path |
220 |
| - #[cfg(all(feature = "pem", feature = "std"))] |
221 |
| - #[cfg_attr(docsrs, doc(cfg(feature = "pem")))] |
222 |
| - #[cfg_attr(docsrs, doc(cfg(feature = "std")))] |
223 |
| - pub fn write_pem_file(&self, path: impl AsRef<Path>) -> Result<()> { |
224 |
| - fs::write(path, self.to_pem().as_bytes())?; |
225 |
| - Ok(()) |
226 |
| - } |
227 |
| - |
228 |
| - /// Parse the [`SubjectPublicKeyInfo`] contained in this [`PublicKeyDocument`] |
229 |
| - pub fn spki(&self) -> SubjectPublicKeyInfo<'_> { |
230 |
| - SubjectPublicKeyInfo::try_from(self.0.as_slice()) |
231 |
| - .expect("constructor failed to validate document") |
232 |
| - } |
233 |
| -} |
234 |
| - |
235 |
| -impl AsRef<[u8]> for PublicKeyDocument { |
236 |
| - fn as_ref(&self) -> &[u8] { |
237 |
| - self.0.as_ref() |
238 |
| - } |
239 |
| -} |
240 |
| - |
241 |
| -impl From<SubjectPublicKeyInfo<'_>> for PublicKeyDocument { |
242 |
| - fn from(spki: SubjectPublicKeyInfo<'_>) -> PublicKeyDocument { |
243 |
| - PublicKeyDocument::from(&spki) |
244 |
| - } |
245 |
| -} |
246 |
| - |
247 |
| -impl From<&SubjectPublicKeyInfo<'_>> for PublicKeyDocument { |
248 |
| - fn from(spki: &SubjectPublicKeyInfo<'_>) -> PublicKeyDocument { |
249 |
| - spki.to_vec() |
250 |
| - .ok() |
251 |
| - .and_then(|buf| buf.try_into().ok()) |
252 |
| - .expect(error::DER_ENCODING_MSG) |
253 |
| - } |
254 |
| -} |
255 |
| - |
256 |
| -impl TryFrom<&[u8]> for PublicKeyDocument { |
257 |
| - type Error = Error; |
258 |
| - |
259 |
| - fn try_from(bytes: &[u8]) -> Result<Self> { |
260 |
| - // Ensure document is well-formed |
261 |
| - SubjectPublicKeyInfo::try_from(bytes)?; |
262 |
| - Ok(Self(bytes.to_owned())) |
263 |
| - } |
264 |
| -} |
265 |
| - |
266 |
| -impl TryFrom<Vec<u8>> for PublicKeyDocument { |
267 |
| - type Error = Error; |
268 |
| - |
269 |
| - fn try_from(bytes: Vec<u8>) -> Result<Self> { |
270 |
| - // Ensure document is well-formed |
271 |
| - SubjectPublicKeyInfo::try_from(bytes.as_slice())?; |
272 |
| - Ok(Self(bytes)) |
273 |
| - } |
274 |
| -} |
275 |
| - |
276 |
| -impl fmt::Debug for PublicKeyDocument { |
277 |
| - fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { |
278 |
| - fmt.debug_tuple("PublicKeyDocument") |
279 |
| - .field(&self.spki()) |
280 |
| - .finish() |
281 |
| - } |
282 |
| -} |
283 |
| - |
284 |
| -#[cfg(feature = "pem")] |
285 |
| -#[cfg_attr(docsrs, doc(cfg(feature = "pem")))] |
286 |
| -impl FromStr for PublicKeyDocument { |
287 |
| - type Err = Error; |
288 |
| - |
289 |
| - fn from_str(s: &str) -> Result<Self> { |
290 |
| - Self::from_pem(s) |
291 |
| - } |
292 |
| -} |
293 |
| - |
294 |
| -/// Write a file containing secret data to the filesystem, restricting the |
295 |
| -/// file permissions so it's only readable by the owner |
296 |
| -#[cfg(all(unix, feature = "std"))] |
297 |
| -fn write_secret_file(path: impl AsRef<Path>, data: &[u8]) -> Result<()> { |
298 |
| - use std::{io::Write, os::unix::fs::OpenOptionsExt}; |
299 |
| - |
300 |
| - /// File permissions for secret data |
301 |
| - #[cfg(unix)] |
302 |
| - const SECRET_FILE_PERMS: u32 = 0o600; |
303 |
| - |
304 |
| - fs::OpenOptions::new() |
305 |
| - .create(true) |
306 |
| - .write(true) |
307 |
| - .truncate(true) |
308 |
| - .mode(SECRET_FILE_PERMS) |
309 |
| - .open(path) |
310 |
| - .and_then(|mut file| file.write_all(data))?; |
311 |
| - |
312 |
| - Ok(()) |
313 |
| -} |
314 |
| - |
315 |
| -/// Write a file containing secret data to the filesystem |
316 |
| -// TODO(tarcieri): permissions hardening on Windows |
317 |
| -#[cfg(all(not(unix), feature = "std"))] |
318 |
| -fn write_secret_file(path: impl AsRef<Path>, data: &[u8]) -> Result<()> { |
319 |
| - fs::write(path, data)?; |
320 |
| - Ok(()) |
321 |
| -} |
| 4 | +#[cfg(feature = "pkcs5")] |
| 5 | +pub(crate) mod encrypted_private_key; |
| 6 | +pub(crate) mod private_key; |
| 7 | +pub(crate) mod public_key; |
0 commit comments