python-tuf/examples/client/client
2025-03-09 06:56:37 +00:00

185 lines
5.1 KiB
Python
Executable file

#!/usr/bin/env python3
"""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 sys
import traceback
from hashlib import sha256
from pathlib import Path
import urllib3
from tuf.api.exceptions import DownloadError, RepositoryError
from tuf.ngclient import Updater
# constants
DOWNLOAD_DIR = "./downloads"
CLIENT_EXAMPLE_DIR = os.path.dirname(os.path.abspath(__file__))
def build_metadata_dir(base_url: str) -> str:
"""build a unique and reproducible directory name for the repository url"""
name = sha256(base_url.encode()).hexdigest()[:8]
# TODO: Make this not windows hostile?
return f"{Path.home()}/.local/share/tuf-example/{name}"
def init_tofu(base_url: str) -> bool:
"""Initialize local trusted metadata (Trust-On-First-Use) and create a
directory for downloads
NOTE: This is unsafe and for demonstration only: the bootstrap root
should be deployed alongside your updater application
"""
metadata_dir = build_metadata_dir(base_url)
response = urllib3.request("GET", f"{base_url}/metadata/1.root.json")
if response.status != 200:
print(f"Failed to download initial root {base_url}/metadata/1.root.json")
return False
Updater(
metadata_dir=metadata_dir,
metadata_base_url=f"{base_url}/metadata/",
target_base_url=f"{base_url}/targets/",
target_dir=DOWNLOAD_DIR,
bootstrap=response.data,
)
print(f"Trust-on-First-Use: Initialized new root in {metadata_dir}")
return True
def download(base_url: str, 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
"""
metadata_dir = build_metadata_dir(base_url)
if not os.path.isfile(f"{metadata_dir}/root.json"):
print(
"Trusted local root not found. Use 'tofu' command to "
"Trust-On-First-Use or copy trusted root metadata to "
f"{metadata_dir}/root.json"
)
return False
print(f"Using trusted root in {metadata_dir}")
try:
# NOTE: initial root should be provided with ``bootstrap`` argument:
# This examples uses unsafe Trust-On-First-Use initialization so it is
# not possible here.
updater = Updater(
metadata_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, DownloadError) as e:
print(f"Failed to download target {target}: {e}")
if logging.root.level < logging.ERROR:
traceback.print_exc()
return False
return True
def main() -> str | 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,
)
client_args.add_argument(
"-u",
"--url",
help="Base repository URL",
default="http://127.0.0.1:8001",
)
# Sub commands
sub_command = client_args.add_subparsers(dest="sub_command")
# Trust-On-First-Use
sub_command.add_parser(
"tofu",
help="Initialize client with Trust-On-First-Use",
)
# 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
if command_args.sub_command == "tofu":
if not init_tofu(command_args.url):
return "Failed to initialize local repository"
elif command_args.sub_command == "download":
if not download(command_args.url, command_args.target):
return f"Failed to download {command_args.target}"
else:
client_args.print_help()
return None
if __name__ == "__main__":
sys.exit(main())