You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

355 lines
12 KiB

//! Handles file formats
use super::*;
use std::{
error,
fmt,
marker::{
PhantomData,
Unpin,
},
};
use tokio::{
prelude::*,
io::{
AsyncWrite,
AsyncBufRead,
AsyncRead,
},
sync::{
mpsc,
},
};
use version::Version;
use crypto::password::{Password,Salt};
use serialise::SaltGen;
/// Trait RAE headers implement
pub trait Header: fmt::Debug
{
/// Check bit for the `SuperHeader` of this header
const CHECK: u16;
/// Name for the `SuperHeader` of this header's text serialisation format
const NAME: &'static str;
/// Compute the hash of this header (used for `SuperHeader<Self>`)
fn hash(&self) -> crypto::sha256::Sha256Hash;
/// Any additional comment to be inserted into *text* output formats of `SuperHeader<Self>` only
#[inline] fn comment(&self) -> &str
{
""
}
/// Create a `SuperHeader<Self>` for this header.
#[inline(always)] fn create_super(&self) -> SuperHeader<Self>
{
SuperHeader::new_for(self)
}
}
pub const RAE_HEADER_BIT: [u8; 4] = *b"RAE0";
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
/// The header before all RAE files.
pub struct SuperHeader<H: Header+?Sized>
{
head: [u8; 4],
vers: Version,
chk: u16,
header_hash: crypto::sha256::Sha256Hash,
_header: PhantomData<H>,
}
impl<H: Header+?Sized> Default for SuperHeader<H>
{
#[inline]
fn default() -> Self
{
Self::new()
}
}
const MAX_TEXT_SZ: Option<usize> = Some(1024 * 1024);
impl<H: Header +?Sized> SuperHeader<H>
{
/// Write this superheader as text bytes to this stream
#[instrument(err, skip(out, passwd))]
pub async fn write_text<T: AsyncWrite+Unpin+?Sized, F: FnOnce(&SaltGen) -> Option<Password>>(&self, out: &mut T, passwd: F) -> Result<usize, eyre::Report>
{
let string = format!(r#"--- {} v{}-{:x} ({}) {} ---
"#, std::str::from_utf8(&self.head[..])
.wrap_err_with(|| eyre::eyre!("Invalid head check write"))
.with_section(|| String::from_utf8_lossy(&self.head[..]).into_owned().header("Head string was"))
.with_section(|| self.head.fmt_view().to_string().header("Head bytes were"))?,
self.vers,
self.vers.to_u32(),
self.header_hash.to_hex_string(), // this is what gets encrypted by password
H::NAME);
out.write_all(string.as_bytes()).await?;
Ok(string.len())
}
/// Read a superheader as text bytes from this stream
#[instrument(err, skip(input, passwd))]
pub async fn read_text<T: AsyncBufRead+Unpin+?Sized, F: FnOnce(&Salt) -> Option<Password>>(input: &mut T, passwd: F) -> Result<Self, eyre::Report>
{
let mut line = String::new();
input.read_line(&mut line).await?;
let line = line.trim();
trace!("Read super-header line: {:?}", line);
let mut chunks = line.split_whitespace();
macro_rules! take_one {
() => {
if let Some(chunk) = chunks.next() {
chunk
} else {
return Err(eyre::eyre!("Not enough information encoded for this string to be valid")
.with_section(|| format!("{:#?}", chunks).header("Split section was"))
.with_note(|| "Expect at least 6 items delimited by whitespace"));
}
}
}
macro_rules! check_eq {
($val:expr) => {
{
let val = $val;
let chunk = take_one!();
if chunk != val {
return Err(eyre::eyre!("Invalid data at chunk {}", chunk)
.with_section(|| format!("{:?}", val).header("Expected"))
.with_section(|| format!("{:?}", chunk).header("Got")));
}
}
}
}
check_eq!("---");
check_eq!(unsafe{std::str::from_utf8_unchecked(&RAE_HEADER_BIT[..])});
let version = {
let enc_str = take_one!();
// Version should be encoded like: v0.0.0*-0 (v<version string>-<version number (hex)>)
let mut spl = enc_str.split('-').fuse();
match (spl.next(), spl.next()) {
(None, _) => Err(eyre::eyre!("Cannot extract version")),
(Some(x), None) => Err(eyre::eyre!("Cannot extract version integer")
.with_section(|| x.to_owned().header("Version part was"))),
(Some(v), Some(i)) => {
trace!("Found version string part {} and integer part {}", v, i);
match u32::from_str_radix(i, 16) {
Ok(i) => Version::try_from_u32(i)
.wrap_err_with(|| eyre::eyre!("Failed to convert integer part to version"))
.with_section(|| i.to_string().header("Integer (decoeed u32) was")),
Err(x) => Err(x).wrap_err_with(|| eyre::eyre!("Failed to decode string part into integer")),
}.with_section(|| i.to_owned().header("Integer part was"))
.with_section(|| v.to_owned().header("Version part was"))
.and_then(|vers| {
let vstr = vers.to_string();
if v.len() < 1 {
Err(eyre::eyre!("Embedded version string was invalid"))
.with_section(|| v.to_owned().header("Embedded was"))
.with_section(move || vstr.header("Decoded was"))
}
else if vstr != &v[1..] {
Err(eyre::eyre!("Embedded version string does not match decoded version string"))
.with_section(|| v.to_owned().header("Embedded was"))
.with_section(move || vstr.header("Decoded was"))
} else {
Ok(vers)
}
})
},
}.wrap_err_with(|| eyre::eyre!("Failed to decode version"))
.with_section(|| enc_str.to_owned().header("The version string was"))?
};
//Parse hash from hex encoded string `3`
let hash = {
let hash_str= take_one!();
let w = || {
if hash_str.len() < 2 {
return Err(eyre::eyre!("Not long enough"))
}
let hash_str = &hash_str[1..(hash_str.len()-1)];
use hex::FromHex;
let bytes = <[u8; crypto::consts::SHA256_SIZE]>::from_hex(hash_str)
.wrap_err_with(|| eyre::eyre!("Hex decode to {} size byte array failed", crypto::consts::SHA256_SIZE))
.with_section(|| hash_str.to_owned().header("Hash string (cut) was"))?;
Ok(unsafe{std::mem::transmute::<_, crypto::sha256::Sha256Hash>(bytes)}) //Sha256Hash is repr(transparent)
};
w()
.wrap_err_with(|| eyre::eyre!("Failed to decode hash"))
.with_section(|| hash_str.to_owned().header("The hash string was"))?
};
check_eq!(H::NAME);
check_eq!("---");
if version.should_warn(&CURRENT_VERSION) {
warn!("Header is read to have deprecated version {}, we are on version {}", version, CURRENT_VERSION)
}
Ok(
Self {
head: RAE_HEADER_BIT,
vers: version,
header_hash: hash,
chk: H::CHECK,
_header: PhantomData,
}
)
}
/// Write this superheader as bytes to this stream
#[instrument(err, skip(out, passwd))]
pub async fn write_bytes<T: AsyncWrite+Unpin+?Sized, F: FnOnce(&SaltGen) -> Option<Password>>(&self, out: &mut T, passwd: F) -> Result<usize, eyre::Report>
{
Ok({out.write_all(&self.head[..]).await?; self.head.len()} +
{out.write_all(self.vers.as_bytes()).await?; std::mem::size_of::<Version>()} +
{out.write_u16(self.chk).await?; 2} +
{out.write_all(self.header_hash.as_ref()).await?; std::mem::size_of::<crypto::sha256::Sha256Hash>()}) //this is what gets encrypted by password
}
/// Read a superheader as bytes from this stream
#[instrument(err, skip(input, passwd))]
pub async fn read_bytes<T: AsyncRead+Unpin+?Sized, F: FnOnce(&Salt) -> Option<Password>>(input: &mut T, passwd: F) -> Result<Self, eyre::Report>
{
let mut new = Self::new();
input.read_exact(&mut new.head[..]).await?;
new.vers = {
let mut bytes = [0u8; std::mem::size_of::<Version>()];
input.read_exact(&mut bytes[..]).await?;
Version::try_from_bytes(bytes)
.wrap_err_with(|| eyre::eyre!("Failed to decode version"))
.with_section(|| bytes.to_broken_hex_string().header("Bytes (hex) were"))?
};
new.chk = input.read_u16().await?;
new.header_hash = {
let mut bytes= crypto::sha256::Sha256Hash::empty();
input.read_exact(bytes.as_mut()).await?;
bytes
};
if new.vers.should_warn(&CURRENT_VERSION) {
warn!("Header ({:?}) is read to have deprecated version {}, we are on version {}", new, new.vers, CURRENT_VERSION)
}
Ok(new)
}
/// Create a new empty superheader
pub fn new() -> Self
{
Self{
head: RAE_HEADER_BIT,
vers: CURRENT_VERSION,
chk: H::CHECK,
header_hash: Default::default(),
_header: PhantomData,
}
}
/// Consume into a new instance with hash for `header`.
pub fn with_hash(self, header: &H) -> Self
{
Self {
header_hash: header.hash(),
..self
}
}
/// Create a new superheader for
pub fn new_for(header: &H) -> Self
{
Self {
header_hash: header.hash(),
..Self::new()
}
}
/// Verify this empty superheader
pub fn verify(&self) -> Result<(), VerificationError<H>>
{
macro_rules! check {
($field:expr, $err:expr) => {
if !$field {
return Err($err);
}
}
}
check!(self.head == RAE_HEADER_BIT, VerificationError::BadHeaderBit);
check!(self.vers.is_compat(&CURRENT_VERSION), VerificationError::IncompatableVersion(self.vers));
check!(self.chk == H::CHECK, VerificationError::BadCheckBit(PhantomData));
Ok(())
}
/// Verify this not-empty header for `header`.
///
/// # Notes
/// This also calls `self.verify()`.
pub fn verify_for(&self, header: &H) -> Result<(), VerificationError<H>>
{
self.verify()?;
if self.header_hash == header.hash() {
Ok(())
} else {
Err(VerificationError::BadHash)
}
}
}
/// Error returned when `SuperHeader` fails to verify.
///
/// `H` is the header type that we were attempting to verify for
#[derive(Debug)]
#[non_exhaustive]
pub enum VerificationError<H: Header+?Sized>
{
BadHeaderBit,
IncompatableVersion(Version),
BadCheckBit(PhantomData<H>),
BadHash,
}
impl<H: Header+?Sized> error::Error for VerificationError<H>{}
impl<H: Header+?Sized> fmt::Display for VerificationError<H>
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
{
write!(f, "failed to verify header for {}: ", std::any::type_name::<H>())?;
match self {
Self::BadHeaderBit => write!(f, "bad header bit"),
Self::IncompatableVersion(vers) => write!(f, "contained incompatable version {}, we are on version {}", vers, CURRENT_VERSION),
Self::BadCheckBit(_) => write!(f, "contained invalid check bit"),
Self::BadHash => write!(f, "contained invalid header hash"),
}
}
}
impl<H: Header+?Sized> serialise::BinarySerialisable for SuperHeader<H>
{
#[inline(always)] fn serialise_bytes<'a, 'b: 'a, T: AsyncWrite+Unpin+?Sized, F: for<'r> FnOnce(&'r SaltGen) -> Option<Password> + 'b>(&'a self, out: &'b mut T, passwd: F) -> LocalBoxFuture<'a, Result<usize, eyre::Report>>
{
self.write_bytes(out, passwd).boxed_local()
}
#[inline(always)] fn deserialise_bytes<'b , T: AsyncRead+Unpin+?Sized, F: for<'r> FnOnce(&'r Salt) -> Option<Password> + 'b>(input: &'b mut T, passwd: F) -> LocalBoxFuture<'b, Result<Self, eyre::Report>>
where Self: 'b
{
Self::read_bytes(input, passwd).boxed_local()
}
}
impl<H: Header+?Sized> serialise::TextSerialiseable for SuperHeader<H>
{
#[inline(always)] fn serialise_text<'a, 'b: 'a, T: AsyncWrite+Unpin+?Sized, F: for<'r> FnOnce(&'r SaltGen) -> Option<Password> + 'b>(&'a self, out: &'b mut T, passwd: F) -> LocalBoxFuture<'a, Result<usize, eyre::Report>>
{
self.write_text(out, passwd).boxed_local()
}
#[inline(always)] fn deserialise_text<'a, T: AsyncBufRead+Unpin+?Sized, F: for<'r> FnOnce(&'r Salt) -> Option<Password> + 'a>(input: &'a mut T, passwd: F) -> LocalBoxFuture<'a, Result<Self, eyre::Report>>
where Self: 'a
{
Self::read_text(input, passwd).boxed_local()
}
}
/// Nopassword text constant
const TEXT_NOPASS: &[u8; 16] = b"0000000000000000";
pub mod key;
const CHECK_KEY: u16 = 0x0001;