use crate::{Decodable, Encodable, Error, Result};
use alloc::{boxed::Box, vec::Vec};
#[cfg(feature = "pem")]
use {crate::pem, alloc::string::String};
#[cfg(feature = "std")]
use std::{fs, path::Path};
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
pub trait Document<'a>: AsRef<[u8]> + Sized + TryFrom<Vec<u8>, Error = Error> {
type Message: Decodable<'a> + Encodable + Sized;
const SENSITIVE: bool;
fn as_der(&self) -> &[u8] {
self.as_ref()
}
fn to_der(&self) -> Box<[u8]> {
self.as_ref().to_vec().into_boxed_slice()
}
fn decode(&'a self) -> Self::Message {
Self::Message::from_der(self.as_ref()).expect("ASN.1 DER document malformed")
}
fn from_der(bytes: &[u8]) -> Result<Self> {
bytes.to_vec().try_into()
}
fn from_msg(msg: &Self::Message) -> Result<Self> {
msg.to_vec()?.try_into()
}
#[cfg(feature = "pem")]
#[cfg_attr(docsrs, doc(cfg(feature = "pem")))]
fn from_pem(s: &str) -> Result<Self>
where
Self: pem::PemLabel,
{
let (label, der_bytes) = pem::decode_vec(s.as_bytes())?;
if label != Self::TYPE_LABEL {
return Err(pem::Error::Label.into());
}
der_bytes.try_into()
}
#[cfg(feature = "pem")]
#[cfg_attr(docsrs, doc(cfg(feature = "pem")))]
fn to_pem(&self, line_ending: pem::LineEnding) -> Result<String>
where
Self: pem::PemLabel,
{
Ok(pem::encode_string(
Self::TYPE_LABEL,
line_ending,
self.as_ref(),
)?)
}
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
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(all(feature = "pem", feature = "std"))))]
fn read_pem_file(path: impl AsRef<Path>) -> Result<Self>
where
Self: pem::PemLabel,
{
Self::from_pem(&fs::read_to_string(path)?)
}
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
fn write_der_file(&self, path: impl AsRef<Path>) -> Result<()> {
write_file(path, self.as_ref(), Self::SENSITIVE)
}
#[cfg(all(feature = "pem", feature = "std"))]
#[cfg_attr(docsrs, doc(cfg(all(feature = "pem", feature = "std"))))]
fn write_pem_file(&self, path: impl AsRef<Path>, line_ending: pem::LineEnding) -> Result<()>
where
Self: pem::PemLabel,
{
write_file(path, self.to_pem(line_ending)?.as_bytes(), Self::SENSITIVE)
}
}
#[cfg(feature = "std")]
fn write_file(path: impl AsRef<Path>, data: &[u8], sensitive: bool) -> Result<()> {
if sensitive {
write_secret_file(path, data)
} else {
Ok(fs::write(path, data)?)
}
}
#[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(())
}