wolfcose 0.1.0

Safe Rust API for wolfSSL wolfCOSE.
#![allow(missing_docs)]

use wolfcose::{
    from_slice, to_vec, Algorithm, CborDeserialize, CborSerialize, CoseKeyBuilder, CoseMac0Message,
    Encrypt0Builder, Mac0Builder, PayloadMode,
};

const DRONE_ID: &str = "drone-17";

#[derive(Debug, PartialEq, CborSerialize, CborDeserialize)]
#[cbor(rename_all = "snake_case")]
struct TelemetryFrame {
    drone_id: String,
    sequence: u64,
    unix_ms: u64,
    position: Position,
    battery_percent: u8,
    flight_mode: FlightMode,
}

#[derive(Debug, PartialEq, CborSerialize, CborDeserialize)]
#[cbor(rename_all = "snake_case")]
struct Position {
    lat_e7: i32,
    lon_e7: i32,
    alt_cm: i32,
    ground_speed_cm_s: u32,
}

#[derive(Debug, PartialEq, CborSerialize, CborDeserialize)]
#[cbor(rename_all = "snake_case")]
enum FlightMode {
    Standby,
    Mission,
    ReturnHome,
}

#[derive(Debug, PartialEq, CborSerialize, CborDeserialize)]
#[cbor(rename_all = "snake_case")]
struct ControlFrame {
    drone_id: String,
    sequence: u64,
    command: ControlCommand,
}

#[derive(Debug, PartialEq, CborSerialize, CborDeserialize)]
#[cbor(rename_all = "snake_case")]
enum ControlCommand {
    Arm,
    Disarm,
    SetWaypoint {
        lat_e7: i32,
        lon_e7: i32,
        alt_cm: i32,
    },
    ReturnHome,
}

fn main() -> wolfcose::Result<()> {
    // Demo-only key material. Production systems need provisioned per-device
    // keys and a nonce/sequence policy that prevents IV reuse for each key.
    let telemetry_key = CoseKeyBuilder::symmetric([0x71u8; 16])
        .algorithm(Algorithm::A128GCM)
        .kid(b"drone-17-telemetry")
        .build()?;
    let control_key = CoseKeyBuilder::symmetric([0x91u8; 32])
        .algorithm(Algorithm::HMAC256)
        .kid(b"drone-17-control")
        .build()?;

    let telemetry = TelemetryFrame {
        drone_id: DRONE_ID.to_owned(),
        sequence: 42,
        unix_ms: 1_725_000_000_123,
        position: Position {
            lat_e7: 377_749_000,
            lon_e7: -1_224_194_000,
            alt_cm: 12_340,
            ground_speed_cm_s: 850,
        },
        battery_percent: 88,
        flight_mode: FlightMode::Mission,
    };

    let telemetry_plaintext = to_vec(&telemetry)?;
    let telemetry_aad = aad("telemetry", DRONE_ID, telemetry.sequence);
    let telemetry_iv = iv_from_sequence(*b"TELM", telemetry.sequence);
    let encrypted_telemetry = Encrypt0Builder::new()
        .key(&telemetry_key)
        .algorithm(Algorithm::A128GCM)
        .iv(&telemetry_iv)
        .external_aad(&telemetry_aad)
        .payload(PayloadMode::Attached(&telemetry_plaintext))
        .encrypt_to_vec()?;

    let mut decrypted = vec![0; telemetry_plaintext.len() + 32];
    let mut decryptor = Encrypt0Builder::new()
        .key(&telemetry_key)
        .external_aad(&telemetry_aad);
    let output = decryptor.decrypt_into(&encrypted_telemetry, None, &mut decrypted)?;
    let decoded_telemetry: TelemetryFrame = from_slice(&decrypted[..output.plaintext_len])?;
    assert_eq!(decoded_telemetry, telemetry);

    let command = ControlFrame {
        drone_id: DRONE_ID.to_owned(),
        sequence: 43,
        command: ControlCommand::SetWaypoint {
            lat_e7: 377_750_000,
            lon_e7: -1_224_190_000,
            alt_cm: 15_000,
        },
    };
    let command_payload = to_vec(&command)?;
    let command_aad = aad("control", DRONE_ID, command.sequence);
    let signed_command = Mac0Builder::new()
        .key(&control_key)
        .algorithm(Algorithm::HMAC256)
        .kid(b"drone-17-control")
        .external_aad(&command_aad)
        .payload(PayloadMode::Attached(&command_payload))
        .mac_to_vec()?;

    let parsed = CoseMac0Message::parse(&signed_command)?;
    assert!(parsed.payload_attached());
    assert_eq!(parsed.unprotected().kid(), Some(&b"drone-17-control"[..]));

    let mut verifier = Mac0Builder::new()
        .key(&control_key)
        .external_aad(&command_aad);
    let verified = verifier.verify(&signed_command, None)?;
    let verified_payload = verified.payload.expect("attached control payload");
    let decoded_command: ControlFrame = from_slice(verified_payload)?;
    assert_eq!(decoded_command, command);

    println!(
        "encrypted_telemetry={} signed_command={} command_kid={:?}",
        encrypted_telemetry.len(),
        signed_command.len(),
        parsed.unprotected().kid().map(String::from_utf8_lossy)
    );
    Ok(())
}

fn aad(channel: &str, drone_id: &str, sequence: u64) -> Vec<u8> {
    format!("drone-protocol/v1:{channel}:{drone_id}:{sequence}").into_bytes()
}

fn iv_from_sequence(prefix: [u8; 4], sequence: u64) -> [u8; 12] {
    // Demonstration IV derivation for a monotonic sequence. Real deployments
    // must guarantee uniqueness for every encryption under the same key.
    let mut iv = [0u8; 12];
    iv[..4].copy_from_slice(&prefix);
    iv[4..].copy_from_slice(&sequence.to_be_bytes());
    iv
}