Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .github/workflows/pkcs8.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,13 @@ jobs:
- run: cargo test --release --no-default-features
- run: cargo test --release
- run: cargo test --release --features alloc
- run: cargo test --release --features 3des
- run: cargo test --release --features des-insecure
- run: cargo test --release --features encryption
- run: cargo test --release --features pem
- run: cargo test --release --features pkcs1
- run: cargo test --release --features pkcs1,alloc
- run: cargo test --release --features pkcs5
- run: cargo test --release --features sha1
- run: cargo test --release --features 3des
- run: cargo test --release --features des-insecure
- run: cargo test --release --features std
- run: cargo test --release --all-features
153 changes: 76 additions & 77 deletions pkcs8/src/document/private_key.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
//! PKCS#8 private key document.

use crate::{Error, PrivateKeyInfo, Result};
use crate::{Error, FromPrivateKey, PrivateKeyInfo, Result, ToPrivateKey};
use alloc::{borrow::ToOwned, vec::Vec};
use core::{
convert::{TryFrom, TryInto},
fmt,
};
use core::{convert::TryFrom, fmt};
use der::Encodable;
use zeroize::{Zeroize, Zeroizing};

Expand All @@ -24,7 +21,10 @@ use {
};

#[cfg(feature = "std")]
use std::{fs, path::Path, str};
use std::{fs, path::Path};

#[cfg(any(feature = "encryption", feature = "std"))]
use core::convert::TryInto;

/// PKCS#8 private key document.
///
Expand All @@ -41,71 +41,6 @@ impl PrivateKeyDocument {
PrivateKeyInfo::try_from(self.0.as_ref()).expect("malformed PrivateKeyDocument")
}

/// Parse [`PrivateKeyDocument`] from ASN.1 DER-encoded PKCS#8
pub fn from_der(bytes: &[u8]) -> Result<Self> {
bytes.try_into()
}

/// Parse [`PrivateKeyDocument`] from PEM-encoded PKCS#8.
///
/// PEM-encoded private keys can be identified by the leading delimiter:
///
/// ```text
/// -----BEGIN PRIVATE KEY-----
/// ```
#[cfg(feature = "pem")]
#[cfg_attr(docsrs, doc(cfg(feature = "pem")))]
pub fn from_pem(s: &str) -> Result<Self> {
let (label, der_bytes) = pem::decode_vec(s.as_bytes())?;

if label != PEM_TYPE_LABEL {
return Err(pem::Error::Label.into());
}

Self::from_der(&*der_bytes)
}

/// Serialize [`PrivateKeyDocument`] as self-zeroizing PEM-encoded PKCS#8 string
/// with the given [`LineEnding`].
#[cfg(feature = "pem")]
#[cfg_attr(docsrs, doc(cfg(feature = "pem")))]
pub fn to_pem(&self, line_ending: LineEnding) -> Result<Zeroizing<String>> {
pem::encode_string(PEM_TYPE_LABEL, line_ending, &self.0)
.map(Zeroizing::new)
.map_err(|_| Error::Pem)
}

/// Load [`PrivateKeyDocument`] from an ASN.1 DER-encoded file on the local
/// filesystem (binary format).
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
pub fn read_der_file(path: impl AsRef<Path>) -> Result<Self> {
fs::read(path)?.try_into()
}

/// Load [`PrivateKeyDocument`] from a PEM-encoded file on the local filesystem.
#[cfg(all(feature = "pem", feature = "std"))]
#[cfg_attr(docsrs, doc(cfg(feature = "pem")))]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
pub fn read_pem_file(path: impl AsRef<Path>) -> Result<Self> {
Self::from_pem(&Zeroizing::new(fs::read_to_string(path)?))
}

/// Write ASN.1 DER-encoded PKCS#8 private key to the given path
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
pub fn write_der_file(&self, path: impl AsRef<Path>) -> Result<()> {
write_secret_file(path, self.as_ref())
}

/// Write PEM-encoded PKCS#8 private key to the given path
#[cfg(all(feature = "pem", feature = "std"))]
#[cfg_attr(docsrs, doc(cfg(feature = "pem")))]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
pub fn write_pem_file(&self, path: impl AsRef<Path>, line_ending: LineEnding) -> Result<()> {
write_secret_file(path, self.to_pem(line_ending)?.as_bytes())
}

/// Encrypt this private key using a symmetric encryption key derived
/// from the provided password.
///
Expand Down Expand Up @@ -156,6 +91,72 @@ impl PrivateKeyDocument {
}
}

