tough 0.22.0

The Update Framework (TUF) repository client
Documentation
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: MIT OR Apache-2.0

use tempfile::TempDir;
use test_utils::{dir_url, read_to_end, test_data};
use tough::{FilesystemTransport, Limits, Repository, RepositoryLoader, TargetName};

mod test_utils;

/// Test that `tough` can process repositories generated by [`tuf`], the reference Python
/// implementation using the `load_default` function.
///
/// [`tuf`]: https://github.com/theupdateframework/tuf
#[tokio::test]
async fn test_tuf_reference_impl() {
    let base = test_data().join("tuf-reference-impl");

    let repo = RepositoryLoader::new(
        &tokio::fs::read(base.join("metadata").join("1.root.json"))
            .await
            .unwrap(),
        dir_url(base.join("metadata")),
        dir_url(base.join("targets")),
    )
    .load()
    .await
    .unwrap();
    assert_tuf_reference_impl(&repo).await;
}

async fn assert_tuf_reference_impl(repo: &Repository) {
    let file1 = TargetName::new("file1.txt").unwrap();
    let file2 = TargetName::new("file2.txt").unwrap();
    let file3 = TargetName::new("file3.txt").unwrap();
    assert_eq!(
        read_to_end(repo.read_target(&file1).await.unwrap().unwrap()).await,
        &b"This is an example target file."[..]
    );
    assert_eq!(
        read_to_end(repo.read_target(&file2).await.unwrap().unwrap()).await,
        &b"This is an another example target file."[..]
    );
    assert_eq!(
        repo.targets()
            .signed
            .targets
            .get(&file1)
            .unwrap()
            .custom
            .get("file_permissions")
            .unwrap(),
        "0644"
    );

    assert!(repo
        .targets()
        .signed
        .delegations
        .as_ref()
        .unwrap()
        .target_is_delegated(&file3));
}

/// Test that `tough` can process repositories generated by [`tuf`], the reference Python
/// implementation using the `load` function with non-default [`Options`].
#[tokio::test]
async fn test_tuf_reference_impl_default_transport() {
    let base = test_data().join("tuf-reference-impl");
    let datastore = TempDir::new().unwrap();

    let repo = RepositoryLoader::new(
        &tokio::fs::read(base.join("metadata").join("1.root.json"))
            .await
            .unwrap(),
        dir_url(base.join("metadata")),
        dir_url(base.join("targets")),
    )
    .transport(FilesystemTransport)
    .limits(Limits {
        max_root_size: 1000,
        max_targets_size: 2000,
        max_timestamp_size: 3000,
        max_snapshot_size: 4000,
        max_root_updates: 1,
    })
    .datastore(datastore.path())
    .load()
    .await
    .unwrap();
    assert_tuf_reference_impl(&repo).await;
}

/// Test that `tough` can load a repository that has some unusual delegate role names. This ensures
/// that percent encoded role names are handled correctly and that path traversal characters in a
/// role name do not cause `tough` to write outside of its datastore.
#[tokio::test]
async fn test_dubious_role_name() {
    let base = test_data().join("dubious-role-names");
    let datastore = TempDir::new().unwrap();

    let repo = RepositoryLoader::new(
        &tokio::fs::read(base.join("metadata").join("1.root.json"))
            .await
            .unwrap(),
        dir_url(base.join("metadata")),
        dir_url(base.join("targets")),
    )
    .datastore(datastore.path())
    .load()
    .await
    .unwrap();

    // Prove that the role name has path traversal characters.
    let expected_rolename = "../../path/like/dubious";
    assert_eq!(
        repo.delegated_role(expected_rolename).unwrap().name,
        expected_rolename
    );

    // Prove that the the role's metadata filename has not been written outside of the datastore.
    let expected_filename = "..%2F..%2Fpath%2Flike%2Fdubious.json";
    assert!(datastore.path().join(expected_filename).is_file())
}