Skip to content

Commit

Permalink
Merge pull request #1675 from kairoaraujo/issue#1518/python-client-ex…
Browse files Browse the repository at this point in the history
…ample-tutorial

TUF Python Client Example/Tutorial
  • Loading branch information
Jussi Kukkonen authored Dec 7, 2021
2 parents a93f618 + 1344410 commit 2db0cd6
Show file tree
Hide file tree
Showing 3 changed files with 249 additions and 0 deletions.
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()

0 comments on commit 2db0cd6

Please sign in to comment.