use crate::{Error, PrivateKeyInfo, Result, SubjectPublicKeyInfo};
use alloc::{borrow::ToOwned, vec::Vec};
use core::{
convert::{TryFrom, TryInto},
fmt,
};
use zeroize::{Zeroize, Zeroizing};
#[cfg(feature = "std")]
use std::{fs, path::Path, str};
#[cfg(feature = "pem")]
use {crate::pem, alloc::string::String, core::str::FromStr};
#[derive(Clone)]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
pub struct PrivateKeyDocument(Zeroizing<Vec<u8>>);
impl PrivateKeyDocument {
pub fn from_der(bytes: &[u8]) -> Result<Self> {
bytes.try_into()
}
#[cfg(feature = "pem")]
#[cfg_attr(docsrs, doc(cfg(feature = "pem")))]
pub fn from_pem(s: &str) -> Result<Self> {
let der_bytes = pem::decode(s, pem::PRIVATE_KEY_BOUNDARY)?;
Self::from_der(&*der_bytes)
}
#[cfg(feature = "pem")]
#[cfg_attr(docsrs, doc(cfg(feature = "pem")))]
pub fn to_pem(&self) -> Zeroizing<String> {
Zeroizing::new(pem::encode(&self.0, pem::PRIVATE_KEY_BOUNDARY))
}
#[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()
}
#[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> {
let bytes = Zeroizing::new(fs::read(path)?);
let pem = str::from_utf8(&*bytes).map_err(|_| Error::Decode)?;
Self::from_pem(pem)
}
#[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())
}
#[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>) -> Result<()> {
write_secret_file(path, self.to_pem().as_bytes())
}
pub fn private_key_info(&self) -> PrivateKeyInfo<'_> {
PrivateKeyInfo::from_der(self.0.as_ref()).expect("constructor failed to validate document")
}
}
impl AsRef<[u8]> for PrivateKeyDocument {
fn as_ref(&self) -> &[u8] {
self.0.as_ref()
}
}
impl TryFrom<&[u8]> for PrivateKeyDocument {
type Error = Error;
fn try_from(bytes: &[u8]) -> Result<Self> {
PrivateKeyInfo::from_der(bytes)?;
Ok(Self(Zeroizing::new(bytes.to_owned())))
}
}
impl TryFrom<Vec<u8>> for PrivateKeyDocument {
type Error = Error;
fn try_from(mut bytes: Vec<u8>) -> Result<Self> {
if PrivateKeyInfo::from_der(&bytes).is_ok() {
Ok(Self(Zeroizing::new(bytes)))
} else {
bytes.zeroize();
Err(Error::Decode)
}
}
}
impl fmt::Debug for PrivateKeyDocument {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt.debug_tuple("PrivateKeyDocument")
.field(&self.private_key_info())
.finish()
}
}
#[cfg(feature = "pem")]
#[cfg_attr(docsrs, doc(cfg(feature = "pem")))]
impl FromStr for PrivateKeyDocument {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
Self::from_pem(s)
}
}
#[derive(Clone)]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
pub struct PublicKeyDocument(Vec<u8>);
impl PublicKeyDocument {
pub fn from_der(bytes: &[u8]) -> Result<Self> {
bytes.try_into()
}
#[cfg(feature = "pem")]
#[cfg_attr(docsrs, doc(cfg(feature = "pem")))]
pub fn from_pem(s: &str) -> Result<Self> {
let der_bytes = pem::decode(s, pem::PUBLIC_KEY_BOUNDARY)?;
Self::from_der(&*der_bytes)
}
#[cfg(feature = "pem")]
#[cfg_attr(docsrs, doc(cfg(feature = "pem")))]
pub fn to_pem(&self) -> String {
pem::encode(&self.0, pem::PUBLIC_KEY_BOUNDARY)
}
#[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()
}
#[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> {
let pem = fs::read_to_string(path)?;
Self::from_pem(&pem)
}
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
pub fn write_der_file(&self, path: impl AsRef<Path>) -> Result<()> {
fs::write(path, self.as_ref())?;
Ok(())
}
#[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>) -> Result<()> {
fs::write(path, self.to_pem().as_bytes())?;
Ok(())
}
pub fn spki(&self) -> SubjectPublicKeyInfo<'_> {
SubjectPublicKeyInfo::from_der(self.0.as_ref())
.expect("constructor failed to validate document")
}
}
impl AsRef<[u8]> for PublicKeyDocument {
fn as_ref(&self) -> &[u8] {
self.0.as_ref()
}
}
impl TryFrom<&[u8]> for PublicKeyDocument {
type Error = Error;
fn try_from(bytes: &[u8]) -> Result<Self> {
SubjectPublicKeyInfo::from_der(bytes)?;
Ok(Self(bytes.to_owned()))
}
}
impl TryFrom<Vec<u8>> for PublicKeyDocument {
type Error = Error;
fn try_from(bytes: Vec<u8>) -> Result<Self> {
SubjectPublicKeyInfo::from_der(&bytes)?;
Ok(Self(bytes))
}
}
impl fmt::Debug for PublicKeyDocument {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt.debug_tuple("PublicKeyDocument")
.field(&self.spki())
.finish()
}
}
#[cfg(feature = "pem")]
#[cfg_attr(docsrs, doc(cfg(feature = "pem")))]
impl FromStr for PublicKeyDocument {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
Self::from_pem(s)
}
}
#[cfg(all(unix, feature = "std"))]
fn write_secret_file(path: impl AsRef<Path>, data: &[u8]) -> Result<()> {
use std::{io::Write, os::unix::fs::OpenOptionsExt};
#[cfg(unix)]
const SECRET_FILE_PERMS: u32 = 0o600;
fs::OpenOptions::new()
.create(true)
.write(true)
.truncate(true)
.mode(SECRET_FILE_PERMS)
.open(path)
.and_then(|mut file| file.write_all(data))?;
Ok(())
}
#[cfg(all(not(unix), feature = "std"))]
fn write_secret_file(path: impl AsRef<Path>, data: &[u8]) -> Result<()> {
fs::write(path, data)?;
Ok(())
}