Skip to content

Commit 7681f45

Browse files
committed
pkcs8: add EncryptedPrivateKeyDocument
1 parent ff44ba6 commit 7681f45

File tree

5 files changed

+424
-320
lines changed

5 files changed

+424
-320
lines changed

pkcs8/src/document.rs

Lines changed: 5 additions & 319 deletions
Original file line numberDiff line numberDiff line change
@@ -1,321 +1,7 @@
1-
//! PKCS#8 documents: serialized PKCS#8 private keys and SPKI public keys
1+
//! Serialized DER-encoded documents stored in heap-backed buffers.
22
// TODO(tarcieri): heapless support?
33

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

Comments
 (0)