impl FromPrivateKey for PrivateKeyDocument {
fn from_pkcs8_private_key_info(private_key: PrivateKeyInfo<'_>) -> Result<Self> {
Ok(Self(Zeroizing::new(private_key.to_vec()?)))
}

fn from_pkcs8_der(bytes: &[u8]) -> Result<Self> {
// Ensure document is well-formed
PrivateKeyInfo::try_from(bytes)?;
Ok(Self(Zeroizing::new(bytes.to_owned())))
}

#[cfg(feature = "pem")]
#[cfg_attr(docsrs, doc(cfg(feature = "pem")))]
fn from_pkcs8_pem(s: &str) -> Result<Self> {
let (label, der_bytes) = pem::decode_vec(s.as_bytes())?;

if label != PEM_TYPE_LABEL {
return Err(pem::Error::Label.into());
}

// Ensure document is well-formed
PrivateKeyInfo::try_from(der_bytes.as_slice())?;
Ok(Self(Zeroizing::new(der_bytes)))
}

#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
fn read_pkcs8_der_file(path: impl AsRef<Path>) -> Result<Self> {
fs::read(path)?.try_into()
}

#[cfg(all(feature = "pem", feature = "std"))]
#[cfg_attr(docsrs, doc(cfg(feature = "pem")))]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
fn read_pkcs8_pem_file(path: impl AsRef<Path>) -> Result<Self> {
Self::from_pkcs8_pem(&Zeroizing::new(fs::read_to_string(path)?))
}
}

impl ToPrivateKey for PrivateKeyDocument {
fn to_pkcs8_der(&self) -> Result<PrivateKeyDocument> {
Ok(self.clone())
}

#[cfg(feature = "pem")]
#[cfg_attr(docsrs, doc(cfg(feature = "pem")))]
fn to_pkcs8_pem(&self, line_ending: LineEnding) -> Result<Zeroizing<String>> {
let pem_doc = pem::encode_string(PEM_TYPE_LABEL, line_ending, self.as_ref())?;
Ok(Zeroizing::new(pem_doc))
}

#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
fn write_pkcs8_der_file(&self, path: impl AsRef<Path>) -> Result<()> {
write_secret_file(path, self.as_ref())
}

#[cfg(all(feature = "pem", feature = "std"))]
#[cfg_attr(docsrs, doc(cfg(feature = "pem")))]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
fn write_pkcs8_pem_file(&self, path: impl AsRef<Path>, line_ending: LineEnding) -> Result<()> {
let pem_doc = self.to_pkcs8_pem(line_ending)?;
write_secret_file(path, pem_doc.as_bytes())
}
}

impl AsRef<[u8]> for PrivateKeyDocument {
fn as_ref(&self) -> &[u8] {
self.0.as_ref()
Expand All @@ -166,25 +167,23 @@ impl TryFrom<PrivateKeyInfo<'_>> for PrivateKeyDocument {
type Error = Error;

fn try_from(private_key_info: PrivateKeyInfo<'_>) -> Result<PrivateKeyDocument> {
PrivateKeyDocument::try_from(&private_key_info)
PrivateKeyDocument::from_pkcs8_private_key_info(private_key_info)
}
}

impl TryFrom<&PrivateKeyInfo<'_>> for PrivateKeyDocument {
type Error = Error;

fn try_from(private_key_info: &PrivateKeyInfo<'_>) -> Result<PrivateKeyDocument> {
private_key_info.to_vec()?.try_into()
PrivateKeyDocument::from_pkcs8_private_key_info(private_key_info.clone())
}
}

impl TryFrom<&[u8]> for PrivateKeyDocument {
type Error = Error;

fn try_from(bytes: &[u8]) -> Result<Self> {
// Ensure document is well-formed
PrivateKeyInfo::try_from(bytes)?;
Ok(Self(Zeroizing::new(bytes.to_owned())))
PrivateKeyDocument::from_pkcs8_der(bytes)
}
}

Expand Down Expand Up @@ -216,7 +215,7 @@ impl FromStr for PrivateKeyDocument {
type Err = Error;

fn from_str(s: &str) -> Result<Self> {
Self::from_pem(s)
Self::from_pkcs8_pem(s)
}
}

Expand Down
73 changes: 39 additions & 34 deletions pkcs8/src/document/public_key.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! SPKI public key document.

