moq-secure 0.1.3

Encryption and signing for Media over Quic (MoQ)
Documentation
mod common;

use common::{hex_decode, read_vectors};

use ed25519_dalek::{SigningKey, VerifyingKey};
use moq_secure::{
    decrypt_frame,
    encrypt_frame,
    InMemoryKeyStore,
};

#[test]
fn rust_nonce_vectors_match_js() {
    let vectors = read_vectors();

    for vector in vectors.nonce_vectors {
        let ctr: u64 = vector.ctr.parse().expect("valid counter");
        let actual = moq_secure::derive_nonce12(vector.key_id, ctr);

        assert_eq!(
            hex::encode(actual),
            vector.nonce,
            "nonce mismatch for key_id={} ctr={}",
            vector.key_id,
            ctr,
        );
    }
}

#[test]
fn rust_consumes_js_frames() {
    let vectors = read_vectors();

    let key_bytes = hex_decode(&vectors.aead_key);
    let key: [u8; 32] = key_bytes.try_into().expect("32-byte key");

    let seed_bytes = hex_decode(&vectors.ed25519_seed);
    let seed: [u8; 32] = seed_bytes.try_into().expect("32-byte seed");

    let signing_key = SigningKey::from_bytes(&seed);
    let verifying_key = VerifyingKey::from(&signing_key);

    let mut key_store = InMemoryKeyStore::empty();
    key_store.set_key(7, key);

    let mut lease_remaining = 0u8;

    for vector in vectors.frames {
        let frame_bytes = hex_decode(&vector.frame);
        let expected_plaintext = hex_decode(&vector.plaintext);

        let plaintext = decrypt_frame(
            &key_store,
            &verifying_key,
            &mut lease_remaining,
            &frame_bytes,
        )
        .unwrap_or_else(|error| {
            panic!("Rust failed to consume {}: {error}", vector.name)
        });

        assert_eq!(
            plaintext,
            expected_plaintext,
            "plaintext mismatch for {}",
            vector.name,
        );

        assert_eq!(
            lease_remaining,
            vector.lease,
            "lease mismatch after {}",
            vector.name,
        );
    }
}

#[test]
fn rust_produces_the_same_frames_as_js() {
    let vectors = read_vectors();

    let key_bytes = hex_decode(&vectors.aead_key);
    let key: [u8; 32] = key_bytes.try_into().expect("32-byte key");

    let seed_bytes = hex_decode(&vectors.ed25519_seed);
    let seed: [u8; 32] = seed_bytes.try_into().expect("32-byte seed");

    let signing_key = SigningKey::from_bytes(&seed);

    let mut key_store = InMemoryKeyStore::empty();
    key_store.set_key(7, key);

    for vector in vectors.frames {
        let frame_bytes = hex_decode(&vector.frame);

        let parsed = moq_secure::Frame::parse(&frame_bytes)
            .expect("JS frame should parse in Rust");

        let generated = encrypt_frame(
            &key_store,
            &signing_key,
            parsed.header.key_id,
            parsed.header.ctr,
            parsed.header.n_signed,
            parsed.header.sig_flag == 1,
            parsed.header.encrypted,
            vector.pad_len,
            &hex_decode(&vector.plaintext),
        )
        .expect("Rust encryption should succeed");

        assert_eq!(
            generated.serialize(),
            frame_bytes,
            "wire mismatch for {}",
            vector.name,
        );
    }
}