use std::collections::HashMap;
use std::convert::TryFrom;
use std::io;
use std::io::{Read, Seek, SeekFrom, Write};
#[macro_use]
extern crate bitflags;
use crypto::hybrid::MLAEncryptionPublicKey;
use layers::compress::COMPRESSION_LAYER_MAGIC;
use layers::encrypt::ENCRYPTION_LAYER_MAGIC;
use layers::strip_head_tail::StripHeadTailReader;
use layers::traits::InnerReaderTrait;
pub mod entry;
use entry::{
ArchiveEntry, ArchiveEntryDataReader, ArchiveEntryId, EntryName, deserialize_entry_name,
serialize_entry_name,
};
mod base64;
pub(crate) mod layers;
use crate::crypto::mlakey::{MLASignatureVerificationPublicKey, MLASigningPrivateKey};
use crate::layers::compress::{
CompressionLayerFailSafeReader, CompressionLayerReader, CompressionLayerWriter,
};
use crate::layers::encrypt::{
EncryptionLayerFailSafeReader, EncryptionLayerReader, EncryptionLayerWriter,
};
use crate::layers::position::PositionLayerWriter;
use crate::layers::raw::{RawLayerFailSafeReader, RawLayerReader, RawLayerWriter};
use crate::layers::signature::{
SIGNATURE_LAYER_MAGIC, SignatureLayerFailSafeReader, SignatureLayerReader, SignatureLayerWriter,
};
use crate::layers::traits::{
InnerWriterTrait, InnerWriterType, LayerFailSafeReader, LayerReader, LayerWriter,
};
pub mod errors;
use crate::errors::{Error, TruncatedReadError};
pub mod config;
use crate::config::{ArchiveReaderConfig, ArchiveWriterConfig, TruncatedReaderConfig};
pub mod crypto;
use crate::crypto::hash::{HashWrapperReader, HashWrapperWriter, Sha256Hash};
use sha2::{Digest, Sha256, Sha512};
mod format;
pub mod helpers;
use format::ArchiveHeader;
#[cfg(test)]
#[macro_use]
extern crate hex_literal;
const MLA_MAGIC: &[u8; 8] = b"MLAFAAAA";
const MLA_FORMAT_VERSION: u32 = 2;
const END_MLA_MAGIC: &[u8; 8] = b"EMLAAAAA";
const FILENAME_MAX_SIZE: u64 = 65536;
const ENTRIES_LAYER_MAGIC: &[u8; 8] = b"MLAENAAA";
const EMPTY_OPTS_SERIALIZATION: &[u8; 1] = &[0];
const EMPTY_TAIL_OPTS_SERIALIZATION: &[u8; 9] = &[0, 1, 0, 0, 0, 0, 0, 0, 0];
#[derive(Debug)]
struct Opts;
impl Opts {
fn from_reader(mut src: impl Read) -> Result<Self, Error> {
let discriminant = u8::deserialize(&mut src)?;
match discriminant {
0 => (),
1 => {
let mut n = [0; 8];
src.read_exact(&mut n)?;
let n = u64::from_le_bytes(n);
let mut v = Vec::new();
src.take(n).read_to_end(&mut v)?;
}
_ => return Err(Error::DeserializationError),
}
Ok(Opts)
}
fn dump(&mut self, mut src: impl Write) -> Result<u64, Error> {
src.write_all(EMPTY_OPTS_SERIALIZATION)?;
Ok(1)
}
}
struct ArchiveFooter {
entries_info: HashMap<EntryName, EntryInfo>,
}
impl ArchiveFooter {
fn serialize_into<W: Write>(
mut dest: W,
files_info: &HashMap<EntryName, ArchiveEntryId>,
ids_info: &HashMap<ArchiveEntryId, EntryInfo>,
) -> Result<(), Error> {
let mut tmp = Vec::new();
for (k, i) in files_info {
let v = ids_info.get(i).ok_or_else(|| {
Error::WrongWriterState(
"[ArchiveFooter seriliaze] Unable to find the ID".to_string(),
)
})?;
tmp.push((k, v));
}
tmp.sort_by_key(|(k, _)| *k);
tmp.len().serialize(&mut dest)?;
let mut footer_serialization_length = 8;
for (k, i) in tmp {
footer_serialization_length += serialize_entry_name(k, &mut dest)?;
footer_serialization_length += i.serialize(&mut dest)?;
}
footer_serialization_length.serialize(&mut dest)?;
Ok(())
}
pub fn deserialize_from<R: Read + Seek>(mut src: R) -> Result<ArchiveFooter, Error> {
let n = u64::deserialize(&mut src)?;
let files_info = (0..n)
.map(|_| {
let name = deserialize_entry_name(&mut src)?;
let info = EntryInfo::deserialize(&mut src)?;
Ok::<_, Error>((name, info))
})
.collect::<Result<HashMap<_, _>, Error>>()?;
Ok(ArchiveFooter {
entries_info: files_info,
})
}
}
#[derive(Debug)]
enum ArchiveEntryBlockType {
EntryStart,
EntryContent,
EndOfArchiveData,
EndOfEntry,
}
impl<W: Write> MLASerialize<W> for ArchiveEntryBlockType {
fn serialize(&self, dest: &mut W) -> Result<u64, Error> {
let byte: u8 = match self {
ArchiveEntryBlockType::EntryStart => 0,
ArchiveEntryBlockType::EntryContent => 1,
ArchiveEntryBlockType::EndOfArchiveData => 0xFE,
ArchiveEntryBlockType::EndOfEntry => 0xFF,
};
byte.serialize(dest)
}
}
impl<R: Read> MLADeserialize<R> for ArchiveEntryBlockType {
fn deserialize(src: &mut R) -> Result<Self, Error> {
let serialized_block_type = u8::deserialize(src)?;
match serialized_block_type {
0 => Ok(ArchiveEntryBlockType::EntryStart),
1 => Ok(ArchiveEntryBlockType::EntryContent),
0xFE => Ok(ArchiveEntryBlockType::EndOfArchiveData),
0xFF => Ok(ArchiveEntryBlockType::EndOfEntry),
_ => Err(Error::WrongBlockSubFileType),
}
}
}
use format::ArchiveEntryBlock;
#[derive(Debug, Clone)]
enum ArchiveWriterState {
OpenedFiles {
ids: Vec<ArchiveEntryId>,
hashes: HashMap<ArchiveEntryId, Sha256>,
},
Finalized,
}
impl ArchiveWriterState {
fn wrap_with_hash<R: Read>(
&mut self,
id: ArchiveEntryId,
src: R,
) -> Result<HashWrapperReader<R>, Error> {
let hash = match self {
ArchiveWriterState::OpenedFiles { hashes, .. } => match hashes.get_mut(&id) {
Some(hash) => hash,
None => {
return Err(Error::WrongWriterState(
"[wrap_with_hash] Unable to find the ID".to_string(),
));
}
},
_ => {
return Err(Error::WrongWriterState(
"[wrap_with_hash] Wrong state".to_string(),
));
}
};
Ok(HashWrapperReader::new(src, hash))
}
}
macro_rules! check_state {
( $x:expr, $y:ident ) => {{
match $x {
ArchiveWriterState::$y { .. } => (),
_ => {
return Err(Error::WrongArchiveWriterState {
current_state: format!("{:?}", $x).to_string(),
expected_state: format! {"{}", "ArchiveWriterState::$y"}.to_string(),
});
}
}
}};
}
macro_rules! check_state_file_opened {
( $x:expr, $y:expr ) => {{
match $x {
ArchiveWriterState::OpenedFiles { ids, hashes } => {
if !ids.contains($y) || !hashes.contains_key($y) {
return Err(Error::WrongArchiveWriterState {
current_state: format!("{:?}", $x).to_string(),
expected_state: "ArchiveWriterState with id $y".to_string(),
});
}
}
_ => {
return Err(Error::WrongArchiveWriterState {
current_state: format!("{:?}", $x).to_string(),
expected_state: "ArchiveWriterState with id $y".to_string(),
});
}
}
}};
}
pub struct ArchiveWriter<'a, W: 'a + InnerWriterTrait> {
dest: Box<PositionLayerWriter<'a, W>>,
state: ArchiveWriterState,
files_info: HashMap<EntryName, ArchiveEntryId>,
ids_info: HashMap<ArchiveEntryId, EntryInfo>,
next_id: ArchiveEntryId,
current_id: ArchiveEntryId,
}
fn vec_remove_item<T: std::cmp::PartialEq>(vec: &mut Vec<T>, item: &T) -> Option<T> {
let pos = vec.iter().position(|x| *x == *item)?;
Some(vec.remove(pos))
}
impl<W: InnerWriterTrait> ArchiveWriter<'_, W> {
pub fn from_config(dest: W, config: ArchiveWriterConfig) -> Result<Self, Error> {
let dest: InnerWriterType<W> = Box::new(RawLayerWriter::new(dest));
let archive_header = ArchiveHeader {
format_version_number: MLA_FORMAT_VERSION,
};
let mut archive_header_hash = Sha512::new();
let mut dest = HashWrapperWriter::new(dest, &mut archive_header_hash);
archive_header.serialize(&mut dest)?;
let mut dest = dest.into_inner();
dest = match config.signature_config {
Some(signature_config) => Box::new(SignatureLayerWriter::new(
dest,
signature_config,
archive_header_hash,
)?),
None => dest,
};
dest = match config.encryption_config {
Some(encryption_config) => {
Box::new(EncryptionLayerWriter::new(dest, &encryption_config)?)
}
None => dest,
};
dest = match config.compression_config {
Some(cfg) => Box::new(CompressionLayerWriter::new(dest, &cfg)?),
None => dest,
};
let mut final_dest = Box::new(PositionLayerWriter::new(dest));
final_dest.reset_position();
final_dest.write_all(ENTRIES_LAYER_MAGIC)?;
let _ = Opts.dump(&mut final_dest)?;
Ok(ArchiveWriter {
dest: final_dest,
state: ArchiveWriterState::OpenedFiles {
ids: Vec::new(),
hashes: HashMap::new(),
},
files_info: HashMap::new(),
ids_info: HashMap::new(),
next_id: ArchiveEntryId(0),
current_id: ArchiveEntryId(0),
})
}
pub fn new(
dest: W,
encryption_public_keys: &[MLAEncryptionPublicKey],
signing_private_keys: &[MLASigningPrivateKey],
) -> Result<Self, Error> {
let config = ArchiveWriterConfig::with_encryption_with_signature(
encryption_public_keys,
signing_private_keys,
)?;
Self::from_config(dest, config)
}
pub fn finalize(mut self) -> Result<W, Error> {
check_state!(self.state, OpenedFiles);
match &mut self.state {
ArchiveWriterState::OpenedFiles { ids, hashes } => {
if !ids.is_empty() || !hashes.is_empty() {
return Err(Error::WrongWriterState(
"[Finalize] At least one file is still open".to_string(),
));
}
}
_ => {
return Err(Error::WrongWriterState(
"[Finalize] State have changes inside finalize".to_string(),
));
}
}
self.state = ArchiveWriterState::Finalized;
ArchiveEntryBlock::EndOfArchiveData::<std::io::Empty> {}.dump(&mut self.dest)?;
ArchiveFooter::serialize_into(&mut self.dest, &self.files_info, &self.ids_info)?;
self.dest.write_all(EMPTY_TAIL_OPTS_SERIALIZATION)?;
let mut final_dest = self.dest.finalize()?;
final_dest.write_all(EMPTY_TAIL_OPTS_SERIALIZATION)?; final_dest.write_all(END_MLA_MAGIC)?;
Ok(final_dest)
}
fn record_offset_and_size_in_index(
&mut self,
id: ArchiveEntryId,
size: u64,
) -> Result<(), Error> {
let offset = self.dest.position();
match self.ids_info.get_mut(&id) {
Some(file_info) => file_info.offsets_and_sizes.push((offset, size)),
None => {
return Err(Error::WrongWriterState(
"[mark_continuous_block] Unable to find the ID".to_string(),
));
}
};
self.current_id = id;
Ok(())
}
pub fn start_entry(&mut self, name: EntryName) -> Result<ArchiveEntryId, Error> {
check_state!(self.state, OpenedFiles);
if self.files_info.contains_key(&name) {
return Err(Error::DuplicateFilename);
}
let id = self.next_id;
self.next_id = ArchiveEntryId(self.next_id.0 + 1);
self.current_id = id;
self.files_info.insert(name.clone(), id);
self.ids_info.insert(
id,
EntryInfo {
offsets_and_sizes: vec![(self.dest.position(), 0)],
},
);
ArchiveEntryBlock::EntryStart::<std::io::Empty> {
name,
id,
opts: Opts,
}
.dump(&mut self.dest)?;
match &mut self.state {
ArchiveWriterState::OpenedFiles { ids, hashes } => {
ids.push(id);
hashes.insert(id, Sha256::default());
}
_ => {
return Err(Error::WrongWriterState(
"[StartFile] State have changes inside start_file".to_string(),
));
}
}
Ok(id)
}
pub fn append_entry_content<U: Read>(
&mut self,
id: ArchiveEntryId,
size: u64,
src: U,
) -> Result<(), Error> {
check_state_file_opened!(&self.state, &id);
if size == 0 {
return Ok(());
}
self.record_offset_and_size_in_index(id, size)?;
let src = self.state.wrap_with_hash(id, src)?;
ArchiveEntryBlock::EntryContent {
id,
length: size,
data: Some(src),
opts: Opts,
}
.dump(&mut self.dest)
}
pub fn end_entry(&mut self, id: ArchiveEntryId) -> Result<(), Error> {
check_state_file_opened!(&self.state, &id);
let hash = match &mut self.state {
ArchiveWriterState::OpenedFiles { ids, hashes } => {
let hash = hashes.remove(&id).ok_or_else(|| {
Error::WrongWriterState("[EndFile] Unable to retrieve the hash".to_string())
})?;
vec_remove_item(ids, &id);
hash.finalize().into()
}
_ => {
return Err(Error::WrongWriterState(
"[EndFile] State have changes inside end_file".to_string(),
));
}
};
self.record_offset_and_size_in_index(id, 0)?;
ArchiveEntryBlock::EndOfEntry::<std::io::Empty> {
id,
hash,
opts: Opts,
}
.dump(&mut self.dest)?;
Ok(())
}
pub fn add_entry<U: Read>(&mut self, name: EntryName, size: u64, src: U) -> Result<(), Error> {
let id = self.start_entry(name)?;
self.append_entry_content(id, size, src)?;
self.end_entry(id)
}
pub fn flush(&mut self) -> io::Result<()> {
self.dest.flush()
}
}
trait MLASerialize<W: Write> {
fn serialize(&self, dest: &mut W) -> Result<u64, Error>;
}
trait MLADeserialize<R: Read> {
fn deserialize(src: &mut R) -> Result<Self, Error>
where
Self: std::marker::Sized;
}
impl<W: Write> MLASerialize<W> for u8 {
fn serialize(&self, dest: &mut W) -> Result<u64, Error> {
dest.write_all(&[*self])?;
Ok(1)
}
}
impl<W: Write> MLASerialize<W> for u64 {
fn serialize(&self, dest: &mut W) -> Result<u64, Error> {
dest.write_all(&self.to_le_bytes())?;
Ok(8)
}
}
impl<R: Read> MLADeserialize<R> for u64 {
fn deserialize(src: &mut R) -> Result<Self, Error> {
let mut n = [0; 8];
src.read_exact(&mut n)
.map_err(|_| Error::DeserializationError)?;
Ok(u64::from_le_bytes(n))
}
}
impl<W: Write> MLASerialize<W> for usize {
fn serialize(&self, dest: &mut W) -> Result<u64, Error> {
let u64self = u64::try_from(*self).map_err(|_| Error::SerializationError)?;
dest.write_all(&u64self.to_le_bytes())?;
Ok(8)
}
}
impl<W: Write> MLASerialize<W> for u32 {
fn serialize(&self, dest: &mut W) -> Result<u64, Error> {
dest.write_all(&self.to_le_bytes())?;
Ok(4)
}
}
impl<R: Read> MLADeserialize<R> for u32 {
fn deserialize(src: &mut R) -> Result<Self, Error> {
let mut n = [0; 4];
src.read_exact(&mut n)
.map_err(|_| Error::DeserializationError)?;
Ok(u32::from_le_bytes(n))
}
}
impl<W: Write> MLASerialize<W> for u16 {
fn serialize(&self, dest: &mut W) -> Result<u64, Error> {
dest.write_all(&self.to_le_bytes())?;
Ok(2)
}
}
impl<R: Read> MLADeserialize<R> for u16 {
fn deserialize(src: &mut R) -> Result<Self, Error> {
let mut n = [0; 2];
src.read_exact(&mut n)
.map_err(|_| Error::DeserializationError)?;
Ok(u16::from_le_bytes(n))
}
}
impl<R: Read> MLADeserialize<R> for u8 {
fn deserialize(src: &mut R) -> Result<Self, Error> {
let mut n = [0; 1];
src.read_exact(&mut n)
.map_err(|_| Error::DeserializationError)?;
Ok(u8::from_le_bytes(n))
}
}
impl<W: Write, A: MLASerialize<W>, B: MLASerialize<W>> MLASerialize<W> for (A, B) {
fn serialize(&self, dest: &mut W) -> Result<u64, Error> {
let mut serialization_length = self.0.serialize(dest)?;
serialization_length += self.1.serialize(dest)?;
Ok(serialization_length)
}
}
impl<W: Write, T: MLASerialize<W>> MLASerialize<W> for Vec<T> {
fn serialize(&self, dest: &mut W) -> Result<u64, Error> {
let u64len = u64::try_from(self.len()).map_err(|_| Error::SerializationError)?;
let mut serialization_length = u64len.serialize(dest)?;
serialization_length += self.as_slice().serialize(dest)?;
Ok(serialization_length)
}
}
impl<W: Write, T: MLASerialize<W>> MLASerialize<W> for &[T] {
fn serialize(&self, dest: &mut W) -> Result<u64, Error> {
let mut serialization_length = 0;
for e in *self {
serialization_length += e.serialize(dest)?;
}
Ok(serialization_length)
}
}
impl<R: Read, T: MLADeserialize<R>> MLADeserialize<R> for Vec<T> {
fn deserialize(src: &mut R) -> Result<Self, Error> {
let n = u64::deserialize(src)?;
let v: Result<Vec<T>, Error> = (0..n).map(|_| T::deserialize(src)).collect();
v
}
}
impl<R: Read, const N: usize> MLADeserialize<R> for [u8; N] {
fn deserialize(src: &mut R) -> Result<Self, Error> {
let mut a = [0; N];
for e in a.iter_mut() {
*e = u8::deserialize(src)?;
}
Ok(a)
}
}
impl<R: Read, T1: MLADeserialize<R>, T2: MLADeserialize<R>> MLADeserialize<R> for (T1, T2) {
fn deserialize(src: &mut R) -> Result<Self, Error> {
Ok((T1::deserialize(src)?, T2::deserialize(src)?))
}
}
#[cfg_attr(test, derive(PartialEq, Eq, Debug, Clone))]
pub(crate) struct EntryInfo {
offsets_and_sizes: Vec<(u64, u64)>,
}
impl<W: Write> MLASerialize<W> for EntryInfo {
fn serialize(&self, mut dest: &mut W) -> Result<u64, Error> {
self.offsets_and_sizes.serialize(&mut dest)
}
}
impl<R: Read> MLADeserialize<R> for EntryInfo {
fn deserialize(src: &mut R) -> Result<Self, Error> {
let offsets_and_sizes = MLADeserialize::deserialize(src)?;
Ok(Self { offsets_and_sizes })
}
}
fn read_layer_magic<R: Read>(src: &mut R) -> Result<[u8; 8], Error> {
let mut buf = [0; 8];
src.read_exact(&mut buf)?;
Ok(buf)
}
pub struct ArchiveReader<'a, R: 'a + InnerReaderTrait> {
src: Box<dyn 'a + LayerReader<'a, R>>,
metadata: Option<ArchiveFooter>,
}
fn read_mla_entries_header(mut src: impl Read) -> Result<(), Error> {
let mut magic = [0u8; 8];
src.read_exact(&mut magic)?;
if magic != *ENTRIES_LAYER_MAGIC {
return Err(Error::WrongMagic);
}
read_mla_entries_header_skip_magic(src)
}
fn read_mla_entries_header_skip_magic(mut src: impl Read) -> Result<(), Error> {
let _ = Opts::from_reader(&mut src)?; Ok(())
}
impl<'b, R: 'b + InnerReaderTrait> ArchiveReader<'b, R> {
pub fn from_config(
mut src: R,
config: ArchiveReaderConfig,
) -> Result<(Self, Vec<MLASignatureVerificationPublicKey>), Error> {
src.rewind()?;
let mut archive_header_hash = Sha512::new();
let mut src = HashWrapperReader::new(src, &mut archive_header_hash);
ArchiveHeader::deserialize(&mut src)?;
let mut raw_src = Box::new(RawLayerReader::new(src.into_inner()));
raw_src.reset_position()?;
let mut src: Box<dyn 'b + LayerReader<'b, R>> = raw_src;
let end_magic_position = src.seek(SeekFrom::End(-8))?;
let end_magic = read_layer_magic(&mut src)?;
if &end_magic != END_MLA_MAGIC {
return Err(Error::WrongEndMagic);
}
src.seek(SeekFrom::End(-16))?;
let mla_footer_options_length = u64::deserialize(&mut src)?;
let mla_tail_len = mla_footer_options_length + 16;
let inner_len = end_magic_position + 8;
src.seek(SeekFrom::Start(0))?;
src = Box::new(StripHeadTailReader::new(
src,
0,
mla_tail_len,
inner_len,
0,
)?);
let mut src = HashWrapperReader::new(src, &mut archive_header_hash);
let mut magic = read_layer_magic(&mut src)?;
let mut src = src.into_inner();
let accept_unencrypted = config.accept_unencrypted;
let mut signed_persistent_encryption_config = None;
let mut keys_with_valid_signatures = Vec::new();
if magic == SIGNATURE_LAYER_MAGIC {
let (sig_layer, new_keys_with_valid_signatures, read_persistent_encryption_config) =
SignatureLayerReader::new_skip_magic(
src,
config.signature_reader_config,
archive_header_hash,
)?;
signed_persistent_encryption_config = read_persistent_encryption_config;
keys_with_valid_signatures = new_keys_with_valid_signatures;
src = Box::new(sig_layer);
src.initialize()?;
magic = read_layer_magic(&mut src)?;
} else if config.signature_reader_config.signature_check {
return Err(Error::SignatureVerificationAskedButNoSignatureLayerFound);
}
if &magic == ENCRYPTION_LAYER_MAGIC {
src = Box::new(EncryptionLayerReader::new_skip_magic(
src,
config.encrypt,
signed_persistent_encryption_config,
)?);
src.initialize()?;
magic = read_layer_magic(&mut src)?;
} else if !accept_unencrypted {
return Err(Error::EncryptionAskedButNotMarkedPresent);
}
if &magic == COMPRESSION_LAYER_MAGIC {
src = Box::new(CompressionLayerReader::new_skip_magic(src)?);
src.initialize()?;
}
src.seek(SeekFrom::End(-8))?;
let entries_footer_options_length = u64::deserialize(&mut src)?;
let entries_footer_length_offset_from_end =
(-16i64) .checked_sub_unsigned(entries_footer_options_length)
.ok_or(Error::DeserializationError)?;
src.seek(SeekFrom::End(entries_footer_length_offset_from_end))?;
let entries_footer_length = u64::deserialize(&mut src)?;
let start_of_entries_footer_from_current = (-8i64)
.checked_sub_unsigned(entries_footer_length)
.ok_or(Error::DeserializationError)?;
src.seek(SeekFrom::Current(start_of_entries_footer_from_current))?;
let metadata = Some(ArchiveFooter::deserialize_from(&mut src)?);
src.rewind()?;
read_mla_entries_header(&mut src)?;
Ok((ArchiveReader { src, metadata }, keys_with_valid_signatures))
}
pub fn list_entries(&self) -> Result<impl Iterator<Item = &EntryName>, Error> {
if let Some(ArchiveFooter {
entries_info: files_info,
..
}) = &self.metadata
{
Ok(files_info.keys())
} else {
Err(Error::MissingMetadata)
}
}
pub fn get_hash(&mut self, name: &EntryName) -> Result<Option<Sha256Hash>, Error> {
if let Some(ArchiveFooter {
entries_info: files_info,
}) = &self.metadata
{
let file_info = match files_info.get(name) {
None => return Ok(None),
Some(finfo) => finfo,
};
let eoe_offset = file_info
.offsets_and_sizes
.last()
.ok_or(Error::DeserializationError)?
.0;
self.src.seek(SeekFrom::Start(eoe_offset))?;
match ArchiveEntryBlock::from(&mut self.src)? {
ArchiveEntryBlock::EndOfEntry { hash, .. } => Ok(Some(hash)),
_ => Err(Error::WrongReaderState(
"[ArchiveReader] last offset must point to a EndOfEntry".to_string(),
)),
}
} else {
Err(Error::MissingMetadata)
}
}
pub fn get_entry(
&mut self,
name: EntryName,
) -> Result<Option<ArchiveEntry<impl InnerReaderTrait>>, Error> {
if let Some(ArchiveFooter {
entries_info: files_info,
}) = &self.metadata
{
let file_info = match files_info.get(&name) {
None => return Ok(None),
Some(finfo) => finfo,
};
if file_info.offsets_and_sizes.is_empty() {
return Err(Error::WrongReaderState(
"[ArchiveReader] A file must have at least one offset".to_string(),
));
}
let reader = ArchiveEntryDataReader::new(&mut self.src, &file_info.offsets_and_sizes)?;
Ok(Some(ArchiveEntry { name, data: reader }))
} else {
Err(Error::MissingMetadata)
}
}
}
pub struct TruncatedArchiveReader<'a, R: 'a + Read> {
src: Box<dyn 'a + LayerFailSafeReader<'a, R>>,
}
const CACHE_SIZE: usize = 8 * 1024 * 1024;
macro_rules! update_error {
( $x:ident = $y:expr ) => {
#[allow(clippy::single_match)]
match $x {
TruncatedReadError::NoError => {
$x = $y;
}
_ => {}
}
};
}
impl<'b, R: 'b + Read> TruncatedArchiveReader<'b, R> {
pub fn from_config(mut src: R, config: TruncatedReaderConfig) -> Result<Self, Error> {
ArchiveHeader::deserialize(&mut src)?;
let mut src: Box<dyn 'b + LayerFailSafeReader<'b, R>> =
Box::new(RawLayerFailSafeReader::new(src));
let accept_unencrypted = config.accept_unencrypted;
let truncated_decryption_mode = config.truncated_decryption_mode;
let mut magic = read_layer_magic(&mut src)?;
if magic == SIGNATURE_LAYER_MAGIC {
src = Box::new(SignatureLayerFailSafeReader::new_skip_magic(src)?);
magic = read_layer_magic(&mut src)?;
}
if &magic == ENCRYPTION_LAYER_MAGIC {
src = Box::new(EncryptionLayerFailSafeReader::new_skip_magic(
src,
config.encrypt,
None,
truncated_decryption_mode,
)?);
magic = read_layer_magic(&mut src)?;
} else if !accept_unencrypted {
return Err(Error::EncryptionAskedButNotMarkedPresent);
}
if &magic == COMPRESSION_LAYER_MAGIC {
src = Box::new(CompressionLayerFailSafeReader::new_skip_magic(src)?);
magic = read_layer_magic(&mut src)?;
}
if &magic != ENTRIES_LAYER_MAGIC {
return Err(Error::DeserializationError);
}
read_mla_entries_header_skip_magic(&mut src)?;
Ok(Self { src })
}
#[allow(clippy::cognitive_complexity)]
pub fn convert_to_archive<W: InnerWriterTrait>(
&mut self,
mut output: ArchiveWriter<W>,
) -> Result<TruncatedReadError, Error> {
let mut error = TruncatedReadError::NoError;
let mut id_failsafe2id_output: HashMap<ArchiveEntryId, ArchiveEntryId> = HashMap::new();
let mut id_failsafe2filename: HashMap<ArchiveEntryId, EntryName> = HashMap::new();
let mut id_failsafe_done = Vec::new();
let mut id_failsafe2hash: HashMap<ArchiveEntryId, Sha256> = HashMap::new();
'read_block: loop {
match ArchiveEntryBlock::from(&mut self.src) {
Err(Error::IOError(err)) => {
if let std::io::ErrorKind::UnexpectedEof = err.kind() {
update_error!(error = TruncatedReadError::UnexpectedEOFOnNextBlock);
break;
}
update_error!(error = TruncatedReadError::IOErrorOnNextBlock(err));
break;
}
Err(err) => {
update_error!(error = TruncatedReadError::ErrorOnNextBlock(err));
break;
}
Ok(block) => {
match block {
ArchiveEntryBlock::EntryStart {
name: filename,
id,
opts: _,
} => {
if let Some(_id_output) = id_failsafe2id_output.get(&id) {
update_error!(error = TruncatedReadError::ArchiveFileIDReuse(id));
break 'read_block;
}
if id_failsafe_done.contains(&id) {
update_error!(
error = TruncatedReadError::ArchiveFileIDAlreadyClose(id)
);
break 'read_block;
}
id_failsafe2filename.insert(id, filename.clone());
let id_output = match output.start_entry(filename.clone()) {
Err(Error::DuplicateFilename) => {
update_error!(
error = TruncatedReadError::FilenameReuse(
filename.raw_content_to_escaped_string()
)
);
break 'read_block;
}
Err(err) => {
return Err(err);
}
Ok(id) => id,
};
id_failsafe2id_output.insert(id, id_output);
id_failsafe2hash.insert(id, Sha256::default());
}
ArchiveEntryBlock::EntryContent { length, id, .. } => {
let id_output = match id_failsafe2id_output.get(&id) {
Some(id_output) => *id_output,
None => {
update_error!(
error = TruncatedReadError::ContentForUnknownFile(id)
);
break 'read_block;
}
};
if id_failsafe_done.contains(&id) {
update_error!(
error = TruncatedReadError::ArchiveFileIDAlreadyClose(id)
);
break 'read_block;
}
let fname = id_failsafe2filename.get(&id).expect(
"`id_failsafe2filename` not more sync with `id_failsafe2id_output`",
);
let hash = id_failsafe2hash.get_mut(&id).expect(
"`id_failsafe2hash` not more sync with `id_failsafe2id_output`",
);
let src = &mut (&mut self.src).take(length);
let mut buf = vec![0; CACHE_SIZE];
'content: loop {
let mut next_write_pos = 0;
'buf_fill: loop {
match src.read(&mut buf[next_write_pos..]) {
Ok(read) => {
if read == 0 {
break 'buf_fill;
}
next_write_pos += read;
}
Err(err) => {
output.append_entry_content(
id_output,
next_write_pos as u64,
&buf[..next_write_pos],
)?;
update_error!(
error = TruncatedReadError::ErrorInFile(
err,
fname.raw_content_to_escaped_string()
)
);
break 'read_block;
}
}
if next_write_pos >= CACHE_SIZE {
break 'buf_fill;
}
}
output.append_entry_content(
id_output,
next_write_pos as u64,
&buf[..next_write_pos],
)?;
hash.update(&buf[..next_write_pos]);
if next_write_pos < CACHE_SIZE {
break 'content;
}
}
}
ArchiveEntryBlock::EndOfEntry {
id,
hash,
opts: Opts,
} => {
let id_output = match id_failsafe2id_output.get(&id) {
Some(id_output) => *id_output,
None => {
update_error!(
error = TruncatedReadError::EOFForUnknownFile(id)
);
break 'read_block;
}
};
if id_failsafe_done.contains(&id) {
update_error!(
error = TruncatedReadError::ArchiveFileIDAlreadyClose(id)
);
break 'read_block;
}
match id_failsafe2hash.remove(&id) {
Some(hash_archive) => {
let computed_hash = hash_archive.finalize();
if computed_hash.as_slice() != hash {
update_error!(
error = TruncatedReadError::HashDiffers {
expected: Vec::from(computed_hash.as_slice()),
obtained: Vec::from(&hash[..]),
}
);
break 'read_block;
}
}
None => {
update_error!(
error = TruncatedReadError::FailSafeReadInternalError
);
break 'read_block;
}
};
output.end_entry(id_output)?;
id_failsafe_done.push(id);
}
ArchiveEntryBlock::EndOfArchiveData => {
update_error!(error = TruncatedReadError::EndOfOriginalArchiveData);
break 'read_block;
}
}
}
};
}
let mut unfinished_files = Vec::new();
for (id_failsafe, id_output) in id_failsafe2id_output {
if id_failsafe_done.contains(&id_failsafe) {
continue;
}
let fname = id_failsafe2filename
.get(&id_failsafe)
.expect("`id_failsafe2filename` not more sync with `id_failsafe2id_output`");
output.end_entry(id_output)?;
unfinished_files.push(fname.clone());
}
if !unfinished_files.is_empty() {
error = TruncatedReadError::UnfinishedFiles {
filenames: unfinished_files,
stopping_error: Box::new(error),
};
}
output.finalize()?;
Ok(error)
}
}
pub mod info;
#[cfg(test)]
pub(crate) mod tests {
use crate::config::TruncatedReaderDecryptionMode;
use crate::crypto::mlakey::{MLAPrivateKey, MLAPublicKey, generate_mla_keypair_from_seed};
use super::*;
use crypto::hybrid::generate_keypair_from_seed;
use rand::distributions::{Distribution, Standard};
use rand::{RngCore, SeedableRng};
use rand_chacha::ChaChaRng;
#[cfg(feature = "send")]
use static_assertions;
#[cfg(feature = "send")]
use std::fs::File;
use std::io::{Cursor, Empty, Read};
#[test]
fn read_dump_header() {
let header = ArchiveHeader {
format_version_number: MLA_FORMAT_VERSION,
};
let mut buf = Vec::new();
header.serialize(&mut buf).unwrap();
println!("{buf:?}");
let header_rebuild = ArchiveHeader::deserialize(&mut buf.as_slice()).unwrap();
assert_eq!(header_rebuild.format_version_number, MLA_FORMAT_VERSION);
}
#[test]
fn dump_block() {
let mut buf = Vec::new();
let id = ArchiveEntryId(0);
let hash = Sha256Hash::default();
ArchiveEntryBlock::EntryStart::<Empty> {
id,
name: EntryName::from_path("foobaré.exe").unwrap(),
opts: Opts,
}
.dump(&mut buf)
.unwrap();
let fake_content = vec![1, 2, 3, 4];
let mut block = ArchiveEntryBlock::EntryContent {
id,
length: fake_content.len() as u64,
data: Some(fake_content.as_slice()),
opts: Opts,
};
block.dump(&mut buf).unwrap();
ArchiveEntryBlock::EndOfEntry::<Empty> {
id,
hash,
opts: Opts,
}
.dump(&mut buf)
.unwrap();
println!("{buf:?}");
}
#[test]
fn new_mla() {
let file = Vec::new();
let (private_key, public_key) = generate_keypair_from_seed([0; 32]);
let config = ArchiveWriterConfig::with_encryption_without_signature(&[public_key]).unwrap();
let mut mla = ArchiveWriter::from_config(file, config).expect("Writer init failed");
let fake_file = vec![1, 2, 3, 4];
mla.add_entry(
EntryName::from_path("my_file").unwrap(),
fake_file.len() as u64,
fake_file.as_slice(),
)
.unwrap();
let fake_file = vec![5, 6, 7, 8];
let fake_file2 = vec![9, 10, 11, 12];
let id = mla
.start_entry(EntryName::from_path("my_file2").unwrap())
.unwrap();
mla.append_entry_content(id, fake_file.len() as u64, fake_file.as_slice())
.unwrap();
mla.append_entry_content(id, fake_file2.len() as u64, fake_file2.as_slice())
.unwrap();
mla.end_entry(id).unwrap();
let dest = mla.finalize().unwrap();
let buf = Cursor::new(dest.as_slice());
let config =
ArchiveReaderConfig::without_signature_verification().with_encryption(&[private_key]);
let mut mla_read = ArchiveReader::from_config(buf, config).unwrap().0;
let mut file = mla_read
.get_entry(EntryName::from_path("my_file").unwrap())
.unwrap()
.unwrap();
let mut rez = Vec::new();
file.data.read_to_end(&mut rez).unwrap();
assert_eq!(rez, vec![1, 2, 3, 4]);
drop(file);
let mut file2 = mla_read
.get_entry(EntryName::from_path("my_file2").unwrap())
.unwrap()
.unwrap();
let mut rez2 = Vec::new();
file2.data.read_to_end(&mut rez2).unwrap();
assert_eq!(rez2, vec![5, 6, 7, 8, 9, 10, 11, 12]);
}
#[test]
fn new_encryption_only_mla() {
let file = Vec::new();
let (private_key, public_key) = generate_keypair_from_seed([0; 32]);
let config = ArchiveWriterConfig::with_encryption_without_signature(&[public_key])
.unwrap()
.without_compression();
let mut mla = ArchiveWriter::from_config(file, config).expect("Writer init failed");
let fake_file = vec![1, 2, 3, 4];
mla.add_entry(
EntryName::from_path("my_file").unwrap(),
fake_file.len() as u64,
fake_file.as_slice(),
)
.unwrap();
let fake_file = vec![5, 6, 7, 8];
let fake_file2 = vec![9, 10, 11, 12];
let id = mla
.start_entry(EntryName::from_path("my_file2").unwrap())
.unwrap();
mla.append_entry_content(id, fake_file.len() as u64, fake_file.as_slice())
.unwrap();
mla.append_entry_content(id, fake_file2.len() as u64, fake_file2.as_slice())
.unwrap();
mla.end_entry(id).unwrap();
let dest = mla.finalize().unwrap();
let buf = Cursor::new(dest.as_slice());
let config =
ArchiveReaderConfig::without_signature_verification().with_encryption(&[private_key]);
let mut mla_read = ArchiveReader::from_config(buf, config).unwrap().0;
let mut file = mla_read
.get_entry(EntryName::from_path("my_file").unwrap())
.unwrap()
.unwrap();
let mut rez = Vec::new();
file.data.read_to_end(&mut rez).unwrap();
assert_eq!(rez, vec![1, 2, 3, 4]);
drop(file);
let mut file2 = mla_read
.get_entry(EntryName::from_path("my_file2").unwrap())
.unwrap()
.unwrap();
let mut rez2 = Vec::new();
file2.data.read_to_end(&mut rez2).unwrap();
assert_eq!(rez2, vec![5, 6, 7, 8, 9, 10, 11, 12]);
}
#[test]
fn new_naked_mla() {
let file = Vec::new();
let config = ArchiveWriterConfig::without_encryption_without_signature()
.unwrap()
.without_compression();
let mut mla = ArchiveWriter::from_config(file, config).expect("Writer init failed");
let fake_file = vec![1, 2, 3, 4];
mla.add_entry(
EntryName::from_path("my_file").unwrap(),
fake_file.len() as u64,
fake_file.as_slice(),
)
.unwrap();
let fake_file = vec![5, 6, 7, 8];
let fake_file2 = vec![9, 10, 11, 12];
let id = mla
.start_entry(EntryName::from_path("my_file2").unwrap())
.unwrap();
mla.append_entry_content(id, fake_file.len() as u64, fake_file.as_slice())
.unwrap();
mla.append_entry_content(id, fake_file2.len() as u64, fake_file2.as_slice())
.unwrap();
mla.end_entry(id).unwrap();
let dest = mla.finalize().unwrap();
let buf = Cursor::new(dest.as_slice());
let config = ArchiveReaderConfig::without_signature_verification().without_encryption();
let mut mla_read = ArchiveReader::from_config(buf, config).unwrap().0;
let mut file = mla_read
.get_entry(EntryName::from_path("my_file").unwrap())
.unwrap()
.unwrap();
let mut rez = Vec::new();
file.data.read_to_end(&mut rez).unwrap();
assert_eq!(rez, vec![1, 2, 3, 4]);
drop(file);
let mut file2 = mla_read
.get_entry(EntryName::from_path("my_file2").unwrap())
.unwrap()
.unwrap();
let mut rez2 = Vec::new();
file2.data.read_to_end(&mut rez2).unwrap();
assert_eq!(rez2, vec![5, 6, 7, 8, 9, 10, 11, 12]);
}
#[test]
fn new_compression_only_mla() {
let file = Vec::new();
let config = ArchiveWriterConfig::without_encryption_without_signature().unwrap();
let mut mla = ArchiveWriter::from_config(file, config).expect("Writer init failed");
let fake_file = vec![1, 2, 3, 4];
mla.add_entry(
EntryName::from_path("my_file").unwrap(),
fake_file.len() as u64,
fake_file.as_slice(),
)
.unwrap();
let fake_file = vec![5, 6, 7, 8];
let fake_file2 = vec![9, 10, 11, 12];
let id = mla
.start_entry(EntryName::from_path("my_file2").unwrap())
.unwrap();
mla.append_entry_content(id, fake_file.len() as u64, fake_file.as_slice())
.unwrap();
mla.append_entry_content(id, fake_file2.len() as u64, fake_file2.as_slice())
.unwrap();
mla.end_entry(id).unwrap();
let dest = mla.finalize().unwrap();
let buf = Cursor::new(dest.as_slice());
let config = ArchiveReaderConfig::without_signature_verification().without_encryption();
let mut mla_read = ArchiveReader::from_config(buf, config).unwrap().0;
let mut file = mla_read
.get_entry(EntryName::from_path("my_file").unwrap())
.unwrap()
.unwrap();
let mut rez = Vec::new();
file.data.read_to_end(&mut rez).unwrap();
assert_eq!(rez, vec![1, 2, 3, 4]);
drop(file);
let mut file2 = mla_read
.get_entry(EntryName::from_path("my_file2").unwrap())
.unwrap()
.unwrap();
let mut rez2 = Vec::new();
file2.data.read_to_end(&mut rez2).unwrap();
assert_eq!(rez2, vec![5, 6, 7, 8, 9, 10, 11, 12]);
}
#[test]
fn new_sig_mla() {
let file = Vec::new();
let (private_key, public_key) = generate_mla_keypair_from_seed([0; 32]);
let config = ArchiveWriterConfig::without_encryption_with_signature(&[private_key
.get_signing_private_key()
.clone()])
.unwrap();
let mut mla = ArchiveWriter::from_config(file, config).expect("Writer init failed");
let fake_file = vec![1, 2, 3, 4];
mla.add_entry(
EntryName::from_path("my_file").unwrap(),
fake_file.len() as u64,
fake_file.as_slice(),
)
.unwrap();
let fake_file = vec![5, 6, 7, 8];
let fake_file2 = vec![9, 10, 11, 12];
let id = mla
.start_entry(EntryName::from_path("my_file2").unwrap())
.unwrap();
mla.append_entry_content(id, fake_file.len() as u64, fake_file.as_slice())
.unwrap();
mla.append_entry_content(id, fake_file2.len() as u64, fake_file2.as_slice())
.unwrap();
mla.end_entry(id).unwrap();
let dest = mla.finalize().unwrap();
let buf = Cursor::new(dest.as_slice());
let config = ArchiveReaderConfig::with_signature_verification(&[public_key
.get_signature_verification_public_key()
.clone()])
.without_encryption();
let mut mla_read = ArchiveReader::from_config(buf, config).unwrap().0;
let mut file = mla_read
.get_entry(EntryName::from_path("my_file").unwrap())
.unwrap()
.unwrap();
let mut rez = Vec::new();
file.data.read_to_end(&mut rez).unwrap();
assert_eq!(rez, vec![1, 2, 3, 4]);
drop(file);
let mut file2 = mla_read
.get_entry(EntryName::from_path("my_file2").unwrap())
.unwrap()
.unwrap();
let mut rez2 = Vec::new();
file2.data.read_to_end(&mut rez2).unwrap();
assert_eq!(rez2, vec![5, 6, 7, 8, 9, 10, 11, 12]);
}
#[allow(clippy::type_complexity)]
pub(crate) fn build_archive(
compression: bool,
encryption: bool,
signature: bool,
interleaved: bool,
) -> (
Vec<u8>,
(MLAPrivateKey, MLAPublicKey),
(MLAPrivateKey, MLAPublicKey),
Vec<(EntryName, Vec<u8>)>,
) {
let (written_archive, sender_keys, receiver_keys, files_content, _, _) =
build_archive2(compression, encryption, signature, interleaved);
(written_archive, sender_keys, receiver_keys, files_content)
}
#[allow(clippy::type_complexity)]
pub(crate) fn build_archive2(
compression: bool,
encryption: bool,
signature: bool,
interleaved: bool,
) -> (
Vec<u8>,
(MLAPrivateKey, MLAPublicKey),
(MLAPrivateKey, MLAPublicKey),
Vec<(EntryName, Vec<u8>)>,
HashMap<EntryName, ArchiveEntryId>,
HashMap<ArchiveEntryId, EntryInfo>,
) {
let file = Vec::new();
let (sender_private_key, sender_public_key) = generate_mla_keypair_from_seed([0; 32]);
let (receiver_private_key, receiver_public_key) = generate_mla_keypair_from_seed([1; 32]);
let config = if encryption {
if signature {
ArchiveWriterConfig::with_encryption_with_signature(
&[receiver_public_key.get_encryption_public_key().clone()],
&[sender_private_key.get_signing_private_key().clone()],
)
} else {
ArchiveWriterConfig::with_encryption_without_signature(&[receiver_public_key
.get_encryption_public_key()
.clone()])
}
} else if signature {
ArchiveWriterConfig::without_encryption_with_signature(&[sender_private_key
.get_signing_private_key()
.clone()])
} else {
ArchiveWriterConfig::without_encryption_without_signature()
}
.unwrap();
let config = if !compression {
config.without_compression()
} else {
config
};
let mut mla = ArchiveWriter::from_config(file, config).expect("Writer init failed");
let fname1 = EntryName::from_arbitrary_bytes(b"my_file1").unwrap();
let fname2 = EntryName::from_arbitrary_bytes(b"my_file2").unwrap();
let fname3 = EntryName::from_arbitrary_bytes(b"my_file3").unwrap();
let fake_file_part1 = vec![1, 2, 3];
let fake_file_part2 = vec![4, 5, 6, 7, 8];
let mut fake_file1 = Vec::new();
fake_file1.extend_from_slice(fake_file_part1.as_slice());
fake_file1.extend_from_slice(fake_file_part2.as_slice());
let fake_file2 = vec![9, 10, 11, 12];
let fake_file3 = vec![13, 14, 15];
if interleaved {
let id_file1 = mla.start_entry(fname1.clone()).unwrap();
mla.append_entry_content(
id_file1,
fake_file_part1.len() as u64,
fake_file_part1.as_slice(),
)
.unwrap();
let id_file2 = mla.start_entry(fname2.clone()).unwrap();
mla.append_entry_content(id_file2, fake_file2.len() as u64, fake_file2.as_slice())
.unwrap();
mla.add_entry(
fname3.clone(),
fake_file3.len() as u64,
fake_file3.as_slice(),
)
.unwrap();
mla.append_entry_content(
id_file1,
fake_file_part2.len() as u64,
fake_file_part2.as_slice(),
)
.unwrap();
mla.end_entry(id_file1).unwrap();
mla.end_entry(id_file2).unwrap();
} else {
mla.add_entry(
fname1.clone(),
fake_file1.len() as u64,
fake_file1.as_slice(),
)
.unwrap();
mla.add_entry(
fname2.clone(),
fake_file2.len() as u64,
fake_file2.as_slice(),
)
.unwrap();
mla.add_entry(
fname3.clone(),
fake_file3.len() as u64,
fake_file3.as_slice(),
)
.unwrap();
}
let files_info = mla.files_info.clone();
let ids_info = mla.ids_info.clone();
let written_archive = mla.finalize().unwrap();
(
written_archive,
(sender_private_key, sender_public_key),
(receiver_private_key, receiver_public_key),
vec![
(fname1, fake_file1),
(fname2, fake_file2),
(fname3, fake_file3),
],
files_info,
ids_info,
)
}
#[test]
fn interleaved_files() {
let (mla, _sender_key, receiver_key, files) = build_archive(true, true, false, true);
let buf = Cursor::new(mla.as_slice());
let config = ArchiveReaderConfig::without_signature_verification()
.with_encryption(&[receiver_key.0.get_decryption_private_key().clone()]);
let mut mla_read = ArchiveReader::from_config(buf, config).unwrap().0;
for (fname, content) in files {
let mut file = mla_read.get_entry(fname).unwrap().unwrap();
let mut rez = Vec::new();
file.data.read_to_end(&mut rez).unwrap();
assert_eq!(rez, content);
}
}
#[test]
fn mla_multi_layering() {
let (private_key, public_key) = generate_keypair_from_seed([0; 32]);
let config_nolayer = ArchiveWriterConfig::without_encryption_without_signature()
.unwrap()
.without_compression();
let config_encrypt =
ArchiveWriterConfig::with_encryption_without_signature(&[public_key.clone()])
.unwrap()
.without_compression();
let config_compress = ArchiveWriterConfig::without_encryption_without_signature().unwrap();
let config_both =
ArchiveWriterConfig::with_encryption_without_signature(&[public_key]).unwrap();
for (config, encryption) in [
(config_nolayer, false),
(config_encrypt, true),
(config_compress, false),
(config_both, true),
] {
let file = Vec::new();
let mut mla = ArchiveWriter::from_config(file, config).expect("Writer init failed");
let fake_file = vec![1, 2, 3, 4];
mla.add_entry(
EntryName::from_path("my_file").unwrap(),
fake_file.len() as u64,
fake_file.as_slice(),
)
.unwrap();
let fake_file = vec![5, 6, 7, 8];
let fake_file2 = vec![9, 10, 11, 12];
let id = mla
.start_entry(EntryName::from_path("my_file2").unwrap())
.unwrap();
mla.append_entry_content(id, fake_file.len() as u64, fake_file.as_slice())
.unwrap();
mla.append_entry_content(id, fake_file2.len() as u64, fake_file2.as_slice())
.unwrap();
mla.end_entry(id).unwrap();
let dest = mla.finalize().unwrap();
let buf = Cursor::new(dest.as_slice());
let config = if encryption {
ArchiveReaderConfig::without_signature_verification()
.with_encryption(&[private_key.clone()])
} else {
ArchiveReaderConfig::without_signature_verification().without_encryption()
};
let mut mla_read = ArchiveReader::from_config(buf, config).unwrap().0;
let mut file = mla_read
.get_entry(EntryName::from_path("my_file").unwrap())
.unwrap()
.unwrap();
let mut rez = Vec::new();
file.data.read_to_end(&mut rez).unwrap();
assert_eq!(rez, vec![1, 2, 3, 4]);
drop(file);
let mut file2 = mla_read
.get_entry(EntryName::from_path("my_file2").unwrap())
.unwrap()
.unwrap();
let mut rez2 = [0u8; 6];
file2.data.read_exact(&mut rez2).unwrap();
assert_eq!(rez2, [5, 6, 7, 8, 9, 10]);
let mut rez3 = Vec::new();
let mut final_rez = Vec::new();
file2.data.read_to_end(&mut rez3).unwrap();
final_rez.extend(rez2);
final_rez.extend(rez3);
assert_eq!(final_rez, vec![5, 6, 7, 8, 9, 10, 11, 12]);
}
}
#[test]
fn list_and_read_files() {
let (mla, _sender_key, receiver_key, files) = build_archive(true, true, false, false);
let buf = Cursor::new(mla.as_slice());
let config = ArchiveReaderConfig::without_signature_verification()
.with_encryption(&[receiver_key.0.get_decryption_private_key().clone()]);
let mut mla_read = ArchiveReader::from_config(buf, config).unwrap().0;
let mut sorted_list: Vec<EntryName> = mla_read.list_entries().unwrap().cloned().collect();
sorted_list.sort();
assert_eq!(
sorted_list,
files.iter().map(|(x, _y)| x.clone()).collect::<Vec<_>>(),
);
for (fname, content) in files.iter().rev() {
let mut mla_file = mla_read.get_entry(fname.clone()).unwrap().unwrap();
assert_eq!(mla_file.name, fname.clone());
let mut buf = Vec::new();
mla_file.data.read_to_end(&mut buf).unwrap();
assert_eq!(&buf, content);
}
}
#[test]
fn convert_failsafe() {
let (dest, _sender_key, receiver_key, files) = build_archive(true, true, false, false);
let config = TruncatedReaderConfig::without_signature_verification_with_encryption(
&[receiver_key.0.get_decryption_private_key().clone()],
TruncatedReaderDecryptionMode::OnlyAuthenticatedData,
);
let mut mla_fsread = TruncatedArchiveReader::from_config(dest.as_slice(), config).unwrap();
let mut dest_w = Vec::new();
let config = ArchiveWriterConfig::with_encryption_without_signature(&[receiver_key
.1
.get_encryption_public_key()
.clone()])
.unwrap();
let mla_w = ArchiveWriter::from_config(&mut dest_w, config).expect("Writer init failed");
match mla_fsread.convert_to_archive(mla_w).unwrap() {
TruncatedReadError::EndOfOriginalArchiveData => {
}
status => {
panic!("Unexpected status: {status}");
}
};
let buf2 = Cursor::new(dest_w.as_slice());
let config = ArchiveReaderConfig::without_signature_verification()
.with_encryption(&[receiver_key.0.get_decryption_private_key().clone()]);
let mut mla_read = ArchiveReader::from_config(buf2, config).unwrap().0;
let mut sorted_list: Vec<EntryName> = mla_read.list_entries().unwrap().cloned().collect();
sorted_list.sort();
assert_eq!(
sorted_list,
files.iter().map(|(x, _y)| x.clone()).collect::<Vec<_>>(),
);
for (fname, content) in files.iter().rev() {
let mut mla_file = mla_read.get_entry(fname.clone()).unwrap().unwrap();
assert_eq!(mla_file.name, fname.clone());
let mut buf = Vec::new();
mla_file.data.read_to_end(&mut buf).unwrap();
assert_eq!(&buf, content);
}
}
#[test]
fn convert_trunc_failsafe() {
for interleaved in &[false, true] {
let (dest, _sender_key, receiver_key, files, files_info, ids_info) =
build_archive2(false, true, false, *interleaved);
let footer_size = {
let mut cursor = Cursor::new(Vec::new());
ArchiveFooter::serialize_into(&mut cursor, &files_info, &ids_info).unwrap();
cursor.position() as usize
};
for remove in &[1, 10, 30, 50, 70, 95, 100] {
let config = TruncatedReaderConfig::without_signature_verification_with_encryption(
&[receiver_key.0.get_decryption_private_key().clone()],
TruncatedReaderDecryptionMode::DataEvenUnauthenticated,
);
let mut mla_fsread = TruncatedArchiveReader::from_config(
&dest[..dest.len() - footer_size - remove],
config,
)
.expect("Unable to create");
let mut dest_w = Vec::new();
let config_w =
ArchiveWriterConfig::with_encryption_without_signature(&[receiver_key
.1
.get_encryption_public_key()
.clone()])
.expect("Writer init failed");
let mla_w = ArchiveWriter::from_config(&mut dest_w, config_w).unwrap();
let _status = mla_fsread.convert_to_archive(mla_w).unwrap();
let buf2 = Cursor::new(dest_w.as_slice());
let config = ArchiveReaderConfig::without_signature_verification()
.with_encryption(&[receiver_key.0.get_decryption_private_key().clone()]);
let mut mla_read = ArchiveReader::from_config(buf2, config).unwrap().0;
let expected = files.iter().map(|(x, _y)| x.clone()).collect::<Vec<_>>();
let mut file_list = mla_read
.list_entries()
.unwrap()
.cloned()
.collect::<Vec<_>>();
file_list.sort();
assert_eq!(
file_list[..],
expected[..file_list.len()],
"File lists not equal {} interleaving and {} bytes removed",
if *interleaved { "with" } else { "without" },
remove
);
for (fname, content) in files.iter().rev() {
let mut mla_file = match mla_read.get_entry(fname.clone()).unwrap() {
Some(mla_file) => mla_file,
None => continue,
};
assert_eq!(mla_file.name, fname.clone());
let mut buf = Vec::new();
mla_file.data.read_to_end(&mut buf).unwrap();
assert_ne!(
buf.len(),
0,
"Read 0 bytes from subfile {} {} interleaving and {} bytes removed",
mla_file.name.raw_content_to_escaped_string(),
if *interleaved { "with" } else { "without" },
remove
);
assert_eq!(&buf[..], &content[..buf.len()]);
}
}
}
}
#[test]
fn avoid_duplicate_filename() {
let buf = Vec::new();
let config = ArchiveWriterConfig::without_encryption_without_signature()
.unwrap()
.without_compression();
let mut mla = ArchiveWriter::from_config(buf, config).unwrap();
mla.add_entry(
EntryName::from_path("Test").unwrap(),
4,
vec![1, 2, 3, 4].as_slice(),
)
.unwrap();
assert!(
mla.add_entry(
EntryName::from_path("Test").unwrap(),
4,
vec![1, 2, 3, 4].as_slice()
)
.is_err()
);
assert!(
mla.start_entry(EntryName::from_path("Test").unwrap())
.is_err()
);
}
#[test]
fn check_file_size() {
for interleaved in &[false, true] {
let (dest, _sender_key, receiver_key, files, files_info, ids_info) =
build_archive2(true, true, false, *interleaved);
for (fname, data) in &files {
let id = files_info.get(fname).unwrap();
let size: u64 = ids_info
.get(id)
.unwrap()
.offsets_and_sizes
.iter()
.map(|p| p.1)
.sum();
assert_eq!(size, data.len() as u64);
}
let buf = Cursor::new(dest.as_slice());
let config = ArchiveReaderConfig::without_signature_verification()
.with_encryption(&[receiver_key.0.get_decryption_private_key().clone()]);
let mut mla_read = ArchiveReader::from_config(buf, config).unwrap().0;
for (fname, data) in &files {
let mla_file = mla_read.get_entry(fname.clone()).unwrap().unwrap();
assert_eq!(mla_file.get_size(), data.len() as u64);
}
}
}
#[test]
fn failsafe_detect_integrity() {
let (mut dest, _sender_key, _receiver_key, files) =
build_archive(false, false, false, false);
let expect = files[0].1.as_slice();
let pos: Vec<usize> = dest
.iter()
.enumerate()
.filter_map(|e| {
if e.0 + expect.len() < dest.len() && &dest[e.0..e.0 + expect.len()] == expect {
Some(e.0)
} else {
None
}
})
.collect();
dest.swap(pos[0], pos[0] + 1);
let mut mla_fsread = TruncatedArchiveReader::from_config(
dest.as_slice(),
TruncatedReaderConfig::without_signature_verification_without_encryption(),
)
.unwrap();
let dest_w = Vec::new();
let config = ArchiveWriterConfig::without_encryption_without_signature()
.unwrap()
.without_compression();
let mla_w = ArchiveWriter::from_config(dest_w, config).expect("Writer init failed");
match mla_fsread.convert_to_archive(mla_w).unwrap() {
TruncatedReadError::UnfinishedFiles {
filenames,
stopping_error,
} => {
assert_eq!(filenames, vec![files[0].0.clone()]);
match *stopping_error {
TruncatedReadError::HashDiffers { .. } => {}
_ => {
panic!("Unexpected stopping_error: {stopping_error}");
}
}
}
status => {
panic!("Unexpected status: {status}");
}
};
}
#[test]
fn get_hash() {
let (dest, _sender_key, receiver_key, files) = build_archive(true, true, false, false);
let buf = Cursor::new(dest.as_slice());
let config = ArchiveReaderConfig::without_signature_verification()
.with_encryption(&[receiver_key.0.get_decryption_private_key().clone()]);
let mut mla_read = ArchiveReader::from_config(buf, config).unwrap().0;
for (filename, content) in files {
let hash = mla_read.get_hash(&filename).unwrap().unwrap();
let mut hasher = Sha256::new();
hasher.update(content);
let result = hasher.finalize();
assert_eq!(result.as_slice(), hash);
}
}
fn make_format_regression_files() -> HashMap<EntryName, Vec<u8>> {
let mut files: HashMap<EntryName, Vec<u8>> = HashMap::new();
let mut simple: Vec<u8> = Vec::new();
for i in 0..=255 {
simple.push(i);
}
let pattern = simple.clone();
files.insert(EntryName::from_path("simple").unwrap(), simple);
let big: Vec<u8> = pattern
.iter()
.cycle()
.take(10 * 1024 * 1024)
.cloned()
.collect();
files.insert(EntryName::from_path("big").unwrap(), big);
for i in 0..=255 {
files.insert(
EntryName::from_path(format!("file_{i}")).unwrap(),
std::iter::repeat_n(i, 0x1000).collect::<Vec<u8>>(),
);
}
let mut sha256sum: Vec<u8> = Vec::new();
let mut info: Vec<(&EntryName, &Vec<_>)> = files.iter().collect();
info.sort_by(|i1, i2| Ord::cmp(&i1.0, &i2.0));
for (fname, content) in info.iter() {
let h = Sha256::digest(content);
sha256sum.extend_from_slice(hex::encode(h).as_bytes());
sha256sum.push(0x20);
sha256sum.push(0x20);
sha256sum.extend(fname.as_arbitrary_bytes());
sha256sum.push(0x0a);
}
files.insert(EntryName::from_path("sha256sum").unwrap(), sha256sum);
files
}
#[test]
fn create_archive_format_version() {
let file = Vec::new();
let priv_bytes: &'static [u8] =
include_bytes!("../../samples/test_mlakey_archive_v2_sender.mlapriv");
let pub_bytes: &'static [u8] =
include_bytes!("../../samples/test_mlakey_archive_v2_receiver.mlapub");
let (_, priv_key) = MLAPrivateKey::deserialize_private_key(priv_bytes)
.unwrap()
.get_private_keys();
let (pub_key, _) = MLAPublicKey::deserialize_public_key(pub_bytes)
.unwrap()
.get_public_keys();
let mut config =
ArchiveWriterConfig::with_encryption_with_signature(&[pub_key], &[priv_key]).unwrap();
if let Some(cfg) = config.encryption_config.as_mut() {
cfg.rng = crate::crypto::MaybeSeededRNG::Seed([0; 32]);
}
if let Some(cfg) = config.signature_config.as_mut() {
cfg.rng = crate::crypto::MaybeSeededRNG::Seed([0; 32]);
}
let mut mla = ArchiveWriter::from_config(file, config).expect("Writer init failed");
let files = make_format_regression_files();
let fname_simple = EntryName::from_path("simple").unwrap();
mla.add_entry(
fname_simple.clone(),
files.get(&fname_simple).unwrap().len() as u64,
files.get(&fname_simple).unwrap().as_slice(),
)
.unwrap();
let fnames: Vec<EntryName> = (0..=255)
.map(|i| format!("file_{i}"))
.map(|s| EntryName::from_path(&s).unwrap())
.collect();
let mut name2id: HashMap<_, _> = HashMap::new();
(0..=255)
.map(|i| {
let id = mla.start_entry(fnames[i].clone()).unwrap();
name2id.insert(&fnames[i], id);
})
.for_each(drop);
(0..=255)
.rev()
.map(|i| {
let id = name2id.get(&fnames[i]).unwrap();
mla.append_entry_content(*id, 32, &files.get(&fnames[i]).unwrap()[..32])
.unwrap();
})
.for_each(drop);
(0..=255)
.map(|i| {
let id = name2id.get(&fnames[i]).unwrap();
let data = &files.get(&fnames[i]).unwrap()[32..];
mla.append_entry_content(*id, data.len() as u64, data)
.unwrap();
})
.for_each(drop);
(0..=255)
.rev()
.map(|i| {
let id = name2id.get(&fnames[i]).unwrap();
mla.end_entry(*id).unwrap();
})
.for_each(drop);
let fname_big = EntryName::from_path("big").unwrap();
mla.add_entry(
fname_big.clone(),
files.get(&fname_big).unwrap().len() as u64,
files.get(&fname_big).unwrap().as_slice(),
)
.unwrap();
let fname_sha256sum = EntryName::from_path("sha256sum").unwrap();
mla.add_entry(
fname_sha256sum.clone(),
files.get(&fname_sha256sum).unwrap().len() as u64,
files.get(&fname_sha256sum).unwrap().as_slice(),
)
.unwrap();
let raw_mla = mla.finalize().unwrap();
std::fs::File::create(std::path::Path::new(&format!(
"../samples/archive_v{MLA_FORMAT_VERSION}.mla"
)))
.unwrap()
.write_all(&raw_mla)
.unwrap();
assert_eq!(
Sha256::digest(&raw_mla).as_slice(),
[
87, 9, 173, 162, 10, 132, 118, 9, 37, 74, 240, 138, 228, 47, 60, 94, 111, 224, 92,
241, 139, 106, 105, 126, 216, 108, 48, 216, 176, 186, 240, 134
]
)
}
#[test]
fn check_archive_format_v2_content() {
let privbytes: &'static [u8] =
include_bytes!("../../samples/test_mlakey_archive_v2_receiver.mlapriv");
let pubbytes: &'static [u8] =
include_bytes!("../../samples/test_mlakey_archive_v2_sender.mlapub");
let mla_data: &'static [u8] = include_bytes!("../../samples/archive_v2.mla");
let files = make_format_regression_files();
let buf = Cursor::new(mla_data);
let (privkey, _) = MLAPrivateKey::deserialize_private_key(privbytes)
.unwrap()
.get_private_keys();
let (_, pubkey) = MLAPublicKey::deserialize_public_key(pubbytes)
.unwrap()
.get_public_keys();
let config = ArchiveReaderConfig::with_signature_verification(&[pubkey])
.with_encryption(&[privkey.clone()]);
let mut mla_read = ArchiveReader::from_config(buf, config).unwrap().0;
let config = TruncatedReaderConfig::without_signature_verification_with_encryption(
&[privkey],
TruncatedReaderDecryptionMode::OnlyAuthenticatedData,
);
let mut mla_fsread = TruncatedArchiveReader::from_config(mla_data, config).unwrap();
let mut dest_w = Vec::new();
let config = ArchiveWriterConfig::without_encryption_without_signature()
.unwrap()
.without_compression();
let mla_w = ArchiveWriter::from_config(&mut dest_w, config).expect("Writer init failed");
if let TruncatedReadError::EndOfOriginalArchiveData =
mla_fsread.convert_to_archive(mla_w).unwrap()
{
} else {
panic!();
}
let buf2 = Cursor::new(dest_w);
let repread_config =
ArchiveReaderConfig::without_signature_verification().without_encryption();
let mut mla_repread = ArchiveReader::from_config(buf2, repread_config).unwrap().0;
assert_eq!(files.len(), mla_read.list_entries().unwrap().count());
assert_eq!(files.len(), mla_repread.list_entries().unwrap().count());
for (fname, content) in files.iter() {
let mut mla_file = mla_read.get_entry(fname.clone()).unwrap().unwrap();
let mut mla_rep_file = mla_repread.get_entry(fname.clone()).unwrap().unwrap();
assert_eq!(mla_file.name, fname.clone());
assert_eq!(mla_rep_file.name, fname.clone());
let mut buf = Vec::new();
mla_file.data.read_to_end(&mut buf).unwrap();
assert_eq!(buf.as_slice(), content.as_slice());
let mut buf = Vec::new();
mla_rep_file.data.read_to_end(&mut buf).unwrap();
assert_eq!(buf.as_slice(), content.as_slice());
}
}
#[test]
fn not_path_entry_name() {
let mut file: Vec<u8> = Vec::new();
let priv_bytes: &'static [u8] =
include_bytes!("../../samples/test_mlakey_archive_v2_sender.mlapriv");
let pub_bytes: &'static [u8] =
include_bytes!("../../samples/test_mlakey_archive_v2_receiver.mlapub");
let (_, priv_key) = MLAPrivateKey::deserialize_private_key(priv_bytes)
.unwrap()
.get_private_keys();
let (pub_key, _) = MLAPublicKey::deserialize_public_key(pub_bytes)
.unwrap()
.get_public_keys();
let mut config =
ArchiveWriterConfig::with_encryption_with_signature(&[pub_key], &[priv_key])
.unwrap()
.without_compression();
if let Some(cfg) = config.encryption_config.as_mut() {
cfg.rng = crate::crypto::MaybeSeededRNG::Seed([0; 32]);
}
if let Some(cfg) = config.signature_config.as_mut() {
cfg.rng = crate::crypto::MaybeSeededRNG::Seed([0; 32]);
}
let mut mla = ArchiveWriter::from_config(&mut file, config).expect("Writer init failed");
let name = EntryName::from_arbitrary_bytes(
b"c:/\0;\xe2\x80\xae\nc\rd\x1b[1;31ma<script>evil\\../\xd8\x01\xc2\x85\xe2\x88\x95",
)
.unwrap();
mla.add_entry(name.clone(), 8, b"' OR 1=1".as_slice())
.expect("start_file");
mla.finalize().unwrap();
std::fs::File::create(std::path::Path::new("../samples/archive_weird.mla"))
.unwrap()
.write_all(&file)
.unwrap();
assert_eq!(
Sha256::digest(&file).as_slice(),
[
124, 113, 95, 161, 149, 236, 55, 42, 5, 86, 236, 116, 51, 213, 27, 82, 252, 124,
47, 197, 114, 95, 126, 48, 239, 230, 226, 132, 102, 206, 28, 255
]
)
}
#[test]
fn empty_blocks() {
let file = Vec::new();
let config = ArchiveWriterConfig::without_encryption_without_signature()
.unwrap()
.without_compression();
let mut mla = ArchiveWriter::from_config(file, config).expect("Writer init failed");
let fname = EntryName::from_path("my_file").unwrap();
let fake_file = vec![1, 2, 3, 4, 5, 6, 7, 8];
let id = mla.start_entry(fname.clone()).expect("start_file");
mla.append_entry_content(id, 4, &fake_file[..4])
.expect("add content");
mla.append_entry_content(id, 0, &fake_file[..1])
.expect("add content empty");
mla.append_entry_content(id, fake_file.len() as u64 - 4, &fake_file[4..])
.expect("add rest");
mla.end_entry(id).unwrap();
let mla_data = mla.finalize().unwrap();
let buf = Cursor::new(mla_data);
let mut mla_read = ArchiveReader::from_config(
buf,
ArchiveReaderConfig::without_signature_verification().without_encryption(),
)
.expect("archive reader")
.0;
let mut out = Vec::new();
mla_read
.get_entry(fname)
.unwrap()
.unwrap()
.data
.read_to_end(&mut out)
.unwrap();
assert_eq!(out.as_slice(), fake_file.as_slice());
}
#[test]
#[ignore]
fn more_than_u32_file() {
let mut rng = ChaChaRng::seed_from_u64(0);
let mut rng_data = ChaChaRng::seed_from_u64(0);
const MORE_THAN_U32: u64 = 0x100010000; const MAX_SIZE: u64 = 5 * 1024 * 1024 * 1024; const CHUNK_SIZE: usize = 10 * 1024 * 1024;
let (private_key, public_key) = generate_keypair_from_seed([0; 32]);
let config = ArchiveWriterConfig::with_encryption_without_signature(&[public_key]).unwrap();
let file = Vec::new();
let mut mla = ArchiveWriter::from_config(file, config).expect("Writer init failed");
let id1 = mla
.start_entry(EntryName::from_path("file_0").unwrap())
.unwrap();
let mut cur_size = 0;
while cur_size < MORE_THAN_U32 {
let size = std::cmp::min(rng.next_u32() as u64, MORE_THAN_U32 - cur_size);
let data: Vec<u8> = Standard
.sample_iter(&mut rng_data)
.take(size as usize)
.collect();
mla.append_entry_content(id1, size, data.as_slice())
.unwrap();
cur_size += size;
}
mla.end_entry(id1).unwrap();
let mut nb_file = 1;
while cur_size < MAX_SIZE {
let id = mla
.start_entry(EntryName::from_path(format!("file_{nb_file:}")).unwrap())
.unwrap();
let size = std::cmp::min(rng.next_u32() as u64, MAX_SIZE - cur_size);
let data: Vec<u8> = Standard
.sample_iter(&mut rng_data)
.take(size as usize)
.collect();
mla.append_entry_content(id, size, data.as_slice()).unwrap();
cur_size += size;
mla.end_entry(id).unwrap();
nb_file += 1;
}
let mla_data = mla.finalize().unwrap();
let buf = Cursor::new(mla_data);
let config =
ArchiveReaderConfig::without_signature_verification().with_encryption(&[private_key]);
let mut mla_read = ArchiveReader::from_config(buf, config)
.expect("archive reader")
.0;
let file_names: Vec<EntryName> = (0..nb_file)
.map(|nb| EntryName::from_path(format!("file_{nb:}")).unwrap())
.collect();
let mut file_list = mla_read
.list_entries()
.unwrap()
.cloned()
.collect::<Vec<_>>();
file_list.sort();
assert_eq!(file_list, file_names);
let mut rng_data = ChaChaRng::seed_from_u64(0);
let mut chunk = vec![0u8; CHUNK_SIZE];
for file_name in file_names.into_iter() {
let mut file_stream = mla_read.get_entry(file_name).unwrap().unwrap().data;
loop {
let read = file_stream.read(&mut chunk).unwrap();
let expect: Vec<u8> = Standard.sample_iter(&mut rng_data).take(read).collect();
assert_eq!(&chunk[..read], expect.as_slice());
if read == 0 {
break;
}
}
}
}
#[test]
fn signature_verification_fails_with_wrong_public_key() {
let file = Vec::new();
let (privkey_correct, _pubkey_correct) = generate_mla_keypair_from_seed([1u8; 32]);
let (_privkey_wrong, pubkey_wrong) = generate_mla_keypair_from_seed([2u8; 32]);
let config = ArchiveWriterConfig::without_encryption_with_signature(&[privkey_correct
.get_signing_private_key()
.clone()])
.unwrap();
let mut writer = ArchiveWriter::from_config(file, config).expect("Writer init failed");
let data = vec![10, 20, 30, 40];
writer
.add_entry(
EntryName::from_path("file1").unwrap(),
data.len() as u64,
data.as_slice(),
)
.unwrap();
let archive = writer.finalize().unwrap();
let buf = Cursor::new(archive.as_slice());
let config = ArchiveReaderConfig::with_signature_verification(&[pubkey_wrong
.get_signature_verification_public_key()
.clone()])
.without_encryption();
let reader_result = ArchiveReader::from_config(buf, config);
assert!(
reader_result.is_err(),
"Verification should fail with wrong public key"
);
}
#[test]
fn signature_verification_fails_on_tampered_archive() {
let file = Vec::new();
let (privkey, pubkey) = generate_mla_keypair_from_seed([5u8; 32]);
let config = ArchiveWriterConfig::without_encryption_with_signature(&[privkey
.get_signing_private_key()
.clone()])
.unwrap();
let mut writer = ArchiveWriter::from_config(file, config).expect("Writer init failed");
let data = vec![50, 60, 70, 80];
writer
.add_entry(
EntryName::from_path("my_file").unwrap(),
data.len() as u64,
data.as_slice(),
)
.unwrap();
let mut archive = writer.finalize().unwrap();
archive[10] ^= 0xFF;
let buf = Cursor::new(archive.as_slice());
let config = ArchiveReaderConfig::with_signature_verification(&[pubkey
.get_signature_verification_public_key()
.clone()])
.without_encryption();
let result = ArchiveReader::from_config(buf, config);
assert!(
result.is_err(),
"Signature verification should fail on tampered archive"
);
}
#[test]
fn signature_verification_fails_if_ed25519_signature_corrupted() {
let file = Vec::new();
let (privkey, pubkey) = generate_mla_keypair_from_seed([7u8; 32]);
let config = ArchiveWriterConfig::without_encryption_with_signature(&[privkey
.get_signing_private_key()
.clone()])
.unwrap();
let mut writer = ArchiveWriter::from_config(file, config).expect("Writer init failed");
let data = vec![10, 20, 30, 40];
writer
.add_entry(
EntryName::from_path("file1").unwrap(),
data.len() as u64,
data.as_slice(),
)
.unwrap();
let mut archive = writer.finalize().unwrap();
let flip_pos = 50;
archive[flip_pos] ^= 0xFF;
let buf = std::io::Cursor::new(archive);
let config = ArchiveReaderConfig::with_signature_verification(&[pubkey
.get_signature_verification_public_key()
.clone()])
.without_encryption();
let reader_result = ArchiveReader::from_config(buf, config);
assert!(
matches!(reader_result, Err(Error::NoValidSignatureFound)),
"Verification should fail if Ed25519 signature is corrupted"
);
}
#[test]
fn signature_verification_fails_if_mldsa87_signature_corrupted() {
let file = Vec::new();
let (privkey, pubkey) = generate_mla_keypair_from_seed([8u8; 32]);
let config = ArchiveWriterConfig::without_encryption_with_signature(&[privkey
.get_signing_private_key()
.clone()])
.unwrap();
let mut writer = ArchiveWriter::from_config(file, config).expect("Writer init failed");
let data = vec![50, 60, 70, 80];
writer
.add_entry(
EntryName::from_path("my_file").unwrap(),
data.len() as u64,
data.as_slice(),
)
.unwrap();
let mut archive = writer.finalize().unwrap();
let flip_pos = 4000;
archive[flip_pos] ^= 0xFF;
let buf = std::io::Cursor::new(archive);
let config = ArchiveReaderConfig::with_signature_verification(&[pubkey
.get_signature_verification_public_key()
.clone()])
.without_encryption();
let reader_result = ArchiveReader::from_config(buf, config);
assert!(
matches!(reader_result, Err(Error::NoValidSignatureFound)),
"Verification should fail if MlDsa87 signature is corrupted"
);
}
#[test]
#[cfg(feature = "send")]
fn test_send() {
static_assertions::assert_cfg!(feature = "send");
static_assertions::assert_impl_all!(File: Send);
static_assertions::assert_impl_all!(ArchiveWriter<File>: Send);
static_assertions::assert_impl_all!(ArchiveReader<File>: Send);
}
}