Expand description
Multi Layer Archive (MLA)
MLA is an archive file format with the following features:
- Support for traditional and post-quantum encryption hybridization with asymmetric keys (HPKE with AES256-GCM and a KEM based on an hybridization of X25519 and post-quantum ML-KEM 1024)
- Support for traditional and post-quantum signature hybridization
- Support for compression (based on
rust-brotli) - Streamable archive creation:
- An archive can be built even over a data-diode
- An entry can be added through chunks of data, without initially knowing the final size
- Entry chunks can be interleaved (one can add the beginning of an entry, start a second one, and then continue adding the first entry’s parts)
- Architecture agnostic and portable to some extent (written entirely in Rust)
- Archive reading is seekable, even if compressed or encrypted. An entry can be accessed in the middle of the archive without reading from the beginning
- If truncated, archives can be
clean-truncatedin order to recover its content to some extent. Two modes are available:- Authenticated recover (default): only authenticated (as in AEAD, there is no signature verification) encrypted chunks of data are retrieved
- Unauthenticated recover: authenticated and unauthenticated encrypted chunks of data are retrieved. Use at your own risk.
- Arguably less prone to bugs, especially while parsing an untrusted archive (Rust safety)
§Security
For security-related information, please refer to our README security section.
§Repository
The MLA repository contains:
mla: the Rust library implementing MLA reader and writermlar: a Rust cli utility wrappingmlafor common actions (create, list, extract…)doc: advanced documentation related to MLA (e.g. format specification)bindings: bindings for other languagessamples: test assetsmla-fuzz-afl: a Rust utility to fuzzmla.github: Continuous Integration needs
§Quick API usage
- Create an archive, with compression, encryption and signature:
use mla::ArchiveWriter;
use mla::config::ArchiveWriterConfig;
use mla::crypto::mlakey::{MLAPrivateKey, MLAPublicKey};
use mla::entry::EntryName;
// for encryption
const RECEIVER_PUB_KEY: &[u8] =
include_bytes!("../../samples/test_mlakey_archive_v2_receiver.mlapub");
// for signing
const SENDER_PRIV_KEY: &[u8] =
include_bytes!("../../samples/test_mlakey_archive_v2_sender.mlapriv");
fn main() {
// For encryption, load the needed receiver public key
let (pub_enc_key, _pub_sig_verif_key) = MLAPublicKey::deserialize_public_key(RECEIVER_PUB_KEY)
.unwrap()
.get_public_keys();
// For signing, load the needed sender private key
let (_priv_dec_key, priv_sig_key) = MLAPrivateKey::deserialize_private_key(SENDER_PRIV_KEY)
.unwrap()
.get_private_keys();
// In production, you may want to zeroize the real `SENDER_PRIV_KEY` or
// associated temporary values of its `Read` implementation here.
// Create an MLA Archive - Output only needs the Write trait.
// Here, a Vec is used but it would tipically be a `File` or a network socket.
let mut buf = Vec::new();
// The use of multiple keys is supported
let config =
ArchiveWriterConfig::with_encryption_with_signature(&[pub_enc_key], &[priv_sig_key])
.unwrap();
// Create the Writer
let mut mla = ArchiveWriter::from_config(&mut buf, config).unwrap();
// Add a file
// This creates an entry named "a/filename" (without first "/"), See `EntryName::from_path`
mla.add_entry(
EntryName::from_path("/a/filename").unwrap(),
4,
&[0, 1, 2, 3][..],
)
.unwrap();
// Complete the archive
mla.finalize().unwrap();
}- Add entries part per part, in a “concurrent” fashion:
use mla::ArchiveWriter;
use mla::config::ArchiveWriterConfig;
use mla::crypto::mlakey::{MLAPrivateKey, MLAPublicKey};
use mla::entry::EntryName;
// for encryption
const RECEIVER_PUB_KEY: &[u8] =
include_bytes!("../../samples/test_mlakey_archive_v2_receiver.mlapub");
// for signing
const SENDER_PRIV_KEY: &[u8] =
include_bytes!("../../samples/test_mlakey_archive_v2_sender.mlapriv");
fn main() {
// For encryption, load the needed receiver public key
let (pub_enc_key, _pub_sig_verif_key) = MLAPublicKey::deserialize_public_key(RECEIVER_PUB_KEY)
.unwrap()
.get_public_keys();
// For signing, load the needed sender private key
let (_priv_dec_key, priv_sig_key) = MLAPrivateKey::deserialize_private_key(SENDER_PRIV_KEY)
.unwrap()
.get_private_keys();
// In production, you may want to zeroize the real `SENDER_PRIV_KEY` or
// associated temporary values of its `Read` implementation here.
// Create an MLA Archive - Output only needs the Write trait
let mut buf = Vec::new();
let config =
ArchiveWriterConfig::with_encryption_with_signature(&[pub_enc_key], &[priv_sig_key])
.unwrap();
// Create the Writer
let mut mla = ArchiveWriter::from_config(&mut buf, config).unwrap();
// An entry is tracked by an id, and follows this API's call order:
// 1. id = start_entry(entry_name);
// 2. append_entry_content(id, content length, content (impl Read))
// 2-bis. repeat 2.
// 3. end_entry(id)
// Start an entry and add content
let id_entry1 = mla
.start_entry(EntryName::from_path("name1").unwrap())
.unwrap();
let entry1_part1 = vec![11, 12, 13, 14];
mla.append_entry_content(
id_entry1,
entry1_part1.len() as u64,
entry1_part1.as_slice(),
)
.unwrap();
// Start a second entry and add content
let id_entry2 = mla
.start_entry(EntryName::from_path("name2").unwrap())
.unwrap();
let entry2_part1 = vec![21, 22, 23, 24];
mla.append_entry_content(
id_entry2,
entry2_part1.len() as u64,
entry2_part1.as_slice(),
)
.unwrap();
// Add an entry as a whole
let entry3 = vec![31, 32, 33, 34];
mla.add_entry(
EntryName::from_path("name3").unwrap(),
entry3.len() as u64,
entry3.as_slice(),
)
.unwrap();
// Add new content to the first entry
let entry1_part2 = vec![15, 16, 17, 18];
mla.append_entry_content(
id_entry1,
entry1_part2.len() as u64,
entry1_part2.as_slice(),
)
.unwrap();
// Mark still opened entries as finished
mla.end_entry(id_entry1).unwrap();
mla.end_entry(id_entry2).unwrap();
// Complete the archive
mla.finalize().unwrap();
}- Read entries from an archive
use mla::ArchiveReader;
use mla::config::ArchiveReaderConfig;
use mla::crypto::mlakey::{MLAPrivateKey, MLAPublicKey};
use mla::entry::EntryName;
use std::io;
const DATA: &[u8] = include_bytes!("../../samples/archive_v2.mla");
// for decryption
const RECEIVER_PRIV_KEY: &[u8] =
include_bytes!("../../samples/test_mlakey_archive_v2_receiver.mlapriv");
// for signature verification
const SENDER_PUB_KEY: &[u8] = include_bytes!("../../samples/test_mlakey_archive_v2_sender.mlapub");
fn main() {
// For decryption, load the needed receiver private key
let (priv_dec_key, _priv_sig_key) = MLAPrivateKey::deserialize_private_key(RECEIVER_PRIV_KEY)
.unwrap()
.get_private_keys();
// For signature verification, load the needed sender public key
let (_pub_enc_key, pub_sig_verif_key) = MLAPublicKey::deserialize_public_key(SENDER_PUB_KEY)
.unwrap()
.get_public_keys();
// Specify the key for the Reader
let config = ArchiveReaderConfig::with_signature_verification(&[pub_sig_verif_key])
.with_encryption(&[priv_dec_key]);
// Read from buf, which needs Read + Seek
let buf = io::Cursor::new(DATA);
let mut mla_read = ArchiveReader::from_config(buf, config).unwrap().0;
// Get a file
let mut entry = mla_read
.get_entry(EntryName::from_path("simple").unwrap()) // or EntryName::from_arbitrary_bytes if name is not representing a file path
.unwrap() // An error can be raised (I/O, decryption, etc.)
.unwrap(); // Option(entry), as the entry might not exist in the archive
// Get back its name, size, and data
// display name interpreted as file path and escape to avoid
// issues with terminal escape sequences for example
println!(
"{} ({} bytes)",
entry.name.to_pathbuf_escaped_string().unwrap(),
entry.get_size()
);
let mut output = Vec::new();
std::io::copy(&mut entry.data, &mut output).unwrap();
// Get back the list of entries names in the archive without
// interpreting them as file paths, so no need to unwrap as
// it cannot fail. ASCII slash is encoded too.
for entry_name in mla_read.list_entries().unwrap() {
println!("{}", entry_name.raw_content_to_escaped_string());
}
}Modules§
- config
ArchiveReaderandArchiveWriterconfiguration- crypto
- Crypto related things like key generation/serialization/deserialization
- entry
- Handling of archive entries and their name
- errors
- Error structs
- helpers
- Some things you may find useful
- info
- Extract information from MLA Header
Structs§
- Archive
Reader - Use this to read an archive
- Archive
Writer - Use this to write an archive
- Truncated
Archive Reader - Use this to convert a truncated archive to one that can be opened with
ArchiveReader, eventually loosing some content and security or performance properties.