lpc55 0.2.1

Host-side tooling to interact with LPC55 chips via the ROM bootloader
Documentation
use std::fs::{self, File};
use std::io::Write;
use std::process::Command;

use tempfile::tempdir;
extern crate hex;

use assert_cmd::prelude::*;
use predicates::prelude::*;

#[test]
fn factory_generates_correctly() {
    let dir = tempdir().unwrap();

    let cfgfile_path = dir.path().join("cfg.toml");
    let mut cfgfile = File::create(cfgfile_path.clone()).unwrap();

    let binfile_path = dir.path().join("factory.bin");

    writeln!(
        cfgfile,
        r#"
[factory-settings]
usb-id = {{ vid = 0x1209, pid = 0xb000 }}
rot-fingerprint = "C7EE3124 DC87EAAE 5A6F7FCC B6C2E458 706835C9 9D5D7082 4EFFAC0F 12A5A875"
debug-access = "Disabled"

[factory-settings.boot-configuration]
failure-port  = 1
failure-pin = 21
speed = "48MHz"
mode = "Usb"

[factory-settings.secure-boot-configuration]
secure-boot-enabled = true
puf-enrollment-disabled = false
puf-keycode-generation-disabled = false
trustzone-mode = "FromImageHeader"
use-rsa4096-keys = false
dice-computation-disabled = true
"#
    )
    .unwrap();

    let mut cmd = Command::cargo_bin("lpc55").unwrap();
    cmd.arg("configure")
        .arg("factory-settings")
        .arg("-o")
        .arg(binfile_path.clone())
        .arg(cfgfile_path);

    cmd.assert().success();

    let data = fs::read(binfile_path).expect("Unable to read output factory file");

    assert_eq!(data.len(), 512);

    // boot config
    assert_eq!(data[0..4], [0x10, 0x01, 0x00, 0xa9]);
    // usb vid pid
    assert_eq!(data[8..12], [0x09, 0x12, 0x00, 0xb0]);

    // nothing
    assert_eq!(data[0x0c..0x10], [0x00u8; 4]);

    // debug policies
    assert_eq!(
        data[0x10..0x18],
        [0xff, 0x83, 0x00, 0x7c, 0x00, 0x00, 0xff, 0xff]
    );

    // nothing
    assert_eq!(data[0x18..0x1c], [0x00u8; 0x1c - 0x18]);

    // secure boot cfg
    assert_eq!(data[0x1c..0x20], [0xc0, 0x00, 0x00, 0xc0]);

    // nothing
    assert_eq!(data[0x20..0x50], [0x00u8; 0x50 - 0x20]);

    // rotkh
    assert_eq!(
        data[0x50..0x70],
        hex::decode("C7EE3124DC87EAAE5A6F7FCCB6C2E458706835C99D5D70824EFFAC0F12A5A875").unwrap()
    );

    // nothing
    assert_eq!(data[0x70..0x200], [0x00u8; 0x200 - 0x70]);
}

#[test]
fn customer_generates_correctly() {
    let dir = tempdir().unwrap();

    let cfgfile_path = dir.path().join("cfg.toml");
    let mut cfgfile = File::create(cfgfile_path.clone()).unwrap();

    let binfile_path = dir.path().join("customer.bin");

    writeln!(
        cfgfile,
        r#"
[customer-settings]
customer-version = 0x030201
nonsecure-firmware-version = 0x060504
secure-firmware-version = 0x090807
rot-keys-status = ["Enabled", "Enabled", "Enabled", "Enabled"]
"#
    )
    .unwrap();

    let mut cmd = Command::cargo_bin("lpc55").unwrap();
    cmd.arg("configure")
        .arg("customer-settings")
        .arg("-o")
        .arg(binfile_path.clone())
        .arg(cfgfile_path);

    cmd.assert().success();

    let data = fs::read(binfile_path).expect("Unable to read output customer file");

    assert_eq!(data.len(), 512);
    // nothing
    assert_eq!(data[0..4], [0u8; 4]);
    // versions
    assert_eq!(data[4..16], [1, 2, 3, 0, 7, 8, 9, 0, 4, 5, 6, 0]);
    // nothing
    assert_eq!(data[0x10..0x18], [0u8; 8]);
    // revocations
    assert_eq!(data[0x18..0x1c], [0x55, 0, 0, 0]);
    // nothing
    assert_eq!(data[0x1c..0x20], [0u8; 4]);
    // debug policies
    assert_eq!(data[0x20..0x28], [0u8; 8]);
    // nothing
    assert_eq!(data[0x28..0x200], [0u8; 0x200 - 0x28]);
}

#[test]
fn custom_debug_policy() {
    let dir = tempdir().unwrap();

    let cfgfile_path = dir.path().join("cfg.toml");
    let mut cfgfile = File::create(cfgfile_path.clone()).unwrap();

    let binfile_path = dir.path().join("customer.bin");

    // Needs to all be inline due to serde bug
    // https://github.com/alexcrichton/toml-rs/issues/225
    writeln!(cfgfile, r#"
[factory-settings]
debug-access = {{ Custom = {{ nonsecure-noninvasive = "Disabled", secure-invasive = "Authenticate", jtag-tap = "Enabled" }} }}
"#).unwrap();

    let mut cmd = Command::cargo_bin("lpc55").unwrap();
    cmd.arg("configure")
        .arg("factory-settings")
        .arg("-o")
        .arg(binfile_path.clone())
        .arg(cfgfile_path);

    cmd.assert().success();

    let data = fs::read(binfile_path).expect("Unable to read output customer file");

    // debug policies, default Enabled
    assert_eq!(
        data[0x10..0x18],
        [0xf7, 0x03, 0x08, 0xfc, 0xf6, 0x03, 0x09, 0xfc]
    );
}

#[test]
fn sha256_seal() {
    let dir = tempdir().unwrap();

    let cfgfile_path = dir.path().join("cfg.toml");
    let mut cfgfile = File::create(cfgfile_path.clone()).unwrap();

    let binfile_path = dir.path().join("factory.bin");

    // Needs to all be inline due to serde bug
    // https://github.com/alexcrichton/toml-rs/issues/225
    writeln!(
        cfgfile,
        r#"
[factory-settings]
usb-id = {{ vid = 0x1209, pid = 0xb000 }}
rot-fingerprint = "C7EE3124 DC87EAAE 5A6F7FCC B6C2E458 706835C9 9D5D7082 4EFFAC0F 12A5A875"
debug-access = "Disabled"
seal = true
"#
    )
    .unwrap();

    let mut cmd = Command::cargo_bin("lpc55").unwrap();
    cmd.arg("configure")
        .arg("factory-settings")
        .arg("-o")
        .arg(binfile_path.clone())
        .arg(cfgfile_path);

    cmd.assert().success();

    let data = fs::read(binfile_path).expect("Unable to read output factory file");

    // sha256
    assert_eq!(
        data[480..512],
        hex::decode("11c38ce9fa006c6fda5f894c5ab679bd0a6dc01dfac2dc3e25a7670bd1e1752d").unwrap()
    );
}

#[test]
fn rotkh() {
    let mut cmd = Command::cargo_bin("lpc55").unwrap();
    cmd.arg("fingerprint-certificates")
        .arg("./example-cfgs/example-cfg.toml");

    cmd.assert().success().stdout(predicate::str::contains(
        "D826E2FD 44F5C254 BC58C62E BF96A938 95C19DC2 25810C95 C8B9E6FD 9F7CC9CB",
    ));
}