Skip to content

Commit 2db0cd6

Browse files
author
Jussi Kukkonen
authored
Merge pull request #1675 from kairoaraujo/issue#1518/python-client-example-tutorial
TUF Python Client Example/Tutorial
2 parents a93f618 + 1344410 commit 2db0cd6

File tree

3 files changed

+249
-0
lines changed

3 files changed

+249
-0
lines changed

examples/client_example/1.root.json

+87
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
{
2+
"signatures": [
3+
{
4+
"keyid": "4e777de0d275f9d28588dd9a1606cc748e548f9e22b6795b7cb3f63f98035fcb",
5+
"sig": "a337d6375fedd2eabfcd6c2ef6c8a9c3bb85dc5a857715f6a6bd41123e7670c4972d8548bcd7248154f3d864bf25f1823af59d74c459f41ea09a02db057ca1245612ebbdb97e782c501dc3e094f7fa8aa1402b03c6ed0635f565e2a26f9f543a89237e15a2faf0c267e2b34c3c38f2a43a28ddcdaf8308a12ead8c6dc47d1b762de313e9ddda8cc5bc25aea1b69d0e5b9199ca02f5dda48c3bff615fd12a7136d00634b9abc6e75c3256106c4d6f12e6c43f6195071355b2857bbe377ce028619b58837696b805040ce144b393d50a472531f430fadfb68d3081b6a8b5e49337e328c9a0a3f11e80b0bc8eb2dc6e78d1451dd857e6e6e6363c3fd14c590aa95e083c9bfc77724d78af86eb7a7ef635eeddaa353030c79f66b3ba9ea11fab456cfe896a826fdfb50a43cd444f762821aada9bcd7b022c0ee85b8768f960343d5a1d3d76374cc0ac9e12a500de0bf5d48569e5398cadadadab045931c398e3bcb6cec88af2437ba91959f956079cbed159fed3938016e6c3b5e446131f81cc5981"
6+
}
7+
],
8+
"signed": {
9+
"_type": "root",
10+
"consistent_snapshot": false,
11+
"expires": "2030-01-01T00:00:00Z",
12+
"keys": {
13+
"4e777de0d275f9d28588dd9a1606cc748e548f9e22b6795b7cb3f63f98035fcb": {
14+
"keyid_hash_algorithms": [
15+
"sha256",
16+
"sha512"
17+
],
18+
"keytype": "rsa",
19+
"keyval": {
20+
"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-----"
21+
},
22+
"scheme": "rsassa-pss-sha256"
23+
},
24+
"59a4df8af818e9ed7abe0764c0b47b4240952aa0d179b5b78346c470ac30278d": {
25+
"keyid_hash_algorithms": [
26+
"sha256",
27+
"sha512"
28+
],
29+
"keytype": "ed25519",
30+
"keyval": {
31+
"public": "edcd0a32a07dce33f7c7873aaffbff36d20ea30787574ead335eefd337e4dacd"
32+
},
33+
"scheme": "ed25519"
34+
},
35+
"65171251a9aff5a8b3143a813481cb07f6e0de4eb197c767837fe4491b739093": {
36+
"keyid_hash_algorithms": [
37+
"sha256",
38+
"sha512"
39+
],
40+
"keytype": "ed25519",
41+
"keyval": {
42+
"public": "89f28bd4ede5ec3786ab923fd154f39588d20881903e69c7b08fb504c6750815"
43+
},
44+
"scheme": "ed25519"
45+
},
46+
"8a1c4a3ac2d515dec982ba9910c5fd79b91ae57f625b9cff25d06bf0a61c1758": {
47+
"keyid_hash_algorithms": [
48+
"sha256",
49+
"sha512"
50+
],
51+
"keytype": "ed25519",
52+
"keyval": {
53+
"public": "82ccf6ac47298ff43bfa0cd639868894e305a99c723ff0515ae2e9856eb5bbf4"
54+
},
55+
"scheme": "ed25519"
56+
}
57+
},
58+
"roles": {
59+
"root": {
60+
"keyids": [
61+
"4e777de0d275f9d28588dd9a1606cc748e548f9e22b6795b7cb3f63f98035fcb"
62+
],
63+
"threshold": 1
64+
},
65+
"snapshot": {
66+
"keyids": [
67+
"59a4df8af818e9ed7abe0764c0b47b4240952aa0d179b5b78346c470ac30278d"
68+
],
69+
"threshold": 1
70+
},
71+
"targets": {
72+
"keyids": [
73+
"65171251a9aff5a8b3143a813481cb07f6e0de4eb197c767837fe4491b739093"
74+
],
75+
"threshold": 1
76+
},
77+
"timestamp": {
78+
"keyids": [
79+
"8a1c4a3ac2d515dec982ba9910c5fd79b91ae57f625b9cff25d06bf0a61c1758"
80+
],
81+
"threshold": 1
82+
}
83+
},
84+
"spec_version": "1.0.0",
85+
"version": 1
86+
}
87+
}

