7
7
import argparse
8
8
import logging
9
9
import os
10
- import shutil
10
+ import sys
11
+ from hashlib import sha256
11
12
from pathlib import Path
13
+ from urllib import request
12
14
13
15
from tuf .api .exceptions import DownloadError , RepositoryError
14
16
from tuf .ngclient import Updater
15
17
16
18
# constants
17
- BASE_URL = "http://127.0.0.1:8000"
18
19
DOWNLOAD_DIR = "./downloads"
19
- METADATA_DIR = f"{ Path .home ()} /.local/share/python-tuf-client-example"
20
20
CLIENT_EXAMPLE_DIR = os .path .dirname (os .path .abspath (__file__ ))
21
21
22
+ def build_metadata_dir (base_url : str ) -> str :
23
+ """build a unique and reproducible directory name for the repository url"""
24
+ name = sha256 (base_url .encode ()).hexdigest ()[:8 ]
25
+ # TODO: Make this not windows hostile?
26
+ return f"{ Path .home ()} /.local/share/tuf-example/{ name } "
22
27
23
- def init () -> None :
24
- """Initialize local trusted metadata and create a directory for downloads"""
28
+
29
+ def init_tofu (base_url : str ) -> bool :
30
+ """Initialize local trusted metadata (Trust-On-First-Use) and create a
31
+ directory for downloads"""
32
+ metadata_dir = build_metadata_dir (base_url )
25
33
26
34
if not os .path .isdir (DOWNLOAD_DIR ):
27
35
os .mkdir (DOWNLOAD_DIR )
28
36
29
- if not os .path .isdir (METADATA_DIR ):
30
- os .makedirs (METADATA_DIR )
37
+ if not os .path .isdir (metadata_dir ):
38
+ os .makedirs (metadata_dir )
31
39
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 } " )
40
+ root_url = f"{ base_url } /metadata/1.root.json"
41
+ try :
42
+ request .urlretrieve (root_url , f"{ metadata_dir } /root.json" )
43
+ except OSError :
44
+ print (f"Failed to download initial root from { root_url } " )
45
+ return False
37
46
38
- else :
39
- print ( f"Found trusted root in { METADATA_DIR } " )
47
+ print ( f"Trust-on-First-Use: Initialized new root in { metadata_dir } " )
48
+ return True
40
49
41
50
42
- def download (target : str ) -> bool :
51
+ def download (base_url : str , target : str ) -> bool :
43
52
"""
44
53
Download the target file using ``ngclient`` Updater.
45
54
@@ -50,11 +59,23 @@ def download(target: str) -> bool:
50
59
Returns:
51
60
A boolean indicating if process was successful
52
61
"""
62
+ metadata_dir = build_metadata_dir (base_url )
63
+
64
+ if not os .path .isfile (f"{ metadata_dir } /root.json" ):
65
+ print (
66
+ "Trusted local root not found. Use 'tofu' command to "
67
+ "Trust-On-First-Use or copy trusted root metadata to "
68
+ f"{ metadata_dir } /root.json"
69
+ )
70
+ return False
71
+
72
+ print (f"Using trusted root in { metadata_dir } " )
73
+
53
74
try :
54
75
updater = Updater (
55
- metadata_dir = METADATA_DIR ,
56
- metadata_base_url = f"{ BASE_URL } /metadata/" ,
57
- target_base_url = f"{ BASE_URL } /targets/" ,
76
+ metadata_dir = metadata_dir ,
77
+ metadata_base_url = f"{ base_url } /metadata/" ,
78
+ target_base_url = f"{ base_url } /targets/" ,
58
79
target_dir = DOWNLOAD_DIR ,
59
80
)
60
81
updater .refresh ()
@@ -74,7 +95,7 @@ def download(target: str) -> bool:
74
95
print (f"Target downloaded and available in { path } " )
75
96
76
97
except (OSError , RepositoryError , DownloadError ) as e :
77
- print (f"Failed to download target { target } : { e } " )
98
+ print (f"Failed: { e } " )
78
99
return False
79
100
80
101
return True
@@ -94,9 +115,22 @@ def main() -> None:
94
115
default = 0 ,
95
116
)
96
117
118
+ client_args .add_argument (
119
+ "-u" ,
120
+ "--url" ,
121
+ help = "Base repository URL" ,
122
+ default = "http://127.0.0.1:8001" ,
123
+ )
124
+
97
125
# Sub commands
98
126
sub_command = client_args .add_subparsers (dest = "sub_command" )
99
127
128
+ # Trust-On-First-Use
129
+ sub_command .add_parser (
130
+ "tofu" ,
131
+ help = "Initialize client with Trust-On-First-Use" ,
132
+ )
133
+
100
134
# Download
101
135
download_parser = sub_command .add_parser (
102
136
"download" ,
@@ -123,14 +157,15 @@ def main() -> None:
123
157
logging .basicConfig (level = loglevel )
124
158
125
159
# initialize the TUF Client Example infrastructure
126
- init ()
127
-
128
- if command_args .sub_command == "download" :
129
- download (command_args .target )
130
-
160
+ if command_args .sub_command == "tofu" :
161
+ if not init_tofu (command_args .url ):
162
+ return "Failed to initialize local repository"
163
+ elif command_args .sub_command == "download" :
164
+ if not download (command_args .url , command_args .target ):
165
+ return f"Failed to download { command_args .target } "
131
166
else :
132
167
client_args .print_help ()
133
168
134
169
135
170
if __name__ == "__main__" :
136
- main ()
171
+ sys . exit ( main () )
0 commit comments