tuftool 0.15.0

Utility for creating and signing The Update Framework (TUF) repositories
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: MIT OR Apache-2.0

mod test_utils;

use crate::test_utils::days;
use assert_cmd::cargo_bin_cmd;
use chrono::Utc;
use tempfile::TempDir;
use test_utils::dir_url;
use tough::{RepositoryLoader, TargetName};

#[tokio::test]
// Ensure we can read a repo created by the `tuftool` binary using the `tough` library
async fn create_command() {
    let timestamp_expiration = Utc::now().checked_add_signed(days(3)).unwrap();
    let timestamp_version: u64 = 1234;
    let snapshot_expiration = Utc::now().checked_add_signed(days(21)).unwrap();
    let snapshot_version: u64 = 5432;
    let targets_expiration = Utc::now().checked_add_signed(days(13)).unwrap();
    let targets_version: u64 = 789;
    let targets_input_dir = test_utils::test_data()
        .join("tuf-reference-impl")
        .join("targets");
    let root_json = test_utils::test_data().join("simple-rsa").join("root.json");
    let root_key = test_utils::test_data().join("snakeoil.pem");
    let repo_dir = TempDir::new().unwrap();

    // Create a repo using tuftool and the reference tuf implementation targets
    cargo_bin_cmd!("tuftool")
        .args([
            "create",
            "-t",
            targets_input_dir.to_str().unwrap(),
            "-o",
            repo_dir.path().to_str().unwrap(),
            "-k",
            root_key.to_str().unwrap(),
            "--root",
            root_json.to_str().unwrap(),
            "--targets-expires",
            targets_expiration.to_rfc3339().as_str(),
            "--targets-version",
            format!("{}", targets_version).as_str(),
            "--snapshot-expires",
            snapshot_expiration.to_rfc3339().as_str(),
            "--snapshot-version",
            format!("{}", snapshot_version).as_str(),
            "--timestamp-expires",
            timestamp_expiration.to_rfc3339().as_str(),
            "--timestamp-version",
            format!("{}", timestamp_version).as_str(),
        ])
        .assert()
        .success();

    // Load our newly created repo
    let repo = RepositoryLoader::new(
        &tokio::fs::read(root_json).await.unwrap(),
        dir_url(repo_dir.path().join("metadata")),
        dir_url(repo_dir.path().join("targets")),
    )
    .load()
    .await
    .unwrap();

    // Ensure we can read the targets
    let file1 = TargetName::new("file1.txt").unwrap();
    assert_eq!(
        test_utils::read_to_end(repo.read_target(&file1).await.unwrap().unwrap()).await,
        &b"This is an example target file."[..]
    );
    let file2 = TargetName::new("file2.txt").unwrap();
    assert_eq!(
        test_utils::read_to_end(repo.read_target(&file2).await.unwrap().unwrap()).await,
        &b"This is an another example target file."[..]
    );
    let file3 = TargetName::new("file3.txt").unwrap();
    assert_eq!(
        test_utils::read_to_end(repo.read_target(&file3).await.unwrap().unwrap()).await,
        &b"This is role1's target file."[..]
    );

    // Ensure the targets.json file is correct
    assert_eq!(repo.targets().signed.version.get(), targets_version);
    assert_eq!(repo.targets().signed.expires, targets_expiration);
    assert_eq!(repo.targets().signed.targets.len(), 3);
    assert_eq!(repo.targets().signed.targets[&file1].length, 31);
    assert_eq!(repo.targets().signed.targets[&file2].length, 39);
    assert_eq!(repo.targets().signed.targets[&file3].length, 28);
    assert_eq!(repo.targets().signatures.len(), 1);

    // Ensure the snapshot.json file is correct
    assert_eq!(repo.snapshot().signed.version.get(), snapshot_version);
    assert_eq!(repo.snapshot().signed.expires, snapshot_expiration);
    assert_eq!(repo.snapshot().signed.meta.len(), 1);
    assert_eq!(
        repo.snapshot().signed.meta["targets.json"].version.get(),
        targets_version
    );
    assert_eq!(repo.snapshot().signatures.len(), 1);

    // Ensure the timestamp.json file is correct
    assert_eq!(repo.timestamp().signed.version.get(), timestamp_version);
    assert_eq!(repo.timestamp().signed.expires, timestamp_expiration);
    assert_eq!(repo.timestamp().signed.meta.len(), 1);
    assert_eq!(
        repo.timestamp().signed.meta["snapshot.json"].version.get(),
        snapshot_version
    );
    assert_eq!(repo.snapshot().signatures.len(), 1);
}

#[test]
// Ensure that the create command fails if none of the keys we give it match up with root.json.
fn create_with_incorrect_key() {
    let base = test_utils::test_data().join("tuf-reference-impl");
    let root_json = base.join("metadata").join("1.root.json");
    let bad_key = test_utils::test_data().join("snakeoil.pem");

    // Call the create command passing a single key that cannot be found in root.json. Assert that
    // the command fails.
    cargo_bin_cmd!("tuftool")
        .args([
            "create",
            "-t",
            "input/dir/does/not/matter",
            "-o",
            "output/dir/does/not/matter",
            "-k",
            bad_key.to_str().unwrap(),
            "--root",
            root_json.to_str().unwrap(),
            "--targets-expires",
            "in 7 days",
            "--targets-version",
            "1234",
            "--snapshot-expires",
            "in 7 days",
            "--snapshot-version",
            "1234",
            "--timestamp-expires",
            "in 7 days",
            "--timestamp-version",
            "1234",
        ])
        .assert()
        .failure();
}

#[test]
// Ensure we fail if no key is provided
fn create_with_no_key() {
    // Misuse the tuftool create command by not passing any keys and assert failure
    cargo_bin_cmd!("tuftool")
        .args([
            "create",
            "-t",
            "/input/dir/does/not/matter",
            "-o",
            "/output/dir/does/not/matter",
            "--root",
            "/root/does/not/matter",
            "--targets-expires",
            "in 7 days",
            "--targets-version",
            "1234",
            "--snapshot-expires",
            "in 7 days",
            "--snapshot-version",
            "1234",
            "--timestamp-expires",
            "in 7 days",
            "--timestamp-version",
            "1234",
        ])
        .assert()
        .failure();
}