use crate::{Decode, Encode, Error, FixedTag, Length, Reader, Result, SliceReader, Tag, Writer};
use alloc::vec::Vec;
use core::fmt::{self, Debug};
#[cfg(feature = "pem")]
use {crate::pem, alloc::string::String};
#[cfg(feature = "std")]
use std::{fs, path::Path};
#[cfg(all(feature = "pem", feature = "std"))]
use alloc::borrow::ToOwned;
#[cfg(feature = "zeroize")]
use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
#[derive(Clone, Eq, PartialEq)]
pub struct Document {
der_bytes: Vec<u8>,
length: Length,
}
impl Document {
pub fn as_bytes(&self) -> &[u8] {
self.der_bytes.as_slice()
}
#[cfg(feature = "zeroize")]
pub fn into_secret(self) -> SecretDocument {
SecretDocument(self)
}
pub fn into_vec(self) -> Vec<u8> {
self.der_bytes
}
pub fn to_vec(&self) -> Vec<u8> {
self.der_bytes.clone()
}
pub fn len(&self) -> Length {
self.length
}
pub fn decode_msg<'a, T: Decode<'a>>(&'a self) -> Result<T> {
T::from_der(self.as_bytes())
}
pub fn encode_msg<T: Encode>(msg: &T) -> Result<Self> {
msg.to_der()?.try_into()
}
#[cfg(feature = "pem")]
pub fn from_pem(pem: &str) -> Result<(&str, Self)> {
let (label, der_bytes) = pem::decode_vec(pem.as_bytes())?;
Ok((label, der_bytes.try_into()?))
}
#[cfg(feature = "pem")]
pub fn to_pem(&self, label: &'static str, line_ending: pem::LineEnding) -> Result<String> {
Ok(pem::encode_string(label, line_ending, self.as_bytes())?)
}
#[cfg(feature = "std")]
pub fn read_der_file(path: impl AsRef<Path>) -> Result<Self> {
fs::read(path)?.try_into()
}
#[cfg(feature = "std")]
pub fn write_der_file(&self, path: impl AsRef<Path>) -> Result<()> {
Ok(fs::write(path, self.as_bytes())?)
}
#[cfg(all(feature = "pem", feature = "std"))]
pub fn read_pem_file(path: impl AsRef<Path>) -> Result<(String, Self)> {
Self::from_pem(&fs::read_to_string(path)?).map(|(label, doc)| (label.to_owned(), doc))
}
#[cfg(all(feature = "pem", feature = "std"))]
pub fn write_pem_file(
&self,
path: impl AsRef<Path>,
label: &'static str,
line_ending: pem::LineEnding,
) -> Result<()> {
let pem = self.to_pem(label, line_ending)?;
Ok(fs::write(path, pem.as_bytes())?)
}
}
impl AsRef<[u8]> for Document {
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}
impl Debug for Document {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("Document(")?;
for byte in self.as_bytes() {
write!(f, "{:02X}", byte)?;
}
f.write_str(")")
}
}
impl<'a> Decode<'a> for Document {
fn decode<R: Reader<'a>>(reader: &mut R) -> Result<Document> {
let header = reader.peek_header()?;
let length = (header.encoded_len()? + header.length)?;
let bytes = reader.read_slice(length)?;
Ok(Self {
der_bytes: bytes.into(),
length,
})
}
}
impl Encode for Document {
fn encoded_len(&self) -> Result<Length> {
Ok(self.len())
}
fn encode(&self, writer: &mut impl Writer) -> Result<()> {
writer.write(self.as_bytes())
}
}
impl FixedTag for Document {
const TAG: Tag = Tag::Sequence;
}
impl TryFrom<&[u8]> for Document {
type Error = Error;
fn try_from(der_bytes: &[u8]) -> Result<Self> {
Self::from_der(der_bytes)
}
}
impl TryFrom<Vec<u8>> for Document {
type Error = Error;
fn try_from(der_bytes: Vec<u8>) -> Result<Self> {
let mut decoder = SliceReader::new(&der_bytes)?;
decode_sequence(&mut decoder)?;
decoder.finish(())?;
let length = der_bytes.len().try_into()?;
Ok(Self { der_bytes, length })
}
}
#[cfg(feature = "zeroize")]
#[derive(Clone)]
pub struct SecretDocument(Document);
#[cfg(feature = "zeroize")]
impl SecretDocument {
pub fn as_bytes(&self) -> &[u8] {
self.0.as_bytes()
}
pub fn to_bytes(&self) -> Zeroizing<Vec<u8>> {
Zeroizing::new(self.0.to_vec())
}
pub fn len(&self) -> Length {
self.0.len()
}
pub fn decode_msg<'a, T: Decode<'a>>(&'a self) -> Result<T> {
self.0.decode_msg()
}
pub fn encode_msg<T: Encode>(msg: &T) -> Result<Self> {
Document::encode_msg(msg).map(Self)
}
#[cfg(feature = "pem")]
pub fn from_pem(pem: &str) -> Result<(&str, Self)> {
Document::from_pem(pem).map(|(label, doc)| (label, Self(doc)))
}
#[cfg(feature = "pem")]
pub fn to_pem(
&self,
label: &'static str,
line_ending: pem::LineEnding,
) -> Result<Zeroizing<String>> {
self.0.to_pem(label, line_ending).map(Zeroizing::new)
}
#[cfg(feature = "std")]
pub fn read_der_file(path: impl AsRef<Path>) -> Result<Self> {
Document::read_der_file(path).map(Self)
}
#[cfg(feature = "std")]
pub fn write_der_file(&self, path: impl AsRef<Path>) -> Result<()> {
write_secret_file(path, self.as_bytes())
}
#[cfg(all(feature = "pem", feature = "std"))]
pub fn read_pem_file(path: impl AsRef<Path>) -> Result<(String, Self)> {
Document::read_pem_file(path).map(|(label, doc)| (label, Self(doc)))
}
#[cfg(all(feature = "pem", feature = "std"))]
pub fn write_pem_file(
&self,
path: impl AsRef<Path>,
label: &'static str,
line_ending: pem::LineEnding,
) -> Result<()> {
write_secret_file(path, self.to_pem(label, line_ending)?.as_bytes())
}
}
#[cfg(feature = "zeroize")]
impl Debug for SecretDocument {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt.debug_struct("SecretDocument").finish_non_exhaustive()
}
}
#[cfg(feature = "zeroize")]
impl Drop for SecretDocument {
fn drop(&mut self) {
self.0.der_bytes.zeroize();
}
}
#[cfg(feature = "zeroize")]
impl From<Document> for SecretDocument {
fn from(doc: Document) -> SecretDocument {
SecretDocument(doc)
}
}
#[cfg(feature = "zeroize")]
impl TryFrom<&[u8]> for SecretDocument {
type Error = Error;
fn try_from(der_bytes: &[u8]) -> Result<Self> {
Document::try_from(der_bytes).map(Self)
}
}
#[cfg(feature = "zeroize")]
impl TryFrom<Vec<u8>> for SecretDocument {
type Error = Error;
fn try_from(der_bytes: Vec<u8>) -> Result<Self> {
Document::try_from(der_bytes).map(Self)
}
}
#[cfg(feature = "zeroize")]
impl ZeroizeOnDrop for SecretDocument {}
fn decode_sequence<'a>(decoder: &mut SliceReader<'a>) -> Result<&'a [u8]> {
let header = decoder.peek_header()?;
header.tag.assert_eq(Tag::Sequence)?;
let len = (header.encoded_len()? + header.length)?;
decoder.read_slice(len)
}
#[cfg(all(unix, feature = "std", feature = "zeroize"))]
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", feature = "zeroize"))]
fn write_secret_file(path: impl AsRef<Path>, data: &[u8]) -> Result<()> {
fs::write(path, data)?;
Ok(())
}