Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TUF Python Client Example/Tutorial #1675

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 87 additions & 0 deletions examples/client_example/1.root.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
{
"signatures": [
{
"keyid": "4e777de0d275f9d28588dd9a1606cc748e548f9e22b6795b7cb3f63f98035fcb",
"sig": "a337d6375fedd2eabfcd6c2ef6c8a9c3bb85dc5a857715f6a6bd41123e7670c4972d8548bcd7248154f3d864bf25f1823af59d74c459f41ea09a02db057ca1245612ebbdb97e782c501dc3e094f7fa8aa1402b03c6ed0635f565e2a26f9f543a89237e15a2faf0c267e2b34c3c38f2a43a28ddcdaf8308a12ead8c6dc47d1b762de313e9ddda8cc5bc25aea1b69d0e5b9199ca02f5dda48c3bff615fd12a7136d00634b9abc6e75c3256106c4d6f12e6c43f6195071355b2857bbe377ce028619b58837696b805040ce144b393d50a472531f430fadfb68d3081b6a8b5e49337e328c9a0a3f11e80b0bc8eb2dc6e78d1451dd857e6e6e6363c3fd14c590aa95e083c9bfc77724d78af86eb7a7ef635eeddaa353030c79f66b3ba9ea11fab456cfe896a826fdfb50a43cd444f762821aada9bcd7b022c0ee85b8768f960343d5a1d3d76374cc0ac9e12a500de0bf5d48569e5398cadadadab045931c398e3bcb6cec88af2437ba91959f956079cbed159fed3938016e6c3b5e446131f81cc5981"
}
],
"signed": {
"_type": "root",
"consistent_snapshot": false,
"expires": "2030-01-01T00:00:00Z",
"keys": {
"4e777de0d275f9d28588dd9a1606cc748e548f9e22b6795b7cb3f63f98035fcb": {
"keyid_hash_algorithms": [
"sha256",
"sha512"
],
"keytype": "rsa",
"keyval": {
"public": "-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEA0GjPoVrjS9eCqzoQ8VRe\nPkC0cI6ktiEgqPfHESFzyxyjC490Cuy19nuxPcJuZfN64MC48oOkR+W2mq4pM51i\nxmdG5xjvNOBRkJ5wUCc8fDCltMUTBlqt9y5eLsf/4/EoBU+zC4SW1iPU++mCsity\nfQQ7U6LOn3EYCyrkH51hZ/dvKC4o9TPYMVxNecJ3CL1q02Q145JlyjBTuM3Xdqsa\nndTHoXSRPmmzgB/1dL/c4QjMnCowrKW06mFLq9RAYGIaJWfM/0CbrOJpVDkATmEc\nMdpGJYDfW/sRQvRdlHNPo24ZW7vkQUCqdRxvnTWkK5U81y7RtjLt1yskbWXBIbOV\nz94GXsgyzANyCT9qRjHXDDz2mkLq+9I2iKtEqaEePcWRu3H6RLahpM/TxFzw684Y\nR47weXdDecPNxWyiWiyMGStRFP4Cg9trcwAGnEm1w8R2ggmWphznCd5dXGhPNjfA\na82yNFY8ubnOUVJOf0nXGg3Edw9iY3xyjJb2+nrsk5f3AgMBAAE=\n-----END PUBLIC KEY-----"
},
"scheme": "rsassa-pss-sha256"
},
"59a4df8af818e9ed7abe0764c0b47b4240952aa0d179b5b78346c470ac30278d": {
"keyid_hash_algorithms": [
"sha256",
"sha512"
],
"keytype": "ed25519",
"keyval": {
"public": "edcd0a32a07dce33f7c7873aaffbff36d20ea30787574ead335eefd337e4dacd"
},
"scheme": "ed25519"
},
"65171251a9aff5a8b3143a813481cb07f6e0de4eb197c767837fe4491b739093": {
"keyid_hash_algorithms": [
"sha256",
"sha512"
],
"keytype": "ed25519",
"keyval": {
"public": "89f28bd4ede5ec3786ab923fd154f39588d20881903e69c7b08fb504c6750815"
},
"scheme": "ed25519"
},
"8a1c4a3ac2d515dec982ba9910c5fd79b91ae57f625b9cff25d06bf0a61c1758": {
"keyid_hash_algorithms": [
"sha256",
"sha512"
],
"keytype": "ed25519",
"keyval": {
"public": "82ccf6ac47298ff43bfa0cd639868894e305a99c723ff0515ae2e9856eb5bbf4"
},
"scheme": "ed25519"
}
},
"roles": {
"root": {
"keyids": [
"4e777de0d275f9d28588dd9a1606cc748e548f9e22b6795b7cb3f63f98035fcb"
],
"threshold": 1
},
"snapshot": {
"keyids": [
"59a4df8af818e9ed7abe0764c0b47b4240952aa0d179b5b78346c470ac30278d"
],
"threshold": 1
},
"targets": {
"keyids": [
"65171251a9aff5a8b3143a813481cb07f6e0de4eb197c767837fe4491b739093"
],
"threshold": 1
},
"timestamp": {
"keyids": [
"8a1c4a3ac2d515dec982ba9910c5fd79b91ae57f625b9cff25d06bf0a61c1758"
],
"threshold": 1
}
},
"spec_version": "1.0.0",
"version": 1
}
}
26 changes: 26 additions & 0 deletions examples/client_example/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# TUF Client Example