use crate::{Error, Result, SubjectPublicKeyInfo};
use crate::{Error, FromPublicKey, Result, SubjectPublicKeyInfo, ToPublicKey};
use alloc::{borrow::ToOwned, vec::Vec};
use core::{
convert::{TryFrom, TryInto},
Expand All @@ -9,7 +9,7 @@ use core::{
use der::Encodable;

#[cfg(feature = "std")]
use std::{fs, path::Path, str};
use std::{fs, path::Path};

#[cfg(feature = "pem")]
use {
Expand All @@ -36,69 +36,74 @@ impl PublicKeyDocument {
pub fn spki(&self) -> SubjectPublicKeyInfo<'_> {
SubjectPublicKeyInfo::try_from(self.0.as_slice()).expect("malformed PublicKeyDocument")
}
}

/// Parse [`PublicKeyDocument`] from ASN.1 DER.
pub fn from_der(bytes: &[u8]) -> Result<Self> {
bytes.try_into()
impl FromPublicKey for PublicKeyDocument {
fn from_spki(spki: SubjectPublicKeyInfo<'_>) -> Result<Self> {
Ok(Self(spki.to_vec()?))
}

fn from_public_key_der(bytes: &[u8]) -> Result<Self> {
// Ensure document is well-formed
SubjectPublicKeyInfo::try_from(bytes)?;
Ok(Self(bytes.to_owned()))
}

/// Parse [`PublicKeyDocument`] from PEM.
///
/// PEM-encoded public keys can be identified by the leading delimiter:
///
/// ```text
/// -----BEGIN PUBLIC KEY-----
/// ```
#[cfg(feature = "pem")]
#[cfg_attr(docsrs, doc(cfg(feature = "pem")))]
pub fn from_pem(s: &str) -> Result<Self> {
fn from_public_key_pem(s: &str) -> Result<Self> {
let (label, der_bytes) = pem::decode_vec(s.as_bytes())?;

if label != PEM_TYPE_LABEL {
return Err(pem::Error::Label.into());
}

Self::from_der(&*der_bytes)
}

/// Serialize [`PublicKeyDocument`] as PEM-encoded PKCS#8 (SPKI) string
/// with the given [`LineEnding`].
#[cfg(feature = "pem")]
#[cfg_attr(docsrs, doc(cfg(feature = "pem")))]
pub fn to_pem(&self, line_ending: LineEnding) -> Result<String> {
pem::encode_string(PEM_TYPE_LABEL, line_ending, &self.0).map_err(|_| Error::Pem)
// Ensure document is well-formed
SubjectPublicKeyInfo::try_from(der_bytes.as_slice())?;
Ok(Self(der_bytes))
}

/// Load [`PublicKeyDocument`] from an ASN.1 DER-encoded file on the local
/// filesystem (binary format).
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
pub fn read_der_file(path: impl AsRef<Path>) -> Result<Self> {
fn read_public_key_der_file(path: impl AsRef<Path>) -> Result<Self> {
fs::read(path)?.try_into()
}

/// Load [`PublicKeyDocument`] from a PEM-encoded file on the local filesystem.
#[cfg(all(feature = "pem", feature = "std"))]
#[cfg_attr(docsrs, doc(cfg(feature = "pem")))]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
pub fn read_pem_file(path: impl AsRef<Path>) -> Result<Self> {
Self::from_pem(&fs::read_to_string(path)?)
fn read_public_key_pem_file(path: impl AsRef<Path>) -> Result<Self> {
Self::from_public_key_pem(&fs::read_to_string(path)?)
}
}

impl ToPublicKey for PublicKeyDocument {
fn to_public_key_der(&self) -> Result<PublicKeyDocument> {
Ok(self.clone())
}

#[cfg(feature = "pem")]
#[cfg_attr(docsrs, doc(cfg(feature = "pem")))]
fn to_public_key_pem(&self, line_ending: LineEnding) -> Result<String> {
Ok(pem::encode_string(PEM_TYPE_LABEL, line_ending, &self.0)?)
}

/// Write ASN.1 DER-encoded public key to the given path
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
pub fn write_der_file(&self, path: impl AsRef<Path>) -> Result<()> {
fn write_public_key_der_file(&self, path: impl AsRef<Path>) -> Result<()> {
fs::write(path, self.as_ref())?;
Ok(())
}

/// Write PEM-encoded public key to the given path
#[cfg(all(feature = "pem", feature = "std"))]
#[cfg_attr(docsrs, doc(cfg(feature = "pem")))]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
pub fn write_pem_file(&self, path: impl AsRef<Path>, line_ending: LineEnding) -> Result<()> {
fs::write(path, self.to_pem(line_ending)?.as_bytes())?;
fn write_public_key_pem_file(
&self,
path: impl AsRef<Path>,
line_ending: LineEnding,
) -> Result<()> {
fs::write(path, self.to_public_key_pem(line_ending)?.as_bytes())?;
Ok(())
}
}
Expand Down Expand Up @@ -159,6 +164,6 @@ impl FromStr for PublicKeyDocument {
type Err = Error;

fn from_str(s: &str) -> Result<Self> {
Self::from_pem(s)
Self::from_public_key_pem(s)
}
}
Loading