mkit-server 0.5.0

Runtime-agnostic core of the mkit server: operation model, errors, runtime and telemetry vocabulary
Documentation
use std::fs;
use std::path::Path;

use mkit_core::hash::{hash, to_hex, to_hex_bytes};
use serde_json::{Value, json};

use super::*;
use crate::error::Code;

fn keys() -> TicketKeys {
    TicketKeys::new(vec![("current".into(), [7; 32]), ("old".into(), [8; 32])]).unwrap()
}

fn bytes_from_hex(hex: &str) -> Vec<u8> {
    hex.as_bytes()
        .chunks_exact(2)
        .map(|pair| u8::from_str_radix(std::str::from_utf8(pair).unwrap(), 16).unwrap())
        .collect()
}

#[test]
fn receipt_roundtrip_under_rotation_and_unknown_key() {
    let old = TicketKeys::new(vec![("old".into(), [8; 32])]).unwrap();
    let receipt = mint(&old, &[0x11; 32], 2, &[0x33; 32], 5, b"etag").unwrap();
    let parsed = verify(&keys(), &receipt).unwrap();
    assert_eq!(parsed.ticket_id, [0x11; 32]);
    assert_eq!(parsed.index, 2);
    assert_eq!(parsed.subtree, [0x33; 32]);
    assert_eq!(parsed.len, 5);
    assert_eq!(parsed.tag, b"etag");
    let unknown = TicketKeys::new(vec![("new".into(), [9; 32])]).unwrap();
    assert_eq!(
        verify(&unknown, &receipt).unwrap_err().code(),
        Code::InvalidArgument
    );
}

#[test]
fn golden_part_receipt_v1() {
    let keys = keys();
    let minted = mint(&keys, &[0x11; 32], 3, &[0x22; 32], 8_388_608, b"part-tag").unwrap();
    let mut forged = minted.clone();
    *forged.last_mut().unwrap() ^= 1;
    let mut unknown = minted.clone();
    unknown[2..9].copy_from_slice(b"unknown");
    let truncated = minted[..minted.len() - 1].to_vec();
    let failure = |name: &str, bytes: &[u8]| {
        json!({
            "name": name, "receipt_hex": to_hex_bytes(bytes), "code": "invalid_argument",
        })
    };
    let fixture = json!({
        "spec": "SPEC-TRANSPORT-CONNECT ยง7.6",
        "note": "TEST SECRET ONLY; BLAKE3 derive_key context mkit-server part receipt v1",
        "key_id": "current",
        "secret_hex": to_hex(&[7; 32]),
        "ticket_id": to_hex(&[0x11; 32]),
        "index": 3,
        "subtree": to_hex(&[0x22; 32]),
        "len": 8_388_608,
        "tag_hex": to_hex_bytes(b"part-tag"),
        "receipt_hex": to_hex_bytes(&minted),
        "failures": [
            failure("bad_mac", &forged),
            failure("unknown_key_id", &unknown),
            failure("malformed", &truncated),
        ],
    });
    let expected = serde_json::to_string_pretty(&fixture).unwrap() + "\n";
    let dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../tests/golden/uploads");
    let name = "part-receipt-v1.json";
    if std::env::var("UPDATE_GOLDEN").as_deref() == Ok("1") {
        fs::write(dir.join(name), &expected).unwrap();
        let manifest = fs::read_to_string(dir.join("MANIFEST.txt")).unwrap();
        let mut lines: Vec<_> = manifest
            .lines()
            .filter(|line| !line.starts_with(&format!("{name} ")))
            .map(str::to_owned)
            .collect();
        lines.push(format!("{name} {}", to_hex(&hash(expected.as_bytes()))));
        fs::write(dir.join("MANIFEST.txt"), lines.join("\n") + "\n").unwrap();
    }
    let stored = fs::read(dir.join(name)).unwrap();
    assert_eq!(stored, expected.as_bytes());
    let manifest = fs::read_to_string(dir.join("MANIFEST.txt")).unwrap();
    assert!(manifest.contains(&format!("{name} {}", to_hex(&hash(&stored)))));
    let parsed: Value = serde_json::from_slice(&stored).unwrap();
    let receipt = bytes_from_hex(parsed["receipt_hex"].as_str().unwrap());
    assert_eq!(verify(&keys, &receipt).unwrap().index, 3);
    for failure in parsed["failures"].as_array().unwrap() {
        let bytes = bytes_from_hex(failure["receipt_hex"].as_str().unwrap());
        assert_eq!(
            verify(&keys, &bytes).unwrap_err().code(),
            Code::InvalidArgument
        );
    }
}