TUF Client Example, using ``python-tuf``.

This TUF Client Example implements the following actions:
- Client Infrastructure Initialization
- Download target files from TUF Repository

The example client expects to find a TUF repository running on localhost. We
can use the static metadata files in ``tests/repository_data/repository``
to set one up.

Run the repository using the Python3 built-in HTTP module, and keep this
session running.

```console
$ python3 -m http.server -d tests/repository_data/repository
Serving HTTP on :: port 8000 (http://[::]:8000/) ...
```

How to use the TUF Client Example to download a target file.

```console
$ ./client_example.py download file1.txt
```
136 changes: 136 additions & 0 deletions examples/client_example/client_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
#!/usr/bin/env python
"""TUF Client Example"""

# Copyright 2012 - 2017, New York University and the TUF contributors
# SPDX-License-Identifier: MIT OR Apache-2.0

import argparse
import logging
import os
import shutil
from pathlib import Path

from tuf.exceptions import RepositoryError
from tuf.ngclient import Updater

# constants
BASE_URL = "http://127.0.0.1:8000"
DOWNLOAD_DIR = "./downloads"
METADATA_DIR = f"{Path.home()}/.local/share/python-tuf-client-example"
CLIENT_EXAMPLE_DIR = os.path.dirname(os.path.abspath(__file__))


def init() -> None:
"""Initialize local trusted metadata and create a directory for downloads"""

if not os.path.isdir(DOWNLOAD_DIR):
os.mkdir(DOWNLOAD_DIR)

if not os.path.isdir(METADATA_DIR):
os.makedirs(METADATA_DIR)

if not os.path.isfile(f"{METADATA_DIR}/root.json"):
shutil.copy(
f"{CLIENT_EXAMPLE_DIR}/1.root.json", f"{METADATA_DIR}/root.json"
)
print(f"Added trusted root in {METADATA_DIR}")

else:
print(f"Found trusted root in {METADATA_DIR}")


def download(target: str) -> bool:
"""
Download the target file using ``ngclient`` Updater.

The Updater refreshes the top-level metadata, get the target information,
verifies if the target is already cached, and in case it is not cached,
downloads the target file.

Returns:
A boolean indicating if process was successful
"""
try:
updater = Updater(
repository_dir=METADATA_DIR,
metadata_base_url=f"{BASE_URL}/metadata/",
target_base_url=f"{BASE_URL}/targets/",
target_dir=DOWNLOAD_DIR,
)
updater.refresh()

info = updater.get_targetinfo(target)

if info is None:
print(f"Target {target} not found")
return True

path = updater.find_cached_target(info)
if path:
print(f"Target is available in {path}")
return True

path = updater.download_target(info)
print(f"Target downloaded and available in {path}")

except (OSError, RepositoryError) as e:
print(str(e))
return False

return True


def main() -> None:
"""Main TUF Client Example function"""

client_args = argparse.ArgumentParser(description="TUF Client Example")

# Global arguments
client_args.add_argument(
"-v",
"--verbose",
help="Output verbosity level (-v, -vv, ...)",
action="count",
default=0,
)

# Sub commands
sub_command = client_args.add_subparsers(dest="sub_command")

# Download
download_parser = sub_command.add_parser(
"download",
help="Download a target file",
)

download_parser.add_argument(
"target",
metavar="TARGET",
help="Target file",
)

command_args = client_args.parse_args()

if command_args.verbose == 0:
loglevel = logging.ERROR
elif command_args.verbose == 1:
loglevel = logging.WARNING
elif command_args.verbose == 2:
loglevel = logging.INFO
else:
loglevel = logging.DEBUG

logging.basicConfig(level=loglevel)

# initialize the TUF Client Example infrastructure
init()

if command_args.sub_command == "download":
download(command_args.target)

else:
client_args.print_help()


if __name__ == "__main__":
main()