vodozemac

repository·main·Indexed 18 days ago

https://github.com/matrix-org/vodozemac

A pure Rust implementation of the Olm (Double Ratchet) and Megolm cryptographic ratchets used for end-to-end encryption in the Matrix protocol. It provides a high-level API for secure communication channels, including SAS (Short Authentication String) and MSC4108 integration, serving as a modern alternative to libolm.

Tokens
15.3K
Snippets
59
Records
66
Agent score
63%

What's inside vodozemac

  1. What is vodozemac?

    main

    vodozemac is a pure Rust implementation of the Olm (Double Ratchet) and Megolm cryptographic ratchets. It provides a high-level API for creating secure communication channels and is designed as a modern alternative to libolm.

    Key features include:

    • Olm Ratchet: For one-to-one secure communication.
    • Megolm Ratchet: For group communication.
    • SAS (Short Authentication String): For verifying secure connections.
    • MSC4108 Integration: Implements the encryption scheme outlined in MSC4108, useful for Matrix client development.
  2. Install requirements for AFL fuzzing

    main

    To set up the AFL-based fuzzing environment for vodozemac, you must install a nightly Rust compiler and the cargo-afl tool. Follow these steps:

    1. Install the nightly Rust toolchain using rustup.
    2. Install cargo-afl via cargo.

    Refer to the Rust Fuzz AFL docs for a complete setup guide.

    rustup toolchain install nightly
    cargo install cargo-afl
  3. Finalize release, tag, and publish from main

    main

    Once a release branch has been merged into main, you must switch to the main branch to perform the actual tagging, publishing to crates.io, and pushing the tag to the repository.

    # 1. Switch to main and sync
    git switch main
    git pull
    
    # 2. Create and push the tag
    cargo release tag --execute
    
    # 3. Publish to crates.io
    cargo release publish --execute
    
    # 4. Push the tag to the repository
    cargo release push --execute
  4. Run the secret zeroization test

    main

    This test verifies that sensitive information is correctly zeroized in memory using a GDB script. Note that this test requires a specific branch of the repository (contrib/test-zeroization) where certain modules, structs, and methods have been made public specifically for testing purposes.

    To run the test, follow these steps:

    1. Switch to the test branch: git checkout contrib/test-zeroization
    2. Navigate to the directory: cd contrib/test-zeroization
    3. Execute the test using make: make

    Expected Output:

    • OK: The test was successful and sensitive information was zeroized.
    • FAIL: The sensitive information was not zeroized correctly.
    git checkout contrib/test-zeroization
    cd contrib/test-zeroization
    make
  5. Build and run AFL fuzz harnesses

    main

    Fuzzing is performed by navigating to a specific harness subdirectory and using the cargo afl command.

    To fuzz a harness (e.g., olm-message-decoding):

    1. Change directory into the harness folder.
    2. Build the harness using cargo afl build.
    3. Start the fuzzing process using cargo afl fuzz, specifying the input directory (-i), output directory (-o), and the path to the compiled target binary.
    cd afl/olm-message-decoding
    cargo afl build
    cargo afl fuzz -i in -o out target/debug/olm-message-decoding
  6. Release and publish vodozemac

    main

    Because the vodozemac repository requires pull requests for all pushes, the release process uses a multi-step workflow involving a release branch and cargo-release. You cannot release directly from main. Instead, you must prepare the release on a branch, merge it via a PR, and then perform the final tagging and publishing from main.

    ### Summary of Workflow
    1. Create a release branch.
    2. Run `cargo release` with `--no-publish --no-tag --no-push` to prepare files.
    3. Push the branch and open a PR.
    4. Merge the PR into `main`.
    5. Switch to `main`, pull the changes, and run `cargo release tag --execute`.
    6. Run `cargo release publish --execute`.
    7. Run `cargo release push --execute`.
  7. Prepare a release on a new branch

    main

    To prepare a release without immediately publishing or tagging, create a dedicated release branch and run cargo release with specific flags to prevent premature actions. This step updates README.md, prepends the CHANGELOG.md using git cliff, and bumps the version in Cargo.toml.

    # 1. Create the release branch
    git switch -c release-x.y.z
    
    # 2. Prepare the release (replace major|minor|patch|rc with the desired version type)
    cargo release --no-publish --no-tag --no-push --execute major|minor|patch|rc
    
    # 3. Push the branch and open a PR
    git push --set-upstream origin/release-x.y.z
  8. Overview of vodozemac

    main

    vodozemac is a Rust implementation of libolm, a cryptographic library used for end-to-end encryption in Matrix. It provides implementations for:

    • Olm: A Double Ratchet algorithm implementation for 1-to-1 private communication with perfect forward secrecy and self-healing properties.
    • Megolm: An AES-based single ratchet for group conversations, allowing efficient encryption for many participants by sharing a symmetric ratchet.
    • SAS (Short Authentication Strings): For verifying identities.
    • Pickling: Mechanisms to serialize and restore internal cryptographic states (e.g., for device dehydration).

    Feature Flags

    • low-level-api (default: off): Exposes low-level structs and functions for advanced use cases. Use with extreme caution as incorrect usage can break sessions.
    • precomputed-tables (default: on): Enables curve25519-dalek precomputed basepoint tables to speed up key generation and Ed25519 signing. This adds ~40 KB to the binary size. To disable for size-sensitive builds, use:
    vodozemac = { version = "0.10.0", default-features = false, features = ["libolm-compat"] }
  9. Manage Olm cryptographic keys with the Account struct

    main

    The Account struct is the central manager for all cryptographic keys on a device. It handles identity keys (Ed25519 for signing and Curve25519 for Diffie-Hellman), one-time keys (OTKs) for establishing new sessions, and fallback keys.

    Key capabilities include:

    • Creating a new account with fresh random keys.
    • Signing messages with the Ed25519 fingerprint key.
    • Generating and managing one-time keys.
    • Creating both inbound and outbound Olm sessions.
    • Serializing/deserializing the account state (pickling).
    • Creating and restoring 'dehydrated devices' (per MSC3814).
    use vodozemac::olm::Account;
    
    // Create a new account
    let mut account = Account::new();
    
    // Sign a message
    let signature = account.sign(b"message to sign");
    
    // Get public identity keys
    let identity = account.identity_keys();
    // identity.ed25519 and identity.curve25519
  10. Use low-level Cipher encryption and MAC methods

    main

    The Cipher struct provides low-level primitives for manual encryption and authentication. These must be used carefully in the correct order to ensure security.

    Encryption Workflow

    1. Call encrypt(plaintext) to get the ciphertext.
    2. Call mac(ciphertext) to generate a Mac object for that ciphertext.

    Warning: encrypt does not provide authentication. You must generate and verify a MAC separately to prevent padding oracle attacks or data tampering.

    Decryption Workflow

    1. Call verify_mac(message, tag) or verify_truncated_mac(message, tag) to validate integrity.
    2. Only if verification succeeds, call decrypt(ciphertext).

    Warning: Calling decrypt without prior MAC verification is insecure.

    // Manual Encryption
    let ciphertext = cipher.encrypt(plaintext);
    let mac = cipher.mac(&ciphertext);
    
    // Manual Decryption
    cipher.verify_mac(&ciphertext, &mac)?;
    let plaintext = cipher.decrypt(&ciphertext)?;
  11. How pickling and unpickling works in vodozemac

    main

    vodozemac supports serializing internal states into a "pickle" to support features like device dehydration. There are two formats:

    1. Legacy pickles: A binary format used by libolm. vodozemac supports unpickling these for interoperability, but not creating them.
    2. Modern pickles: Uses Serde. The exact format is not mandated, but you can use any Serde-supported format (like JSON).

    Important: Serialization Pattern

    Core structs like olm::Account, olm::Session, megolm::GroupSession, and megolm::InboundGroupSession do not implement serde::Serialize directly. To serialize them, you must first call the .pickle() method, which returns a special serializable struct. To restore the original struct, call .unpickle() or convert the pickle struct back into the original type.

    Example: Encrypting and restoring an Olm Account

    use anyhow::Result;
    use vodozemac::olm::{Account, AccountPickle};
    
    const PICKLE_KEY: [u8; 32] = [0u8; 32];
    
    fn main() -> Result<()>{
        let mut account = Account::new();
    
        account.generate_one_time_keys(10);
        account.generate_fallback_key();
    
        // Serialize and encrypt the account state
        let pickle = account.pickle().encrypt(&PICKLE_KEY);
    
        // Restore the account from the encrypted pickle
        let account2: Account = AccountPickle::from_encrypted(&pickle, &PICKLE_KEY)?.into();
    
        assert_eq!(account.identity_keys(), account2.identity_keys());
    
        Ok(())
    }