From d498bc01c171e835530dac868878e1f8f2ebfc55 Mon Sep 17 00:00:00 2001 From: Lukas Puehringer Date: Tue, 25 Jan 2022 17:43:48 +0100 Subject: [PATCH] doc: drop documentation for legacy tools Remove documentation for legacy client, repository/developer tool and command line tools, which will be removed in subsequent commits. See #1797 and #1798 for replacing ATTACKS.md and QUICKSTART.md. Signed-off-by: Lukas Puehringer --- docs/CLI.md | 447 --------------- docs/GETTING_STARTED.rst | 10 - docs/QUICKSTART.md | 149 ----- docs/TUTORIAL.md | 696 ------------------------ docs/conf.py | 5 - docs/images/repository_tool-diagram.png | Bin 91920 -> 0 bytes tuf/ATTACKS.md | 323 ----------- tuf/README-developer-tools.md | 342 ------------ tuf/README.md | 5 - tuf/client/README.md | 151 ----- 10 files changed, 2128 deletions(-) delete mode 100644 docs/CLI.md delete mode 100644 docs/GETTING_STARTED.rst delete mode 100644 docs/QUICKSTART.md delete mode 100644 docs/TUTORIAL.md delete mode 100644 docs/images/repository_tool-diagram.png delete mode 100644 tuf/ATTACKS.md delete mode 100644 tuf/README-developer-tools.md delete mode 100644 tuf/README.md delete mode 100644 tuf/client/README.md diff --git a/docs/CLI.md b/docs/CLI.md deleted file mode 100644 index c07b73be7c..0000000000 --- a/docs/CLI.md +++ /dev/null @@ -1,447 +0,0 @@ -# Command-Line Interface # - -The TUF command-line interface (CLI) requires a full -[TUF installation](INSTALLATION.rst). Be sure to include the installation of -extra dependencies and C extensions ( -```python3 -m pip install securesystemslib[crypto,pynacl]```). - -The use of the CLI is documented with examples below. - ----- -# Basic Examples # - -## Create a repository ## - -Create a TUF repository in the current working directory. A cryptographic key -is created and set for each top-level role. The written Targets metadata does -not sign for any targets, nor does it delegate trust to any roles. The -`--init` call will also set up a client directory. By default, these -directories will be `./tufrepo` and `./tufclient`. - -```Bash -$ repo.py --init -``` - -Optionally, the repository can be written to a specified location. -```Bash -$ repo.py --init --path -``` - -The default top-level key files created with `--init` are saved to disk -encrypted, with a default password of 'pw'. Instead of using the default -password, the user can enter one on the command line for each top-level role. -These optional command-line options also work with other CLI actions (e.g., -repo.py --add). -```Bash -$ repo.py --init [--targets_pw, --root_pw, --snapshot_pw, --timestamp_pw] -``` - - - -Create a bare TUF repository in the current working directory. A cryptographic -key is *not* created nor set for each top-level role. -```Bash -$ repo.py --init --bare -``` - - - -Create a TUF repository with [consistent -snapshots](https://github.com/theupdateframework/specification/blob/master/tuf-spec.md#7-consistent-snapshots) -enabled, where target filenames have their hash prepended (e.g., -`.README.txt`), and metadata filenames have their version numbers -prepended (e.g., `.snapshot.json`). -```Bash -$ repo.py --init --consistent -``` - - - -## Add a target file ## - -Copy a target file to the repo and add it to the Targets metadata (or the -Targets role specified in --role). More than one target file, or directory, -may be specified in --add. The --recursive option may be toggled to also -include files in subdirectories of a specified directory. The Snapshot -and Timestamp metadata are also updated and signed automatically, but this -behavior can be toggled off with --no_release. -```Bash -$ repo.py --add -$ repo.py --add [--recursive] -``` - -Similar to the --init case, the repository location can be chosen. -```Bash -$ repo.py --add --path -``` - - - -## Remove a target file ## - -Remove a target file from the Targets metadata (or the Targets role specified -in --role). More than one target file or glob pattern may be specified in ---remove. The Snapshot and Timestamp metadata are also updated and signed -automatically, but this behavior can be toggled off with --no_release. - -```Bash -$ repo.py --remove ... -``` - -Examples: - -Remove all target files, that match `foo*.tgz,` from the Targets metadata. -```Bash -$ repo.py --remove "foo*.tgz" -``` - -Remove all target files from the `my_role` metadata. -```Bash -$ repo.py --remove "*" --role my_role --sign tufkeystore/my_role_key -``` - - -## Generate key ## -Generate a cryptographic key. The generated key can later be used to sign -specific metadata with `--sign`. The supported key types are: `ecdsa`, -`ed25519`, and `rsa`. If a keytype is not given, an Ed25519 key is generated. - -If adding a top-level key to a bare repo (i.e., repo.py --init --bare), -the filenames of the top-level keys must be "root_key," "targets_key," -"snapshot_key," "timestamp_key." The filename can vary for any additional -top-level key. -```Bash -$ repo.py --key -$ repo.py --key -$ repo.py --key [--path --pw [my_password], - --filename ] -``` - -Instead of using a default password, the user can enter one on the command -line or be prompted for it via password masking. -```Bash -$ repo.py --key ecdsa --pw my_password -``` - -```Bash -$ repo.py --key rsa --pw -Enter a password for the RSA key (...): -Confirm: -``` - - - -## Sign metadata ## -Sign, with the specified key(s), the metadata of the role indicated in --role. -The Snapshot and Timestamp role are also automatically signed, if possible, but -this behavior can be disabled with --no_release. -```Bash -$ repo.py --sign ... [--role , --path ] -``` - -For example, to sign the delegated `foo` metadata: -```Bash -$ repo.py --sign --role foo -``` - - - -## Trust keys ## - -The Root role specifies the trusted keys of the top-level roles, including -itself. The --trust command-line option, in conjunction with --pubkeys and ---role, can be used to indicate the trusted keys of a role. - -```Bash -$ repo.py --trust --pubkeys --role -``` - -For example: -```Bash -$ repo.py --init --bare -$ repo.py --trust --pubkeys tufkeystore/my_key.pub tufkeystore/my_key_too.pub - --role root -``` - - - -### Distrust keys ### - -Conversely, the Root role can discontinue trust of specified key(s). - -Example of how to discontinue trust of a key: -```Bash -$ repo.py --distrust --pubkeys tufkeystore/my_key_too.pub --role root -``` - - - -## Delegations ## - -Delegate trust of target files from the Targets role (or the one specified in ---role) to some other role (--delegatee). --delegatee is trusted to sign for -target files that match the delegated glob pattern(s). The --delegate option -does not create metadata for the delegated role, rather it updates the -delegator's metadata to list the delegation to --delegatee. The Snapshot and -Timestamp metadata are also updated and signed automatically, but this behavior -can be toggled off with --no_release. - -```Bash -$ repo.py --delegate ... --delegatee --pubkeys - ... [--role --terminating --threshold ---sign ] -``` - -For example, to delegate trust of `foo*.gz` packages to the `foo` role: - -``` -$ repo.py --delegate "foo*.tgz" --delegatee foo --pubkeys tufkeystore/foo.pub -``` - - - -## Revocations ## - -Revoke trust of target files from a delegated role (--delegatee). The -"targets" role performs the revocation if --role is not specified. The ---revoke option does not delete the metadata belonging to --delegatee, instead -it removes the delegation to it from the delegator's (or --role) metadata. The -Snapshot and Timestamp metadata are also updated and signed automatically, but -this behavior can be toggled off with --no_release. - - -```Bash -$ repo.py --revoke --delegatee [--role ---sign ] -``` - - - -## Verbosity ## - -Set the verbosity of the logger (2, by default). The lower the number, the -greater the verbosity. Logger messages are saved to `tuf.log` in the current -working directory. -```Bash -$ repo.py --verbose <0-5> -``` - - - -## Clean ## - -Delete the repo in the current working directory, or the one specified with -`--path`. Specifically, the `tufrepo`, `tufclient`, and `tufkeystore` -directories are deleted. - -```Bash -$ repo.py --clean -$ repo.py --clean --path -``` ----- - - - - - - - - -# Further Examples # - -## Basic Update Delivery ## - -Steps: - -(1) initialize a repo. - -(2) delegate trust of target files to another role. - -(3) add a trusted file to the delegated role. - -(4) fetch the trusted file from the delegated role. - -```Bash -Step (1) -$ repo.py --init - -Step (2) -$ repo.py --key ed25519 --filename mykey -$ repo.py --delegate "README.*" --delegatee myrole --pubkeys tufkeystore/mykey.pub -$ repo.py --sign tufkeystore/mykey --role myrole -Enter a password for the encrypted key (tufkeystore/mykey): -$ echo "my readme text" > README.txt - -Step (3) -$ repo.py --add README.txt --role myrole --sign tufkeystore/mykey -Enter a password for the encrypted key (tufkeystore/mykey): -``` - -Serve the repo -```Bash -$ python3 -m http.server 8001 -``` - -```Bash -Step (4) -$ client.py --repo http://localhost:8001 README.txt -$ tree . -. -├── tuf.log -├── tufrepo -│   └── metadata -│   ├── current -│   │   ├── 1.root.json -│   │   ├── myrole.json -│   │   ├── root.json -│   │   ├── snapshot.json -│   │   ├── targets.json -│   │   └── timestamp.json -│   └── previous -│   ├── 1.root.json -│   ├── root.json -│   ├── snapshot.json -│   ├── targets.json -│   └── timestamp.json -└── tuftargets - └── README.txt - - 5 directories, 13 files -``` - - -## Correcting a Key ## -The filename of the top-level keys must be "root_key," "targets_key," -"snapshot_key," and "root_key." The filename can vary for any additional -top-level key. - -Steps: - -(1) initialize a repo containing default keys for the top-level roles. -(2) distrust the default key for the root role. -(3) create a new key and trust its use with the root role. -(4) sign the root metadata file. - -```Bash -Step (1) -$ repo.py --init - -Step (2) -$ repo.py --distrust --pubkeys tufkeystore/root_key.pub --role root - -Step (3) -$ repo.py --key ed25519 --filename root_key -$ repo.py --trust --pubkeys tufkeystore/root_key.pub --role root - -Step (4) -$ repo.py --sign tufkeystore/root_key --role root -Enter a password for the encrypted key (tufkeystore/root_key): -``` - - -## More Update Delivery ## - -Steps: - -(1) create a bare repo. - -(2) add keys to the top-level roles. - -(3) delegate trust of particular target files to another role X, where role X -has a signature threshold 2 and is marked as a terminating delegation. The -keys for role X and Y should be created prior to performing the delegation. - -(4) Delegate from role X to role Y. - -(5) have role X sign for a file also signed by the Targets role, to demonstrate -the expected file that should be downloaded by the client. - -(6) perform an update. - -(7) halt the server, add README.txt to the Targets role, restart the server, -and fetch the Target's role README.txt. - -(8) Add LICENSE to 'role_y' and demonstrate that the client must not fetch it -because 'role_x' is a terminating delegation (and hasn't signed for it). - -```Bash -Steps (1) and (2) -$ repo.py --init --consistent --bare -$ repo.py --key ed25519 --filename root_key -$ repo.py --trust --pubkeys tufkeystore/root_key.pub --role root -$ repo.py --key ecdsa --filename targets_key -$ repo.py --trust --pubkeys tufkeystore/targets_key.pub --role targets -$ repo.py --key rsa --filename snapshot_key -$ repo.py --trust --pubkeys tufkeystore/snapshot_key.pub --role snapshot -$ repo.py --key ecdsa --filename timestamp_key -$ repo.py --trust --pubkeys tufkeystore/timestamp_key.pub --role timestamp -$ repo.py --sign tufkeystore/root_key --role root -Enter a password for the encrypted key (tufkeystore/root_key): -$ repo.py --sign tufkeystore/targets_key --role targets -Enter a password for the encrypted key (tufkeystore/targets_key): -``` - -```Bash -Steps (3) and (4) -$ repo.py --key ed25519 --filename key_x -$ repo.py --key ed25519 --filename key_x2 - -$ repo.py --delegate "README.*" "LICENSE" --delegatee role_x --pubkeys - tufkeystore/key_x.pub tufkeystore/key_x2.pub --threshold 2 --terminating -$ repo.py --sign tufkeystore/key_x tufkeystore/key_x2 --role role_x - -$ repo.py --key ed25519 --filename key_y - -$ repo.py --delegate "README.*" "LICENSE" --delegatee role_y --role role_x - --pubkeys tufkeystore/key_y.pub --sign tufkeystore/key_x tufkeystore/key_x2 - -$ repo.py --sign tufkeystore/key_y --role role_y -``` - -```Bash -Steps (5) and (6) -$ echo "role_x's readme" > README.txt -$ repo.py --add README.txt --role role_x --sign tufkeystore/key_x tufkeystore/key_x2 -``` - -Serve the repo -```Bash -$ python3 -m http.server 8001 -``` - -Fetch the role x's README.txt -```Bash -$ client.py --repo http://localhost:8001 README.txt -$ cat tuftargets/README.txt -role_x's readme -``` - - -```Bash -Step (7) -halt the server... - -$ echo "Target role's readme" > README.txt -$ repo.py --add README.txt - -restart the server... -``` - -```Bash -$ rm -rf tuftargets/ tuf.log -$ client.py --repo http://localhost:8001 README.txt -$ cat tuftargets/README.txt -Target role's readme -``` - -```Bash -Step (8) -$ echo "role_y's license" > LICENSE -$ repo.py --add LICENSE --role role_y --sign tufkeystore/key_y -``` - -```Bash -$ rm -rf tuftargets/ tuf.log -$ client.py --repo http://localhost:8001 LICENSE -Error: 'LICENSE' not found. -``` diff --git a/docs/GETTING_STARTED.rst b/docs/GETTING_STARTED.rst deleted file mode 100644 index f958373975..0000000000 --- a/docs/GETTING_STARTED.rst +++ /dev/null @@ -1,10 +0,0 @@ -Getting Started ---------------- - -- `Overview of TUF `_ -- `Installation `_ -- Beginner Tutorials (using the basic command-line interface): - - `Quickstart `_ - - `CLI Documentation and Examples `_ -- `Advanced Tutorial `_ -- `Guidelines for Contributors `_ diff --git a/docs/QUICKSTART.md b/docs/QUICKSTART.md deleted file mode 100644 index 6d35fb1d7d..0000000000 --- a/docs/QUICKSTART.md +++ /dev/null @@ -1,149 +0,0 @@ -# Quickstart # - -In this quickstart tutorial, we'll use the basic TUF command-line interface -(CLI), which includes the `repo.py` script and the `client.py` script, to set -up a repository with an update and metadata about that update, then download -and verify that update as a client. - -Unlike the underlying TUF modules that the CLI uses, the CLI itself is a bit -bare-bones. Using the CLI is the easiest way to familiarize yourself with -how TUF works, however. It will serve as a very basic update system. - ----- - -**Step (0)** - Make sure TUF is installed. - -Make sure that TUF is installed, along with some of the optional cryptographic -libraries and C extensions. Try this command to do that: -`python3 -m pip install securesystemslib[colors,crypto,pynacl] tuf` - -If you run into errors during that pip command, please consult the more -detailed [TUF Installation Instructions](INSTALLATION.rst). (There are some -system libraries that you may need to install first.) - - -**Step (1)** - Create a basic repository and client. - -The following command will set up a basic update repository and basic client -that knows about the repository. `tufrepo`, `tufkeystore`, and -`tufclient` directories will be created in the current directory. - -```Bash -$ repo.py --init -``` - -Four sets of keys are created in the `tufkeystore` directory. Initial metadata -about the repository is created in the `tufrepo` directory, and also provided -to the client in the `tufclient` directory. - - -**Step (2)** - Add an update to the repository. - -We'll create a target file that will later be delivered as an update to clients. -Metadata about that file will be created and signed, and added to the -repository's metadata. - -```Bash -$ echo 'Test file' > testfile -$ repo.py --add testfile -$ tree tufrepo/ -tufrepo/ -├── metadata -│   ├── 1.root.json -│   ├── root.json -│   ├── snapshot.json -│   ├── targets.json -│   └── timestamp.json -├── metadata.staged -│   ├── 1.root.json -│   ├── root.json -│   ├── snapshot.json -│   ├── targets.json -│   └── timestamp.json -└── targets - └── testfile - - 3 directories, 11 files -``` - -The new file `testfile` is added to the repository, and metadata is updated in -the `tufrepo` directory. The Targets metadata (`targets.json`) now includes -the file size and hashes of the `testfile` target file, and this metadata is -signed by the Targets role's key, so that clients can verify that metadata -about `testfile` and then verify `testfile` itself. - - -**Step (3)** - Serve the repo. - -We'll host a toy http server containing the `testfile` update and the -repository's metadata. - -```Bash -$ cd "tufrepo/" -$ python3 -m http.server 8001 -``` - -**Step (4)** - Obtain and verify the `testfile` update on a client. - -The client can request the package `testfile` from the repository. TUF will -download and verify metadata from the repository as necessary to determine -what the trustworthy hashes and length of `testfile` are, then download -the target `testfile` from the repository and keep it only if it matches that -trustworthy metadata. - -```Bash -$ cd "../tufclient/" -$ client.py --repo http://localhost:8001 testfile -$ tree -. -├── tufrepo -│   └── metadata -│   ├── current -│   │   ├── 1.root.json -│   │   ├── root.json -│   │   ├── snapshot.json -│   │   ├── targets.json -│   │   └── timestamp.json -│   └── previous -│   ├── 1.root.json -│   ├── root.json -│   ├── snapshot.json -│   ├── targets.json -│   └── timestamp.json -└── tuftargets - └── testfile - - 5 directories, 11 files -``` - -Now that a trustworthy update target has been obtained, an updater can proceed -however it normally would to install or use the update. - ----- - -### Next Steps - -TUF provides functionality for both ends of a software update system, the -**update provider** and the **update client**. - -`repo.py` made use of `tuf.repository_tool`'s functionality for an update -provider, helping you produce and sign metadata about your updates. - -`client.py` made use of `tuf.client.updater`'s client-side functionality, -performing download and the critical verification steps for metadata and the -update itself. - -You can look at [CLI.md](CLI.md) to toy with the TUF CLI a bit more. -After that, try out using the underlying modules for a great deal more control. -The more detailed [Advanced Tutorial](TUTORIAL.md) shows you how to use the -underlying modules, `repository_tool` and `updater`. - -Ultimately, a sophisticated update client will use or re-implement those -underlying modules. The TUF design is intended to play well with any update -workflow. - -Please provide feedback or questions for this or other tutorials, or -TUF in general, by checking out -[our contact info](https://github.com/theupdateframework/python-tuf#contact), or -creating [issues](https://github.com/theupdateframework/python-tuf/issues) in this -repository! diff --git a/docs/TUTORIAL.md b/docs/TUTORIAL.md deleted file mode 100644 index d8659e7213..0000000000 --- a/docs/TUTORIAL.md +++ /dev/null @@ -1,696 +0,0 @@ -# Advanced Tutorial # - -## Table of Contents ## -- [How to Create and Modify a TUF Repository](#how-to-create-and-modify-a-tuf-repository) - - [Overview](#overview) - - [Keys](#keys) - - [Create RSA Keys](#create-rsa-keys) - - [Import RSA Keys](#import-rsa-keys) - - [Create and Import Ed25519 Keys](#create-and-import-ed25519-keys) - - [Create Top-level Metadata](#create-top-level-metadata) - - [Create Root](#create-root) - - [Create Timestamp, Snapshot, Targets](#create-timestamp-snapshot-targets) - - [Targets](#targets) - - [Add Target Files](#add-target-files) - - [Remove Target Files](#remove-target-files) - - [Delegations](#delegations) - - [Revoke Delegated Role](#revoke-delegated-role) - - [Wrap-up](#wrap-up) -- [Delegate to Hashed Bins](#delegate-to-hashed-bins) -- [Consistent Snapshots](#consistent-snapshots) -- [How to Perform an Update](#how-to-perform-an-update) - -## How to Create and Modify a TUF Repository ## - -### Overview ### -A software update system must follow two steps to integrate The Update -Framework (TUF). First, it must add the framework to the client side of the -update system. The [tuf.client.updater](../tuf/client/README.md) module assists in -integrating TUF on the client side. Second, the software repository on the -server side must be modified to include a minimum of four top-level metadata -(root.json, targets.json, snapshot.json, and timestamp.json). No additional -software is required to convert a software repository to a TUF one. The -low-level repository tool that generates the required TUF metadata for a -software repository is the focus of this tutorial. There is also separate -document that [demonstrates how TUF protects against malicious -updates](../tuf/ATTACKS.md). - -The [repository tool](../tuf/repository_tool.py) contains functions to generate -all of the files needed to populate and manage a TUF repository. The tool may -either be imported into a Python module, or used with the Python interpreter in -interactive mode. - -A repository object that encapsulates the metadata files of the repository can -be created or loaded by the repository tool. Repository maintainers can modify -the repository object to manipulate the metadata files stored on the -repository. TUF clients use the metadata files to validate files requested and -downloaded. In addition to the repository object, where the majority of -changes are made, the repository tool provides functions to generate and -persist cryptographic keys. The framework utilizes cryptographic keys to sign -and verify metadata files. - -To begin, cryptographic keys are generated with the repository tool. However, -before metadata files can be validated by clients and target files fetched in a -secure manner, public keys must be pinned to particular metadata roles and -metadata signed by role's private keys. After covering keys, the four required -top-level metadata are created next. Examples are given demonstrating the -expected work flow, where the metadata roles are created in a specific order, -keys imported and loaded, and metadata signed and written to disk. Lastly, -target files are added to the repository, and a custom delegation performed to -extend the default roles of the repository. By the end, a fully populated TUF -repository is generated that can be used by clients to securely download -updates. - -### Keys ### -The repository tool supports multiple public-key algorithms, such as -[RSA](https://en.wikipedia.org/wiki/RSA_%28cryptosystem%29) and -[Ed25519](https://ed25519.cr.yp.to/), and multiple cryptography libraries. - -Using [RSA-PSS](https://tools.ietf.org/html/rfc8017#section-8.1) or -[ECDSA](https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm) -signatures requires the [cryptography](https://cryptography.io/) library. If -generation of Ed25519 signatures is needed -[PyNaCl](https://github.com/pyca/pynacl) library should be installed. This -tutorial assumes both dependencies are installed: refer to -[Installation Instructions](INSTALLATION.rst#install-with-more-cryptographic-flexibility) -for details. - -The Ed25519 and ECDSA keys are stored in JSON format and RSA keys are stored in PEM -format. Private keys are encrypted and passphrase-protected (strengthened with -PBKDF2-HMAC-SHA256.) Generating, importing, and loading cryptographic key -files can be done with functions available in the repository tool. - -To start, a public and private RSA key pair is generated with the -`generate_and_write_rsa_keypair()` function. The keys generated next are -needed to sign the repository metadata files created in upcoming sub-sections. - -Note: In the instructions below, lines that start with `>>>` denote commands -that should be entered by the reader, `#` begins the start of a comment, and -text without prepended symbols is the output of a command. - -#### Create RSA Keys #### -```python ->>> from tuf.repository_tool import * - -# Generate and write the first of two root keys for the TUF repository. The -# following function creates an RSA key pair, where the private key is saved to -# "root_key" and the public key to "root_key.pub" (both saved to the current -# working directory). ->>> generate_and_write_rsa_keypair(password="password", filepath="root_key", bits=2048) - -# If the key length is unspecified, it defaults to 3072 bits. A length of less -# than 2048 bits raises an exception. A similar function is available to supply -# a password on the prompt. If an empty password is entered, the private key -# is saved unencrypted. ->>> generate_and_write_rsa_keypair_with_prompt(filepath="root_key2") -enter password to encrypt private key file '/path/to/root_key2' -(leave empty if key should not be encrypted): -Confirm: -``` -The following four key files should now exist: - -1. **root_key** -2. **root_key.pub** -3. **root_key2** -4. **root_key2.pub** - -If a filepath is not given, the KEYID of the generated key is used as the -filename. The key files are written to the current working directory. -```python -# Continuing from the previous section . . . ->>> generate_and_write_rsa_keypair_with_prompt() -enter password to encrypt private key file '/path/to/KEYID' -(leave empty if key should not be encrypted): -Confirm: -``` - -### Import RSA Keys ### -```python -# Continuing from the previous section . . . - -# Import an existing public key. ->>> public_root_key = import_rsa_publickey_from_file("root_key.pub") - -# Import an existing private key. Importing a private key requires a password, -# whereas importing a public key does not. ->>> private_root_key = import_rsa_privatekey_from_file("root_key") -enter password to decrypt private key file '/path/to/root_key' -(leave empty if key not encrypted): -``` - -### Create and Import Ed25519 Keys ### -```Python -# Continuing from the previous section . . . - -# The same generation and import functions as for rsa keys exist for ed25519 ->>> generate_and_write_ed25519_keypair_with_prompt(filepath='ed25519_key') -enter password to encrypt private key file '/path/to/ed25519_key' -(leave empty if key should not be encrypted): -Confirm: - -# Import the ed25519 public key just created . . . ->>> public_ed25519_key = import_ed25519_publickey_from_file('ed25519_key.pub') - -# and its corresponding private key. ->>> private_ed25519_key = import_ed25519_privatekey_from_file('ed25519_key') -enter password to decrypt private key file '/path/to/ed25519_key' -(leave empty if key should not be encrypted): -``` - -Note: Methods are also available to generate and write keys from memory. -* generate_ed25519_key() -* generate_ecdsa_key() -* generate_rsa_key() - -* import_ecdsakey_from_pem(pem) -* import_rsakey_from_pem(pem) - -### Create Top-level Metadata ### -The [metadata document](METADATA.md) outlines the JSON files that must exist -on a TUF repository. The following sub-sections demonstrate the -`repository_tool.py` calls repository maintainers may issue to generate the -required roles. The top-level roles to be created are `root`, `timestamp`, -`snapshot`, and `target`. - -We begin with `root`, the locus of trust that specifies the public keys of the -top-level roles, including itself. - - -#### Create Root #### -```python -# Continuing from the previous section . . . - -# Create a new Repository object that holds the file path to the TUF repository -# and the four top-level role objects (Root, Targets, Snapshot, Timestamp). -# Metadata files are created when repository.writeall() or repository.write() -# are called. The repository directory is created if it does not exist. You -# may see log messages indicating any directories created. ->>> repository = create_new_repository("repository") - -# The Repository instance, 'repository', initially contains top-level Metadata -# objects. Add one of the public keys, created in the previous section, to the -# root role. Metadata is considered valid if it is signed by the public key's -# corresponding private key. ->>> repository.root.add_verification_key(public_root_key) - -# A role's verification key(s) (to be more precise, the verification key's -# keyid) may be queried. Other attributes include: signing_keys, version, -# signatures, expiration, threshold, and delegations (attribute available only -# to a Targets role). ->>> repository.root.keys -['b23514431a53676595922e955c2d547293da4a7917e3ca243a175e72bbf718df'] - -# Add a second public key to the root role. Although previously generated and -# saved to a file, the second public key must be imported before it can added -# to a role. ->>> public_root_key2 = import_rsa_publickey_from_file("root_key2.pub") ->>> repository.root.add_verification_key(public_root_key2) - -# The threshold of each role defaults to 1. Maintainers may change the -# threshold value, but repository_tool.py validates thresholds and warns users. -# Set the threshold of the root role to 2, which means the root metadata file -# is considered valid if it's signed by at least two valid keys. We also load -# the second private key, which hasn't been imported yet. ->>> repository.root.threshold = 2 ->>> private_root_key2 = import_rsa_privatekey_from_file("root_key2", password="password") - -# Load the root signing keys to the repository, which writeall() or write() -# (write multiple roles, or a single role, to disk) use to sign the root -# metadata. ->>> repository.root.load_signing_key(private_root_key) ->>> repository.root.load_signing_key(private_root_key2) - -# repository.status() shows missing verification and signing keys for the -# top-level roles, and whether signatures can be created (also see #955). -# This output shows that so far only the "root" role meets the key threshold and -# can successfully sign its metadata. ->>> repository.status() -'targets' role contains 0 / 1 public keys. -'snapshot' role contains 0 / 1 public keys. -'timestamp' role contains 0 / 1 public keys. -'root' role contains 2 / 2 signatures. -'targets' role contains 0 / 1 signatures. - -# In the next section we update the other top-level roles and create a repository -# with valid metadata. -``` - -#### Create Timestamp, Snapshot, Targets -Now that `root.json` has been set, the other top-level roles may be created. -The signing keys added to these roles must correspond to the public keys -specified by the Root role. - -On the client side, `root.json` must always exist. The other top-level roles, -created next, are requested by repository clients in (Root -> Timestamp -> -Snapshot -> Targets) order to ensure required metadata is downloaded in a -secure manner. - -```python -# Continuing from the previous section . . . - -# 'datetime' module needed to optionally set a role's expiration. ->>> import datetime - -# Generate keys for the remaining top-level roles. The root keys have been set above. ->>> generate_and_write_rsa_keypair(password='password', filepath='targets_key') ->>> generate_and_write_rsa_keypair(password='password', filepath='snapshot_key') ->>> generate_and_write_rsa_keypair(password='password', filepath='timestamp_key') - -# Add the verification keys of the remaining top-level roles. - ->>> repository.targets.add_verification_key(import_rsa_publickey_from_file('targets_key.pub')) ->>> repository.snapshot.add_verification_key(import_rsa_publickey_from_file('snapshot_key.pub')) ->>> repository.timestamp.add_verification_key(import_rsa_publickey_from_file('timestamp_key.pub')) - -# Import the signing keys of the remaining top-level roles. ->>> private_targets_key = import_rsa_privatekey_from_file('targets_key', password='password') ->>> private_snapshot_key = import_rsa_privatekey_from_file('snapshot_key', password='password') ->>> private_timestamp_key = import_rsa_privatekey_from_file('timestamp_key', password='password') - -# Load the signing keys of the remaining roles so that valid signatures are -# generated when repository.writeall() is called. ->>> repository.targets.load_signing_key(private_targets_key) ->>> repository.snapshot.load_signing_key(private_snapshot_key) ->>> repository.timestamp.load_signing_key(private_timestamp_key) - -# Optionally set the expiration date of the timestamp role. By default, roles -# are set to expire as follows: root(1 year), targets(3 months), snapshot(1 -# week), timestamp(1 day). ->>> repository.timestamp.expiration = datetime.datetime(2080, 10, 28, 12, 8) - -# Mark roles for metadata update (see #964, #958) ->>> repository.mark_dirty(['root', 'snapshot', 'targets', 'timestamp']) - -# Write all metadata to "repository/metadata.staged/" ->>> repository.writeall() -``` - -### Targets ### -TUF makes it possible for clients to validate downloaded target files by -including a target file's length, hash(es), and filepath in metadata. The -filepaths are relative to a `targets/` directory on the software repository. A -TUF client can download a target file by first updating the latest copy of -metadata (and thus available targets), verifying that their length and hashes -are valid, and saving the target file(s) locally to complete the update -process. - -In this section, the target files intended for clients are added to a -repository and listed in `targets.json` metadata. - -#### Add Target Files #### - -The repository maintainer adds target files to roles (e.g., `targets` and -`unclaimed`) by specifying their filepaths. The target files must exist at the -specified filepaths before the repository tool can generate and add their -(hash(es), length, and filepath) to metadata. - -First, the actual target files are manually created and saved to the `targets/` -directory of the repository: - -```Bash -# Create and save target files to the targets directory of the software -# repository. -$ cd repository/targets/ -$ echo 'file1' > file1.txt -$ echo 'file2' > file2.txt -$ echo 'file3' > file3.txt -$ mkdir myproject; echo 'file4' > myproject/file4.txt -$ cd ../../ -``` - -With the target files available on the `targets/` directory of the software -repository, the `add_targets()` method of a Targets role can be called to add -the target filepaths to metadata. - -```python -# Continuing from the previous section . . . - -# NOTE: If you exited the Python interactive interpreter above you need to -# re-import the repository_tool-functions and re-load the repository and -# signing keys. ->>> from tuf.repository_tool import * - -# The 'os' module is needed to gather file attributes, which will be included -# in a custom field for some of the target files added to metadata. ->>> import os - -# Load the repository created in the previous section. This repository so far -# contains metadata for the top-level roles, but no target paths are yet listed -# in targets metadata. ->>> repository = load_repository('repository') - -# Create a list of all targets in the directory. ->>> list_of_targets = ['file1.txt', 'file2.txt', 'file3.txt'] - -# Add the list of target paths to the metadata of the top-level Targets role. -# Any target file paths that might already exist are NOT replaced, and -# add_targets() does not create or move target files on the file system. Any -# target paths added to a role must fall under the expected targets directory, -# otherwise an exception is raised. The targets added to a role should actually -# exist once writeall() or write() is called, so that the hash and size of -# these targets can be included in Targets metadata. ->>> repository.targets.add_targets(list_of_targets) - -# Individual target files may also be added to roles, including custom data -# about the target. In the example below, file permissions of the target -# (octal number specifying file access for owner, group, others e.g., 0755) is -# added alongside the default fileinfo. All target objects in metadata include -# the target's filepath, hash, and length. -# Note: target path passed to add_target() method has to be relative -# to the targets directory or an exception is raised. ->>> target4_filepath = 'myproject/file4.txt' ->>> target4_abspath = os.path.abspath(os.path.join('repository', 'targets', target4_filepath)) ->>> octal_file_permissions = oct(os.stat(target4_abspath).st_mode)[4:] ->>> custom_file_permissions = {'file_permissions': octal_file_permissions} ->>> repository.targets.add_target(target4_filepath, custom_file_permissions) -``` - -The private keys of roles affected by the changes above must now be imported and -loaded. `targets.json` must be signed because a target file was added to its -metadata. `snapshot.json` keys must be loaded and its metadata signed because -`targets.json` has changed. Similarly, since `snapshot.json` has changed, the -`timestamp.json` role must also be signed. - -```Python -# Continuing from the previous section . . . - -# The private key of the updated targets metadata must be re-loaded before it -# can be signed and written (Note the load_repository() call above). ->>> private_targets_key = import_rsa_privatekey_from_file('targets_key') -enter password to decrypt private key file '/path/to/targets_key' -(leave empty if key not encrypted): - ->>> repository.targets.load_signing_key(private_targets_key) - -# Due to the load_repository() and new versions of metadata, we must also load -# the private keys of Snapshot and Timestamp to generate a valid set of metadata. ->>> private_snapshot_key = import_rsa_privatekey_from_file('snapshot_key') -enter password to decrypt private key file '/path/to/snapshot_key' -(leave empty if key not encrypted): ->>> repository.snapshot.load_signing_key(private_snapshot_key) - ->>> private_timestamp_key = import_rsa_privatekey_from_file('timestamp_key') -enter password to decrypt private key file '/path/to/timestamp_key' -(leave empty if key not encrypted): ->>> repository.timestamp.load_signing_key(private_timestamp_key) - -# Mark roles for metadata update (see #964, #958) ->>> repository.mark_dirty(['snapshot', 'targets', 'timestamp']) - -# Generate new versions of the modified top-level metadata (targets, snapshot, -# and timestamp). ->>> repository.writeall() -``` - -#### Remove Target Files #### - -Target files previously added to roles may also be removed. Removing a target -file requires first removing the target from a role and then writing the -new metadata to disk. -```python -# Continuing from the previous section . . . - -# Remove a target file listed in the "targets" metadata. The target file is -# not actually deleted from the file system. ->>> repository.targets.remove_target('myproject/file4.txt') - -# Mark roles for metadata update (see #964, #958) ->>> repository.mark_dirty(['snapshot', 'targets', 'timestamp']) - ->>> repository.writeall() -``` - -#### Excursion: Dump Metadata and Append Signature #### - -The following two functions are intended for those that wish to independently -sign metadata. Repository maintainers can dump the portion of metadata that is -normally signed, sign it with an external signing tool, and append the -signature to already existing metadata. - -First, the signable portion of metadata can be generated as follows: - -```Python ->>> signable_content = dump_signable_metadata('repository/metadata.staged/timestamp.json') -``` - -Then, use a tool like securesystemslib to create a signature over the signable -portion. *Note, to make the signing key count towards the role's signature -threshold, it needs to be added to `root.json`, e.g. via -`repository.timestamp.add_verification_key(key)` (not shown in below snippet).* -```python ->>> from securesystemslib.formats import encode_canonical ->>> from securesystemslib.keys import create_signature ->>> private_ed25519_key = import_ed25519_privatekey_from_file('ed25519_key') -enter password to decrypt private key file '/path/to/ed25519_key' ->>> signature = create_signature( -... private_ed25519_key, encode_canonical(signable_content).encode()) -``` - -Finally, append the signature to the metadata -```Python ->>> append_signature(signature, 'repository/metadata.staged/timestamp.json') -``` - -Note that the format of the signature is the format expected in metadata, which -is a dictionary that contains a KEYID, the signature itself, etc. See the -specification and [METADATA.md](METADATA.md) for a detailed example. - -### Delegations ### -All of the target files available on the software repository created so far -have been added to one role (the top-level Targets role). However, what if -multiple developers are responsible for the files of a project? What if -responsibility separation is desired? Performing a delegation, where one role -delegates trust of some paths to another role, is an option for integrators -that require additional roles on top of the top-level roles available by -default. - -In the next sub-section, the `unclaimed` role is delegated from the top-level -`targets` role. The `targets` role specifies the delegated role's public keys, -the paths it is trusted to provide, and its role name. - -```python -# Continuing from the previous section . . . - -# Generate a key for a new delegated role named "unclaimed". ->>> generate_and_write_rsa_keypair(password='password', filepath='unclaimed_key', bits=2048) ->>> public_unclaimed_key = import_rsa_publickey_from_file('unclaimed_key.pub') - -# Make a delegation (delegate trust of 'myproject/*.txt' files) from "targets" -# to "unclaimed", where "unclaimed" initially contains zero targets. ->>> repository.targets.delegate('unclaimed', [public_unclaimed_key], ['myproject/*.txt']) - -# Thereafter, we can access the delegated role by its name to e.g. add target -# files, just like we did with the top-level targets role. ->>> repository.targets("unclaimed").add_target("myproject/file4.txt") - -# Load the private key of "unclaimed" so that unclaimed's metadata can be -# signed, and valid metadata created. ->>> private_unclaimed_key = import_rsa_privatekey_from_file('unclaimed_key', password='password') - ->>> repository.targets("unclaimed").load_signing_key(private_unclaimed_key) - -# Mark roles for metadata update (see #964, #958) ->>> repository.mark_dirty(['snapshot', 'targets','timestamp', 'unclaimed']) - ->>> repository.writeall() -``` - - - - -#### Wrap-up #### - -In summary, the five steps a repository maintainer follows to create a TUF -repository are: - -1. Create a directory for the software repository that holds the TUF metadata and the target files. -2. Create top-level roles (`root.json`, `snapshot.json`, `targets.json`, and `timestamp.json`.) -3. Add target files to the `targets` role. -4. Optionally, create delegated roles to distribute target files. -5. Write the changes. - -The repository tool saves repository changes to a `metadata.staged` directory. -Repository maintainers may push finalized changes to the "live" repository by -copying the staged directory to its destination. -```Bash -# Copy the staged metadata directory changes to the live repository. -$ cp -r "repository/metadata.staged/" "repository/metadata/" -``` - -## Consistent Snapshots ## -The basic TUF repository we have generated above is adequate for repositories -that have some way of guaranteeing consistency of repository data. A community -software repository is one example where consistency of files and metadata can -become an issue. Repositories of this kind are continually updated by multiple -maintainers and software authors uploading their packages, increasing the -likelihood that a client downloading version X of a release unexpectedly -requests the target files of a version Y just released. - -To guarantee consistency of metadata and target files, a repository may -optionally support multiple versions of `snapshot.json` simultaneously, where a -client with version 1 of `snapshot.json` can download `target_file.zip` and -another client with version 2 of `snapshot.json` can also download a different -`target_file.zip` (same file name, but different file digest.) If the -`consistent_snapshot` parameter of writeall() or write() are `True`, metadata -and target file names on the file system have their digests prepended (note: -target file names specified in metadata do not contain digests in their names.) - -The repository maintainer is responsible for the duration of multiple versions -of metadata and target files available on a repository. Generating consistent -metadata and target files on the repository is enabled by setting the -`consistent_snapshot` argument of `writeall()` or `write()` . Note that -changing the consistent_snapshot setting involves writing a new version of -root. - - - -## Delegate to Hashed Bins ## -Why use hashed bin delegations? - -For software update systems with a large number of target files, delegating to -hashed bins (a special type of delegated role) might be an easier alternative -to manually performing the delegations. How many target files should each -delegated role contain? How will these delegations affect the number of -metadata that clients must additionally download in a typical update? Hashed -bin delegations are available to integrators that rather not deal with the -management of delegated roles and a great number of target files. - -A large number of target files may be distributed to multiple hashed bins with -`delegate_hashed_bins()`. The metadata files of delegated roles will be nearly -equal in size (i.e., target file paths are uniformly distributed by calculating -the target filepath's digest and determining which bin it should reside in.) -The updater client will use "lazy bin walk" (visit and download the minimum -metadata required to find a target) to find a target file's hashed bin -destination. This method is intended for repositories with a large number of -target files, a way of easily distributing and managing the metadata that lists -the targets, and minimizing the number of metadata files (and size) downloaded -by the client. - -The `delegate_hashed_bins()` method has the following form: -```Python -delegate_hashed_bins(list_of_targets, keys_of_hashed_bins, number_of_bins) -``` - -We next provide a complete example of retrieving target paths to add to hashed -bins, performing the hashed bin delegations, signing them, and delegating paths -to some role. - -```Python -# Continuing from the previous section . . . - -# Remove 'myproject/file4.txt' from unclaimed role and instead further delegate -# all targets in myproject/ to hashed bins. ->>> repository.targets('unclaimed').remove_target("myproject/file4.txt") - -# Get a list of target paths for the hashed bins. ->>> targets = ['myproject/file4.txt'] - -# Delegate trust to 32 hashed bin roles. Each role is responsible for the set -# of target files, determined by the path hash prefix. TUF evenly distributes -# hexadecimal ranges over the chosen number of bins (see output). -# To initialize the bins we use one key, which TUF warns us about (see output). -# However, we can assign separate keys to each bin, with the method used in -# previous sections, accessing a bin by its hash prefix range name, e.g.: -# "repository.targets('00-07').add_verification_key('public_00-07_key')". ->>> repository.targets('unclaimed').delegate_hashed_bins( -... targets, [public_unclaimed_key], 32) -Creating hashed bin delegations. -1 total targets. -32 hashed bins. -256 total hash prefixes. -Each bin ranges over 8 hash prefixes. -Adding a verification key that has already been used. [repeated 32x] - -# The hashed bin roles can also be accessed by iterating the "delegations" -# property of the delegating role, which we do here to load the signing key. ->>> for delegation in repository.targets('unclaimed').delegations: -... delegation.load_signing_key(private_unclaimed_key) - -# Mark roles for metadata update (see #964, #958) ->>> repository.mark_dirty(['00-07', '08-0f', '10-17', '18-1f', '20-27', '28-2f', -... '30-37', '38-3f', '40-47', '48-4f', '50-57', '58-5f', '60-67', '68-6f', -... '70-77', '78-7f', '80-87', '88-8f', '90-97', '98-9f', 'a0-a7', 'a8-af', -... 'b0-b7', 'b8-bf', 'c0-c7', 'c8-cf', 'd0-d7', 'd8-df', 'e0-e7', 'e8-ef', -... 'f0-f7', 'f8-ff', 'snapshot', 'timestamp', 'unclaimed']) - ->>> repository.writeall() - -``` - -## How to Perform an Update ## - -The following [repository tool](../tuf/repository_tool.py) function creates a directory -structure that a client downloading new software using TUF (via -[tuf/client/updater.py](../tuf/client/updater.py)) expects. The `root.json` metadata file must exist, and -also the directories that hold the metadata files downloaded from a repository. -Software updaters integrating TUF may use this directory to store TUF updates -saved on the client side. - -```python ->>> from tuf.repository_tool import * ->>> create_tuf_client_directory("repository/", "client/tufrepo/") -``` - -`create_tuf_client_directory()` moves metadata from `repository/metadata` to -`client/` in this example. The repository in `repository/` may be the -repository example created earlier in this document. - -## Test TUF Locally ## -Run the local TUF repository server. -```Bash -$ cd "repository/"; python3 -m http.server 8001 -``` - -We next retrieve targets from the TUF repository and save them to `client/`. -The `client.py` script is available to download metadata and files from a -specified repository. In a different command-line prompt, where `tuf` is -installed . . . -```Bash -$ cd "client/" -$ ls -tufrepo/ - -$ client.py --repo http://localhost:8001 file1.txt -$ ls . tuftargets/ -.: -tufrepo tuftargets - -tuftargets/: -file1.txt -``` diff --git a/docs/conf.py b/docs/conf.py index 4577404ba3..7f880e3dbc 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -35,11 +35,6 @@ # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -# This pattern also affects html_static_path and html_extra_path. -exclude_patterns = ['GETTING_STARTED.rst'] - # -- Options for HTML output ------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for diff --git a/docs/images/repository_tool-diagram.png b/docs/images/repository_tool-diagram.png deleted file mode 100644 index 6bfbdeb0b7d997549985dda97826a2d866d856dc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 91920 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≶77JSzt1?FAv&Cj`h^hslX=G3~I?Y>kT_de!~ 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 z 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 diff --git a/tuf/ATTACKS.md b/tuf/ATTACKS.md deleted file mode 100644 index 416e164ddb..0000000000 --- a/tuf/ATTACKS.md +++ /dev/null @@ -1,323 +0,0 @@ -# Demonstrate protection against malicious updates - -## Table of Contents ## -- [Blocking Malicious Updates](#blocking-malicious-updates) - - [Arbitrary Package Attack](#arbitrary-package-attack) - - [Rollback Attack](#rollback-attack) - - [Indefinite Freeze Attack](#indefinite-freeze-attack) - - [Endless Data Attack](#endless-data-attack) - - [Compromised Key Attack](#compromised-key-attack) - - [Slow Retrieval Attack](#slow-retrieval-attack) -- [Conclusion](#conclusion) - -## Blocking Malicious Updates ## -TUF protects against a number of attacks, some of which include rollback, -arbitrary package, and mix and match attacks. We begin this document on -blocking malicious updates by demonstrating how the client rejects a target -file downloaded from the software repository that doesn't match what is listed -in TUF metadata. - -The following demonstration requires and operates on the repository created in -the [repository management -tutorial](https://github.com/theupdateframework/python-tuf/blob/develop/tuf/README.md). - -### Arbitrary Package Attack ### -In an arbitrary package attack, an attacker installs anything they want on the -client system. That is, an attacker can provide arbitrary files in response to -download requests and the files will not be detected as illegitimate. We -simulate an arbitrary package attack by creating a "malicious" target file -that our client attempts to fetch. - -```Bash -$ mv 'repository/targets/file2.txt' 'repository/targets/file2.txt.backup' -$ echo 'bad_target' > 'repository/targets/file2.txt' -``` - -We next reset our local timestamp (so that a new update is prompted), and -the target files previously downloaded by the client. -```Bash -$ rm -rf "client/targets/" "client/metadata/current/timestamp.json" -``` - -The client now performs an update and should detect the invalid target file... -Note: The following command should be executed in the "client/" directory. -```Bash -$ python3 basic_client.py --repo http://localhost:8001 -Error: No working mirror was found: - localhost:8001: BadHashError() -``` - -The log file (tuf.log) saved to the current working directory contains more -information on the update procedure and the cause of the BadHashError. - -```Bash -... - -BadHashError: Observed -hash ('f569179171c86aa9ed5e8b1d6c94dfd516123189568d239ed57d818946aaabe7') != -expected hash (u'67ee5478eaadb034ba59944eb977797b49ca6aa8d3574587f36ebcbeeb65f70e') -[2016-10-20 19:45:16,079 UTC] [tuf.client.updater] [ERROR] [_get_file:1415@updater.py] -Failed to update /file2.txt from all mirrors: {u'http://localhost:8001/targets/file2.txt': BadHashError()} -``` - -Note: The "malicious" target file should be removed and the original file2.txt -restored, otherwise the following examples will fail with BadHashError -exceptions: - -```Bash -$ mv 'repository/targets/file2.txt.backup' 'repository/targets/file2.txt' -``` - -### Indefinite Freeze Attack ### -In an indefinite freeze attack, an attacker continues to present a software -update system with the same files the client has already seen. The result is -that the client does not know that new files are available. Although the -client would be unable to prevent an attacker or compromised repository from -feeding it stale metadata, it can at least detect when an attacker is doing so -indefinitely. The signed metadata used by TUF contains an "expires" field that -indicates when metadata should no longer be trusted. - -In the following simulation, the client first tries to perform an update. - -```Bash -$ python3 basic_client.py --repo http://localhost:8001 -``` - -According to the logger (`tuf.log` file in the current working directory), -everything appears to be up-to-date. The remote server should also show that -the client retrieved only the timestamp.json file. Let's suppose now that an -attacker continues to feed our client the same stale metadata. If we were to -move the time to a future date that would cause metadata to expire, the TUF -framework should raise an exception or error to indicate that the metadata -should no longer be trusted. - -```Bash -$ sudo date -s '2080-12-25 12:34:56' -Wed Dec 25 12:34:56 EST 2080 - -$ python3 basic_client.py --repo http://localhost:8001 -Error: No working mirror was found: - u'localhost:8001': ExpiredMetadataError(u"Metadata u'root' expired on Tue Jan 1 00:00:00 2030 (UTC).",) -``` - -Note: Reset the date to continue with the rest of the attacks. - - -### Rollback Attack ### -In a rollback attack, an attacker presents a software update system with older -files than those the client has already seen, causing the client to use files -older than those the client knows about. We begin this example by saving the -current version of the Timestamp file available on the repository. This saved -file will later be served to the client to see if it is rejected. The client -should not accept versions of metadata that is older than previously trusted. - -Navigate to the directory containing the server's files and save the current -timestamp.json to a temporary location: -```Bash -$ cp repository/metadata/timestamp.json /tmp -``` - -We should next generate a new Timestamp file on the repository side. -```Bash -$ python3 ->>> from tuf.repository_tool import * ->>> repository = load_repository('repository') ->>> repository.timestamp.version -1 ->>> repository.timestamp.version = 2 ->>> repository.dirty_roles() -Dirty roles: [u'timestamp'] ->>> private_timestamp_key = import_rsa_privatekey_from_file("keystore/timestamp_key") -Enter a password for the encrypted RSA file (/path/to/keystore/timestamp_key): ->>> repository.timestamp.load_signing_key(private_timestamp_key) ->>> repository.write('timestamp') - -$ cp repository/metadata.staged/* repository/metadata -``` - -Now start the HTTP server from the directory containing the 'repository' -subdirectory. -```Bash -$ python3 -m SimpleHTTPServer 8001 -``` - -And perform an update so that the client retrieves the updated timestamp.json. -```Bash -$ python3 basic_client.py --repo http://localhost:8001 -``` - -Finally, move the previous timestamp.json file to the current live repository -and have the client try to download the outdated version. The client should -reject it! -```Bash -$ cp /tmp/timestamp.json repository/metadata/ -$ cd repository; python3 -m SimpleHTTPServer 8001 -``` - -On the client side, perform an update... -```Bash -$ python3 basic_client.py --repo http://localhost:8001 -Error: No working mirror was found: - u'localhost:8001': ReplayedMetadataError() -``` - -The tuf.log file contains more information about the ReplayedMetadataError -exception and update process. Please reset timestamp.json to the latest -version, which can be found in the 'repository/metadata.staged' subdirectory. - -```Bash -$ cp repository/metadata.staged/timestamp.json repository/metadata -``` - - -### Endless Data Attack ### -In an endless data attack, an attacker responds to a file download request with -an endless stream of data, causing harm to clients (e.g., a disk partition -filling up or memory exhaustion). In this simulated attack, we append extra -data to one of the target files available on the software repository. The -client should only download the exact number of bytes it expects for a -requested target file (according to what is listed in trusted TUF metadata). - -```Bash -$ cp repository/targets/file1.txt /tmp -$ python3 -c "print 'a' * 1000" >> repository/targets/file1.txt -``` - -Now delete the local metadata and target files on the client side so -that remote metadata and target files are downloaded again. -```Bash -$ rm -rf client/targets/ -$ rm client/metadata/current/snapshot.json* client/metadata/current/timestamp.json* -``` - -Lastly, perform an update to verify that the file1.txt is downloaded up to the -expected size, and no more. The target file available on the software -repository does contain more data than expected, though. - -```Bash -$ python3 basic_client.py --repo http://localhost:8001 -``` - -At this point, part of the "file1.txt" file should have been fetched. That is, -up to 31 bytes of it should have been downloaded, and the rest of the maliciously -appended data ignored. If we inspect the logger, we'd discover the following: - -```Bash -[2016-10-06 21:37:39,092 UTC] [tuf.download] [INFO] [_download_file:235@download.py] -Downloading: u'http://localhost:8001/targets/file1.txt' - -[2016-10-06 21:37:39,145 UTC] [tuf.download] [INFO] [_check_downloaded_length:610@download.py] -Downloaded 31 bytes out of the expected 31 bytes. - -[2016-10-06 21:37:39,145 UTC] [tuf.client.updater] [INFO] [_get_file:1372@updater.py] -Not decompressing http://localhost:8001/targets/file1.txt - -[2016-10-06 21:37:39,145 UTC] [tuf.client.updater] [INFO] [_check_hashes:778@updater.py] -The file's sha256 hash is correct: 65b8c67f51c993d898250f40aa57a317d854900b3a04895464313e48785440da -``` - -Indeed, the sha256 sum of the first 31 bytes of the "file1.txt" available -on the repository should match to what is trusted. The client did not -downloaded the appended data. - -Note: Restore file1.txt - -```Bash -$ cp /tmp/file1.txt repository/targets/ -``` - - -### Compromised Key Attack ### -An attacker who compromise less than a given threshold of keys is limited in -scope. This includes relying on a single online key (such as only being -protected by SSL) or a single offline key (such as most software update systems -use to sign files). In this example, we attempt to sign a role file with -less-than-a-threshold number of keys. A single key (suppose this is a -compromised key) is used to demonstrate that roles must be signed with the -total number of keys required for the role. In order to compromise a role, an -attacker would have to compromise a threshold of keys. This approach of -requiring a threshold number of signatures provides compromise resilience. - -Let's attempt to sign a new snapshot file with a less-than-threshold number of -keys. The client should reject the partially signed snapshot file served by -the repository (or imagine that it is a compromised software repository). - -```Bash -$ python3 ->>> from tuf.repository_tool import * ->>> repository = load_repository('repository') ->>> version = repository.root.version ->>> repository.root.version = version + 1 ->>> private_root_key = import_rsa_privatekey_from_file("keystore/root_key", password="password") ->>> repository.root.load_signing_key(private_root_key) ->>> private_root_key2 = import_rsa_privatekey_from_file("keystore/root_key2", password="password") ->>> repository.root.load_signing_key(private_root_key2) - ->>> repository.snapshot.version = 8 ->>> repository.snapshot.threshold = 2 ->>> private_snapshot_key = import_rsa_privatekey_from_file("keystore/snapshot_key", password="password") ->>> repository.snapshot.load_signing_key(private_snapshot_key) - ->>> repository.timestamp.version = 8 ->>> private_timestamp_key = import_rsa_privatekey_from_file("keystore/timestamp_key", password="password") ->>> repository.timestamp.load_signing_key(private_timestamp_key) - ->>> repository.write('root') ->>> repository.write('snapshot') ->>> repository.write('timestamp') - -$ cp repository/metadata.staged/* repository/metadata -``` - -The client now attempts to refresh the top-level metadata and the -partially written snapshot.json, which should be rejected. - -```Bash -$ python3 basic_client.py --repo http://localhost:8001 -Error: No working mirror was found: - u'localhost:8001': BadSignatureError() -``` - - -### Slow Retrieval Attack ### -In a slow retrieval attack, an attacker responds to clients with a very slow -stream of data that essentially results in the client never continuing the -update process. In this example, we simulate a slow retrieval attack by -spawning a server that serves data at a slow rate to our update client data. -TUF should not be vulnerable to this attack, and the framework should raise an -exception or error when it detects that a malicious server is serving it data -at a slow enough rate. - -We first spawn the server that slowly streams data to the client. The -'slow_retrieval_server_old.py' module (can be found in the tests/ directory of the -source code) should be copied over to the server's 'repository/' directory from -which to launch it. - -```Bash -# Before launching the slow retrieval server, copy 'slow_retrieval_server_old.py' -# to the 'repository/' directory and run it from that directory as follows: -$ python3 slow_retrieval_server_old.py 8002 mode_2 -``` - -The client may now make a request to the slow retrieval server on port 8002. -However, before doing so, we'll reduce (for the purposes of this demo) the -minimum average download rate allowed and download chunk size. Open the -'settings.py' module and set MIN_AVERAGE_DOWNLOAD_SPEED = 5 and CHUNK_SIZE = 1. -This should make it so that the client detects the slow retrieval server's -delayed streaming. - -```Bash -$ python3 basic_client.py --verbose 1 --repo http://localhost:8002 -Error: No working mirror was found: - u'localhost:8002': SlowRetrievalError() -``` - -The framework should detect the slow retrieval attack and raise a -SlowRetrievalError exception to the client application. - - -## Conclusion ## -These are just some of the attacks that TUF provides protection against. For -more attacks and updater weaknesses, please see the -[Security](https://theupdateframework.io/security/) -page. diff --git a/tuf/README-developer-tools.md b/tuf/README-developer-tools.md deleted file mode 100644 index 1b593400a5..0000000000 --- a/tuf/README-developer-tools.md +++ /dev/null @@ -1,342 +0,0 @@ -# The Update Framework Developer Tool: How to Update your Project Securely on a TUF Repository - -## Table of Contents -- [Overview](#overview) -- [Creating a Simple Project](#creating_a_simple_project) - - [Generating a Key](#generating_a_key) - - [The Project Class](#the_project_class) - - [Signing and Writing the Metadata](#signing_and_writing_the_metadata) -- [Loading an Existing Project](#loading_an_existing_project) -- [Delegations](#delegations) -- [Managing Keys](#managing_keys) -- [Managing Targets](#managing_targets) - - -## Overview -The Update Framework (TUF) is a Python-based security system for software -updates. In order to prevent your users from downloading vulnerable or malicious -code disguised as updates to your software, TUF requires that each update you -release include certain metadata verifying your authorship of the files. - -The TUF developer tools are a Python Library that enables you to create and -maintain the required metadata for files hosted on a TUF Repository. (We call -these files “targets,” to distinguish them from the metadata associated with -them. Both of these together comprise a complete “project”.) You will use these -tools to generate the keys and metadata you need to claim and secure your files -on the repository, and to update the metadata and sign it with those keys -whenever you upload a new version of those files. - -This document will teach you how to use these tools in two parts. The first -part walks through the creation of a minimal-complexity TUF project, which is -all you need to get started, and can be expanded later. The second part details -the full functionality of the tools, which offer a finer degree of control in -securing your project. - - -## Creating a Simple Project -This section walks through the creation of a small example project with just -one target. Once created, this project will be fully functional, and can be -modified as needed. - - -### Generating a Key -First, we will need to generate a key to sign the metadata. Keys are generated -in pairs: one public and the other private. The private key is -password-protected and is used to sign metadata. The public key can be shared -freely, and is used to verify signatures made by the private key. You will need -to share your public key with the repository hosting your project so they can -verify your metadata is signed by the right person. - -The generate\_and\_write\_rsa\_keypair function will create two key files named -"path/to/key.pub", which is the public key and "path/to/key", which -is the private key. - -``` ->>> from tuf.developer_tool import * ->>> generate_and_write_rsa_keypair_with_prompt(filepath="path/to/key") -enter password to encrypt private key file 'path/to/key' -(leave empty if key should not be encrypted): -Confirm: ->>> -``` - -We can also use the bits parameter to set a different key length (the default -is 3072). We can also `generate_and_write_rsa_keypair` with a `password` -parameter if a prompt is not desired. - -In this example we will be using rsa keys, but ed25519 keys are also supported. - -Now we have a key for our project, we can proceed to create our project. - - -### The Project Class -The TUF developer tool is built around the Project class, which is used to -organize groups of targets associated with a single set of metadata. A single -Project instance is used to keep track of all the target files and metadata -files in one project. The Project also keeps track of the keys and signatures, -so that it can update all the metadata with the correct changes and signatures -on a single command. - -Before creating a project, you must know where it will be located in the TUF -Repository. In the following example, we will create a project to be hosted as -"repo/unclaimed/example_project" within the repository, and store a local copy -of the metadata at "path/to/metadata". The project will comprise a single -target file, "local/path/to/example\_project/target\_1" locally, and we will -secure it with the key generated above. - -First, we must import the generated keys. We can do that by issuing the -following command: - -``` ->>> public_key = import_rsa_publickey_from_file("path/to/keys.pub") -``` - -After importing the key, we can generate a new project with the following -command: - -``` ->>> project = create_new_project(project_name="example_project", -... metadata_directory="local/path/to/metadata/", -... targets_directory="local/path/to/example_project", -... location_in_repository="repo/unclaimed", key=public_key) -``` - -Let's list the arguments and make sense out of this rather long function call: - -- create a project named example_project: the name of the metadata file will match this name -- the metadata will be located in "local/path/to/metadata", this means all of the generated files -for this project will be located here -- the targets are located in local/path/to/example project. If your targets are located in some other -place, you can point the targets directory there. Files must reside under the path local/path/to/example_project or else it won't be possible to add them. -- location\_in\_repository points to repo/unclaimed, this will be prepended to the paths in the generated metadata so the signatures all match. - -Now the project is in memory and we can do different operations on it such as -adding and removing targets, delegating files, changing signatures and keys, -etc. For the moment we are interested in adding our one and only target inside -the project. - -To add a target, we issue the following method: - -``` ->>> project.add_target("local/path/to/example_project/target_1") -``` - -Note that the file "target\_1" should be located in -"local/path/to/example\_project", or this method will throw an -error. - -At this point, the metadata is not valid. We have assigned a key to the -project, but we have not *signed* it with that key. Signing is the process of -generating a signature with our private key so it can be verified with the -public key by the server (upon uploading) and by the clients (when updating). - - -### Signing and Writing the Metadata ### -In order to sign the metadata, we need to import the private key corresponding -to the public key we added to the project. One the key is loaded to the project, -it will automatically be used to sign the metadata whenever it is written. - -``` ->>> private_key = import_rsa_privatekey_from_file("path/to/key") -Enter password for the RSA key: ->>> project.load_signing_key(private_key) ->>> project.write() -``` - -When all changes to the project have been written, the metadata is ready to be -uploaded to the repository, and it is safe to exit the Python interpreter, or -to delete the Project instance. - -The project can be loaded later to update changes to the project. The metadata -contains checksums that have to match the actual files or else it won't be -accepted by the upstream repository. - -At this point, if you have followed all the steps in this document so far -(substituting appropriate names and filepaths) you will have created a basic -TUF project, which can be expanded as needed. The simplest way to get your -project secured is to add all your files using add\_target() (or see [Managing -Keys](#managing_keys) on how to add whole directories). If your project has -several contributors, you may want to consider adding -[delegations](#delegations) to your project. - - -## Loading an Existing Project -To make changes to existing metadata, we will need the Project again. We can -restore it with the load_project() function. - -``` ->>> from tuf.developer_tool import * ->>> project = load_project("local/path/to/metadata") -``` -Each time the project is loaded anew, the necessary private keys must also be -loaded in order to sign metadata. - -``` ->>> private_key = import_rsa_privatekey_from_file("path/to/key") -Enter a password for the RSA key: ->>> project.load_signing_key(private_key) ->>> project.write() -``` - -If your project does not use any delegations, the five commands above are all -you need to update your project's metadata. - - -## Delegations - -The project we created above is secured entirely by one key. If you want to -allow someone else to update part of your project independently, you will need -to delegate a new role for them. For example, we can do the following: - -``` ->>> other_key = import_rsa_publickey_from_file(“another_public_key.pub”) ->>> targets = ['local/path/to/newtarget'] ->>> project.delegate(“newrole”, [other_key], targets) -``` - -The new role is now an attribute of the Project instance, and contains the same -methods as Project. For example, we can add targets in the same way as before: - -``` ->>> project(“newrole”).add_target(“delegated_1”) -``` - -Recall that we input the other person’s key as part of a list. That list can -contain any number of public keys. We can also add keys to the role after -creating it using the [add\_verification\_key()](#adding_a_key_to_a_delegation) -method. - -### Delegated Paths - -By default, a delegated role is permitted to add and modify targets anywhere in -the Project's targets directory. We can delegate trust of paths to a role to -limit this permission. - -``` ->>> project.add_paths(["delegated/filepath"], "newrole") -``` - -This will prevent the delegated role from signing targets whose local filepaths -do not begin with "delegated/filepath". We can delegate several filepaths to a -role by adding them to the list in the first parameter, or by invoking the -method again. A role with multiple delegated paths can add targets to any of -them. - -Note that this method is invoked from the parent role (in this case, the Project) -and takes the delegated role name as an argument. - -### Nested Delegations - -It is possible for a delegated role to have delegations of its own. We can do -this by calling delegate() on a delegated role: - -``` ->>> project("newrole").delegate(“nestedrole”, [key], targets) -``` - -Nested delegations function no differently than first-order delegations. to -demonstrate, adding a target to nested delegation looks like this: - -``` ->>> project("newrole")("nestedrole").add_target("foo") -``` - -### Revoking Delegations -Delegations can be revoked, removing the delegated role from the project. - -``` ->>> project.revoke("newrole") -``` - - -## Managing Keys -This section describes the key-related functions and parameters not covered in -the [Creating a Simple Project](#creating_a_simple_project) section. - -### Additional Parameters for Key Generation -When generating keys, it is possible to specify the length of the key in bits -and its password as parameters: - -``` ->>> generate_and_write_rsa_keypair(password="pw", filepath="path/to/key", bits=2048) -``` -The bits parameter defaults to 3072, and values below 2048 will raise an error. -The password parameter is only intended to be used in scripts. - - -### Adding a Key to a Delegation -New verifications keys can be added to an existing delegation using -add\_verification\_key(): - -``` ->>> project("rolename").add_verification_key(pubkey) -``` - -A delegation can have several verification keys at once. By default, a -delegated role with multiple keys can be written using any one of their -corresponding signing keys. To modify this behavior, you can change the -delegated role's [threshold](#delegation_thrsholds). - -### Removing a Key from a Delegation -Verification keys can also be removed, like this: - -``` ->>> project("rolename").remove_verification_key(pubkey) -``` - -Remember that a project can only have one key, so this method will return an -error if there is already a key assigned to it. In order to replace a key we -must first delete the existing one and then add the new one. It is possible to -omit the key parameter in the create\_new\_project() function, and add the key -later. - -### Changing the Project Key -Each Project instance can only have one verification key. This key can be -replaced by removing it and adding a new key, in that order. - -``` ->>> project.remove_verification_key(oldkey) ->>> project.add_verification_key(new) -``` - - -### Delegation Thresholds - -Every delegated role has a threshold, which determines how many of its signing -keys need to be loaded to write the role. The threshold defaults to 1, and -should not exceed the number of verification keys assigned to the role. The -threshold can be accessed as a property of a delegated role. - -``` ->>> project("rolename").threshold = 2 -``` - -The above line will set the "rolename" role's threshold to 2. - - -## Managing Targets -There are supporting functions of the targets library to make the project -maintenance easier. These functions are described in this section. - -### Adding Targets by Directory -This function is especially useful when creating a new project to add all the -files contained in the targets directory. The following code block illustrates -the usage of this function: - -``` ->>> list_of_targets = \ -... project.get_filepaths_in_directory(“path/within/targets/folder”, -... recursive_walk=False, follow_links=False) ->>> project.add_targets(list_of_targets) -``` - -### Deleting Targets from a Project -It is possible that we want to delete existing targets inside our project. To -stop the developer tool from tracking this file we can issue the following -command: - -``` ->>> project.remove_target(“target_1”) -``` - -Now the target file won't be part of the metadata. diff --git a/tuf/README.md b/tuf/README.md deleted file mode 100644 index dbc53b61b5..0000000000 --- a/tuf/README.md +++ /dev/null @@ -1,5 +0,0 @@ -[Quickstart](../docs/QUICKSTART.md) - -[CLI](../docs/CLI.md) - -[Tutorial](../docs/TUTORIAL.md) diff --git a/tuf/client/README.md b/tuf/client/README.md deleted file mode 100644 index 29b838bc4d..0000000000 --- a/tuf/client/README.md +++ /dev/null @@ -1,151 +0,0 @@ -# updater.py -**updater.py** is intended as the only TUF module that software update -systems need to utilize for a low-level integration. It provides a single -class representing an updater that includes methods to download, install, and -verify metadata or target files in a secure manner. Importing -**tuf.client.updater** and instantiating its main class is all that is -required by the client prior to a TUF update request. The importation and -instantiation steps allow TUF to load all of the required metadata files -and set the repository mirror information. - -The **tuf.repository_tool** module can be used to create a TUF repository. See -[tuf/README](../README.md) for more information on creating TUF repositories. - - -## Overview of the Update Process - -1. The software update system instructs TUF to check for updates. - -2. TUF downloads and verifies timestamp.json. - -3. If timestamp.json indicates that snapshot.json has changed, TUF downloads and -verifies snapshot.json. - -4. TUF determines which metadata files listed in snapshot.json differ from those -described in the last snapshot.json that TUF has seen. If root.json has changed, -the update process starts over using the new root.json. - -5. TUF provides the software update system with a list of available files -according to targets.json. - -6. The software update system instructs TUF to download a specific target -file. - -7. TUF downloads and verifies the file and then makes the file available to -the software update system. - - -If at any point in the above procedure there is a problem (i.e., if unexpired, -signed, valid metadata cannot be retrieved from the repository), the Root file -is downloaded and the process is retried once more (and only once to avoid an -infinite loop). Optionally, the software update system using the framework -can decide how to proceed rather than automatically downloading a new Root file. - - -## Example Client -### Refresh TUF Metadata -```Python -# The client first imports the 'updater.py' module, the only module the -# client is required to import. The client will utilize a single class -# from this module. -import tuf.client.updater -import tuf.settings - -# The only other module the client interacts with is 'settings'. The -# client accesses this module solely to set the repository directory. -# This directory will hold the files downloaded from a remote repository. -tuf.settings.repositories_directory = 'path/to/local_repository' - -# Next, the client creates a dictionary object containing the repository -# mirrors. The client may download content from any one of these mirrors. -# In the example below, a single mirror named 'mirror1' is defined. The -# mirror is located at 'http://localhost:8001', and all of the metadata -# and targets files can be found in the 'metadata' and 'targets' directory, -# respectively. If the client wishes to only download target files from -# specific directories on the mirror, the 'confined_target_dirs' field -# should be set. In this example, the client hasn't set confined_target_dirs, -# which is interpreted as no confinement. In other words, the client can download -# targets from any directory or subdirectories. If the client had chosen -# 'targets1/', they would have been confined to the '/targets/targets1/' -# directory on the 'http://localhost:8001' mirror. -repository_mirrors = {'mirror1': {'url_prefix': 'http://localhost:8001', - 'metadata_path': 'metadata', - 'targets_path': 'targets'}} - -# The updater may now be instantiated. The Updater class of 'updater.py' -# is called with two arguments. The first argument assigns a name to this -# particular updater and the second argument the repository mirrors defined -# above. -updater = tuf.client.updater.Updater('updater', repository_mirrors) - -# The client calls the refresh() method to ensure it has the latest -# copies of the top-level metadata files (i.e., Root, Targets, Snapshot, -# Timestamp). -updater.refresh() -``` - - -### Download Specific Target File -```Python -# Example demonstrating an update that downloads a specific target. - -# Refresh the metadata of the top-level roles (i.e., Root, Targets, Snapshot, Timestamp). -updater.refresh() - -# get_one_valid_targetinfo() updates role metadata when required. In other -# words, if the client doesn't possess the metadata that lists 'LICENSE.txt', -# get_one_valid_targetinfo() will try to fetch / update it. -target = updater.get_one_valid_targetinfo('LICENSE.txt') -updated_target = updater.updated_targets([target], destination_directory) - -for target in updated_target: - updater.download_target(target, destination_directory) - # Client code here may also reference target information (including 'custom') - # by directly accessing the dictionary entries of the target. The 'custom' - # entry is additional file information explicitly set by the remote repository. - target_path = target['filepath'] - target_length = target['fileinfo']['length'] - target_hashes = target['fileinfo']['hashes'] - target_custom_data = target['fileinfo']['custom'] - - # Remove any files from the destination directory that are no longer being - # tracked. For example, a target file from a previous snapshot that has since - # been removed on the remote repository. - updater.remove_obsolete_targets(destination_directory) -``` - -### A Simple Integration Example with client.py -``` Bash -# Assume a simple TUF repository has been setup with 'repo.py'. -$ client.py --repo http://localhost:8001 - -# Metadata and target files are silently updated. An exception is only raised if an error, -# or attack, is detected. Inspect 'tuf.log' for the outcome of the update process. - -$ cat tuf.log -[2013-12-16 16:17:05,267 UTC] [tuf.download] [INFO][_download_file:726@download.py] -Downloading: http://localhost:8001/metadata/timestamp.json - -[2013-12-16 16:17:05,269 UTC] [tuf.download] [WARNING][_check_content_length:589@download.py] -reported_length (545) < required_length (2048) - -[2013-12-16 16:17:05,269 UTC] [tuf.download] [WARNING][_check_downloaded_length:656@download.py] -Downloaded 545 bytes, but expected 2048 bytes. There is a difference of 1503 bytes! - -[2013-12-16 16:17:05,611 UTC] [tuf.download] [INFO][_download_file:726@download.py] -Downloading: http://localhost:8001/metadata/snapshot.json - -[2013-12-16 16:17:05,612 UTC] [tuf.client.updater] [INFO][_check_hashes:636@updater.py] -The file's sha256 hash is correct: 782675fadd650eeb2926d33c401b5896caacf4fd6766498baf2bce2f3b739db4 - -[2013-12-16 16:17:05,951 UTC] [tuf.download] [INFO][_download_file:726@download.py] -Downloading: http://localhost:8001/metadata/targets.json - -[2013-12-16 16:17:05,952 UTC] [tuf.client.updater] [INFO][_check_hashes:636@updater.py] -The file's sha256 hash is correct: a5019c28a1595c43a14cad2b6252c4d1db472dd6412a9204181ad6d61b1dd69a - -[2013-12-16 16:17:06,299 UTC] [tuf.download] [INFO][_download_file:726@download.py] -Downloading: http://localhost:8001/targets/file1.txt - -[2013-12-16 16:17:06,303 UTC] [tuf.client.updater] [INFO][_check_hashes:636@updater.py] -The file's sha256 hash is correct: ecdc5536f73bdae8816f0ea40726ef5e9b810d914493075903bb90623d97b1d8