sigstore._internal.tuf
TUF functionality for sigstore-python
.
1# Copyright 2022 The Sigstore Authors 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15""" 16TUF functionality for `sigstore-python`. 17""" 18 19from __future__ import annotations 20 21import logging 22from functools import lru_cache 23from pathlib import Path 24from urllib import parse 25 26import platformdirs 27from tuf.api import exceptions as TUFExceptions 28from tuf.ngclient import Updater, UpdaterConfig 29 30from sigstore import __version__ 31from sigstore._utils import read_embedded 32from sigstore.errors import RootError, TUFError 33 34_logger = logging.getLogger(__name__) 35 36DEFAULT_TUF_URL = "https://tuf-repo-cdn.sigstore.dev" 37STAGING_TUF_URL = "https://tuf-repo-cdn.sigstage.dev" 38 39 40def _get_dirs(url: str) -> tuple[Path, Path]: 41 """ 42 Given a TUF repository URL, return suitable local metadata and cache directories. 43 44 These directories are not guaranteed to already exist. 45 """ 46 47 app_name = "sigstore-python" 48 app_author = "sigstore" 49 50 repo_base = parse.quote(url, safe="") 51 52 tuf_data_dir = Path(platformdirs.user_data_dir(app_name, app_author)) / "tuf" 53 tuf_cache_dir = Path(platformdirs.user_cache_dir(app_name, app_author)) / "tuf" 54 55 return (tuf_data_dir / repo_base), (tuf_cache_dir / repo_base) 56 57 58class TrustUpdater: 59 """Internal trust root (certificates and keys) downloader. 60 61 TrustUpdater discovers the currently valid certificates and keys and 62 securely downloads them from the remote TUF repository at 'url'. 63 64 TrustUpdater expects to find an initial root.json in either the local 65 metadata directory for this URL, or (as special case for the sigstore.dev 66 production and staging instances) in the application resources. 67 """ 68 69 def __init__(self, url: str, offline: bool = False) -> None: 70 """ 71 Create a new `TrustUpdater`, pulling from the given `url`. 72 73 The URL is expected to match one of `sigstore-python`'s known TUF 74 roots, i.e. for the production or staging Sigstore TUF repos. 75 76 If not `offline`, TrustUpdater will update the TUF metadata from 77 the remote repository. 78 """ 79 self._repo_url = url 80 self._metadata_dir, self._targets_dir = _get_dirs(url) 81 82 rsrc_prefix: str 83 if self._repo_url == DEFAULT_TUF_URL: 84 rsrc_prefix = "prod" 85 elif self._repo_url == STAGING_TUF_URL: 86 rsrc_prefix = "staging" 87 else: 88 raise RootError 89 90 # Initialize metadata dir 91 self._metadata_dir.mkdir(parents=True, exist_ok=True) 92 tuf_root = self._metadata_dir / "root.json" 93 94 if not tuf_root.exists(): 95 try: 96 root_json = read_embedded("root.json", rsrc_prefix) 97 except FileNotFoundError as e: 98 raise RootError from e 99 100 tuf_root.write_bytes(root_json) 101 102 # Initialize targets cache dir 103 self._targets_dir.mkdir(parents=True, exist_ok=True) 104 trusted_root_target = self._targets_dir / "trusted_root.json" 105 106 if not trusted_root_target.exists(): 107 try: 108 trusted_root_json = read_embedded("trusted_root.json", rsrc_prefix) 109 except FileNotFoundError as e: 110 raise RootError from e 111 112 trusted_root_target.write_bytes(trusted_root_json) 113 114 _logger.debug(f"TUF metadata: {self._metadata_dir}") 115 _logger.debug(f"TUF targets cache: {self._targets_dir}") 116 117 self._updater: None | Updater = None 118 if offline: 119 _logger.warning( 120 "TUF repository is loaded in offline mode; updates will not be performed" 121 ) 122 else: 123 # Initialize and update the toplevel TUF metadata 124 self._updater = Updater( 125 metadata_dir=str(self._metadata_dir), 126 metadata_base_url=self._repo_url, 127 target_base_url=parse.urljoin(f"{self._repo_url}/", "targets/"), 128 target_dir=str(self._targets_dir), 129 config=UpdaterConfig(app_user_agent=f"sigstore-python/{__version__}"), 130 ) 131 try: 132 self._updater.refresh() 133 except Exception as e: 134 raise TUFError("Failed to refresh TUF metadata") from e 135 136 @lru_cache() 137 def get_trusted_root_path(self) -> str: 138 """Return local path to currently valid trusted root file""" 139 if not self._updater: 140 _logger.debug("Using unverified trusted root from cache") 141 return str(self._targets_dir / "trusted_root.json") 142 143 root_info = self._updater.get_targetinfo("trusted_root.json") 144 if root_info is None: 145 raise TUFError("Unsupported TUF configuration: no trusted root") 146 path = self._updater.find_cached_target(root_info) 147 if path is None: 148 try: 149 path = self._updater.download_target(root_info) 150 except ( 151 TUFExceptions.DownloadError, 152 TUFExceptions.RepositoryError, 153 ) as e: 154 raise TUFError("Failed to download trusted key bundle") from e 155 156 _logger.debug("Found and verified trusted root") 157 return path
59class TrustUpdater: 60 """Internal trust root (certificates and keys) downloader. 61 62 TrustUpdater discovers the currently valid certificates and keys and 63 securely downloads them from the remote TUF repository at 'url'. 64 65 TrustUpdater expects to find an initial root.json in either the local 66 metadata directory for this URL, or (as special case for the sigstore.dev 67 production and staging instances) in the application resources. 68 """ 69 70 def __init__(self, url: str, offline: bool = False) -> None: 71 """ 72 Create a new `TrustUpdater`, pulling from the given `url`. 73 74 The URL is expected to match one of `sigstore-python`'s known TUF 75 roots, i.e. for the production or staging Sigstore TUF repos. 76 77 If not `offline`, TrustUpdater will update the TUF metadata from 78 the remote repository. 79 """ 80 self._repo_url = url 81 self._metadata_dir, self._targets_dir = _get_dirs(url) 82 83 rsrc_prefix: str 84 if self._repo_url == DEFAULT_TUF_URL: 85 rsrc_prefix = "prod" 86 elif self._repo_url == STAGING_TUF_URL: 87 rsrc_prefix = "staging" 88 else: 89 raise RootError 90 91 # Initialize metadata dir 92 self._metadata_dir.mkdir(parents=True, exist_ok=True) 93 tuf_root = self._metadata_dir / "root.json" 94 95 if not tuf_root.exists(): 96 try: 97 root_json = read_embedded("root.json", rsrc_prefix) 98 except FileNotFoundError as e: 99 raise RootError from e 100 101 tuf_root.write_bytes(root_json) 102 103 # Initialize targets cache dir 104 self._targets_dir.mkdir(parents=True, exist_ok=True) 105 trusted_root_target = self._targets_dir / "trusted_root.json" 106 107 if not trusted_root_target.exists(): 108 try: 109 trusted_root_json = read_embedded("trusted_root.json", rsrc_prefix) 110 except FileNotFoundError as e: 111 raise RootError from e 112 113 trusted_root_target.write_bytes(trusted_root_json) 114 115 _logger.debug(f"TUF metadata: {self._metadata_dir}") 116 _logger.debug(f"TUF targets cache: {self._targets_dir}") 117 118 self._updater: None | Updater = None 119 if offline: 120 _logger.warning( 121 "TUF repository is loaded in offline mode; updates will not be performed" 122 ) 123 else: 124 # Initialize and update the toplevel TUF metadata 125 self._updater = Updater( 126 metadata_dir=str(self._metadata_dir), 127 metadata_base_url=self._repo_url, 128 target_base_url=parse.urljoin(f"{self._repo_url}/", "targets/"), 129 target_dir=str(self._targets_dir), 130 config=UpdaterConfig(app_user_agent=f"sigstore-python/{__version__}"), 131 ) 132 try: 133 self._updater.refresh() 134 except Exception as e: 135 raise TUFError("Failed to refresh TUF metadata") from e 136 137 @lru_cache() 138 def get_trusted_root_path(self) -> str: 139 """Return local path to currently valid trusted root file""" 140 if not self._updater: 141 _logger.debug("Using unverified trusted root from cache") 142 return str(self._targets_dir / "trusted_root.json") 143 144 root_info = self._updater.get_targetinfo("trusted_root.json") 145 if root_info is None: 146 raise TUFError("Unsupported TUF configuration: no trusted root") 147 path = self._updater.find_cached_target(root_info) 148 if path is None: 149 try: 150 path = self._updater.download_target(root_info) 151 except ( 152 TUFExceptions.DownloadError, 153 TUFExceptions.RepositoryError, 154 ) as e: 155 raise TUFError("Failed to download trusted key bundle") from e 156 157 _logger.debug("Found and verified trusted root") 158 return path
Internal trust root (certificates and keys) downloader.
TrustUpdater discovers the currently valid certificates and keys and securely downloads them from the remote TUF repository at 'url'.
TrustUpdater expects to find an initial root.json in either the local metadata directory for this URL, or (as special case for the sigstore.dev production and staging instances) in the application resources.
70 def __init__(self, url: str, offline: bool = False) -> None: 71 """ 72 Create a new `TrustUpdater`, pulling from the given `url`. 73 74 The URL is expected to match one of `sigstore-python`'s known TUF 75 roots, i.e. for the production or staging Sigstore TUF repos. 76 77 If not `offline`, TrustUpdater will update the TUF metadata from 78 the remote repository. 79 """ 80 self._repo_url = url 81 self._metadata_dir, self._targets_dir = _get_dirs(url) 82 83 rsrc_prefix: str 84 if self._repo_url == DEFAULT_TUF_URL: 85 rsrc_prefix = "prod" 86 elif self._repo_url == STAGING_TUF_URL: 87 rsrc_prefix = "staging" 88 else: 89 raise RootError 90 91 # Initialize metadata dir 92 self._metadata_dir.mkdir(parents=True, exist_ok=True) 93 tuf_root = self._metadata_dir / "root.json" 94 95 if not tuf_root.exists(): 96 try: 97 root_json = read_embedded("root.json", rsrc_prefix) 98 except FileNotFoundError as e: 99 raise RootError from e 100 101 tuf_root.write_bytes(root_json) 102 103 # Initialize targets cache dir 104 self._targets_dir.mkdir(parents=True, exist_ok=True) 105 trusted_root_target = self._targets_dir / "trusted_root.json" 106 107 if not trusted_root_target.exists(): 108 try: 109 trusted_root_json = read_embedded("trusted_root.json", rsrc_prefix) 110 except FileNotFoundError as e: 111 raise RootError from e 112 113 trusted_root_target.write_bytes(trusted_root_json) 114 115 _logger.debug(f"TUF metadata: {self._metadata_dir}") 116 _logger.debug(f"TUF targets cache: {self._targets_dir}") 117 118 self._updater: None | Updater = None 119 if offline: 120 _logger.warning( 121 "TUF repository is loaded in offline mode; updates will not be performed" 122 ) 123 else: 124 # Initialize and update the toplevel TUF metadata 125 self._updater = Updater( 126 metadata_dir=str(self._metadata_dir), 127 metadata_base_url=self._repo_url, 128 target_base_url=parse.urljoin(f"{self._repo_url}/", "targets/"), 129 target_dir=str(self._targets_dir), 130 config=UpdaterConfig(app_user_agent=f"sigstore-python/{__version__}"), 131 ) 132 try: 133 self._updater.refresh() 134 except Exception as e: 135 raise TUFError("Failed to refresh TUF metadata") from e
Create a new TrustUpdater
, pulling from the given url
.
The URL is expected to match one of sigstore-python
's known TUF
roots, i.e. for the production or staging Sigstore TUF repos.
If not offline
, TrustUpdater will update the TUF metadata from
the remote repository.
137 @lru_cache() 138 def get_trusted_root_path(self) -> str: 139 """Return local path to currently valid trusted root file""" 140 if not self._updater: 141 _logger.debug("Using unverified trusted root from cache") 142 return str(self._targets_dir / "trusted_root.json") 143 144 root_info = self._updater.get_targetinfo("trusted_root.json") 145 if root_info is None: 146 raise TUFError("Unsupported TUF configuration: no trusted root") 147 path = self._updater.find_cached_target(root_info) 148 if path is None: 149 try: 150 path = self._updater.download_target(root_info) 151 except ( 152 TUFExceptions.DownloadError, 153 TUFExceptions.RepositoryError, 154 ) as e: 155 raise TUFError("Failed to download trusted key bundle") from e 156 157 _logger.debug("Found and verified trusted root") 158 return path
Return local path to currently valid trusted root file