examples/client_example/README.md

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# TUF Client Example
2+
3+
4+
TUF Client Example, using ``python-tuf``.
5+
6+
This TUF Client Example implements the following actions:
7+
- Client Infrastructure Initialization
8+
- Download target files from TUF Repository
9+
10+
The example client expects to find a TUF repository running on localhost. We
11+
can use the static metadata files in ``tests/repository_data/repository``
12+
to set one up.
13+
14+
Run the repository using the Python3 built-in HTTP module, and keep this
15+
session running.
16+
17+
```console
18+
$ python3 -m http.server -d tests/repository_data/repository
19+
Serving HTTP on :: port 8000 (http://[::]:8000/) ...
20+
```
21+
22+
How to use the TUF Client Example to download a target file.
23+
24+
```console
25+
$ ./client_example.py download file1.txt
26+
```
+136
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
#!/usr/bin/env python
2+
"""TUF Client Example"""
3+
4+
# Copyright 2012 - 2017, New York University and the TUF contributors
5+
# SPDX-License-Identifier: MIT OR Apache-2.0
6+
7+
import argparse
8+
import logging
9+
import os
10+
import shutil
11+
from pathlib import Path
12+
13+
from tuf.exceptions import RepositoryError
14+
from tuf.ngclient import Updater
15+
16+
# constants
17+
BASE_URL = "http://127.0.0.1:8000"
18+
DOWNLOAD_DIR = "./downloads"
19+
METADATA_DIR = f"{Path.home()}/.local/share/python-tuf-client-example"
20+
CLIENT_EXAMPLE_DIR = os.path.dirname(os.path.abspath(__file__))
21+
22+
23+
def init() -> None:
24+
"""Initialize local trusted metadata and create a directory for downloads"""
25+
26+
if not os.path.isdir(DOWNLOAD_DIR):
27+
os.mkdir(DOWNLOAD_DIR)
28+
29+
if not os.path.isdir(METADATA_DIR):
30+
os.makedirs(METADATA_DIR)
31+
32+
if not os.path.isfile(f"{METADATA_DIR}/root.json"):
33+
shutil.copy(
34+
f"{CLIENT_EXAMPLE_DIR}/1.root.json", f"{METADATA_DIR}/root.json"
35+
)
36+
print(f"Added trusted root in {METADATA_DIR}")
37+
38+
else:
39+
print(f"Found trusted root in {METADATA_DIR}")
40+
41+
42+
def download(target: str) -> bool:
43+
"""
44+
Download the target file using ``ngclient`` Updater.
45+
46+
The Updater refreshes the top-level metadata, get the target information,
47+
verifies if the target is already cached, and in case it is not cached,
48+
downloads the target file.
49+
50+
Returns:
51+
A boolean indicating if process was successful
52+
"""
53+
try:
54+
updater = Updater(
55+
repository_dir=METADATA_DIR,
56+
metadata_base_url=f"{BASE_URL}/metadata/",
57+
target_base_url=f"{BASE_URL}/targets/",
58+
target_dir=DOWNLOAD_DIR,
59+
)
60+
updater.refresh()
61+
62+
info = updater.get_targetinfo(target)
63+
64+
if info is None:
65+
print(f"Target {target} not found")
66+
return True
67+
68+
path = updater.find_cached_target(info)
69+
if path:
70+
print(f"Target is available in {path}")
71+
return True
72+
73+
path = updater.download_target(info)
74+
print(f"Target downloaded and available in {path}")
75+
76+
except (OSError, RepositoryError) as e:
77+
print(str(e))
78+
return False
79+
80+
return True
81+
82+
83+
def main() -> None:
84+
"""Main TUF Client Example function"""
85+
86+
client_args = argparse.ArgumentParser(description="TUF Client Example")
87+
88+
# Global arguments
89+
client_args.add_argument(
90+
"-v",
91+
"--verbose",
92+
help="Output verbosity level (-v, -vv, ...)",
93+
action="count",
94+
default=0,
95+
)
96+
97+
# Sub commands
98+
sub_command = client_args.add_subparsers(dest="sub_command")
99+
100+
# Download
101+
download_parser = sub_command.add_parser(
102+
"download",
103+
help="Download a target file",
104+
)
105+
106+
download_parser.add_argument(
107+
"target",
108+
metavar="TARGET",
109+
help="Target file",
110+
)
111+
112+
command_args = client_args.parse_args()
113+
114+
if command_args.verbose == 0:
115+
loglevel = logging.ERROR
116+
elif command_args.verbose == 1:
117+
loglevel = logging.WARNING
118+
elif command_args.verbose == 2:
119+
loglevel = logging.INFO
120+
else:
121+
loglevel = logging.DEBUG
122+
123+
logging.basicConfig(level=loglevel)
124+
125+
# initialize the TUF Client Example infrastructure
126+
init()
127+
128+
if command_args.sub_command == "download":
129+
download(command_args.target)
130+
131+
else:
132+
client_args.print_help()
133+
134+
135+
if __name__ == "__main__":
136+
main()

0 commit comments

Comments
 (0)