-
Notifications
You must be signed in to change notification settings - Fork 272
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1675 from kairoaraujo/issue#1518/python-client-ex…
…ample-tutorial TUF Python Client Example/Tutorial
- Loading branch information
Showing
3 changed files
with
249 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() |