77import argparse
88import logging
99import os
10- import shutil
10+ import sys
1111import traceback
12+ from hashlib import sha256
1213from pathlib import Path
14+ from urllib import request
1315
1416from tuf .api .exceptions import DownloadError , RepositoryError
1517from tuf .ngclient import Updater
1618
1719# constants
18- BASE_URL = "http://127.0.0.1:8000"
1920DOWNLOAD_DIR = "./downloads"
20- METADATA_DIR = f"{ Path .home ()} /.local/share/python-tuf-client-example"
2121CLIENT_EXAMPLE_DIR = os .path .dirname (os .path .abspath (__file__ ))
2222
23+ def build_metadata_dir (base_url : str ) -> str :
24+ """build a unique and reproducible directory name for the repository url"""
25+ name = sha256 (base_url .encode ()).hexdigest ()[:8 ]
26+ # TODO: Make this not windows hostile?
27+ return f"{ Path .home ()} /.local/share/tuf-example/{ name } "
2328
24- def init () -> None :
25- """Initialize local trusted metadata and create a directory for downloads"""
29+
30+ def init_tofu (base_url : str ) -> bool :
31+ """Initialize local trusted metadata (Trust-On-First-Use) and create a
32+ directory for downloads"""
33+ metadata_dir = build_metadata_dir (base_url )
2634
2735 if not os .path .isdir (DOWNLOAD_DIR ):
2836 os .mkdir (DOWNLOAD_DIR )
2937
30- if not os .path .isdir (METADATA_DIR ):
31- os .makedirs (METADATA_DIR )
38+ if not os .path .isdir (metadata_dir ):
39+ os .makedirs (metadata_dir )
3240
33- if not os .path .isfile (f"{ METADATA_DIR } /root.json" ):
34- shutil .copy (
35- f"{ CLIENT_EXAMPLE_DIR } /1.root.json" , f"{ METADATA_DIR } /root.json"
36- )
37- print (f"Added trusted root in { METADATA_DIR } " )
41+ root_url = f"{ base_url } /metadata/1.root.json"
42+ try :
43+ request .urlretrieve (root_url , f"{ metadata_dir } /root.json" )
44+ except OSError :
45+ print (f"Failed to download initial root from { root_url } " )
46+ return False
3847
39- else :
40- print ( f"Found trusted root in { METADATA_DIR } " )
48+ print ( f"Trust-on-First-Use: Initialized new root in { metadata_dir } " )
49+ return True
4150
4251
43- def download (target : str ) -> bool :
52+ def download (base_url : str , target : str ) -> bool :
4453 """
4554 Download the target file using ``ngclient`` Updater.
4655
@@ -51,11 +60,23 @@ def download(target: str) -> bool:
5160 Returns:
5261 A boolean indicating if process was successful
5362 """
63+ metadata_dir = build_metadata_dir (base_url )
64+
65+ if not os .path .isfile (f"{ metadata_dir } /root.json" ):
66+ print (
67+ "Trusted local root not found. Use 'tofu' command to "
68+ "Trust-On-First-Use or copy trusted root metadata to "
69+ f"{ metadata_dir } /root.json"
70+ )
71+ return False
72+
73+ print (f"Using trusted root in { metadata_dir } " )
74+
5475 try :
5576 updater = Updater (
56- metadata_dir = METADATA_DIR ,
57- metadata_base_url = f"{ BASE_URL } /metadata/" ,
58- target_base_url = f"{ BASE_URL } /targets/" ,
77+ metadata_dir = metadata_dir ,
78+ metadata_base_url = f"{ base_url } /metadata/" ,
79+ target_base_url = f"{ base_url } /targets/" ,
5980 target_dir = DOWNLOAD_DIR ,
6081 )
6182 updater .refresh ()
@@ -97,9 +118,22 @@ def main() -> None:
97118 default = 0 ,
98119 )
99120
121+ client_args .add_argument (
122+ "-u" ,
123+ "--url" ,
124+ help = "Base repository URL" ,
125+ default = "http://127.0.0.1:8001" ,
126+ )
127+
100128 # Sub commands
101129 sub_command = client_args .add_subparsers (dest = "sub_command" )
102130
131+ # Trust-On-First-Use
132+ sub_command .add_parser (
133+ "tofu" ,
134+ help = "Initialize client with Trust-On-First-Use" ,
135+ )
136+
103137 # Download
104138 download_parser = sub_command .add_parser (
105139 "download" ,
@@ -126,14 +160,15 @@ def main() -> None:
126160 logging .basicConfig (level = loglevel )
127161
128162 # initialize the TUF Client Example infrastructure
129- init ()
130-
131- if command_args .sub_command == "download" :
132- download (command_args .target )
133-
163+ if command_args .sub_command == "tofu" :
164+ if not init_tofu (command_args .url ):
165+ return "Failed to initialize local repository"
166+ elif command_args .sub_command == "download" :
167+ if not download (command_args .url , command_args .target ):
168+ return f"Failed to download { command_args .target } "
134169 else :
135170 client_args .print_help ()
136171
137172
138173if __name__ == "__main__" :
139- main ()
174+ sys . exit ( main () )
0 commit comments