Skip to main content

Crate mla

Crate mla 

Source
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-truncated in 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 writer
  • mlar: a Rust cli utility wrapping mla for common actions (create, list, extract…)
  • doc : advanced documentation related to MLA (e.g. format specification)
  • bindings : bindings for other languages
  • samples : test assets
  • mla-fuzz-afl : a Rust utility to fuzz mla
  • .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
ArchiveReader and ArchiveWriter configuration
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§

ArchiveReader
Use this to read an archive
ArchiveWriter
Use this to write an archive
TruncatedArchiveReader
Use this to convert a truncated archive to one that can be opened with ArchiveReader, eventually loosing some content and security or performance properties.