2014-06-03 18:32:44 +00:00
|
|
|
#!/usr/bin/env python
|
|
|
|
|
|
2013-10-22 18:02:01 +00:00
|
|
|
"""
|
|
|
|
|
<Program Name>
|
2014-01-23 17:03:31 +00:00
|
|
|
repository_tool.py
|
2013-10-22 18:02:01 +00:00
|
|
|
|
|
|
|
|
<Author>
|
|
|
|
|
Vladimir Diaz <vladimir.v.diaz@gmail.com>
|
|
|
|
|
|
|
|
|
|
<Started>
|
|
|
|
|
October 19, 2013
|
|
|
|
|
|
|
|
|
|
<Copyright>
|
|
|
|
|
See LICENSE for licensing information.
|
|
|
|
|
|
|
|
|
|
<Purpose>
|
2014-02-13 18:15:35 +00:00
|
|
|
Provide a tool that can create a TUF repository. It can be used with the
|
|
|
|
|
Python interpreter in interactive mode, or imported directly into a Python
|
|
|
|
|
module. See 'tuf/README' for the complete guide to using
|
|
|
|
|
'tuf.repository_tool.py'.
|
2013-10-22 18:02:01 +00:00
|
|
|
"""
|
|
|
|
|
|
2013-11-12 20:00:26 +00:00
|
|
|
# Help with Python 3 compatibility, where the print statement is a function, an
|
|
|
|
|
# implicit relative import is invalid, and the '/' operator performs true
|
|
|
|
|
# division. Example: print 'hello world' raises a 'SyntaxError' exception.
|
|
|
|
|
from __future__ import print_function
|
|
|
|
|
from __future__ import absolute_import
|
|
|
|
|
from __future__ import division
|
2014-04-29 18:27:34 +00:00
|
|
|
from __future__ import unicode_literals
|
2013-11-12 20:00:26 +00:00
|
|
|
|
2013-10-29 19:23:26 +00:00
|
|
|
import os
|
|
|
|
|
import errno
|
|
|
|
|
import time
|
2014-04-15 16:52:35 +00:00
|
|
|
import datetime
|
2013-10-29 19:23:26 +00:00
|
|
|
import logging
|
2013-11-12 20:00:26 +00:00
|
|
|
import tempfile
|
|
|
|
|
import shutil
|
2013-10-30 14:32:56 +00:00
|
|
|
import json
|
Finish initial implementation of Issue #151 and reading consistent snapshots.
Support multiple hash algorithms, where the generated digests of metadata and
target files is included in metadata (and filenames if 'consistent_snapshots'
is True). Previously, only a single hash algorithm was supported, and it was
set by default to 'sha256' in code. Repository maintainers may now choose any,
and/or multiple, hash algorithms from those supported by TUF. By default,
'sha256' is used when generating digests.
Support the recent change to the TUF specification, where writing consistent
snapshots may include N versions of identical metadata and targets, if N hash
algorithms is used by the repository when generating metadata.
Update code affected by the recent changes to the specification, such as
targets that may include digests in their filename.
Support consistent snapshots of compressed metadata, including repositories
that provide multiple versions of metadata with different digests included
in their filenames.
The repository tools can now load repositories that include consistent snapshots
of metadata and targets, including those with multiple (i.e., multiple digests
prepended to filenames) consistent snapshots of files.
The client code may now read repositories with 'consistent_snapshots': true in
Root metadata, and properly request and update files with digests included.
2014-01-17 16:05:40 +00:00
|
|
|
import random
|
2013-10-22 18:02:01 +00:00
|
|
|
|
|
|
|
|
import tuf
|
|
|
|
|
import tuf.formats
|
2013-10-30 14:32:56 +00:00
|
|
|
import tuf.util
|
2013-10-29 19:23:26 +00:00
|
|
|
import tuf.keydb
|
|
|
|
|
import tuf.roledb
|
2013-10-22 18:02:01 +00:00
|
|
|
import tuf.keys
|
2013-11-05 13:22:21 +00:00
|
|
|
import tuf.sig
|
2013-10-29 19:23:26 +00:00
|
|
|
import tuf.log
|
2014-01-18 23:37:27 +00:00
|
|
|
import tuf.conf
|
2014-06-03 18:32:44 +00:00
|
|
|
import tuf.repository_lib as repo_lib
|
|
|
|
|
from tuf.repository_lib import generate_and_write_rsa_keypair
|
|
|
|
|
from tuf.repository_lib import generate_and_write_ed25519_keypair
|
|
|
|
|
from tuf.repository_lib import import_rsa_publickey_from_file
|
|
|
|
|
from tuf.repository_lib import import_ed25519_publickey_from_file
|
|
|
|
|
from tuf.repository_lib import import_rsa_privatekey_from_file
|
|
|
|
|
from tuf.repository_lib import import_ed25519_privatekey_from_file
|
|
|
|
|
from tuf.repository_lib import create_tuf_client_directory
|
|
|
|
|
from tuf.repository_lib import disable_console_log_messages
|
2015-06-02 12:29:22 +00:00
|
|
|
|
|
|
|
|
import iso8601
|
|
|
|
|
import six
|
2013-10-22 18:02:01 +00:00
|
|
|
|
2014-06-03 18:32:44 +00:00
|
|
|
|
2013-10-29 19:23:26 +00:00
|
|
|
# See 'log.py' to learn how logging is handled in TUF.
|
2014-01-23 17:03:31 +00:00
|
|
|
logger = logging.getLogger('tuf.repository_tool')
|
2013-10-22 18:02:01 +00:00
|
|
|
|
2014-03-05 16:38:23 +00:00
|
|
|
# Add a console handler so that users are aware of potentially unintended
|
|
|
|
|
# states, such as multiple roles that share keys.
|
|
|
|
|
tuf.log.add_console_handler()
|
2014-08-19 19:20:59 +00:00
|
|
|
tuf.log.set_console_log_level(logging.INFO)
|
2014-03-05 16:38:23 +00:00
|
|
|
|
2014-01-23 17:03:31 +00:00
|
|
|
# The algorithm used by the repository to generate the digests of the
|
|
|
|
|
# target filepaths, which are included in metadata files and may be prepended
|
|
|
|
|
# to the filenames of consistent snapshots.
|
2014-01-21 19:42:28 +00:00
|
|
|
HASH_FUNCTION = 'sha256'
|
|
|
|
|
|
2013-12-20 17:47:27 +00:00
|
|
|
# The targets and metadata directory names. Metadata files are written
|
|
|
|
|
# to the staged metadata directory instead of the "live" one.
|
2013-11-22 16:13:11 +00:00
|
|
|
METADATA_STAGED_DIRECTORY_NAME = 'metadata.staged'
|
|
|
|
|
METADATA_DIRECTORY_NAME = 'metadata'
|
2013-10-29 19:23:26 +00:00
|
|
|
TARGETS_DIRECTORY_NAME = 'targets'
|
|
|
|
|
|
2014-06-03 18:32:44 +00:00
|
|
|
# The extension of TUF metadata.
|
|
|
|
|
METADATA_EXTENSION = '.json'
|
2013-12-09 16:11:23 +00:00
|
|
|
|
2013-10-29 19:23:26 +00:00
|
|
|
# Expiration date delta, in seconds, of the top-level roles. A metadata
|
2013-10-22 18:02:01 +00:00
|
|
|
# expiration date is set by taking the current time and adding the expiration
|
|
|
|
|
# seconds listed below.
|
2013-10-29 19:23:26 +00:00
|
|
|
|
2014-01-27 16:35:38 +00:00
|
|
|
# Initial 'root.json' expiration time of 1 year.
|
2013-10-29 19:23:26 +00:00
|
|
|
ROOT_EXPIRATION = 31556900
|
|
|
|
|
|
2014-01-27 16:35:38 +00:00
|
|
|
# Initial 'targets.json' expiration time of 3 months.
|
2013-10-22 18:02:01 +00:00
|
|
|
TARGETS_EXPIRATION = 7889230
|
|
|
|
|
|
2014-01-29 16:26:56 +00:00
|
|
|
# Initial 'snapshot.json' expiration time of 1 week.
|
|
|
|
|
SNAPSHOT_EXPIRATION = 604800
|
2013-10-22 18:02:01 +00:00
|
|
|
|
2014-01-27 16:35:38 +00:00
|
|
|
# Initial 'timestamp.json' expiration time of 1 day.
|
2013-10-22 18:02:01 +00:00
|
|
|
TIMESTAMP_EXPIRATION = 86400
|
|
|
|
|
|
2014-05-03 22:03:25 +00:00
|
|
|
try:
|
|
|
|
|
tuf.keys.check_crypto_libraries(['rsa', 'ed25519', 'general'])
|
|
|
|
|
|
2016-01-28 20:22:20 +00:00
|
|
|
except tuf.UnsupportedLibraryError: #pragma: no cover
|
|
|
|
|
logger.warn('Warning: The repository and developer tools require'
|
|
|
|
|
' additional libraries, which can be installed as follows:'
|
|
|
|
|
'\n $ pip install tuf[tools]')
|
2014-05-03 22:03:25 +00:00
|
|
|
|
|
|
|
|
|
2013-11-05 13:22:21 +00:00
|
|
|
class Repository(object):
|
2013-10-22 18:02:01 +00:00
|
|
|
"""
|
|
|
|
|
<Purpose>
|
2013-12-20 21:06:25 +00:00
|
|
|
Represent a TUF repository that contains the metadata of the top-level
|
2014-01-27 16:35:38 +00:00
|
|
|
roles, including all those delegated from the 'targets.json' role. The
|
2013-12-20 21:06:25 +00:00
|
|
|
repository object returned provides access to the top-level roles, and any
|
|
|
|
|
delegated targets that are added as the repository is modified. For
|
|
|
|
|
example, a Repository object named 'repository' provides the following
|
|
|
|
|
access by default:
|
|
|
|
|
|
|
|
|
|
repository.root.version = 2
|
2014-06-03 18:32:44 +00:00
|
|
|
repository.timestamp.expiration = datetime.datetime(2015, 8, 8, 12, 0)
|
2014-01-29 16:26:56 +00:00
|
|
|
repository.snapshot.add_verification_key(...)
|
2013-12-20 21:06:25 +00:00
|
|
|
repository.targets.delegate('unclaimed', ...)
|
|
|
|
|
|
|
|
|
|
Delegating a role from 'targets' updates the attributes of the parent
|
|
|
|
|
delegation, which then provides:
|
|
|
|
|
|
2014-01-29 16:26:56 +00:00
|
|
|
repository.targets('unclaimed').add_verification_key(...)
|
2013-12-20 21:06:25 +00:00
|
|
|
|
|
|
|
|
|
2013-10-22 18:02:01 +00:00
|
|
|
<Arguments>
|
2013-11-05 13:22:21 +00:00
|
|
|
repository_directory:
|
2013-12-20 17:47:27 +00:00
|
|
|
The root folder of the repository that contains the metadata and targets
|
|
|
|
|
sub-directories.
|
2013-11-05 13:22:21 +00:00
|
|
|
|
|
|
|
|
metadata_directory:
|
2013-12-20 17:47:27 +00:00
|
|
|
The metadata sub-directory contains the files of the top-level
|
2014-01-27 16:35:38 +00:00
|
|
|
roles, including all roles delegated from 'targets.json'.
|
2013-11-05 13:22:21 +00:00
|
|
|
|
|
|
|
|
targets_directory:
|
2013-12-20 17:47:27 +00:00
|
|
|
The targets sub-directory contains all the target files that are
|
|
|
|
|
downloaded by clients and are referenced in TUF Metadata. The hashes and
|
|
|
|
|
file lengths are listed in Metadata files so that they are securely
|
|
|
|
|
downloaded. Metadata files are similarly referenced in the top-level
|
|
|
|
|
metadata.
|
2013-10-22 18:02:01 +00:00
|
|
|
|
|
|
|
|
<Exceptions>
|
2013-11-05 13:22:21 +00:00
|
|
|
tuf.FormatError, if the arguments are improperly formatted.
|
2013-10-22 18:02:01 +00:00
|
|
|
|
|
|
|
|
<Side Effects>
|
2013-12-20 17:47:27 +00:00
|
|
|
Creates top-level role objects and assigns them as attributes.
|
2013-10-22 18:02:01 +00:00
|
|
|
|
|
|
|
|
<Returns>
|
2013-12-20 17:47:27 +00:00
|
|
|
A Repository object that contains default Metadata objects for the top-level
|
|
|
|
|
roles.
|
2013-10-22 18:02:01 +00:00
|
|
|
"""
|
|
|
|
|
|
2013-10-29 19:23:26 +00:00
|
|
|
def __init__(self, repository_directory, metadata_directory, targets_directory):
|
2013-11-05 13:22:21 +00:00
|
|
|
|
|
|
|
|
# Do the arguments have the correct format?
|
2013-12-19 19:10:03 +00:00
|
|
|
# Ensure the arguments have the appropriate number of objects and object
|
|
|
|
|
# types, and that all dict keys are properly named.
|
|
|
|
|
# Raise 'tuf.FormatError' if any are improperly formatted.
|
2013-11-05 13:22:21 +00:00
|
|
|
tuf.formats.PATH_SCHEMA.check_match(repository_directory)
|
|
|
|
|
tuf.formats.PATH_SCHEMA.check_match(metadata_directory)
|
|
|
|
|
tuf.formats.PATH_SCHEMA.check_match(targets_directory)
|
2013-10-29 19:23:26 +00:00
|
|
|
|
2013-11-05 13:22:21 +00:00
|
|
|
self._repository_directory = repository_directory
|
|
|
|
|
self._metadata_directory = metadata_directory
|
|
|
|
|
self._targets_directory = targets_directory
|
2013-10-29 19:23:26 +00:00
|
|
|
|
|
|
|
|
# Set the top-level role objects.
|
|
|
|
|
self.root = Root()
|
2014-01-29 16:26:56 +00:00
|
|
|
self.snapshot = Snapshot()
|
2013-10-29 19:23:26 +00:00
|
|
|
self.timestamp = Timestamp()
|
2013-11-12 20:00:26 +00:00
|
|
|
self.targets = Targets(self._targets_directory, 'targets')
|
2013-11-25 20:01:27 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2015-10-20 13:16:39 +00:00
|
|
|
def write(self, write_partial=False, consistent_snapshot=False,
|
2015-10-27 21:49:25 +00:00
|
|
|
compression_algorithms=['gz']):
|
2013-11-25 20:01:27 +00:00
|
|
|
"""
|
|
|
|
|
<Purpose>
|
2013-12-20 17:47:27 +00:00
|
|
|
Write all the JSON Metadata objects to their corresponding files.
|
|
|
|
|
write() raises an exception if any of the role metadata to be written to
|
|
|
|
|
disk is invalid, such as an insufficient threshold of signatures, missing
|
|
|
|
|
private keys, etc.
|
2013-11-25 20:01:27 +00:00
|
|
|
|
|
|
|
|
<Arguments>
|
2015-08-26 15:49:04 +00:00
|
|
|
write_partial:
|
2013-12-20 17:47:27 +00:00
|
|
|
A boolean indicating whether partial metadata should be written to
|
|
|
|
|
disk. Partial metadata may be written to allow multiple maintainters
|
|
|
|
|
to independently sign and update role metadata. write() raises an
|
|
|
|
|
exception if a metadata role cannot be written due to not having enough
|
|
|
|
|
signatures.
|
2014-01-13 14:34:21 +00:00
|
|
|
|
2014-01-30 13:11:35 +00:00
|
|
|
consistent_snapshot:
|
2014-01-13 14:34:21 +00:00
|
|
|
A boolean indicating whether written metadata and target files should
|
2015-10-27 20:11:11 +00:00
|
|
|
include a version number in the filename (i.e.,
|
|
|
|
|
<version_number>.root.json, <version_number>.targets.json.gz,
|
|
|
|
|
<version_number>.README.json, where <version_number> is the file's
|
|
|
|
|
SHA256 digest. Example: 13.root.json'
|
2015-10-20 13:16:39 +00:00
|
|
|
|
2015-10-27 21:49:25 +00:00
|
|
|
compression_algorithms:
|
2015-10-20 13:16:39 +00:00
|
|
|
A list of compression algorithms. Each of these algorithms will be
|
|
|
|
|
used to compress all of the metadata available on the repository.
|
|
|
|
|
By default, all metadata is compressed with gzip.
|
2013-11-25 20:01:27 +00:00
|
|
|
|
|
|
|
|
<Exceptions>
|
2014-01-27 15:55:14 +00:00
|
|
|
tuf.UnsignedMetadataError, if any of the top-level and delegated roles do
|
|
|
|
|
not have the minimum threshold of signatures.
|
2013-11-25 20:01:27 +00:00
|
|
|
|
|
|
|
|
<Side Effects>
|
|
|
|
|
Creates metadata files in the repository's metadata directory.
|
|
|
|
|
|
|
|
|
|
<Returns>
|
|
|
|
|
None.
|
|
|
|
|
"""
|
|
|
|
|
|
2013-12-19 19:10:03 +00:00
|
|
|
# Does 'write_partial' have the correct format?
|
2013-12-12 14:26:25 +00:00
|
|
|
# Ensure the arguments have the appropriate number of objects and object
|
|
|
|
|
# types, and that all dict keys are properly named.
|
2013-12-19 19:10:03 +00:00
|
|
|
# Raise 'tuf.FormatError' if any are improperly formatted.
|
2013-11-25 20:01:27 +00:00
|
|
|
tuf.formats.BOOLEAN_SCHEMA.check_match(write_partial)
|
2015-10-20 13:16:39 +00:00
|
|
|
tuf.formats.BOOLEAN_SCHEMA.check_match(consistent_snapshot)
|
2015-10-27 21:49:25 +00:00
|
|
|
tuf.formats.COMPRESSIONS_SCHEMA.check_match(compression_algorithms)
|
2015-10-20 13:16:39 +00:00
|
|
|
|
2013-11-25 20:01:27 +00:00
|
|
|
# At this point the tuf.keydb and tuf.roledb stores must be fully
|
2014-05-01 16:59:34 +00:00
|
|
|
# populated, otherwise write() throwns a 'tuf.UnsignedMetadataError'
|
|
|
|
|
# exception if any of the top-level roles are missing signatures, keys, etc.
|
2013-11-25 20:01:27 +00:00
|
|
|
|
2016-02-19 21:07:19 +00:00
|
|
|
# Write the metadata files of all the delegated roles. Ensure target paths
|
|
|
|
|
# are allowed, metadata is valid and properly signed, and required files
|
|
|
|
|
# and directories are created.
|
|
|
|
|
delegated_rolenames = tuf.roledb.get_delegated_rolenames('targets')
|
|
|
|
|
for delegated_rolename in delegated_rolenames:
|
|
|
|
|
delegated_filename = os.path.join(self._metadata_directory,
|
|
|
|
|
delegated_rolename + METADATA_EXTENSION)
|
|
|
|
|
roleinfo = tuf.roledb.get_roleinfo(delegated_rolename)
|
|
|
|
|
delegated_targets = list(roleinfo['paths'].keys())
|
|
|
|
|
parent_rolename = tuf.roledb.get_parent_rolename(delegated_rolename)
|
|
|
|
|
parent_roleinfo = tuf.roledb.get_roleinfo(parent_rolename)
|
|
|
|
|
parent_delegations = parent_roleinfo['delegations']
|
2016-02-19 15:09:48 +00:00
|
|
|
|
2016-02-19 21:07:19 +00:00
|
|
|
# Raise exception if any of the targets of 'delegated_rolename' are not
|
|
|
|
|
# allowed.
|
|
|
|
|
tuf.util.ensure_all_targets_allowed(delegated_rolename, delegated_targets,
|
|
|
|
|
parent_delegations)
|
|
|
|
|
|
|
|
|
|
# Ensure the parent directories of 'metadata_filepath' exist, otherwise an
|
|
|
|
|
# IO exception is raised if 'metadata_filepath' is written to a
|
|
|
|
|
# sub-directory.
|
|
|
|
|
tuf.util.ensure_parent_dir(delegated_filename)
|
|
|
|
|
|
|
|
|
|
repo_lib._generate_and_write_metadata(delegated_rolename,
|
|
|
|
|
delegated_filename,
|
2014-06-03 18:32:44 +00:00
|
|
|
write_partial,
|
|
|
|
|
self._targets_directory,
|
|
|
|
|
self._metadata_directory,
|
|
|
|
|
consistent_snapshot)
|
2014-05-27 17:55:48 +00:00
|
|
|
|
2014-01-27 16:35:38 +00:00
|
|
|
# Generate the 'root.json' metadata file.
|
2013-11-25 20:01:27 +00:00
|
|
|
# _generate_and_write_metadata() raises a 'tuf.Error' exception if the
|
|
|
|
|
# metadata cannot be written.
|
2014-06-03 18:32:44 +00:00
|
|
|
root_filename = repo_lib.ROOT_FILENAME
|
2014-01-14 15:01:17 +00:00
|
|
|
root_filename = os.path.join(self._metadata_directory, root_filename)
|
2013-11-25 20:01:27 +00:00
|
|
|
|
2014-05-27 17:55:48 +00:00
|
|
|
signable_junk, root_filename = \
|
2014-06-03 18:32:44 +00:00
|
|
|
repo_lib._generate_and_write_metadata('root', root_filename, write_partial,
|
|
|
|
|
self._targets_directory,
|
|
|
|
|
self._metadata_directory,
|
2015-10-27 20:11:11 +00:00
|
|
|
consistent_snapshot)
|
2014-05-27 17:55:48 +00:00
|
|
|
|
2014-01-27 16:35:38 +00:00
|
|
|
# Generate the 'targets.json' metadata file.
|
2014-06-03 18:32:44 +00:00
|
|
|
targets_filename = repo_lib.TARGETS_FILENAME
|
2014-01-14 15:01:17 +00:00
|
|
|
targets_filename = os.path.join(self._metadata_directory, targets_filename)
|
2014-02-13 18:15:35 +00:00
|
|
|
|
2014-05-27 17:55:48 +00:00
|
|
|
signable_junk, targets_filename = \
|
2014-06-03 18:32:44 +00:00
|
|
|
repo_lib._generate_and_write_metadata('targets', targets_filename,
|
|
|
|
|
write_partial,
|
|
|
|
|
self._targets_directory,
|
|
|
|
|
self._metadata_directory,
|
|
|
|
|
consistent_snapshot)
|
2013-11-25 20:01:27 +00:00
|
|
|
|
2014-01-29 16:26:56 +00:00
|
|
|
# Generate the 'snapshot.json' metadata file.
|
2014-06-03 18:32:44 +00:00
|
|
|
snapshot_filename = repo_lib.SNAPSHOT_FILENAME
|
2014-01-29 16:26:56 +00:00
|
|
|
snapshot_filename = os.path.join(self._metadata_directory, snapshot_filename)
|
2014-02-13 18:15:35 +00:00
|
|
|
filenames = {'root': root_filename, 'targets': targets_filename}
|
|
|
|
|
snapshot_signable = None
|
2013-11-25 20:01:27 +00:00
|
|
|
|
2014-05-27 17:55:48 +00:00
|
|
|
snapshot_signable, snapshot_filename = \
|
2014-06-03 18:32:44 +00:00
|
|
|
repo_lib._generate_and_write_metadata('snapshot', snapshot_filename,
|
|
|
|
|
write_partial,
|
|
|
|
|
self._targets_directory,
|
|
|
|
|
self._metadata_directory,
|
|
|
|
|
consistent_snapshot, filenames)
|
2014-05-27 17:55:48 +00:00
|
|
|
|
2014-01-27 16:35:38 +00:00
|
|
|
# Generate the 'timestamp.json' metadata file.
|
2014-06-03 18:32:44 +00:00
|
|
|
timestamp_filename = repo_lib.TIMESTAMP_FILENAME
|
2014-01-14 15:01:17 +00:00
|
|
|
timestamp_filename = os.path.join(self._metadata_directory, timestamp_filename)
|
2014-01-29 16:26:56 +00:00
|
|
|
filenames = {'snapshot': snapshot_filename}
|
2014-02-13 18:15:35 +00:00
|
|
|
|
2014-06-03 18:32:44 +00:00
|
|
|
repo_lib._generate_and_write_metadata('timestamp', timestamp_filename,
|
|
|
|
|
write_partial,
|
|
|
|
|
self._targets_directory,
|
|
|
|
|
self._metadata_directory,
|
|
|
|
|
consistent_snapshot, filenames)
|
2013-11-25 20:01:27 +00:00
|
|
|
|
|
|
|
|
# Delete the metadata of roles no longer in 'tuf.roledb'. Obsolete roles
|
2014-03-05 16:38:23 +00:00
|
|
|
# may have been revoked and should no longer have their metadata files
|
2016-02-19 21:07:19 +00:00
|
|
|
# available on disk, otherwise loading a repository may unintentionally load
|
|
|
|
|
# them.
|
2014-06-03 18:32:44 +00:00
|
|
|
repo_lib._delete_obsolete_metadata(self._metadata_directory,
|
|
|
|
|
snapshot_signable['signed'],
|
|
|
|
|
consistent_snapshot)
|
2013-11-25 20:01:27 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def write_partial(self):
|
|
|
|
|
"""
|
|
|
|
|
<Purpose>
|
|
|
|
|
Write all the JSON Metadata objects to their corresponding files, but
|
|
|
|
|
allow metadata files to contain an invalid threshold of signatures.
|
|
|
|
|
|
|
|
|
|
<Arguments>
|
|
|
|
|
None.
|
|
|
|
|
|
|
|
|
|
<Exceptions>
|
2013-12-20 17:47:27 +00:00
|
|
|
None.
|
2013-11-25 20:01:27 +00:00
|
|
|
|
|
|
|
|
<Side Effects>
|
|
|
|
|
Creates metadata files in the repository's metadata directory.
|
|
|
|
|
|
|
|
|
|
<Returns>
|
|
|
|
|
None.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
self.write(write_partial=True)
|
2013-10-22 18:02:01 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2013-11-05 13:22:21 +00:00
|
|
|
def status(self):
|
|
|
|
|
"""
|
|
|
|
|
<Purpose>
|
Refactor and fix status() in repository_tool.py.
Update and refactor status() following the changes to how metadata is written.
Minor comment change to conf.py.
Example output:
'root' role contains 1 / 1 signatures.
'targets' role contains 1 / 1 signatures.
'release' role contains 1 / 1 signatures.
'timestamp' role contains 1 / 1 signatures.
# Verify invalid number of public and private keys.
'timestamp' role contains 0 / 1 signing keys.
# Determine the delegated roles with invalid metadata.
Delegated roles with insufficient keys:
['targets/unclaimed/1', 'targets/unclaimed/0', 'targets/unclaimed/2', 'targets/unclaimed/5', 'targets/unclaimed/4', 'targets/unclaimed/7', 'targets/unclaimed/6', 'targets/unclaimed/9', 'targets/unclaimed/f', 'targets/unclaimed/3', 'targets/unclaimed/a', 'targets/unclaimed/c', 'targets/unclaimed/b', 'targets/unclaimed/e', 'targets/unclaimed/d', 'targets/unclaimed/8']
2014-01-24 15:54:10 +00:00
|
|
|
Determine the status of the top-level roles, including those delegated by
|
2014-01-29 16:26:56 +00:00
|
|
|
the Targets role. status() checks if each role provides sufficient public
|
|
|
|
|
and private keys, signatures, and that a valid metadata file is generated
|
|
|
|
|
if write() were to be called. Metadata files are temporarily written so
|
|
|
|
|
that file hashes and lengths may be verified, determine if delegated role
|
|
|
|
|
trust is fully obeyed, and target paths valid according to parent roles.
|
|
|
|
|
status() does not do a simple check for number of threshold keys and
|
|
|
|
|
signatures.
|
2013-12-20 17:47:27 +00:00
|
|
|
|
2013-11-05 13:22:21 +00:00
|
|
|
<Arguments>
|
|
|
|
|
None.
|
|
|
|
|
|
|
|
|
|
<Exceptions>
|
2013-12-20 17:47:27 +00:00
|
|
|
None.
|
2013-11-05 13:22:21 +00:00
|
|
|
|
|
|
|
|
<Side Effects>
|
2013-12-20 17:47:27 +00:00
|
|
|
Generates and writes temporary metadata files.
|
2013-11-05 13:22:21 +00:00
|
|
|
|
|
|
|
|
<Returns>
|
2013-11-12 20:00:26 +00:00
|
|
|
None.
|
2013-11-05 13:22:21 +00:00
|
|
|
"""
|
2013-11-12 20:00:26 +00:00
|
|
|
|
|
|
|
|
temp_repository_directory = None
|
|
|
|
|
|
Refactor and fix status() in repository_tool.py.
Update and refactor status() following the changes to how metadata is written.
Minor comment change to conf.py.
Example output:
'root' role contains 1 / 1 signatures.
'targets' role contains 1 / 1 signatures.
'release' role contains 1 / 1 signatures.
'timestamp' role contains 1 / 1 signatures.
# Verify invalid number of public and private keys.
'timestamp' role contains 0 / 1 signing keys.
# Determine the delegated roles with invalid metadata.
Delegated roles with insufficient keys:
['targets/unclaimed/1', 'targets/unclaimed/0', 'targets/unclaimed/2', 'targets/unclaimed/5', 'targets/unclaimed/4', 'targets/unclaimed/7', 'targets/unclaimed/6', 'targets/unclaimed/9', 'targets/unclaimed/f', 'targets/unclaimed/3', 'targets/unclaimed/a', 'targets/unclaimed/c', 'targets/unclaimed/b', 'targets/unclaimed/e', 'targets/unclaimed/d', 'targets/unclaimed/8']
2014-01-24 15:54:10 +00:00
|
|
|
# Generate and write temporary metadata so that full verification of
|
|
|
|
|
# metadata is possible, such as verifying signatures, digests, and file
|
|
|
|
|
# content. Ensure temporary files generated are removed after verification
|
|
|
|
|
# results are completed.
|
2013-11-12 20:00:26 +00:00
|
|
|
try:
|
|
|
|
|
temp_repository_directory = tempfile.mkdtemp()
|
Refactor and fix status() in repository_tool.py.
Update and refactor status() following the changes to how metadata is written.
Minor comment change to conf.py.
Example output:
'root' role contains 1 / 1 signatures.
'targets' role contains 1 / 1 signatures.
'release' role contains 1 / 1 signatures.
'timestamp' role contains 1 / 1 signatures.
# Verify invalid number of public and private keys.
'timestamp' role contains 0 / 1 signing keys.
# Determine the delegated roles with invalid metadata.
Delegated roles with insufficient keys:
['targets/unclaimed/1', 'targets/unclaimed/0', 'targets/unclaimed/2', 'targets/unclaimed/5', 'targets/unclaimed/4', 'targets/unclaimed/7', 'targets/unclaimed/6', 'targets/unclaimed/9', 'targets/unclaimed/f', 'targets/unclaimed/3', 'targets/unclaimed/a', 'targets/unclaimed/c', 'targets/unclaimed/b', 'targets/unclaimed/e', 'targets/unclaimed/d', 'targets/unclaimed/8']
2014-01-24 15:54:10 +00:00
|
|
|
targets_directory = self._targets_directory
|
2013-11-12 20:00:26 +00:00
|
|
|
metadata_directory = os.path.join(temp_repository_directory,
|
2013-11-22 16:13:11 +00:00
|
|
|
METADATA_STAGED_DIRECTORY_NAME)
|
2013-11-12 20:00:26 +00:00
|
|
|
os.mkdir(metadata_directory)
|
|
|
|
|
|
2013-11-05 13:22:21 +00:00
|
|
|
|
Refactor and fix status() in repository_tool.py.
Update and refactor status() following the changes to how metadata is written.
Minor comment change to conf.py.
Example output:
'root' role contains 1 / 1 signatures.
'targets' role contains 1 / 1 signatures.
'release' role contains 1 / 1 signatures.
'timestamp' role contains 1 / 1 signatures.
# Verify invalid number of public and private keys.
'timestamp' role contains 0 / 1 signing keys.
# Determine the delegated roles with invalid metadata.
Delegated roles with insufficient keys:
['targets/unclaimed/1', 'targets/unclaimed/0', 'targets/unclaimed/2', 'targets/unclaimed/5', 'targets/unclaimed/4', 'targets/unclaimed/7', 'targets/unclaimed/6', 'targets/unclaimed/9', 'targets/unclaimed/f', 'targets/unclaimed/3', 'targets/unclaimed/a', 'targets/unclaimed/c', 'targets/unclaimed/b', 'targets/unclaimed/e', 'targets/unclaimed/d', 'targets/unclaimed/8']
2014-01-24 15:54:10 +00:00
|
|
|
# Retrieve the roleinfo of the delegated roles, exluding the top-level
|
|
|
|
|
# targets role.
|
2013-11-12 20:00:26 +00:00
|
|
|
delegated_roles = tuf.roledb.get_delegated_rolenames('targets')
|
|
|
|
|
insufficient_keys = []
|
|
|
|
|
insufficient_signatures = []
|
Refactor and fix status() in repository_tool.py.
Update and refactor status() following the changes to how metadata is written.
Minor comment change to conf.py.
Example output:
'root' role contains 1 / 1 signatures.
'targets' role contains 1 / 1 signatures.
'release' role contains 1 / 1 signatures.
'timestamp' role contains 1 / 1 signatures.
# Verify invalid number of public and private keys.
'timestamp' role contains 0 / 1 signing keys.
# Determine the delegated roles with invalid metadata.
Delegated roles with insufficient keys:
['targets/unclaimed/1', 'targets/unclaimed/0', 'targets/unclaimed/2', 'targets/unclaimed/5', 'targets/unclaimed/4', 'targets/unclaimed/7', 'targets/unclaimed/6', 'targets/unclaimed/9', 'targets/unclaimed/f', 'targets/unclaimed/3', 'targets/unclaimed/a', 'targets/unclaimed/c', 'targets/unclaimed/b', 'targets/unclaimed/e', 'targets/unclaimed/d', 'targets/unclaimed/8']
2014-01-24 15:54:10 +00:00
|
|
|
|
|
|
|
|
# Iterate the list of delegated roles and determine the list of invalid
|
|
|
|
|
# roles. First verify the public and private keys, and then the generated
|
|
|
|
|
# metadata file.
|
2013-11-12 20:00:26 +00:00
|
|
|
for delegated_role in delegated_roles:
|
Refactor and fix status() in repository_tool.py.
Update and refactor status() following the changes to how metadata is written.
Minor comment change to conf.py.
Example output:
'root' role contains 1 / 1 signatures.
'targets' role contains 1 / 1 signatures.
'release' role contains 1 / 1 signatures.
'timestamp' role contains 1 / 1 signatures.
# Verify invalid number of public and private keys.
'timestamp' role contains 0 / 1 signing keys.
# Determine the delegated roles with invalid metadata.
Delegated roles with insufficient keys:
['targets/unclaimed/1', 'targets/unclaimed/0', 'targets/unclaimed/2', 'targets/unclaimed/5', 'targets/unclaimed/4', 'targets/unclaimed/7', 'targets/unclaimed/6', 'targets/unclaimed/9', 'targets/unclaimed/f', 'targets/unclaimed/3', 'targets/unclaimed/a', 'targets/unclaimed/c', 'targets/unclaimed/b', 'targets/unclaimed/e', 'targets/unclaimed/d', 'targets/unclaimed/8']
2014-01-24 15:54:10 +00:00
|
|
|
filename = delegated_role + METADATA_EXTENSION
|
|
|
|
|
filename = os.path.join(metadata_directory, filename)
|
|
|
|
|
|
|
|
|
|
# Ensure the parent directories of 'filename' exist, otherwise an
|
|
|
|
|
# IO exception is raised if 'filename' is written to a sub-directory.
|
|
|
|
|
tuf.util.ensure_parent_dir(filename)
|
|
|
|
|
|
|
|
|
|
# Append any invalid roles to the 'insufficient_keys' and
|
|
|
|
|
# 'insufficient_signatures' lists
|
2013-11-12 20:00:26 +00:00
|
|
|
try:
|
2014-06-03 18:32:44 +00:00
|
|
|
repo_lib._check_role_keys(delegated_role)
|
Refactor and fix status() in repository_tool.py.
Update and refactor status() following the changes to how metadata is written.
Minor comment change to conf.py.
Example output:
'root' role contains 1 / 1 signatures.
'targets' role contains 1 / 1 signatures.
'release' role contains 1 / 1 signatures.
'timestamp' role contains 1 / 1 signatures.
# Verify invalid number of public and private keys.
'timestamp' role contains 0 / 1 signing keys.
# Determine the delegated roles with invalid metadata.
Delegated roles with insufficient keys:
['targets/unclaimed/1', 'targets/unclaimed/0', 'targets/unclaimed/2', 'targets/unclaimed/5', 'targets/unclaimed/4', 'targets/unclaimed/7', 'targets/unclaimed/6', 'targets/unclaimed/9', 'targets/unclaimed/f', 'targets/unclaimed/3', 'targets/unclaimed/a', 'targets/unclaimed/c', 'targets/unclaimed/b', 'targets/unclaimed/e', 'targets/unclaimed/d', 'targets/unclaimed/8']
2014-01-24 15:54:10 +00:00
|
|
|
|
2016-01-28 20:22:20 +00:00
|
|
|
except tuf.InsufficientKeysError:
|
2013-11-12 20:00:26 +00:00
|
|
|
insufficient_keys.append(delegated_role)
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
try:
|
2014-06-03 18:32:44 +00:00
|
|
|
repo_lib._generate_and_write_metadata(delegated_role, filename, False,
|
|
|
|
|
targets_directory,
|
|
|
|
|
metadata_directory)
|
2016-01-28 20:22:20 +00:00
|
|
|
except tuf.UnsignedMetadataError:
|
2013-11-12 20:00:26 +00:00
|
|
|
insufficient_signatures.append(delegated_role)
|
Refactor and fix status() in repository_tool.py.
Update and refactor status() following the changes to how metadata is written.
Minor comment change to conf.py.
Example output:
'root' role contains 1 / 1 signatures.
'targets' role contains 1 / 1 signatures.
'release' role contains 1 / 1 signatures.
'timestamp' role contains 1 / 1 signatures.
# Verify invalid number of public and private keys.
'timestamp' role contains 0 / 1 signing keys.
# Determine the delegated roles with invalid metadata.
Delegated roles with insufficient keys:
['targets/unclaimed/1', 'targets/unclaimed/0', 'targets/unclaimed/2', 'targets/unclaimed/5', 'targets/unclaimed/4', 'targets/unclaimed/7', 'targets/unclaimed/6', 'targets/unclaimed/9', 'targets/unclaimed/f', 'targets/unclaimed/3', 'targets/unclaimed/a', 'targets/unclaimed/c', 'targets/unclaimed/b', 'targets/unclaimed/e', 'targets/unclaimed/d', 'targets/unclaimed/8']
2014-01-24 15:54:10 +00:00
|
|
|
|
2014-05-03 22:03:25 +00:00
|
|
|
# Log the verification results of the delegated roles and return
|
Refactor and fix status() in repository_tool.py.
Update and refactor status() following the changes to how metadata is written.
Minor comment change to conf.py.
Example output:
'root' role contains 1 / 1 signatures.
'targets' role contains 1 / 1 signatures.
'release' role contains 1 / 1 signatures.
'timestamp' role contains 1 / 1 signatures.
# Verify invalid number of public and private keys.
'timestamp' role contains 0 / 1 signing keys.
# Determine the delegated roles with invalid metadata.
Delegated roles with insufficient keys:
['targets/unclaimed/1', 'targets/unclaimed/0', 'targets/unclaimed/2', 'targets/unclaimed/5', 'targets/unclaimed/4', 'targets/unclaimed/7', 'targets/unclaimed/6', 'targets/unclaimed/9', 'targets/unclaimed/f', 'targets/unclaimed/3', 'targets/unclaimed/a', 'targets/unclaimed/c', 'targets/unclaimed/b', 'targets/unclaimed/e', 'targets/unclaimed/d', 'targets/unclaimed/8']
2014-01-24 15:54:10 +00:00
|
|
|
# immediately after each invalid case.
|
2013-11-12 20:00:26 +00:00
|
|
|
if len(insufficient_keys):
|
2016-01-28 20:22:20 +00:00
|
|
|
logger.info('Delegated roles with insufficient'
|
|
|
|
|
' keys:\n' + repr(insufficient_keys))
|
2013-11-12 20:00:26 +00:00
|
|
|
return
|
Refactor and fix status() in repository_tool.py.
Update and refactor status() following the changes to how metadata is written.
Minor comment change to conf.py.
Example output:
'root' role contains 1 / 1 signatures.
'targets' role contains 1 / 1 signatures.
'release' role contains 1 / 1 signatures.
'timestamp' role contains 1 / 1 signatures.
# Verify invalid number of public and private keys.
'timestamp' role contains 0 / 1 signing keys.
# Determine the delegated roles with invalid metadata.
Delegated roles with insufficient keys:
['targets/unclaimed/1', 'targets/unclaimed/0', 'targets/unclaimed/2', 'targets/unclaimed/5', 'targets/unclaimed/4', 'targets/unclaimed/7', 'targets/unclaimed/6', 'targets/unclaimed/9', 'targets/unclaimed/f', 'targets/unclaimed/3', 'targets/unclaimed/a', 'targets/unclaimed/c', 'targets/unclaimed/b', 'targets/unclaimed/e', 'targets/unclaimed/d', 'targets/unclaimed/8']
2014-01-24 15:54:10 +00:00
|
|
|
|
2013-11-12 20:00:26 +00:00
|
|
|
if len(insufficient_signatures):
|
2016-01-28 20:22:20 +00:00
|
|
|
logger.info('Delegated roles with insufficient'
|
|
|
|
|
' signatures:\n' + repr(insufficient_signatures))
|
2013-11-12 20:00:26 +00:00
|
|
|
return
|
|
|
|
|
|
2014-05-03 22:03:25 +00:00
|
|
|
# Verify the top-level roles and log the results.
|
2014-06-03 18:32:44 +00:00
|
|
|
repo_lib._log_status_of_top_level_roles(targets_directory,
|
|
|
|
|
metadata_directory)
|
2013-11-25 20:01:27 +00:00
|
|
|
|
2013-11-12 20:00:26 +00:00
|
|
|
finally:
|
|
|
|
|
shutil.rmtree(temp_repository_directory, ignore_errors=True)
|
2013-11-05 13:22:21 +00:00
|
|
|
|
|
|
|
|
|
2014-04-01 00:58:11 +00:00
|
|
|
@staticmethod
|
2014-04-04 22:03:27 +00:00
|
|
|
def get_filepaths_in_directory(files_directory, recursive_walk=False,
|
2013-10-29 19:23:26 +00:00
|
|
|
followlinks=True):
|
|
|
|
|
"""
|
|
|
|
|
<Purpose>
|
2013-12-20 17:47:27 +00:00
|
|
|
Walk the given 'files_directory' and build a list of target files found.
|
2013-10-22 18:02:01 +00:00
|
|
|
|
2013-10-29 19:23:26 +00:00
|
|
|
<Arguments>
|
|
|
|
|
files_directory:
|
|
|
|
|
The path to a directory of target files.
|
2013-10-22 18:02:01 +00:00
|
|
|
|
2013-10-29 19:23:26 +00:00
|
|
|
recursive_walk:
|
|
|
|
|
To recursively walk the directory, set recursive_walk=True.
|
2013-10-22 18:02:01 +00:00
|
|
|
|
2013-10-29 19:23:26 +00:00
|
|
|
followlinks:
|
|
|
|
|
To follow symbolic links, set followlinks=True.
|
2013-10-22 18:02:01 +00:00
|
|
|
|
2013-10-29 19:23:26 +00:00
|
|
|
<Exceptions>
|
2013-11-12 20:00:26 +00:00
|
|
|
tuf.FormatError, if the arguments are improperly formatted.
|
|
|
|
|
|
2013-12-20 17:47:27 +00:00
|
|
|
tuf.Error, if 'file_directory' is not a valid directory.
|
|
|
|
|
|
2013-10-29 19:23:26 +00:00
|
|
|
Python IO exceptions.
|
|
|
|
|
|
|
|
|
|
<Side Effects>
|
|
|
|
|
None.
|
|
|
|
|
|
|
|
|
|
<Returns>
|
2014-01-21 19:42:28 +00:00
|
|
|
A list of absolute paths to target files in the given 'files_directory'.
|
2013-10-29 19:23:26 +00:00
|
|
|
"""
|
2013-10-22 18:02:01 +00:00
|
|
|
|
2013-11-12 20:00:26 +00:00
|
|
|
# Do the arguments have the correct format?
|
2013-12-19 19:10:03 +00:00
|
|
|
# Ensure the arguments have the appropriate number of objects and object
|
|
|
|
|
# types, and that all dict keys are properly named.
|
|
|
|
|
# Raise 'tuf.FormatError' if any are improperly formatted.
|
2013-11-12 20:00:26 +00:00
|
|
|
tuf.formats.PATH_SCHEMA.check_match(files_directory)
|
2013-11-25 17:04:21 +00:00
|
|
|
tuf.formats.BOOLEAN_SCHEMA.check_match(recursive_walk)
|
|
|
|
|
tuf.formats.BOOLEAN_SCHEMA.check_match(followlinks)
|
2013-11-12 20:00:26 +00:00
|
|
|
|
2013-12-20 17:47:27 +00:00
|
|
|
# Ensure a valid directory is given.
|
2013-11-12 20:00:26 +00:00
|
|
|
if not os.path.isdir(files_directory):
|
2016-01-28 20:22:20 +00:00
|
|
|
raise tuf.Error(repr(files_directory) + ' is not a directory.')
|
2013-12-20 17:47:27 +00:00
|
|
|
|
2014-04-01 00:58:11 +00:00
|
|
|
# A list of the target filepaths found in 'files_directory'.
|
2013-10-29 19:23:26 +00:00
|
|
|
targets = []
|
|
|
|
|
|
|
|
|
|
# FIXME: We need a way to tell Python 2, but not Python 3, to return
|
|
|
|
|
# filenames in Unicode; see #61 and:
|
|
|
|
|
# http://docs.python.org/2/howto/unicode.html#unicode-filenames
|
|
|
|
|
for dirpath, dirnames, filenames in os.walk(files_directory,
|
|
|
|
|
followlinks=followlinks):
|
|
|
|
|
for filename in filenames:
|
|
|
|
|
full_target_path = os.path.join(dirpath, filename)
|
|
|
|
|
targets.append(full_target_path)
|
|
|
|
|
|
|
|
|
|
# Prune the subdirectories to walk right now if we do not wish to
|
|
|
|
|
# recursively walk files_directory.
|
|
|
|
|
if recursive_walk is False:
|
|
|
|
|
del dirnames[:]
|
|
|
|
|
|
|
|
|
|
return targets
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Metadata(object):
|
2013-10-22 18:02:01 +00:00
|
|
|
"""
|
|
|
|
|
<Purpose>
|
2013-12-20 17:47:27 +00:00
|
|
|
Provide a base class to represent a TUF Metadata role. There are four
|
2014-01-29 16:26:56 +00:00
|
|
|
top-level roles: Root, Targets, Snapshot, and Timestamp. The Metadata class
|
2013-12-20 17:47:27 +00:00
|
|
|
provides methods that are needed by all top-level roles, such as adding
|
|
|
|
|
and removing public keys, private keys, and signatures. Metadata
|
|
|
|
|
attributes, such as rolename, version, threshold, expiration, key list, and
|
|
|
|
|
compressions, is also provided by the Metadata base class.
|
|
|
|
|
|
2013-10-22 18:02:01 +00:00
|
|
|
<Arguments>
|
2013-12-20 17:47:27 +00:00
|
|
|
None.
|
2013-10-22 18:02:01 +00:00
|
|
|
|
|
|
|
|
<Exceptions>
|
2013-12-20 17:47:27 +00:00
|
|
|
None.
|
2013-10-22 18:02:01 +00:00
|
|
|
|
|
|
|
|
<Side Effects>
|
2013-12-20 17:47:27 +00:00
|
|
|
None.
|
2013-10-22 18:02:01 +00:00
|
|
|
|
|
|
|
|
<Returns>
|
2013-12-20 17:47:27 +00:00
|
|
|
None.
|
2013-10-22 18:02:01 +00:00
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
def __init__(self):
|
2013-11-05 13:22:21 +00:00
|
|
|
self._rolename = None
|
|
|
|
|
|
2013-11-12 20:00:26 +00:00
|
|
|
|
|
|
|
|
|
2014-01-29 16:26:56 +00:00
|
|
|
def add_verification_key(self, key):
|
2013-10-29 19:23:26 +00:00
|
|
|
"""
|
|
|
|
|
<Purpose>
|
2013-12-20 17:47:27 +00:00
|
|
|
Add 'key' to the role. Adding a key, which should contain only the public
|
|
|
|
|
portion, signifies the corresponding private key and signatures the role
|
|
|
|
|
is expected to provide. A threshold of signatures is required for a role
|
|
|
|
|
to be considered properly signed. If a metadata file contains an
|
|
|
|
|
insufficient threshold of signatures, it must not be accepted.
|
2013-10-29 19:23:26 +00:00
|
|
|
|
|
|
|
|
>>>
|
|
|
|
|
>>>
|
|
|
|
|
>>>
|
2013-10-22 18:02:01 +00:00
|
|
|
|
2013-10-29 19:23:26 +00:00
|
|
|
<Arguments>
|
|
|
|
|
key:
|
2013-12-20 17:47:27 +00:00
|
|
|
The role key to be added, conformant to 'tuf.formats.ANYKEY_SCHEMA'.
|
|
|
|
|
Adding a public key to a role means that its corresponding private key
|
|
|
|
|
must generate and add its signature to the role. A threshold number of
|
|
|
|
|
signatures is required for a role to be fully signed.
|
2013-10-22 18:02:01 +00:00
|
|
|
|
2013-10-29 19:23:26 +00:00
|
|
|
<Exceptions>
|
2013-12-20 17:47:27 +00:00
|
|
|
tuf.FormatError, if the 'key' argument is improperly formatted.
|
2013-10-22 18:02:01 +00:00
|
|
|
|
2013-10-29 19:23:26 +00:00
|
|
|
<Side Effects>
|
2013-12-20 17:47:27 +00:00
|
|
|
The role's entries in 'tuf.keydb.py' and 'tuf.roledb.py' are updated.
|
2013-10-29 19:23:26 +00:00
|
|
|
|
|
|
|
|
<Returns>
|
|
|
|
|
None.
|
|
|
|
|
"""
|
2013-11-12 20:00:26 +00:00
|
|
|
|
|
|
|
|
# Does 'key' have the correct format?
|
2013-12-19 19:10:03 +00:00
|
|
|
# Ensure the arguments have the appropriate number of objects and object
|
|
|
|
|
# types, and that all dict keys are properly named.
|
|
|
|
|
# Raise 'tuf.FormatError' if any are improperly formatted.
|
2013-10-29 19:23:26 +00:00
|
|
|
tuf.formats.ANYKEY_SCHEMA.check_match(key)
|
|
|
|
|
|
2013-12-20 17:47:27 +00:00
|
|
|
# Ensure 'key', which should contain the public portion, is added to
|
2014-03-03 19:53:21 +00:00
|
|
|
# 'tuf.keydb.py'. Add 'key' to the list of recognized keys. Keys may be
|
|
|
|
|
# shared, so do not raise an exception if 'key' has already been loaded.
|
2013-10-29 19:23:26 +00:00
|
|
|
try:
|
|
|
|
|
tuf.keydb.add_key(key)
|
2014-03-03 19:53:21 +00:00
|
|
|
|
2016-01-28 20:22:20 +00:00
|
|
|
except tuf.KeyAlreadyExistsError:
|
|
|
|
|
logger.warning('Adding a verification key that has already been used.')
|
2014-03-05 16:38:23 +00:00
|
|
|
|
2013-10-29 19:23:26 +00:00
|
|
|
keyid = key['keyid']
|
2013-11-12 20:00:26 +00:00
|
|
|
roleinfo = tuf.roledb.get_roleinfo(self.rolename)
|
2013-12-20 17:47:27 +00:00
|
|
|
|
|
|
|
|
# Add 'key' to the role's entry in 'tuf.roledb.py' and avoid duplicates.
|
2013-11-12 20:00:26 +00:00
|
|
|
if keyid not in roleinfo['keyids']:
|
|
|
|
|
roleinfo['keyids'].append(keyid)
|
2013-12-20 17:47:27 +00:00
|
|
|
|
2013-11-12 20:00:26 +00:00
|
|
|
tuf.roledb.update_roleinfo(self._rolename, roleinfo)
|
|
|
|
|
|
2013-11-05 13:22:21 +00:00
|
|
|
|
2013-11-12 20:00:26 +00:00
|
|
|
|
2014-01-29 16:26:56 +00:00
|
|
|
def remove_verification_key(self, key):
|
2013-11-12 20:00:26 +00:00
|
|
|
"""
|
|
|
|
|
<Purpose>
|
2013-12-20 17:47:27 +00:00
|
|
|
Remove 'key' from the role's currently recognized list of role keys.
|
2014-02-21 17:16:56 +00:00
|
|
|
The role expects a threshold number of signatures.
|
2013-11-12 20:00:26 +00:00
|
|
|
|
|
|
|
|
>>>
|
|
|
|
|
>>>
|
|
|
|
|
>>>
|
|
|
|
|
|
|
|
|
|
<Arguments>
|
|
|
|
|
key:
|
2013-12-20 17:47:27 +00:00
|
|
|
The role's key, conformant to 'tuf.formats.ANYKEY_SCHEMA'. 'key'
|
2014-02-21 17:16:56 +00:00
|
|
|
should contain only the public portion, as only the public key is
|
|
|
|
|
needed. The 'add_verification_key()' method should have previously
|
|
|
|
|
added 'key'.
|
2013-11-12 20:00:26 +00:00
|
|
|
|
|
|
|
|
<Exceptions>
|
2013-12-20 17:47:27 +00:00
|
|
|
tuf.FormatError, if the 'key' argument is improperly formatted.
|
2014-02-21 17:16:56 +00:00
|
|
|
|
|
|
|
|
tuf.Error, if the 'key' argument has not been previously added.
|
|
|
|
|
|
2013-11-12 20:00:26 +00:00
|
|
|
<Side Effects>
|
2013-12-20 17:47:27 +00:00
|
|
|
Updates the role's 'tuf.roledb.py' entry.
|
2013-11-12 20:00:26 +00:00
|
|
|
|
|
|
|
|
<Returns>
|
|
|
|
|
None.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
# Does 'key' have the correct format?
|
2013-12-19 19:10:03 +00:00
|
|
|
# Ensure the arguments have the appropriate number of objects and object
|
|
|
|
|
# types, and that all dict keys are properly named.
|
|
|
|
|
# Raise 'tuf.FormatError' if any are improperly formatted.
|
2013-11-12 20:00:26 +00:00
|
|
|
tuf.formats.ANYKEY_SCHEMA.check_match(key)
|
|
|
|
|
|
|
|
|
|
keyid = key['keyid']
|
|
|
|
|
roleinfo = tuf.roledb.get_roleinfo(self.rolename)
|
2013-12-20 17:47:27 +00:00
|
|
|
|
2013-11-12 20:00:26 +00:00
|
|
|
if keyid in roleinfo['keyids']:
|
|
|
|
|
roleinfo['keyids'].remove(keyid)
|
2013-12-20 17:47:27 +00:00
|
|
|
|
2013-11-12 20:00:26 +00:00
|
|
|
tuf.roledb.update_roleinfo(self._rolename, roleinfo)
|
2014-02-21 17:16:56 +00:00
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
raise tuf.Error('Verification key not found.')
|
2013-11-12 20:00:26 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def load_signing_key(self, key):
|
|
|
|
|
"""
|
|
|
|
|
<Purpose>
|
2013-12-20 17:47:27 +00:00
|
|
|
Load the role key, which must contain the private portion, so that role
|
|
|
|
|
signatures may be generated when the role's metadata file is eventually
|
|
|
|
|
written to disk.
|
2013-11-12 20:00:26 +00:00
|
|
|
|
|
|
|
|
>>>
|
|
|
|
|
>>>
|
|
|
|
|
>>>
|
|
|
|
|
|
|
|
|
|
<Arguments>
|
|
|
|
|
key:
|
2013-12-20 17:47:27 +00:00
|
|
|
The role's key, conformant to 'tuf.formats.ANYKEY_SCHEMA'. It must
|
|
|
|
|
contain the private key, so that role signatures may be generated when
|
|
|
|
|
write() or write_partial() is eventually called to generate valid
|
|
|
|
|
metadata files.
|
2013-11-12 20:00:26 +00:00
|
|
|
|
|
|
|
|
<Exceptions>
|
|
|
|
|
tuf.FormatError, if 'key' is improperly formatted.
|
|
|
|
|
|
2013-12-20 17:47:27 +00:00
|
|
|
tuf.Error, if the private key is not found in 'key'.
|
2013-11-12 20:00:26 +00:00
|
|
|
|
|
|
|
|
<Side Effects>
|
2013-12-20 17:47:27 +00:00
|
|
|
Updates the role's 'tuf.keydb.py' and 'tuf.roledb.py' entries.
|
2013-11-12 20:00:26 +00:00
|
|
|
|
|
|
|
|
<Returns>
|
|
|
|
|
None.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
# Does 'key' have the correct format?
|
2013-12-19 19:10:03 +00:00
|
|
|
# Ensure the arguments have the appropriate number of objects and object
|
|
|
|
|
# types, and that all dict keys are properly named.
|
|
|
|
|
# Raise 'tuf.FormatError' if any are improperly formatted.
|
2013-11-12 20:00:26 +00:00
|
|
|
tuf.formats.ANYKEY_SCHEMA.check_match(key)
|
2013-12-20 17:47:27 +00:00
|
|
|
|
|
|
|
|
# Ensure the private portion of the key is available, otherwise signatures
|
|
|
|
|
# cannot be generated when the metadata file is written to disk.
|
2013-11-12 20:00:26 +00:00
|
|
|
if not len(key['keyval']['private']):
|
2016-01-28 20:22:20 +00:00
|
|
|
raise tuf.Error('This is not a private key.')
|
2013-11-12 20:00:26 +00:00
|
|
|
|
2013-12-20 17:47:27 +00:00
|
|
|
# Has the key, with the private portion included, been added to the keydb?
|
|
|
|
|
# The public version of the key may have been previously added.
|
2013-11-12 20:00:26 +00:00
|
|
|
try:
|
|
|
|
|
tuf.keydb.add_key(key)
|
2014-02-24 17:14:24 +00:00
|
|
|
|
2016-01-28 20:22:20 +00:00
|
|
|
except tuf.KeyAlreadyExistsError:
|
2013-11-12 20:00:26 +00:00
|
|
|
tuf.keydb.remove_key(key['keyid'])
|
|
|
|
|
tuf.keydb.add_key(key)
|
|
|
|
|
|
2013-12-20 17:47:27 +00:00
|
|
|
# Update the role's 'signing_keys' field in 'tuf.roledb.py'.
|
2013-11-12 20:00:26 +00:00
|
|
|
roleinfo = tuf.roledb.get_roleinfo(self.rolename)
|
|
|
|
|
if key['keyid'] not in roleinfo['signing_keyids']:
|
|
|
|
|
roleinfo['signing_keyids'].append(key['keyid'])
|
2013-12-20 17:47:27 +00:00
|
|
|
|
2013-11-12 20:00:26 +00:00
|
|
|
tuf.roledb.update_roleinfo(self.rolename, roleinfo)
|
2014-02-24 17:14:24 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2013-11-12 20:00:26 +00:00
|
|
|
def unload_signing_key(self, key):
|
|
|
|
|
"""
|
|
|
|
|
<Purpose>
|
2013-12-20 17:47:27 +00:00
|
|
|
Remove a previously loaded role private key (i.e., load_signing_key()).
|
2014-02-13 15:03:25 +00:00
|
|
|
The keyid of the 'key' is removed from the list of recognized signing
|
|
|
|
|
keys.
|
2013-11-12 20:00:26 +00:00
|
|
|
|
|
|
|
|
>>>
|
|
|
|
|
>>>
|
|
|
|
|
>>>
|
|
|
|
|
|
|
|
|
|
<Arguments>
|
|
|
|
|
key:
|
2013-12-20 17:47:27 +00:00
|
|
|
The role key to be unloaded, conformant to 'tuf.formats.ANYKEY_SCHEMA'.
|
2013-11-12 20:00:26 +00:00
|
|
|
|
|
|
|
|
<Exceptions>
|
2013-12-20 17:47:27 +00:00
|
|
|
tuf.FormatError, if the 'key' argument is improperly formatted.
|
2013-11-12 20:00:26 +00:00
|
|
|
|
2014-02-21 17:16:56 +00:00
|
|
|
tuf.Error, if the 'key' argument has not been previously loaded.
|
|
|
|
|
|
2013-11-12 20:00:26 +00:00
|
|
|
<Side Effects>
|
2013-12-20 17:47:27 +00:00
|
|
|
Updates the signing keys of the role in 'tuf.roledb.py'.
|
2013-11-12 20:00:26 +00:00
|
|
|
|
|
|
|
|
<Returns>
|
|
|
|
|
None.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
# Does 'key' have the correct format?
|
2013-12-19 19:10:03 +00:00
|
|
|
# Ensure the arguments have the appropriate number of objects and object
|
|
|
|
|
# types, and that all dict keys are properly named.
|
|
|
|
|
# Raise 'tuf.FormatError' if any are improperly formatted.
|
2013-11-12 20:00:26 +00:00
|
|
|
tuf.formats.ANYKEY_SCHEMA.check_match(key)
|
|
|
|
|
|
2013-12-20 17:47:27 +00:00
|
|
|
# Update the role's 'signing_keys' field in 'tuf.roledb.py'.
|
2013-11-12 20:00:26 +00:00
|
|
|
roleinfo = tuf.roledb.get_roleinfo(self.rolename)
|
2013-12-20 17:47:27 +00:00
|
|
|
|
2013-11-12 20:00:26 +00:00
|
|
|
if key['keyid'] in roleinfo['signing_keyids']:
|
|
|
|
|
roleinfo['signing_keyids'].remove(key['keyid'])
|
2013-12-20 17:47:27 +00:00
|
|
|
|
2013-11-12 20:00:26 +00:00
|
|
|
tuf.roledb.update_roleinfo(self.rolename, roleinfo)
|
2014-02-21 17:16:56 +00:00
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
raise tuf.Error('Signing key not found.')
|
|
|
|
|
|
2013-11-12 20:00:26 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def add_signature(self, signature):
|
|
|
|
|
"""
|
|
|
|
|
<Purpose>
|
2013-12-20 17:47:27 +00:00
|
|
|
Add a signature to the role. A role is considered fully signed if it
|
|
|
|
|
contains a threshold of signatures. The 'signature' should have been
|
|
|
|
|
generated by the private key corresponding to one of the role's expected
|
|
|
|
|
keys.
|
2013-11-12 20:00:26 +00:00
|
|
|
|
|
|
|
|
>>>
|
|
|
|
|
>>>
|
|
|
|
|
>>>
|
|
|
|
|
|
|
|
|
|
<Arguments>
|
2013-12-20 17:47:27 +00:00
|
|
|
signature:
|
|
|
|
|
The signature to be added to the role, conformant to
|
|
|
|
|
'tuf.formats.SIGNATURE_SCHEMA'.
|
2013-11-12 20:00:26 +00:00
|
|
|
|
|
|
|
|
<Exceptions>
|
2013-12-20 17:47:27 +00:00
|
|
|
tuf.FormatError, if the 'signature' argument is improperly formatted.
|
2013-11-12 20:00:26 +00:00
|
|
|
|
|
|
|
|
<Side Effects>
|
2013-12-20 17:47:27 +00:00
|
|
|
Adds 'signature', if not already added, to the role's 'signatures' field
|
|
|
|
|
in 'tuf.roledb.py'.
|
2013-11-12 20:00:26 +00:00
|
|
|
|
|
|
|
|
<Returns>
|
|
|
|
|
None.
|
|
|
|
|
"""
|
|
|
|
|
|
2013-12-19 19:10:03 +00:00
|
|
|
# Does 'signature' have the correct format?
|
|
|
|
|
# Ensure the arguments have the appropriate number of objects and object
|
|
|
|
|
# types, and that all dict keys are properly named.
|
|
|
|
|
# Raise 'tuf.FormatError' if any are improperly formatted.
|
2013-11-12 20:00:26 +00:00
|
|
|
tuf.formats.SIGNATURE_SCHEMA.check_match(signature)
|
|
|
|
|
|
|
|
|
|
roleinfo = tuf.roledb.get_roleinfo(self.rolename)
|
2013-12-20 17:47:27 +00:00
|
|
|
|
2014-02-13 15:03:25 +00:00
|
|
|
# Ensure the roleinfo contains a 'signatures' field.
|
2013-11-12 20:00:26 +00:00
|
|
|
if 'signatures' not in roleinfo:
|
|
|
|
|
roleinfo['signatures'] = []
|
2013-12-20 17:47:27 +00:00
|
|
|
|
|
|
|
|
# Update the role's roleinfo by adding 'signature', if it has not been
|
|
|
|
|
# added.
|
2013-11-12 20:00:26 +00:00
|
|
|
if signature not in roleinfo['signatures']:
|
|
|
|
|
roleinfo['signatures'].append(signature)
|
|
|
|
|
tuf.roledb.update_roleinfo(self.rolename, roleinfo)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def remove_signature(self, signature):
|
|
|
|
|
"""
|
|
|
|
|
<Purpose>
|
2013-12-20 17:47:27 +00:00
|
|
|
Remove a previously loaded, or added, role 'signature'. A role must
|
|
|
|
|
contain a threshold number of signatures to be considered fully signed.
|
2013-11-12 20:00:26 +00:00
|
|
|
|
|
|
|
|
>>>
|
|
|
|
|
>>>
|
|
|
|
|
>>>
|
|
|
|
|
|
|
|
|
|
<Arguments>
|
2013-12-20 17:47:27 +00:00
|
|
|
signature:
|
|
|
|
|
The role signature to remove, conformant to
|
|
|
|
|
'tuf.formats.SIGNATURE_SCHEMA'.
|
2013-11-12 20:00:26 +00:00
|
|
|
|
|
|
|
|
<Exceptions>
|
2013-12-20 17:47:27 +00:00
|
|
|
tuf.FormatError, if the 'signature' argument is improperly formatted.
|
2013-11-12 20:00:26 +00:00
|
|
|
|
2014-02-13 15:03:25 +00:00
|
|
|
tuf.Error, if 'signature' has not been previously added to this role.
|
|
|
|
|
|
2013-11-12 20:00:26 +00:00
|
|
|
<Side Effects>
|
2013-12-20 17:47:27 +00:00
|
|
|
Updates the 'signatures' field of the role in 'tuf.roledb.py'.
|
2013-11-12 20:00:26 +00:00
|
|
|
|
|
|
|
|
<Returns>
|
|
|
|
|
None.
|
|
|
|
|
"""
|
|
|
|
|
|
2013-12-19 19:10:03 +00:00
|
|
|
# Does 'signature' have the correct format?
|
|
|
|
|
# Ensure the arguments have the appropriate number of objects and object
|
|
|
|
|
# types, and that all dict keys are properly named.
|
|
|
|
|
# Raise 'tuf.FormatError' if any are improperly formatted.
|
2013-11-12 20:00:26 +00:00
|
|
|
tuf.formats.SIGNATURE_SCHEMA.check_match(signature)
|
|
|
|
|
|
|
|
|
|
roleinfo = tuf.roledb.get_roleinfo(self.rolename)
|
2013-12-20 17:47:27 +00:00
|
|
|
|
2013-11-12 20:00:26 +00:00
|
|
|
if signature in roleinfo['signatures']:
|
|
|
|
|
roleinfo['signatures'].remove(signature)
|
2013-12-20 17:47:27 +00:00
|
|
|
|
2013-11-12 20:00:26 +00:00
|
|
|
tuf.roledb.update_roleinfo(self.rolename, roleinfo)
|
|
|
|
|
|
2014-02-13 15:03:25 +00:00
|
|
|
else:
|
|
|
|
|
raise tuf.Error('Signature not found.')
|
|
|
|
|
|
2013-11-12 20:00:26 +00:00
|
|
|
|
|
|
|
|
|
2013-11-05 13:22:21 +00:00
|
|
|
@property
|
2013-11-12 20:00:26 +00:00
|
|
|
def signatures(self):
|
|
|
|
|
"""
|
2013-12-20 17:47:27 +00:00
|
|
|
<Purpose>
|
|
|
|
|
A getter method that returns the role's signatures. A role is considered
|
|
|
|
|
fully signed if it contains a threshold number of signatures, where each
|
|
|
|
|
signature must be provided by the generated by the private key. Keys
|
2014-01-29 16:26:56 +00:00
|
|
|
are added to a role with the add_verification_key() method.
|
2013-12-20 17:47:27 +00:00
|
|
|
|
|
|
|
|
<Arguments>
|
|
|
|
|
None.
|
|
|
|
|
|
|
|
|
|
<Exceptions>
|
|
|
|
|
None.
|
|
|
|
|
|
|
|
|
|
<Side Effects>
|
|
|
|
|
None.
|
|
|
|
|
|
|
|
|
|
<Returns>
|
|
|
|
|
A list of signatures, conformant to 'tuf.formats.SIGNATURES_SCHEMA'.
|
2013-11-12 20:00:26 +00:00
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
roleinfo = tuf.roledb.get_roleinfo(self.rolename)
|
2013-11-15 01:36:28 +00:00
|
|
|
signatures = roleinfo['signatures']
|
2013-11-14 19:24:07 +00:00
|
|
|
|
|
|
|
|
return signatures
|
2013-11-12 20:00:26 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
2013-11-14 19:24:07 +00:00
|
|
|
def keys(self):
|
2013-11-12 20:00:26 +00:00
|
|
|
"""
|
2013-12-20 17:47:27 +00:00
|
|
|
<Purpose>
|
|
|
|
|
A getter method that returns the role's keyids of the keys. The role
|
|
|
|
|
is expected to eventually contain a threshold of signatures generated
|
2014-04-08 18:56:39 +00:00
|
|
|
by the private keys of each of the role's keys (returned here as a keyid.)
|
2013-12-20 17:47:27 +00:00
|
|
|
|
|
|
|
|
<Arguments>
|
|
|
|
|
None.
|
|
|
|
|
|
|
|
|
|
<Exceptions>
|
|
|
|
|
None.
|
|
|
|
|
|
|
|
|
|
<Side Effects>
|
|
|
|
|
None.
|
|
|
|
|
|
|
|
|
|
<Returns>
|
|
|
|
|
A list of the role's keyids (i.e., keyids of the keys).
|
2013-11-12 20:00:26 +00:00
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
roleinfo = tuf.roledb.get_roleinfo(self.rolename)
|
2013-11-14 19:24:07 +00:00
|
|
|
keyids = roleinfo['keyids']
|
|
|
|
|
|
|
|
|
|
return keyids
|
2013-11-12 20:00:26 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def rolename(self):
|
|
|
|
|
"""
|
2013-12-20 17:47:27 +00:00
|
|
|
<Purpose>
|
|
|
|
|
Return the role's name.
|
|
|
|
|
Examples: 'root', 'timestamp', 'targets/unclaimed/django'.
|
|
|
|
|
|
|
|
|
|
<Arguments>
|
|
|
|
|
None.
|
|
|
|
|
|
|
|
|
|
<Exceptions>
|
|
|
|
|
None.
|
|
|
|
|
|
|
|
|
|
<Side Effects>
|
|
|
|
|
None.
|
|
|
|
|
|
|
|
|
|
<Returns>
|
|
|
|
|
The role's name, conformant to 'tuf.formats.ROLENAME_SCHEMA'.
|
|
|
|
|
Examples: 'root', 'timestamp', 'targets/unclaimed/django'.
|
2013-11-12 20:00:26 +00:00
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
return self._rolename
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def version(self):
|
|
|
|
|
"""
|
2013-12-20 17:47:27 +00:00
|
|
|
<Purpose>
|
|
|
|
|
A getter method that returns the role's version number, conformant to
|
|
|
|
|
'tuf.formats.VERSION_SCHEMA'.
|
|
|
|
|
|
|
|
|
|
<Arguments>
|
|
|
|
|
None.
|
|
|
|
|
|
|
|
|
|
<Exceptions>
|
|
|
|
|
None.
|
|
|
|
|
|
|
|
|
|
<Side Effects>
|
|
|
|
|
None.
|
|
|
|
|
|
|
|
|
|
<Returns>
|
|
|
|
|
The role's version number, conformant to 'tuf.formats.VERSION_SCHEMA'.
|
2013-11-12 20:00:26 +00:00
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
roleinfo = tuf.roledb.get_roleinfo(self.rolename)
|
|
|
|
|
version = roleinfo['version']
|
|
|
|
|
|
|
|
|
|
return version
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@version.setter
|
|
|
|
|
def version(self, version):
|
|
|
|
|
"""
|
|
|
|
|
<Purpose>
|
2013-12-20 17:47:27 +00:00
|
|
|
A setter method that updates the role's version number. TUF clients
|
|
|
|
|
download new metadata with version number greater than the version
|
|
|
|
|
currently trusted. New metadata start at version 1 when either write()
|
|
|
|
|
or write_partial() is called. Version numbers are automatically
|
|
|
|
|
incremented, when the write methods are called, as follows:
|
|
|
|
|
|
|
|
|
|
1. write_partial==True and the metadata is the first to be written.
|
|
|
|
|
|
|
|
|
|
2. write_partial=False (i.e., write()), the metadata was not loaded as
|
|
|
|
|
partially written, and a write_partial is not needed.
|
2013-11-12 20:00:26 +00:00
|
|
|
|
|
|
|
|
>>>
|
|
|
|
|
>>>
|
|
|
|
|
>>>
|
|
|
|
|
|
|
|
|
|
<Arguments>
|
2013-12-20 17:47:27 +00:00
|
|
|
version:
|
|
|
|
|
The role's version number, conformant to 'tuf.formats.VERSION_SCHEMA'.
|
2013-11-12 20:00:26 +00:00
|
|
|
|
|
|
|
|
<Exceptions>
|
2013-12-20 17:47:27 +00:00
|
|
|
tuf.FormatError, if the 'version' argument is improperly formatted.
|
2013-11-12 20:00:26 +00:00
|
|
|
|
|
|
|
|
<Side Effects>
|
2013-12-20 17:47:27 +00:00
|
|
|
Modifies the 'version' attribute of the Repository object and updates
|
|
|
|
|
the role's version in 'tuf.roledb.py'.
|
2013-11-12 20:00:26 +00:00
|
|
|
|
|
|
|
|
<Returns>
|
|
|
|
|
None.
|
2013-11-05 13:22:21 +00:00
|
|
|
"""
|
2013-11-12 20:00:26 +00:00
|
|
|
|
|
|
|
|
# Does 'version' have the correct format?
|
2013-12-19 19:10:03 +00:00
|
|
|
# Ensure the arguments have the appropriate number of objects and object
|
|
|
|
|
# types, and that all dict keys are properly named.
|
|
|
|
|
# Raise 'tuf.FormatError' if any are improperly formatted.
|
2013-11-12 20:00:26 +00:00
|
|
|
tuf.formats.METADATAVERSION_SCHEMA.check_match(version)
|
|
|
|
|
|
|
|
|
|
roleinfo = tuf.roledb.get_roleinfo(self.rolename)
|
|
|
|
|
roleinfo['version'] = version
|
|
|
|
|
|
|
|
|
|
tuf.roledb.update_roleinfo(self._rolename, roleinfo)
|
|
|
|
|
|
|
|
|
|
|
2013-11-05 13:22:21 +00:00
|
|
|
|
2013-11-12 20:00:26 +00:00
|
|
|
@property
|
|
|
|
|
def threshold(self):
|
|
|
|
|
"""
|
2013-12-20 17:47:27 +00:00
|
|
|
<Purpose>
|
|
|
|
|
Return the role's threshold value. A role is considered fully signed if
|
|
|
|
|
a threshold number of signatures is available.
|
|
|
|
|
|
|
|
|
|
<Arguments>
|
|
|
|
|
None.
|
|
|
|
|
|
|
|
|
|
<Exceptions>
|
|
|
|
|
None.
|
|
|
|
|
|
|
|
|
|
<Side Effects>
|
|
|
|
|
None.
|
|
|
|
|
|
|
|
|
|
<Returns>
|
|
|
|
|
The role's threshold value, conformant to 'tuf.formats.THRESHOLD_SCHEMA'.
|
2013-11-05 13:22:21 +00:00
|
|
|
"""
|
2013-11-14 19:24:07 +00:00
|
|
|
|
|
|
|
|
roleinfo = tuf.roledb.get_roleinfo(self._rolename)
|
|
|
|
|
threshold = roleinfo['threshold']
|
2013-11-05 13:22:21 +00:00
|
|
|
|
2013-11-14 19:24:07 +00:00
|
|
|
return threshold
|
2013-11-05 13:22:21 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@threshold.setter
|
|
|
|
|
def threshold(self, threshold):
|
2013-10-29 19:23:26 +00:00
|
|
|
"""
|
|
|
|
|
<Purpose>
|
2013-12-20 17:47:27 +00:00
|
|
|
A setter method that modified the threshold value of the role. Metadata
|
|
|
|
|
is considered fully signed if a 'threshold' number of signatures is
|
|
|
|
|
available.
|
2013-10-29 19:23:26 +00:00
|
|
|
|
|
|
|
|
>>>
|
|
|
|
|
>>>
|
|
|
|
|
>>>
|
|
|
|
|
|
|
|
|
|
<Arguments>
|
|
|
|
|
threshold:
|
2013-12-20 17:47:27 +00:00
|
|
|
An integer value that sets the role's threshold value, or the miminum
|
|
|
|
|
number of signatures needed for metadata to be considered fully
|
|
|
|
|
signed. Conformant to 'tuf.formats.THRESHOLD_SCHEMA'.
|
2013-10-29 19:23:26 +00:00
|
|
|
|
|
|
|
|
<Exceptions>
|
2013-12-20 17:47:27 +00:00
|
|
|
tuf.FormatError, if the 'threshold' argument is improperly formatted.
|
2013-10-29 19:23:26 +00:00
|
|
|
|
|
|
|
|
<Side Effects>
|
2013-12-20 17:47:27 +00:00
|
|
|
Modifies the threshold attribute of the Repository object and updates
|
|
|
|
|
the roles threshold in 'tuf.roledb.py'.
|
2013-10-29 19:23:26 +00:00
|
|
|
|
|
|
|
|
<Returns>
|
|
|
|
|
None.
|
|
|
|
|
"""
|
2013-11-12 20:00:26 +00:00
|
|
|
|
|
|
|
|
# Does 'threshold' have the correct format?
|
2013-12-19 19:10:03 +00:00
|
|
|
# Ensure the arguments have the appropriate number of objects and object
|
|
|
|
|
# types, and that all dict keys are properly named.
|
|
|
|
|
# Raise 'tuf.FormatError' if any are improperly formatted.
|
2013-10-29 19:23:26 +00:00
|
|
|
tuf.formats.THRESHOLD_SCHEMA.check_match(threshold)
|
|
|
|
|
|
2013-11-05 13:22:21 +00:00
|
|
|
roleinfo = tuf.roledb.get_roleinfo(self._rolename)
|
2013-10-29 19:23:26 +00:00
|
|
|
roleinfo['threshold'] = threshold
|
|
|
|
|
|
2013-11-05 13:22:21 +00:00
|
|
|
tuf.roledb.update_roleinfo(self._rolename, roleinfo)
|
2013-10-29 19:23:26 +00:00
|
|
|
|
|
|
|
|
|
2013-11-05 13:22:21 +00:00
|
|
|
@property
|
|
|
|
|
def expiration(self):
|
|
|
|
|
"""
|
|
|
|
|
<Purpose>
|
2013-12-20 17:47:27 +00:00
|
|
|
A getter method that returns the role's expiration datetime.
|
2013-11-05 13:22:21 +00:00
|
|
|
|
|
|
|
|
>>>
|
|
|
|
|
>>>
|
|
|
|
|
>>>
|
|
|
|
|
|
|
|
|
|
<Arguments>
|
|
|
|
|
None.
|
|
|
|
|
|
|
|
|
|
<Exceptions>
|
|
|
|
|
None.
|
|
|
|
|
|
|
|
|
|
<Side Effects>
|
|
|
|
|
None.
|
|
|
|
|
|
|
|
|
|
<Returns>
|
2014-04-15 16:52:35 +00:00
|
|
|
The role's expiration datetime, a datetime.datetime() object.
|
2013-11-05 13:22:21 +00:00
|
|
|
"""
|
2013-11-12 20:00:26 +00:00
|
|
|
|
|
|
|
|
roleinfo = tuf.roledb.get_roleinfo(self.rolename)
|
2014-04-15 16:52:35 +00:00
|
|
|
expires = roleinfo['expires']
|
2013-11-05 13:22:21 +00:00
|
|
|
|
2014-04-19 18:27:53 +00:00
|
|
|
expires_datetime_object = iso8601.parse_date(expires)
|
|
|
|
|
|
|
|
|
|
return expires_datetime_object
|
2013-11-05 13:22:21 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@expiration.setter
|
2014-04-15 16:52:35 +00:00
|
|
|
def expiration(self, datetime_object):
|
2013-11-05 13:22:21 +00:00
|
|
|
"""
|
|
|
|
|
<Purpose>
|
2013-12-20 17:47:27 +00:00
|
|
|
A setter method for the role's expiration datetime. The top-level
|
2013-12-19 19:10:03 +00:00
|
|
|
roles have a default expiration (e.g., ROOT_EXPIRATION), but may later
|
2013-12-20 17:47:27 +00:00
|
|
|
be modified by this setter method.
|
2013-12-19 19:10:03 +00:00
|
|
|
|
2013-11-05 13:22:21 +00:00
|
|
|
>>>
|
|
|
|
|
>>>
|
|
|
|
|
>>>
|
|
|
|
|
|
|
|
|
|
<Arguments>
|
2014-04-15 16:52:35 +00:00
|
|
|
datetime_object:
|
|
|
|
|
The datetime expiration of the role, a datetime.datetime() object.
|
2013-11-05 13:22:21 +00:00
|
|
|
|
|
|
|
|
<Exceptions>
|
2014-04-15 16:52:35 +00:00
|
|
|
tuf.FormatError, if 'datetime_object' is not a datetime.datetime() object.
|
|
|
|
|
|
|
|
|
|
tuf.Error, if 'datetime_object' has already expired.
|
|
|
|
|
|
2013-11-05 13:22:21 +00:00
|
|
|
<Side Effects>
|
2014-05-05 15:12:23 +00:00
|
|
|
Modifies the expiration attribute of the Repository object.
|
|
|
|
|
The datetime given will be truncated to microseconds = 0
|
2013-11-05 13:22:21 +00:00
|
|
|
|
|
|
|
|
<Returns>
|
|
|
|
|
None.
|
|
|
|
|
"""
|
|
|
|
|
|
2014-04-15 16:52:35 +00:00
|
|
|
# Is 'datetime_object' a datetime.datetime() object?
|
|
|
|
|
# Raise 'tuf.FormatError' if not.
|
|
|
|
|
if not isinstance(datetime_object, datetime.datetime):
|
2016-01-28 20:22:20 +00:00
|
|
|
raise tuf.FormatError(repr(datetime_object) + ' is not a'
|
|
|
|
|
' datetime.datetime() object.')
|
2014-04-15 16:52:35 +00:00
|
|
|
|
2014-05-02 23:15:57 +00:00
|
|
|
# truncate the microseconds value to produce a correct schema string
|
|
|
|
|
# of the form yyyy-mm-ddThh:mm:ssZ
|
|
|
|
|
datetime_object = datetime_object.replace(microsecond = 0)
|
|
|
|
|
|
2014-04-15 16:52:35 +00:00
|
|
|
# Ensure the expiration has not already passed.
|
2014-04-19 18:27:53 +00:00
|
|
|
current_datetime_object = \
|
|
|
|
|
tuf.formats.unix_timestamp_to_datetime(int(time.time()))
|
2014-04-17 16:27:28 +00:00
|
|
|
|
2014-04-19 18:27:53 +00:00
|
|
|
if datetime_object < current_datetime_object:
|
2016-01-28 20:22:20 +00:00
|
|
|
raise tuf.Error(repr(self.rolename) + ' has already expired.')
|
2013-12-19 19:10:03 +00:00
|
|
|
|
2014-04-17 16:27:28 +00:00
|
|
|
# Update the role's 'expires' entry in 'tuf.roledb.py'.
|
2013-11-12 20:00:26 +00:00
|
|
|
roleinfo = tuf.roledb.get_roleinfo(self.rolename)
|
2014-04-19 18:27:53 +00:00
|
|
|
expires = datetime_object.isoformat() + 'Z'
|
|
|
|
|
roleinfo['expires'] = expires
|
2014-04-15 16:52:35 +00:00
|
|
|
|
2013-11-12 20:00:26 +00:00
|
|
|
tuf.roledb.update_roleinfo(self.rolename, roleinfo)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def signing_keys(self):
|
|
|
|
|
"""
|
2013-12-19 19:10:03 +00:00
|
|
|
<Purpose>
|
2013-12-20 17:47:27 +00:00
|
|
|
A getter method that returns a list of the role's signing keys.
|
2013-12-19 19:10:03 +00:00
|
|
|
|
|
|
|
|
>>>
|
|
|
|
|
>>>
|
|
|
|
|
>>>
|
|
|
|
|
|
|
|
|
|
<Arguments>
|
|
|
|
|
None.
|
|
|
|
|
|
|
|
|
|
<Exceptions>
|
|
|
|
|
None.
|
|
|
|
|
|
|
|
|
|
<Side Effects>
|
|
|
|
|
None.
|
|
|
|
|
|
|
|
|
|
<Returns>
|
|
|
|
|
A list of keyids of the role's signing keys, conformant to
|
|
|
|
|
'tuf.formats.KEYIDS_SCHEMA'.
|
2013-11-12 20:00:26 +00:00
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
roleinfo = tuf.roledb.get_roleinfo(self.rolename)
|
2013-11-14 19:24:07 +00:00
|
|
|
signing_keyids = roleinfo['signing_keyids']
|
2013-11-05 13:22:21 +00:00
|
|
|
|
2013-11-14 19:24:07 +00:00
|
|
|
return signing_keyids
|
2013-11-05 13:22:21 +00:00
|
|
|
|
|
|
|
|
|
2013-11-14 19:24:07 +00:00
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def compressions(self):
|
|
|
|
|
"""
|
2013-12-19 19:10:03 +00:00
|
|
|
<Purpose>
|
2013-12-20 17:47:27 +00:00
|
|
|
A getter method that returns a list of the file compression algorithms
|
2013-12-19 19:10:03 +00:00
|
|
|
used when the metadata is written to disk. If ['gz'] is set for the
|
2014-06-03 18:32:44 +00:00
|
|
|
'targets.json' role, the metadata files 'targets.json' and
|
|
|
|
|
'targets.json.gz' are written.
|
2013-12-19 19:10:03 +00:00
|
|
|
|
|
|
|
|
>>>
|
|
|
|
|
>>>
|
|
|
|
|
>>>
|
|
|
|
|
|
|
|
|
|
<Arguments>
|
|
|
|
|
None.
|
|
|
|
|
|
|
|
|
|
<Exceptions>
|
|
|
|
|
None.
|
|
|
|
|
|
|
|
|
|
<Side Effects>
|
|
|
|
|
None.
|
|
|
|
|
|
|
|
|
|
<Returns>
|
|
|
|
|
A list of compression algorithms, conformant to
|
|
|
|
|
'tuf.formats.COMPRESSIONS_SCHEMA'.
|
2013-10-22 18:02:01 +00:00
|
|
|
"""
|
|
|
|
|
|
2014-03-13 16:31:36 +00:00
|
|
|
roleinfo = tuf.roledb.get_roleinfo(self.rolename)
|
2013-11-14 19:24:07 +00:00
|
|
|
compressions = roleinfo['compressions']
|
2013-10-22 18:02:01 +00:00
|
|
|
|
2013-11-14 19:24:07 +00:00
|
|
|
return compressions
|
2013-10-22 18:02:01 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2013-11-14 19:24:07 +00:00
|
|
|
@compressions.setter
|
|
|
|
|
def compressions(self, compression_list):
|
2013-10-22 18:02:01 +00:00
|
|
|
"""
|
2013-12-19 19:10:03 +00:00
|
|
|
<Purpose>
|
2013-12-20 17:47:27 +00:00
|
|
|
A setter method for the file compression algorithms used when the
|
2014-01-27 16:35:38 +00:00
|
|
|
metadata is written to disk. If ['gz'] is set for the 'targets.json' role
|
|
|
|
|
the metadata files 'targets.json' and 'targets.json.gz' are written.
|
2013-12-19 19:10:03 +00:00
|
|
|
|
|
|
|
|
>>>
|
|
|
|
|
>>>
|
|
|
|
|
>>>
|
|
|
|
|
|
|
|
|
|
<Arguments>
|
|
|
|
|
compression_list:
|
|
|
|
|
A list of file compression algorithms, conformant to
|
|
|
|
|
'tuf.formats.COMPRESSIONS_SCHEMA'.
|
|
|
|
|
|
|
|
|
|
<Exceptions>
|
|
|
|
|
tuf.FormatError, if 'compression_list' is improperly formatted.
|
|
|
|
|
|
|
|
|
|
<Side Effects>
|
|
|
|
|
Updates the role's compression algorithms listed in 'tuf.roledb.py'.
|
|
|
|
|
|
|
|
|
|
<Returns>
|
|
|
|
|
None.
|
2013-11-14 19:24:07 +00:00
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
# Does 'compression_name' have the correct format?
|
2013-12-19 19:10:03 +00:00
|
|
|
# Ensure the arguments have the appropriate number of objects and object
|
|
|
|
|
# types, and that all dict keys are properly named.
|
|
|
|
|
# Raise 'tuf.FormatError' if any are improperly formatted.
|
2013-11-14 19:24:07 +00:00
|
|
|
tuf.formats.COMPRESSIONS_SCHEMA.check_match(compression_list)
|
|
|
|
|
|
|
|
|
|
roleinfo = tuf.roledb.get_roleinfo(self.rolename)
|
2013-12-19 19:10:03 +00:00
|
|
|
|
|
|
|
|
# Add the compression algorithms of 'compression_list' to the role's
|
|
|
|
|
# entry in 'tuf.roledb.py'.
|
|
|
|
|
for compression in compression_list:
|
|
|
|
|
if compression not in roleinfo['compressions']:
|
Address Issues #165, #158, and #147.
Issue 147: Finalize conversion of all written metadata behavior. This commit ensures that compressed and uncompressed metadata is also written as outlined in the issue.
Issue 158: As requested, updater.refresh() may now unsafely fetch (i.e., unknown file size and hash) Root metadata if valid top-level metadata cannot be downloaded successfully (e.g., top-level keys may have been revoked). The repository must also sign the new Root file (at least until all clients have updated) with any revoked keys so that clients may successfully update. After unsafely updating Root, the top-level metadata is updated again as normal (and only once to avoid an infinite loop). By default, refresh() unsafely updates Root if only invalid top-level metadata can be downloaded, although this behavior may be overriden by the caller if they wish. Changed default behavior: refresh(self, unsafely_update_root_if_necessary=True)
Issue 165: Delegated roles are no longer added as attributes of a Targets object by libtuf.py (e.g., repository.targets.delegated_role). The previous bahavior restricted rolenames to Python identifiers (i.e., can only include letters, numbers, the underscore character, and must start with a nonnumeric character). Now, delegated roles may be referenced as strings (e.g., repository.targets('recently-claimed')) and include characters other than '_'. In addition, methods have been added to return all the delegated rolesnames of a target (e.g., repository.targets.get_delegated_rolenames()) and the immediate delegated Target objects of a role. Previous behavior: repository.targets.unclaimed.django.version = 8
Current behavior: repository.targets('unclaimed')('django').version = 8.
2014-01-02 17:18:44 +00:00
|
|
|
roleinfo['compressions'].append(compression)
|
2013-12-19 19:10:03 +00:00
|
|
|
|
2013-11-14 19:24:07 +00:00
|
|
|
tuf.roledb.update_roleinfo(self.rolename, roleinfo)
|
2013-10-22 18:02:01 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Root(Metadata):
|
|
|
|
|
"""
|
|
|
|
|
<Purpose>
|
2013-12-19 19:10:03 +00:00
|
|
|
Represent a Root role object. The root role is responsible for
|
|
|
|
|
listing the public keys and threshold of all the top-level roles, including
|
|
|
|
|
itself. Top-level metadata is rejected if it does not comply with what is
|
|
|
|
|
specified by the Root role.
|
|
|
|
|
|
|
|
|
|
This Root object sub-classes Metadata, so the expected Metadata
|
|
|
|
|
operations like adding/removing public keys, signatures, private keys, and
|
|
|
|
|
updating metadata attributes (e.g., version and expiration) is supported.
|
|
|
|
|
Since Root is a top-level role and must exist, a default Root object
|
|
|
|
|
is instantiated when a new Repository object is created.
|
2013-10-22 18:02:01 +00:00
|
|
|
|
|
|
|
|
>>>
|
|
|
|
|
>>>
|
|
|
|
|
>>>
|
|
|
|
|
|
|
|
|
|
<Arguments>
|
2013-12-19 19:10:03 +00:00
|
|
|
None.
|
|
|
|
|
|
2013-10-22 18:02:01 +00:00
|
|
|
<Exceptions>
|
2013-12-19 19:10:03 +00:00
|
|
|
None.
|
2013-10-22 18:02:01 +00:00
|
|
|
|
|
|
|
|
<Side Effects>
|
2013-12-19 19:10:03 +00:00
|
|
|
A 'root' role is added to 'tuf.roledb.py'.
|
2013-10-22 18:02:01 +00:00
|
|
|
|
|
|
|
|
<Returns>
|
2013-12-19 19:10:03 +00:00
|
|
|
None.
|
2013-10-22 18:02:01 +00:00
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
|
|
2013-10-29 19:23:26 +00:00
|
|
|
super(Root, self).__init__()
|
|
|
|
|
|
2013-11-05 13:22:21 +00:00
|
|
|
self._rolename = 'root'
|
2013-11-12 20:00:26 +00:00
|
|
|
|
2014-01-29 16:26:56 +00:00
|
|
|
# By default, 'snapshot' metadata is set to expire 1 week from the current
|
2013-12-19 19:10:03 +00:00
|
|
|
# time. The expiration may be modified.
|
2014-04-19 18:27:53 +00:00
|
|
|
expiration = \
|
|
|
|
|
tuf.formats.unix_timestamp_to_datetime(int(time.time() + ROOT_EXPIRATION))
|
|
|
|
|
expiration = expiration.isoformat() + 'Z'
|
2013-11-12 20:00:26 +00:00
|
|
|
|
|
|
|
|
roleinfo = {'keyids': [], 'signing_keyids': [], 'threshold': 1,
|
2014-01-30 13:11:35 +00:00
|
|
|
'signatures': [], 'version': 0, 'consistent_snapshot': False,
|
2014-01-13 14:34:21 +00:00
|
|
|
'compressions': [''], 'expires': expiration,
|
|
|
|
|
'partial_loaded': False}
|
2013-11-12 20:00:26 +00:00
|
|
|
try:
|
|
|
|
|
tuf.roledb.add_role(self._rolename, roleinfo)
|
2014-02-13 15:03:25 +00:00
|
|
|
|
2016-01-28 20:22:20 +00:00
|
|
|
except tuf.RoleAlreadyExistsError:
|
2013-11-12 20:00:26 +00:00
|
|
|
pass
|
2013-10-22 18:02:01 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Timestamp(Metadata):
|
|
|
|
|
"""
|
|
|
|
|
<Purpose>
|
2013-12-19 19:10:03 +00:00
|
|
|
Represent a Timestamp role object. The timestamp role is responsible for
|
2014-01-29 16:26:56 +00:00
|
|
|
referencing the latest version of the Snapshot role. Under normal
|
2013-12-19 19:10:03 +00:00
|
|
|
conditions, it is the only role to be downloaded from a remote repository
|
|
|
|
|
without a known file length and hash. An upper length limit is set, though.
|
|
|
|
|
Also, its signatures are also verified to be valid according to the Root
|
|
|
|
|
role. If invalid metadata can only be downloaded by the client, Root
|
|
|
|
|
is the only other role that is downloaded without a known length and hash.
|
|
|
|
|
This case may occur if a role's signing keys have been revoked and a newer
|
|
|
|
|
Root file is needed to list the updated keys.
|
|
|
|
|
|
|
|
|
|
This Timestamp object sub-classes Metadata, so the expected Metadata
|
|
|
|
|
operations like adding/removing public keys, signatures, private keys, and
|
|
|
|
|
updating metadata attributes (e.g., version and expiration) is supported.
|
2014-01-29 16:26:56 +00:00
|
|
|
Since Snapshot is a top-level role and must exist, a default Timestamp object
|
2013-12-19 19:10:03 +00:00
|
|
|
is instantiated when a new Repository object is created.
|
2013-10-22 18:02:01 +00:00
|
|
|
|
|
|
|
|
>>>
|
|
|
|
|
>>>
|
|
|
|
|
>>>
|
|
|
|
|
|
|
|
|
|
<Arguments>
|
2013-12-19 19:10:03 +00:00
|
|
|
None.
|
2013-10-22 18:02:01 +00:00
|
|
|
|
|
|
|
|
<Exceptions>
|
2013-12-19 19:10:03 +00:00
|
|
|
None.
|
2013-10-22 18:02:01 +00:00
|
|
|
|
|
|
|
|
<Side Effects>
|
2013-12-19 19:10:03 +00:00
|
|
|
A 'timestamp' role is added to 'tuf.roledb.py'.
|
2013-10-22 18:02:01 +00:00
|
|
|
|
|
|
|
|
<Returns>
|
2013-12-19 19:10:03 +00:00
|
|
|
None.
|
2013-10-22 18:02:01 +00:00
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
def __init__(self):
|
2013-10-29 19:23:26 +00:00
|
|
|
|
|
|
|
|
super(Timestamp, self).__init__()
|
|
|
|
|
|
2013-11-12 20:00:26 +00:00
|
|
|
self._rolename = 'timestamp'
|
|
|
|
|
|
2014-01-29 16:26:56 +00:00
|
|
|
# By default, 'snapshot' metadata is set to expire 1 week from the current
|
2013-12-19 19:10:03 +00:00
|
|
|
# time. The expiration may be modified.
|
2014-04-19 18:27:53 +00:00
|
|
|
expiration = \
|
|
|
|
|
tuf.formats.unix_timestamp_to_datetime(int(time.time() + TIMESTAMP_EXPIRATION))
|
|
|
|
|
expiration = expiration.isoformat() + 'Z'
|
2013-10-29 19:23:26 +00:00
|
|
|
|
2013-11-12 20:00:26 +00:00
|
|
|
roleinfo = {'keyids': [], 'signing_keyids': [], 'threshold': 1,
|
2013-12-11 19:14:16 +00:00
|
|
|
'signatures': [], 'version': 0, 'compressions': [''],
|
|
|
|
|
'expires': expiration, 'partial_loaded': False}
|
2013-11-12 20:00:26 +00:00
|
|
|
|
|
|
|
|
try:
|
2013-11-14 19:24:07 +00:00
|
|
|
tuf.roledb.add_role(self.rolename, roleinfo)
|
2014-02-13 15:03:25 +00:00
|
|
|
|
2016-01-28 20:22:20 +00:00
|
|
|
except tuf.RoleAlreadyExistsError:
|
2013-11-12 20:00:26 +00:00
|
|
|
pass
|
2013-10-22 18:02:01 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2014-01-29 16:26:56 +00:00
|
|
|
class Snapshot(Metadata):
|
2013-10-22 18:02:01 +00:00
|
|
|
"""
|
|
|
|
|
<Purpose>
|
2014-01-29 16:26:56 +00:00
|
|
|
Represent a Snapshot role object. The snapshot role is responsible for
|
2013-12-19 19:10:03 +00:00
|
|
|
referencing the other top-level roles (excluding Timestamp) and all
|
|
|
|
|
delegated roles.
|
|
|
|
|
|
2014-01-29 16:26:56 +00:00
|
|
|
This Snapshot object sub-classes Metadata, so the expected
|
2013-12-19 19:10:03 +00:00
|
|
|
Metadata operations like adding/removing public keys, signatures, private
|
|
|
|
|
keys, and updating metadata attributes (e.g., version and expiration) is
|
2014-01-29 16:26:56 +00:00
|
|
|
supported. Since Snapshot is a top-level role and must exist, a default
|
|
|
|
|
Snapshot object is instantiated when a new Repository object is created.
|
2013-10-22 18:02:01 +00:00
|
|
|
|
|
|
|
|
>>>
|
|
|
|
|
>>>
|
|
|
|
|
>>>
|
|
|
|
|
|
|
|
|
|
<Arguments>
|
2013-12-19 19:10:03 +00:00
|
|
|
None.
|
2013-10-22 18:02:01 +00:00
|
|
|
|
|
|
|
|
<Exceptions>
|
2013-12-19 19:10:03 +00:00
|
|
|
None.
|
2013-10-22 18:02:01 +00:00
|
|
|
|
|
|
|
|
<Side Effects>
|
2014-01-29 16:26:56 +00:00
|
|
|
A 'snapshot' role is added to 'tuf.roledb.py'.
|
2013-10-22 18:02:01 +00:00
|
|
|
|
|
|
|
|
<Returns>
|
2013-12-19 19:10:03 +00:00
|
|
|
None.
|
2013-10-22 18:02:01 +00:00
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
def __init__(self):
|
2013-10-29 19:23:26 +00:00
|
|
|
|
2014-01-29 16:26:56 +00:00
|
|
|
super(Snapshot, self).__init__()
|
2013-10-29 19:23:26 +00:00
|
|
|
|
2014-01-29 16:26:56 +00:00
|
|
|
self._rolename = 'snapshot'
|
2013-12-19 19:10:03 +00:00
|
|
|
|
2014-01-29 16:26:56 +00:00
|
|
|
# By default, 'snapshot' metadata is set to expire 1 week from the current
|
2013-12-19 19:10:03 +00:00
|
|
|
# time. The expiration may be modified.
|
2014-04-19 18:27:53 +00:00
|
|
|
expiration = \
|
|
|
|
|
tuf.formats.unix_timestamp_to_datetime(int(time.time() + SNAPSHOT_EXPIRATION))
|
|
|
|
|
expiration = expiration.isoformat() + 'Z'
|
|
|
|
|
|
2013-11-12 20:00:26 +00:00
|
|
|
roleinfo = {'keyids': [], 'signing_keyids': [], 'threshold': 1,
|
2013-12-11 19:14:16 +00:00
|
|
|
'signatures': [], 'version': 0, 'compressions': [''],
|
|
|
|
|
'expires': expiration, 'partial_loaded': False}
|
2013-11-12 20:00:26 +00:00
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
tuf.roledb.add_role(self._rolename, roleinfo)
|
2014-02-13 15:03:25 +00:00
|
|
|
|
2016-01-28 20:22:20 +00:00
|
|
|
except tuf.RoleAlreadyExistsError:
|
2013-11-12 20:00:26 +00:00
|
|
|
pass
|
2013-10-22 18:02:01 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Targets(Metadata):
|
|
|
|
|
"""
|
|
|
|
|
<Purpose>
|
2013-12-19 19:10:03 +00:00
|
|
|
Represent a Targets role object. Targets roles include the top-level role
|
2014-01-27 16:35:38 +00:00
|
|
|
'targets.json' and all delegated roles (e.g., 'targets/unclaimed/django').
|
2013-12-19 19:10:03 +00:00
|
|
|
The expected operations of Targets metadata is included, such as adding
|
|
|
|
|
and removing repository target files, making and revoking delegations, and
|
|
|
|
|
listing the target files provided by it.
|
|
|
|
|
|
|
|
|
|
Adding or removing a delegation causes the attributes of the Targets object
|
|
|
|
|
to be updated. That is, if the 'django' Targets object is delegated by
|
|
|
|
|
'targets/unclaimed', a new attribute is added so that the following
|
|
|
|
|
code statement is supported:
|
Address Issues #165, #158, and #147.
Issue 147: Finalize conversion of all written metadata behavior. This commit ensures that compressed and uncompressed metadata is also written as outlined in the issue.
Issue 158: As requested, updater.refresh() may now unsafely fetch (i.e., unknown file size and hash) Root metadata if valid top-level metadata cannot be downloaded successfully (e.g., top-level keys may have been revoked). The repository must also sign the new Root file (at least until all clients have updated) with any revoked keys so that clients may successfully update. After unsafely updating Root, the top-level metadata is updated again as normal (and only once to avoid an infinite loop). By default, refresh() unsafely updates Root if only invalid top-level metadata can be downloaded, although this behavior may be overriden by the caller if they wish. Changed default behavior: refresh(self, unsafely_update_root_if_necessary=True)
Issue 165: Delegated roles are no longer added as attributes of a Targets object by libtuf.py (e.g., repository.targets.delegated_role). The previous bahavior restricted rolenames to Python identifiers (i.e., can only include letters, numbers, the underscore character, and must start with a nonnumeric character). Now, delegated roles may be referenced as strings (e.g., repository.targets('recently-claimed')) and include characters other than '_'. In addition, methods have been added to return all the delegated rolesnames of a target (e.g., repository.targets.get_delegated_rolenames()) and the immediate delegated Target objects of a role. Previous behavior: repository.targets.unclaimed.django.version = 8
Current behavior: repository.targets('unclaimed')('django').version = 8.
2014-01-02 17:18:44 +00:00
|
|
|
repository.targets('unclaimed')('django').version = 2
|
2013-12-19 19:10:03 +00:00
|
|
|
|
|
|
|
|
Likewise, revoking a delegation causes removal of the delegation attribute.
|
|
|
|
|
|
|
|
|
|
This Targets object sub-classes Metadata, so the expected
|
|
|
|
|
Metadata operations like adding/removing public keys, signatures, private
|
|
|
|
|
keys, and updating metadata attributes (e.g., version and expiration) is
|
|
|
|
|
supported. Since Targets is a top-level role and must exist, a default
|
2014-01-27 16:35:38 +00:00
|
|
|
Targets object (for 'targets.json', not delegated roles) is instantiated when
|
2013-12-19 19:10:03 +00:00
|
|
|
a new Repository object is created.
|
2013-10-22 18:02:01 +00:00
|
|
|
|
|
|
|
|
>>>
|
|
|
|
|
>>>
|
|
|
|
|
>>>
|
|
|
|
|
|
|
|
|
|
<Arguments>
|
2013-11-05 13:22:21 +00:00
|
|
|
targets_directory:
|
|
|
|
|
The targets directory of the Repository object.
|
2013-10-22 18:02:01 +00:00
|
|
|
|
2013-12-18 16:49:28 +00:00
|
|
|
rolename:
|
|
|
|
|
The rolename of this Targets object.
|
|
|
|
|
|
|
|
|
|
roleinfo:
|
|
|
|
|
An already populated roleinfo object of 'rolename'. Conformant to
|
|
|
|
|
'tuf.formats.ROLEDB_SCHEMA'.
|
|
|
|
|
|
2013-10-22 18:02:01 +00:00
|
|
|
<Exceptions>
|
2013-12-18 16:49:28 +00:00
|
|
|
tuf.FormatError, if the arguments are improperly formatted.
|
2013-10-22 18:02:01 +00:00
|
|
|
|
|
|
|
|
<Side Effects>
|
2014-04-07 19:28:46 +00:00
|
|
|
Modifies the roleinfo of the targets role in 'tuf.roledb', or creates
|
|
|
|
|
a default one named 'targets'.
|
2013-11-05 13:22:21 +00:00
|
|
|
|
2013-10-22 18:02:01 +00:00
|
|
|
<Returns>
|
2013-11-05 13:22:21 +00:00
|
|
|
None.
|
2013-10-22 18:02:01 +00:00
|
|
|
"""
|
|
|
|
|
|
2014-04-07 19:28:46 +00:00
|
|
|
def __init__(self, targets_directory, rolename='targets', roleinfo=None):
|
2013-11-05 13:22:21 +00:00
|
|
|
|
|
|
|
|
# Do the arguments have the correct format?
|
2013-12-11 19:14:16 +00:00
|
|
|
# Ensure the arguments have the appropriate number of objects and object
|
|
|
|
|
# types, and that all dict keys are properly named.
|
2013-12-04 14:14:06 +00:00
|
|
|
# Raise 'tuf.FormatError' if any are improperly formatted.
|
2013-11-05 13:22:21 +00:00
|
|
|
tuf.formats.PATH_SCHEMA.check_match(targets_directory)
|
2013-11-12 20:00:26 +00:00
|
|
|
tuf.formats.ROLENAME_SCHEMA.check_match(rolename)
|
2013-12-04 14:14:06 +00:00
|
|
|
|
2013-11-12 20:00:26 +00:00
|
|
|
if roleinfo is not None:
|
|
|
|
|
tuf.formats.ROLEDB_SCHEMA.check_match(roleinfo)
|
|
|
|
|
|
2013-11-05 13:22:21 +00:00
|
|
|
super(Targets, self).__init__()
|
|
|
|
|
self._targets_directory = targets_directory
|
|
|
|
|
self._rolename = rolename
|
|
|
|
|
self._target_files = []
|
Address Issues #165, #158, and #147.
Issue 147: Finalize conversion of all written metadata behavior. This commit ensures that compressed and uncompressed metadata is also written as outlined in the issue.
Issue 158: As requested, updater.refresh() may now unsafely fetch (i.e., unknown file size and hash) Root metadata if valid top-level metadata cannot be downloaded successfully (e.g., top-level keys may have been revoked). The repository must also sign the new Root file (at least until all clients have updated) with any revoked keys so that clients may successfully update. After unsafely updating Root, the top-level metadata is updated again as normal (and only once to avoid an infinite loop). By default, refresh() unsafely updates Root if only invalid top-level metadata can be downloaded, although this behavior may be overriden by the caller if they wish. Changed default behavior: refresh(self, unsafely_update_root_if_necessary=True)
Issue 165: Delegated roles are no longer added as attributes of a Targets object by libtuf.py (e.g., repository.targets.delegated_role). The previous bahavior restricted rolenames to Python identifiers (i.e., can only include letters, numbers, the underscore character, and must start with a nonnumeric character). Now, delegated roles may be referenced as strings (e.g., repository.targets('recently-claimed')) and include characters other than '_'. In addition, methods have been added to return all the delegated rolesnames of a target (e.g., repository.targets.get_delegated_rolenames()) and the immediate delegated Target objects of a role. Previous behavior: repository.targets.unclaimed.django.version = 8
Current behavior: repository.targets('unclaimed')('django').version = 8.
2014-01-02 17:18:44 +00:00
|
|
|
self._delegated_roles = {}
|
2013-12-18 16:49:28 +00:00
|
|
|
|
|
|
|
|
# By default, Targets objects are set to expire 3 months from the current
|
|
|
|
|
# time. May be later modified.
|
2014-04-19 18:27:53 +00:00
|
|
|
expiration = \
|
|
|
|
|
tuf.formats.unix_timestamp_to_datetime(int(time.time() + TARGETS_EXPIRATION))
|
|
|
|
|
expiration = expiration.isoformat() + 'Z'
|
2013-11-12 20:00:26 +00:00
|
|
|
|
2013-12-18 16:49:28 +00:00
|
|
|
# If 'roleinfo' is not provided, set an initial default.
|
2013-11-12 20:00:26 +00:00
|
|
|
if roleinfo is None:
|
2013-12-11 19:14:16 +00:00
|
|
|
roleinfo = {'keyids': [], 'signing_keyids': [], 'threshold': 1,
|
|
|
|
|
'version': 0, 'compressions': [''], 'expires': expiration,
|
2014-06-23 17:33:01 +00:00
|
|
|
'signatures': [], 'paths': {}, 'path_hash_prefixes': [],
|
2013-12-11 19:14:16 +00:00
|
|
|
'partial_loaded': False, 'delegations': {'keys': {},
|
|
|
|
|
'roles': []}}
|
2013-12-18 16:49:28 +00:00
|
|
|
|
|
|
|
|
# Add the new role to the 'tuf.roledb'.
|
2013-11-12 20:00:26 +00:00
|
|
|
try:
|
2013-11-22 16:13:11 +00:00
|
|
|
tuf.roledb.add_role(self.rolename, roleinfo)
|
2014-02-13 15:03:25 +00:00
|
|
|
|
2016-01-28 20:22:20 +00:00
|
|
|
except tuf.RoleAlreadyExistsError:
|
2013-11-12 20:00:26 +00:00
|
|
|
pass
|
2013-11-05 13:22:21 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Address Issues #165, #158, and #147.
Issue 147: Finalize conversion of all written metadata behavior. This commit ensures that compressed and uncompressed metadata is also written as outlined in the issue.
Issue 158: As requested, updater.refresh() may now unsafely fetch (i.e., unknown file size and hash) Root metadata if valid top-level metadata cannot be downloaded successfully (e.g., top-level keys may have been revoked). The repository must also sign the new Root file (at least until all clients have updated) with any revoked keys so that clients may successfully update. After unsafely updating Root, the top-level metadata is updated again as normal (and only once to avoid an infinite loop). By default, refresh() unsafely updates Root if only invalid top-level metadata can be downloaded, although this behavior may be overriden by the caller if they wish. Changed default behavior: refresh(self, unsafely_update_root_if_necessary=True)
Issue 165: Delegated roles are no longer added as attributes of a Targets object by libtuf.py (e.g., repository.targets.delegated_role). The previous bahavior restricted rolenames to Python identifiers (i.e., can only include letters, numbers, the underscore character, and must start with a nonnumeric character). Now, delegated roles may be referenced as strings (e.g., repository.targets('recently-claimed')) and include characters other than '_'. In addition, methods have been added to return all the delegated rolesnames of a target (e.g., repository.targets.get_delegated_rolenames()) and the immediate delegated Target objects of a role. Previous behavior: repository.targets.unclaimed.django.version = 8
Current behavior: repository.targets('unclaimed')('django').version = 8.
2014-01-02 17:18:44 +00:00
|
|
|
def __call__(self, rolename):
|
|
|
|
|
"""
|
|
|
|
|
<Purpose>
|
|
|
|
|
Allow callable Targets object so that delegated roles may be referenced
|
|
|
|
|
by their string rolenames. Rolenames may include characters like '-' and
|
|
|
|
|
are not restricted to Python identifiers.
|
|
|
|
|
|
|
|
|
|
<Arguments>
|
|
|
|
|
rolename:
|
|
|
|
|
The rolename of the delegated role. 'rolename' must be a role
|
|
|
|
|
previously delegated by this Targets role.
|
|
|
|
|
|
|
|
|
|
<Exceptions>
|
|
|
|
|
tuf.FormatError, if the arguments are improperly formatted.
|
|
|
|
|
|
2014-05-01 16:59:34 +00:00
|
|
|
tuf.UnknownRoleError, if 'rolename' has not been delegated by this
|
|
|
|
|
Targets object.
|
|
|
|
|
|
Address Issues #165, #158, and #147.
Issue 147: Finalize conversion of all written metadata behavior. This commit ensures that compressed and uncompressed metadata is also written as outlined in the issue.
Issue 158: As requested, updater.refresh() may now unsafely fetch (i.e., unknown file size and hash) Root metadata if valid top-level metadata cannot be downloaded successfully (e.g., top-level keys may have been revoked). The repository must also sign the new Root file (at least until all clients have updated) with any revoked keys so that clients may successfully update. After unsafely updating Root, the top-level metadata is updated again as normal (and only once to avoid an infinite loop). By default, refresh() unsafely updates Root if only invalid top-level metadata can be downloaded, although this behavior may be overriden by the caller if they wish. Changed default behavior: refresh(self, unsafely_update_root_if_necessary=True)
Issue 165: Delegated roles are no longer added as attributes of a Targets object by libtuf.py (e.g., repository.targets.delegated_role). The previous bahavior restricted rolenames to Python identifiers (i.e., can only include letters, numbers, the underscore character, and must start with a nonnumeric character). Now, delegated roles may be referenced as strings (e.g., repository.targets('recently-claimed')) and include characters other than '_'. In addition, methods have been added to return all the delegated rolesnames of a target (e.g., repository.targets.get_delegated_rolenames()) and the immediate delegated Target objects of a role. Previous behavior: repository.targets.unclaimed.django.version = 8
Current behavior: repository.targets('unclaimed')('django').version = 8.
2014-01-02 17:18:44 +00:00
|
|
|
<Side Effects>
|
|
|
|
|
Modifies the roleinfo of the targets role in 'tuf.roledb'.
|
|
|
|
|
|
|
|
|
|
<Returns>
|
2014-05-01 16:59:34 +00:00
|
|
|
The Targets object of 'rolename'.
|
Address Issues #165, #158, and #147.
Issue 147: Finalize conversion of all written metadata behavior. This commit ensures that compressed and uncompressed metadata is also written as outlined in the issue.
Issue 158: As requested, updater.refresh() may now unsafely fetch (i.e., unknown file size and hash) Root metadata if valid top-level metadata cannot be downloaded successfully (e.g., top-level keys may have been revoked). The repository must also sign the new Root file (at least until all clients have updated) with any revoked keys so that clients may successfully update. After unsafely updating Root, the top-level metadata is updated again as normal (and only once to avoid an infinite loop). By default, refresh() unsafely updates Root if only invalid top-level metadata can be downloaded, although this behavior may be overriden by the caller if they wish. Changed default behavior: refresh(self, unsafely_update_root_if_necessary=True)
Issue 165: Delegated roles are no longer added as attributes of a Targets object by libtuf.py (e.g., repository.targets.delegated_role). The previous bahavior restricted rolenames to Python identifiers (i.e., can only include letters, numbers, the underscore character, and must start with a nonnumeric character). Now, delegated roles may be referenced as strings (e.g., repository.targets('recently-claimed')) and include characters other than '_'. In addition, methods have been added to return all the delegated rolesnames of a target (e.g., repository.targets.get_delegated_rolenames()) and the immediate delegated Target objects of a role. Previous behavior: repository.targets.unclaimed.django.version = 8
Current behavior: repository.targets('unclaimed')('django').version = 8.
2014-01-02 17:18:44 +00:00
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
# Do the arguments have the correct format?
|
|
|
|
|
# Ensure the arguments have the appropriate number of objects and object
|
|
|
|
|
# types, and that all dict keys are properly named.
|
|
|
|
|
# Raise 'tuf.FormatError' if any are improperly formatted.
|
|
|
|
|
tuf.formats.ROLENAME_SCHEMA.check_match(rolename)
|
|
|
|
|
|
|
|
|
|
if rolename in self._delegated_roles:
|
|
|
|
|
return self._delegated_roles[rolename]
|
2014-04-30 16:46:37 +00:00
|
|
|
|
Address Issues #165, #158, and #147.
Issue 147: Finalize conversion of all written metadata behavior. This commit ensures that compressed and uncompressed metadata is also written as outlined in the issue.
Issue 158: As requested, updater.refresh() may now unsafely fetch (i.e., unknown file size and hash) Root metadata if valid top-level metadata cannot be downloaded successfully (e.g., top-level keys may have been revoked). The repository must also sign the new Root file (at least until all clients have updated) with any revoked keys so that clients may successfully update. After unsafely updating Root, the top-level metadata is updated again as normal (and only once to avoid an infinite loop). By default, refresh() unsafely updates Root if only invalid top-level metadata can be downloaded, although this behavior may be overriden by the caller if they wish. Changed default behavior: refresh(self, unsafely_update_root_if_necessary=True)
Issue 165: Delegated roles are no longer added as attributes of a Targets object by libtuf.py (e.g., repository.targets.delegated_role). The previous bahavior restricted rolenames to Python identifiers (i.e., can only include letters, numbers, the underscore character, and must start with a nonnumeric character). Now, delegated roles may be referenced as strings (e.g., repository.targets('recently-claimed')) and include characters other than '_'. In addition, methods have been added to return all the delegated rolesnames of a target (e.g., repository.targets.get_delegated_rolenames()) and the immediate delegated Target objects of a role. Previous behavior: repository.targets.unclaimed.django.version = 8
Current behavior: repository.targets('unclaimed')('django').version = 8.
2014-01-02 17:18:44 +00:00
|
|
|
else:
|
2016-01-28 20:22:20 +00:00
|
|
|
raise tuf.UnknownRoleError(repr(rolename) + ' has not been delegated'
|
|
|
|
|
' by ' + repr(self.rolename))
|
Address Issues #165, #158, and #147.
Issue 147: Finalize conversion of all written metadata behavior. This commit ensures that compressed and uncompressed metadata is also written as outlined in the issue.
Issue 158: As requested, updater.refresh() may now unsafely fetch (i.e., unknown file size and hash) Root metadata if valid top-level metadata cannot be downloaded successfully (e.g., top-level keys may have been revoked). The repository must also sign the new Root file (at least until all clients have updated) with any revoked keys so that clients may successfully update. After unsafely updating Root, the top-level metadata is updated again as normal (and only once to avoid an infinite loop). By default, refresh() unsafely updates Root if only invalid top-level metadata can be downloaded, although this behavior may be overriden by the caller if they wish. Changed default behavior: refresh(self, unsafely_update_root_if_necessary=True)
Issue 165: Delegated roles are no longer added as attributes of a Targets object by libtuf.py (e.g., repository.targets.delegated_role). The previous bahavior restricted rolenames to Python identifiers (i.e., can only include letters, numbers, the underscore character, and must start with a nonnumeric character). Now, delegated roles may be referenced as strings (e.g., repository.targets('recently-claimed')) and include characters other than '_'. In addition, methods have been added to return all the delegated rolesnames of a target (e.g., repository.targets.get_delegated_rolenames()) and the immediate delegated Target objects of a role. Previous behavior: repository.targets.unclaimed.django.version = 8
Current behavior: repository.targets('unclaimed')('django').version = 8.
2014-01-02 17:18:44 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2013-11-05 13:22:21 +00:00
|
|
|
@property
|
|
|
|
|
def target_files(self):
|
|
|
|
|
"""
|
|
|
|
|
<Purpose>
|
2013-12-18 16:49:28 +00:00
|
|
|
A getter method that returns the target files added thus far to this
|
|
|
|
|
Targets object.
|
2013-11-05 13:22:21 +00:00
|
|
|
|
|
|
|
|
>>>
|
|
|
|
|
>>>
|
|
|
|
|
>>>
|
|
|
|
|
|
|
|
|
|
<Arguments>
|
2013-12-18 16:49:28 +00:00
|
|
|
None.
|
2013-11-05 13:22:21 +00:00
|
|
|
|
|
|
|
|
<Exceptions>
|
2013-12-18 16:49:28 +00:00
|
|
|
None.
|
2013-11-05 13:22:21 +00:00
|
|
|
|
|
|
|
|
<Side Effects>
|
2013-12-18 16:49:28 +00:00
|
|
|
None.
|
2013-11-05 13:22:21 +00:00
|
|
|
|
|
|
|
|
<Returns>
|
|
|
|
|
None.
|
|
|
|
|
"""
|
|
|
|
|
|
2013-11-12 20:00:26 +00:00
|
|
|
target_files = tuf.roledb.get_roleinfo(self._rolename)['paths']
|
|
|
|
|
|
|
|
|
|
return target_files
|
2013-10-22 18:02:01 +00:00
|
|
|
|
|
|
|
|
|
2013-10-29 19:23:26 +00:00
|
|
|
|
2014-01-23 17:03:31 +00:00
|
|
|
def add_restricted_paths(self, list_of_directory_paths, child_rolename):
|
2014-01-22 17:52:55 +00:00
|
|
|
"""
|
|
|
|
|
<Purpose>
|
2014-01-23 17:03:31 +00:00
|
|
|
Add 'list_of_directory_paths' to the restricted paths of 'child_rolename'.
|
|
|
|
|
The updater client verifies the target paths specified by child roles, and
|
|
|
|
|
searches for targets by visiting these restricted paths. A child role may
|
|
|
|
|
only provide targets specifically listed in the delegations field of the
|
|
|
|
|
parent, or a target that falls under a restricted path.
|
2014-01-22 17:52:55 +00:00
|
|
|
|
|
|
|
|
>>>
|
|
|
|
|
>>>
|
|
|
|
|
>>>
|
|
|
|
|
|
|
|
|
|
<Arguments>
|
|
|
|
|
list_of_directory_paths:
|
2014-01-23 17:03:31 +00:00
|
|
|
A list of directory paths 'child_rolename' should also be restricted to.
|
|
|
|
|
|
|
|
|
|
child_rolename:
|
|
|
|
|
The child delegation that requires an update to its restricted paths,
|
2014-04-10 15:34:40 +00:00
|
|
|
as listed in the parent role's delegations (e.g., 'Django' in
|
|
|
|
|
'targets/unclaimed/Django').
|
2014-01-22 17:52:55 +00:00
|
|
|
|
|
|
|
|
<Exceptions>
|
|
|
|
|
tuf.Error, if a directory path in 'list_of_directory_paths' is not a
|
2014-01-23 17:03:31 +00:00
|
|
|
directory, or not under the repository's targets directory. If
|
|
|
|
|
'child_rolename' has not been delegated yet.
|
2014-01-22 17:52:55 +00:00
|
|
|
|
|
|
|
|
<Side Effects>
|
2014-01-23 17:03:31 +00:00
|
|
|
Modifies this Targets' delegations field.
|
2014-01-22 17:52:55 +00:00
|
|
|
|
|
|
|
|
<Returns>
|
|
|
|
|
None.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
# Does 'filepath' have the correct format?
|
|
|
|
|
# Ensure the arguments have the appropriate number of objects and object
|
|
|
|
|
# types, and that all dict keys are properly named.
|
|
|
|
|
# Raise 'tuf.FormatError' if there is a mismatch.
|
|
|
|
|
tuf.formats.PATHS_SCHEMA.check_match(list_of_directory_paths)
|
2014-01-23 17:03:31 +00:00
|
|
|
tuf.formats.ROLENAME_SCHEMA.check_match(child_rolename)
|
2014-01-22 17:52:55 +00:00
|
|
|
|
2014-01-23 17:03:31 +00:00
|
|
|
# A list of verified paths to be added to the child role's entry in the
|
|
|
|
|
# parent's delegations.
|
2014-01-22 17:52:55 +00:00
|
|
|
directory_paths = []
|
2014-01-23 17:03:31 +00:00
|
|
|
|
|
|
|
|
# Ensure the 'child_rolename' has been delegated, otherwise it will not
|
|
|
|
|
# have an entry in the parent role's delegations field.
|
|
|
|
|
full_child_rolename = self._rolename + '/' + child_rolename
|
|
|
|
|
if not tuf.roledb.role_exists(full_child_rolename):
|
2014-06-23 00:01:28 +00:00
|
|
|
raise tuf.Error(repr(full_child_rolename) + ' has not been delegated.')
|
2014-01-22 17:52:55 +00:00
|
|
|
|
2014-01-23 17:03:31 +00:00
|
|
|
# Are the paths in 'list_of_directory_paths' valid?
|
2014-01-22 17:52:55 +00:00
|
|
|
for directory_path in list_of_directory_paths:
|
|
|
|
|
directory_path = os.path.abspath(directory_path)
|
|
|
|
|
if not os.path.isdir(directory_path):
|
2016-01-28 20:22:20 +00:00
|
|
|
raise tuf.Error(repr(directory_path) + ' is not a directory.')
|
2014-01-22 17:52:55 +00:00
|
|
|
|
2014-01-23 17:03:31 +00:00
|
|
|
# Are the paths in the repository's targets directory? Append a trailing
|
|
|
|
|
# path separator with os.path.join(path, '').
|
2014-01-22 17:52:55 +00:00
|
|
|
targets_directory = os.path.join(self._targets_directory, '')
|
|
|
|
|
directory_path = os.path.join(directory_path, '')
|
|
|
|
|
if not directory_path.startswith(targets_directory):
|
2016-01-28 20:22:20 +00:00
|
|
|
raise tuf.Error(repr(directory_path) + ' is not under the'
|
|
|
|
|
' Repository\'s targets directory: ' + repr(self._targets_directory))
|
2014-01-22 17:52:55 +00:00
|
|
|
|
2014-01-25 21:40:53 +00:00
|
|
|
directory_paths.append(directory_path[len(self._targets_directory):])
|
2014-01-22 17:52:55 +00:00
|
|
|
|
2014-01-23 17:03:31 +00:00
|
|
|
# Get the current role's roleinfo, so that its delegations field can be
|
|
|
|
|
# updated.
|
2014-01-22 17:52:55 +00:00
|
|
|
roleinfo = tuf.roledb.get_roleinfo(self._rolename)
|
2014-01-23 17:03:31 +00:00
|
|
|
|
|
|
|
|
# Update the restricted paths of 'child_rolename'.
|
|
|
|
|
for role in roleinfo['delegations']['roles']:
|
|
|
|
|
if role['name'] == full_child_rolename:
|
|
|
|
|
restricted_paths = role['paths']
|
|
|
|
|
|
|
|
|
|
for directory_path in directory_paths:
|
|
|
|
|
if directory_path not in restricted_paths:
|
|
|
|
|
restricted_paths.append(directory_path)
|
|
|
|
|
|
2014-01-22 17:52:55 +00:00
|
|
|
tuf.roledb.update_roleinfo(self._rolename, roleinfo)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2014-06-23 17:33:01 +00:00
|
|
|
def add_target(self, filepath, custom=None):
|
2013-10-22 18:02:01 +00:00
|
|
|
"""
|
|
|
|
|
<Purpose>
|
2013-12-18 16:49:28 +00:00
|
|
|
Add a filepath (must be under the repository's targets directory) to the
|
|
|
|
|
Targets object.
|
2013-11-05 13:22:21 +00:00
|
|
|
|
2014-01-27 15:55:14 +00:00
|
|
|
This method does not actually create 'filepath' on the file system.
|
|
|
|
|
'filepath' must already exist on the file system.
|
2013-10-22 18:02:01 +00:00
|
|
|
|
|
|
|
|
>>>
|
|
|
|
|
>>>
|
|
|
|
|
>>>
|
|
|
|
|
|
|
|
|
|
<Arguments>
|
|
|
|
|
filepath:
|
2013-12-18 16:49:28 +00:00
|
|
|
The path of the target file. It must be located in the repository's
|
|
|
|
|
targets directory.
|
2013-10-22 18:02:01 +00:00
|
|
|
|
2014-06-24 12:58:29 +00:00
|
|
|
custom:
|
|
|
|
|
An optional object providing additional information about the file.
|
|
|
|
|
|
2013-10-22 18:02:01 +00:00
|
|
|
<Exceptions>
|
2013-11-05 13:22:21 +00:00
|
|
|
tuf.FormatError, if 'filepath' is improperly formatted.
|
2013-10-22 18:02:01 +00:00
|
|
|
|
2013-12-18 16:49:28 +00:00
|
|
|
tuf.Error, if 'filepath' is not found under the repository's targets
|
|
|
|
|
directory.
|
|
|
|
|
|
2013-10-22 18:02:01 +00:00
|
|
|
<Side Effects>
|
2013-11-05 13:22:21 +00:00
|
|
|
Adds 'filepath' to this role's list of targets. This role's
|
|
|
|
|
'tuf.roledb.py' is also updated.
|
2013-10-22 18:02:01 +00:00
|
|
|
|
|
|
|
|
<Returns>
|
2013-11-05 13:22:21 +00:00
|
|
|
None.
|
2013-10-22 18:02:01 +00:00
|
|
|
"""
|
2013-11-05 13:22:21 +00:00
|
|
|
|
|
|
|
|
# Does 'filepath' have the correct format?
|
2013-12-18 16:49:28 +00:00
|
|
|
# Ensure the arguments have the appropriate number of objects and object
|
|
|
|
|
# types, and that all dict keys are properly named.
|
2013-11-05 13:22:21 +00:00
|
|
|
# Raise 'tuf.FormatError' if there is a mismatch.
|
|
|
|
|
tuf.formats.PATH_SCHEMA.check_match(filepath)
|
2014-06-23 17:33:01 +00:00
|
|
|
if custom is None:
|
|
|
|
|
custom = {}
|
|
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
tuf.formats.CUSTOM_SCHEMA.check_match(custom)
|
2013-11-05 13:22:21 +00:00
|
|
|
|
|
|
|
|
filepath = os.path.abspath(filepath)
|
2013-12-18 16:49:28 +00:00
|
|
|
|
|
|
|
|
# Ensure 'filepath' is found under the repository's targets directory.
|
2014-01-27 15:55:14 +00:00
|
|
|
if not filepath.startswith(self._targets_directory):
|
2016-01-28 20:22:20 +00:00
|
|
|
raise tuf.Error(repr(filepath) + ' is not under the Repository\'s'
|
|
|
|
|
' targets directory: ' + repr(self._targets_directory))
|
2013-11-05 13:22:21 +00:00
|
|
|
|
|
|
|
|
# Add 'filepath' (i.e., relative to the targets directory) to the role's
|
2014-01-27 15:55:14 +00:00
|
|
|
# list of targets. 'filepath' will be verified as an allowed path according
|
|
|
|
|
# to this Targets parent role when write() is called. Not verifying
|
|
|
|
|
# 'filepath' here allows freedom to add targets and parent restrictions
|
|
|
|
|
# in any order, and minimize the number of times these checks are performed.
|
2013-11-05 13:22:21 +00:00
|
|
|
if os.path.isfile(filepath):
|
|
|
|
|
|
2013-12-18 16:49:28 +00:00
|
|
|
# Update the role's 'tuf.roledb.py' entry and avoid duplicates.
|
2013-11-05 13:22:21 +00:00
|
|
|
targets_directory_length = len(self._targets_directory)
|
|
|
|
|
roleinfo = tuf.roledb.get_roleinfo(self._rolename)
|
2014-01-25 21:40:53 +00:00
|
|
|
relative_path = filepath[targets_directory_length:]
|
2013-11-15 01:36:28 +00:00
|
|
|
if relative_path not in roleinfo['paths']:
|
2014-06-23 17:33:01 +00:00
|
|
|
roleinfo['paths'].update({relative_path: custom})
|
2013-11-05 13:22:21 +00:00
|
|
|
tuf.roledb.update_roleinfo(self._rolename, roleinfo)
|
|
|
|
|
|
|
|
|
|
else:
|
2016-01-28 20:22:20 +00:00
|
|
|
raise tuf.Error(repr(filepath) + ' is not a valid file.')
|
2013-11-05 13:22:21 +00:00
|
|
|
|
|
|
|
|
|
2013-10-22 18:02:01 +00:00
|
|
|
|
2013-11-05 13:22:21 +00:00
|
|
|
def add_targets(self, list_of_targets):
|
|
|
|
|
"""
|
|
|
|
|
<Purpose>
|
|
|
|
|
Add a list of target filepaths (all relative to 'self.targets_directory').
|
2013-12-20 17:47:27 +00:00
|
|
|
This method does not actually create files on the file system. The
|
2013-11-05 13:22:21 +00:00
|
|
|
list of target must already exist.
|
|
|
|
|
|
|
|
|
|
>>>
|
|
|
|
|
>>>
|
|
|
|
|
>>>
|
|
|
|
|
|
|
|
|
|
<Arguments>
|
|
|
|
|
list_of_targets:
|
2013-12-18 16:49:28 +00:00
|
|
|
A list of target filepaths that are added to the paths of this Targets
|
|
|
|
|
object.
|
2013-11-05 13:22:21 +00:00
|
|
|
|
|
|
|
|
<Exceptions>
|
2013-12-18 16:49:28 +00:00
|
|
|
tuf.FormatError, if the arguments are improperly formatted.
|
|
|
|
|
|
|
|
|
|
tuf.Error, if any of the paths listed in 'list_of_targets' is not found
|
|
|
|
|
under the repository's targets directory or is invalid.
|
2013-11-05 13:22:21 +00:00
|
|
|
|
|
|
|
|
<Side Effects>
|
2013-12-18 16:49:28 +00:00
|
|
|
This Targets' roleinfo is updated with the paths in 'list_of_targets'.
|
|
|
|
|
|
2013-11-05 13:22:21 +00:00
|
|
|
<Returns>
|
|
|
|
|
None.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
# Does 'list_of_targets' have the correct format?
|
2013-12-18 16:49:28 +00:00
|
|
|
# Ensure the arguments have the appropriate number of objects and object
|
|
|
|
|
# types, and that all dict keys are properly named.
|
|
|
|
|
# Raise 'tuf.FormatError' if there is a mismatch.
|
2013-11-05 13:22:21 +00:00
|
|
|
tuf.formats.RELPATHS_SCHEMA.check_match(list_of_targets)
|
|
|
|
|
|
2013-11-12 20:00:26 +00:00
|
|
|
# Update the tuf.roledb entry.
|
2013-11-05 13:22:21 +00:00
|
|
|
targets_directory_length = len(self._targets_directory)
|
|
|
|
|
relative_list_of_targets = []
|
2013-12-18 16:49:28 +00:00
|
|
|
|
|
|
|
|
# Ensure the paths in 'list_of_targets' are valid and fall under the
|
2014-01-27 15:55:14 +00:00
|
|
|
# repository's targets directory. The paths of 'list_of_targets' will be
|
|
|
|
|
# verified as allowed paths according to this Targets parent role when
|
|
|
|
|
# write() is called. Not verifying filepaths here allows the freedom to add
|
|
|
|
|
# targets and parent restrictions in any order, and minimize the number of
|
|
|
|
|
# times these checks are performed.
|
2013-11-05 13:22:21 +00:00
|
|
|
for target in list_of_targets:
|
2013-11-12 20:00:26 +00:00
|
|
|
filepath = os.path.abspath(target)
|
2015-04-02 20:38:18 +00:00
|
|
|
|
2014-01-25 21:40:53 +00:00
|
|
|
if not filepath.startswith(self._targets_directory+os.sep):
|
2016-01-28 20:22:20 +00:00
|
|
|
raise tuf.Error(repr(filepath) + ' is not under the Repository\'s'
|
|
|
|
|
' targets directory: ' + repr(self._targets_directory))
|
2013-12-18 16:49:28 +00:00
|
|
|
|
2013-11-05 13:22:21 +00:00
|
|
|
if os.path.isfile(filepath):
|
2014-01-25 21:40:53 +00:00
|
|
|
relative_list_of_targets.append(filepath[targets_directory_length:])
|
2014-04-30 16:46:37 +00:00
|
|
|
|
2013-11-05 13:22:21 +00:00
|
|
|
else:
|
2016-01-28 20:22:20 +00:00
|
|
|
raise tuf.Error(repr(filepath) + ' is not a valid file.')
|
2013-11-05 13:22:21 +00:00
|
|
|
|
2013-12-18 16:49:28 +00:00
|
|
|
# Update this Targets 'tuf.roledb.py' entry.
|
2013-11-05 13:22:21 +00:00
|
|
|
roleinfo = tuf.roledb.get_roleinfo(self._rolename)
|
2013-11-25 01:23:12 +00:00
|
|
|
for relative_target in relative_list_of_targets:
|
|
|
|
|
if relative_target not in roleinfo['paths']:
|
2014-06-23 17:33:01 +00:00
|
|
|
roleinfo['paths'].update({relative_target: {}})
|
2015-04-02 20:38:18 +00:00
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
continue
|
2013-12-18 16:49:28 +00:00
|
|
|
|
2013-11-15 01:36:28 +00:00
|
|
|
tuf.roledb.update_roleinfo(self.rolename, roleinfo)
|
2013-10-22 18:02:01 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def remove_target(self, filepath):
|
|
|
|
|
"""
|
|
|
|
|
<Purpose>
|
2013-12-18 16:49:28 +00:00
|
|
|
Remove the target 'filepath' from this Targets' 'paths' field. 'filepath'
|
|
|
|
|
is relative to the targets directory.
|
2013-10-22 18:02:01 +00:00
|
|
|
|
|
|
|
|
>>>
|
|
|
|
|
>>>
|
|
|
|
|
>>>
|
|
|
|
|
|
|
|
|
|
<Arguments>
|
|
|
|
|
filepath:
|
2013-12-18 16:49:28 +00:00
|
|
|
The target to remove from this Targets object, relative to the
|
|
|
|
|
repository's targets directory.
|
2013-10-22 18:02:01 +00:00
|
|
|
|
|
|
|
|
<Exceptions>
|
2013-11-05 13:22:21 +00:00
|
|
|
tuf.FormatError, if 'filepath' is improperly formatted.
|
2013-10-22 18:02:01 +00:00
|
|
|
|
2014-02-21 17:16:56 +00:00
|
|
|
tuf.Error, if 'filepath' is not under the repository's targets directory,
|
|
|
|
|
or not found.
|
2013-11-12 20:00:26 +00:00
|
|
|
|
2013-10-22 18:02:01 +00:00
|
|
|
<Side Effects>
|
2013-12-18 16:49:28 +00:00
|
|
|
Modifies this Targets 'tuf.roledb.py' entry.
|
2013-11-12 20:00:26 +00:00
|
|
|
|
2013-10-22 18:02:01 +00:00
|
|
|
<Returns>
|
2013-11-05 13:22:21 +00:00
|
|
|
None.
|
2013-10-22 18:02:01 +00:00
|
|
|
"""
|
2013-11-12 20:00:26 +00:00
|
|
|
|
|
|
|
|
# Does 'filepath' have the correct format?
|
2013-12-18 16:49:28 +00:00
|
|
|
# Ensure the arguments have the appropriate number of objects and object
|
|
|
|
|
# types, and that all dict keys are properly named.
|
2013-11-12 20:00:26 +00:00
|
|
|
# Raise 'tuf.FormatError' if there is a mismatch.
|
|
|
|
|
tuf.formats.RELPATH_SCHEMA.check_match(filepath)
|
|
|
|
|
|
|
|
|
|
filepath = os.path.abspath(filepath)
|
|
|
|
|
targets_directory_length = len(self._targets_directory)
|
|
|
|
|
|
2013-12-18 16:49:28 +00:00
|
|
|
# Ensure 'filepath' is under the repository targets directory.
|
2014-01-25 21:40:53 +00:00
|
|
|
if not filepath.startswith(self._targets_directory+os.sep):
|
2016-01-28 20:22:20 +00:00
|
|
|
raise tuf.Error(repr(filepath) + ' is not under the Repository\'s'
|
|
|
|
|
' targets directory: ' + repr(self._targets_directory))
|
2013-11-12 20:00:26 +00:00
|
|
|
|
2013-12-18 16:49:28 +00:00
|
|
|
# The relative filepath is listed in 'paths'.
|
2014-01-25 21:40:53 +00:00
|
|
|
relative_filepath = filepath[targets_directory_length:]
|
2013-12-18 16:49:28 +00:00
|
|
|
|
|
|
|
|
# Remove 'relative_filepath', if found, and update this Targets roleinfo.
|
2013-11-12 20:00:26 +00:00
|
|
|
fileinfo = tuf.roledb.get_roleinfo(self.rolename)
|
|
|
|
|
if relative_filepath in fileinfo['paths']:
|
2014-06-23 17:33:01 +00:00
|
|
|
del fileinfo['paths'][relative_filepath]
|
2014-02-21 17:16:56 +00:00
|
|
|
tuf.roledb.update_roleinfo(self.rolename, fileinfo)
|
|
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
raise tuf.Error('Target file path not found.')
|
2013-11-25 02:12:14 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def clear_targets(self):
|
|
|
|
|
"""
|
|
|
|
|
<Purpose>
|
2013-12-18 16:49:28 +00:00
|
|
|
Remove all the target filepaths in the "paths" field of this Targets.
|
2013-11-25 02:12:14 +00:00
|
|
|
|
|
|
|
|
>>>
|
|
|
|
|
>>>
|
|
|
|
|
>>>
|
|
|
|
|
|
|
|
|
|
<Arguments>
|
|
|
|
|
None
|
|
|
|
|
|
|
|
|
|
<Exceptions>
|
|
|
|
|
None.
|
|
|
|
|
|
|
|
|
|
<Side Effects>
|
2013-12-18 16:49:28 +00:00
|
|
|
Modifies this Targets' 'tuf.roledb.py' entry.
|
2013-11-25 02:12:14 +00:00
|
|
|
|
|
|
|
|
<Returns>
|
|
|
|
|
None.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
roleinfo = tuf.roledb.get_roleinfo(self.rolename)
|
2014-06-23 17:33:01 +00:00
|
|
|
roleinfo['paths'] = {}
|
2013-11-25 02:12:14 +00:00
|
|
|
|
|
|
|
|
tuf.roledb.update_roleinfo(self.rolename, roleinfo)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Address Issues #165, #158, and #147.
Issue 147: Finalize conversion of all written metadata behavior. This commit ensures that compressed and uncompressed metadata is also written as outlined in the issue.
Issue 158: As requested, updater.refresh() may now unsafely fetch (i.e., unknown file size and hash) Root metadata if valid top-level metadata cannot be downloaded successfully (e.g., top-level keys may have been revoked). The repository must also sign the new Root file (at least until all clients have updated) with any revoked keys so that clients may successfully update. After unsafely updating Root, the top-level metadata is updated again as normal (and only once to avoid an infinite loop). By default, refresh() unsafely updates Root if only invalid top-level metadata can be downloaded, although this behavior may be overriden by the caller if they wish. Changed default behavior: refresh(self, unsafely_update_root_if_necessary=True)
Issue 165: Delegated roles are no longer added as attributes of a Targets object by libtuf.py (e.g., repository.targets.delegated_role). The previous bahavior restricted rolenames to Python identifiers (i.e., can only include letters, numbers, the underscore character, and must start with a nonnumeric character). Now, delegated roles may be referenced as strings (e.g., repository.targets('recently-claimed')) and include characters other than '_'. In addition, methods have been added to return all the delegated rolesnames of a target (e.g., repository.targets.get_delegated_rolenames()) and the immediate delegated Target objects of a role. Previous behavior: repository.targets.unclaimed.django.version = 8
Current behavior: repository.targets('unclaimed')('django').version = 8.
2014-01-02 17:18:44 +00:00
|
|
|
def get_delegated_rolenames(self):
|
|
|
|
|
"""
|
|
|
|
|
<Purpose>
|
|
|
|
|
Return all delegations of a role, including any made by child delegations.
|
|
|
|
|
If ['a/b/', 'a/b/c/', 'a/b/c/d'] have been delegated,
|
|
|
|
|
repository.a.get_delegated_rolenames() returns:
|
|
|
|
|
['a/b', 'a/b/c', 'a/b/c/d'].
|
|
|
|
|
|
|
|
|
|
<Arguments>
|
|
|
|
|
None.
|
|
|
|
|
|
|
|
|
|
<Exceptions>
|
|
|
|
|
None.
|
|
|
|
|
|
|
|
|
|
<Side Effects>
|
|
|
|
|
None.
|
|
|
|
|
|
|
|
|
|
<Returns>
|
|
|
|
|
A list of rolenames.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
return tuf.roledb.get_delegated_rolenames(self.rolename)
|
|
|
|
|
|
|
|
|
|
|
2014-06-08 00:29:18 +00:00
|
|
|
def delegate(self, rolename, public_keys, list_of_targets, threshold=1,
|
|
|
|
|
backtrack=True, restricted_paths=None, path_hash_prefixes=None):
|
2013-10-22 18:02:01 +00:00
|
|
|
"""
|
|
|
|
|
<Purpose>
|
2013-12-18 16:49:28 +00:00
|
|
|
Create a new delegation, where 'rolename' is a child delegation of this
|
|
|
|
|
Targets object. The keys and roles database is updated, including the
|
|
|
|
|
delegations field of this Targets. The delegation of 'rolename' is added
|
Address Issues #165, #158, and #147.
Issue 147: Finalize conversion of all written metadata behavior. This commit ensures that compressed and uncompressed metadata is also written as outlined in the issue.
Issue 158: As requested, updater.refresh() may now unsafely fetch (i.e., unknown file size and hash) Root metadata if valid top-level metadata cannot be downloaded successfully (e.g., top-level keys may have been revoked). The repository must also sign the new Root file (at least until all clients have updated) with any revoked keys so that clients may successfully update. After unsafely updating Root, the top-level metadata is updated again as normal (and only once to avoid an infinite loop). By default, refresh() unsafely updates Root if only invalid top-level metadata can be downloaded, although this behavior may be overriden by the caller if they wish. Changed default behavior: refresh(self, unsafely_update_root_if_necessary=True)
Issue 165: Delegated roles are no longer added as attributes of a Targets object by libtuf.py (e.g., repository.targets.delegated_role). The previous bahavior restricted rolenames to Python identifiers (i.e., can only include letters, numbers, the underscore character, and must start with a nonnumeric character). Now, delegated roles may be referenced as strings (e.g., repository.targets('recently-claimed')) and include characters other than '_'. In addition, methods have been added to return all the delegated rolesnames of a target (e.g., repository.targets.get_delegated_rolenames()) and the immediate delegated Target objects of a role. Previous behavior: repository.targets.unclaimed.django.version = 8
Current behavior: repository.targets('unclaimed')('django').version = 8.
2014-01-02 17:18:44 +00:00
|
|
|
and accessible (e.g., 'repository.targets(rolename).
|
2013-12-18 16:49:28 +00:00
|
|
|
|
|
|
|
|
Actual metadata files are not updated, only when repository.write() or
|
|
|
|
|
repository.write_partial() is called.
|
2013-10-22 18:02:01 +00:00
|
|
|
|
|
|
|
|
>>>
|
|
|
|
|
>>>
|
|
|
|
|
>>>
|
|
|
|
|
|
|
|
|
|
<Arguments>
|
|
|
|
|
rolename:
|
2013-12-18 16:49:28 +00:00
|
|
|
The name of the delegated role, as in 'django' (i.e., not the full
|
|
|
|
|
rolename).
|
2013-10-22 18:02:01 +00:00
|
|
|
|
|
|
|
|
public_keys:
|
2014-03-05 16:38:23 +00:00
|
|
|
A list of TUF key objects in 'ANYKEYLIST_SCHEMA' format. The list
|
2013-12-18 16:49:28 +00:00
|
|
|
may contain any of the supported key types: RSAKEY_SCHEMA,
|
|
|
|
|
ED25519KEY_SCHEMA, etc.
|
2013-10-22 18:02:01 +00:00
|
|
|
|
2013-11-05 13:22:21 +00:00
|
|
|
list_of_targets:
|
2013-12-18 16:49:28 +00:00
|
|
|
A list of target filepaths that are added to the paths of 'rolename'.
|
2014-01-22 17:52:55 +00:00
|
|
|
'list_of_targets' is a list of target filepaths, and can be empty.
|
2013-11-05 13:22:21 +00:00
|
|
|
|
2013-12-18 16:49:28 +00:00
|
|
|
threshold:
|
|
|
|
|
The threshold number of keys of 'rolename'.
|
2014-06-08 00:29:18 +00:00
|
|
|
|
|
|
|
|
backtrack:
|
|
|
|
|
Boolean that indicates whether this role allows the updater client
|
|
|
|
|
to continue searching for targets (target files it is trusted to list
|
|
|
|
|
but has not yet specified) in other delegations. If 'backtrack' is
|
|
|
|
|
False and 'updater.target()' does not find 'example_target.tar.gz' in
|
|
|
|
|
this role, a 'tuf.UnknownTargetError' exception should be raised. If
|
|
|
|
|
'backtrack' is True (default), and 'target/other_role' is also trusted
|
|
|
|
|
with 'example_target.tar.gz' and has listed it, updater.target()
|
|
|
|
|
should backtrack and return the target file specified by
|
|
|
|
|
'target/other_role'.
|
2013-11-12 20:00:26 +00:00
|
|
|
|
2013-11-05 13:22:21 +00:00
|
|
|
restricted_paths:
|
2013-12-18 16:49:28 +00:00
|
|
|
A list of restricted directory or file paths of 'rolename'. Any target
|
|
|
|
|
files added to 'rolename' must fall under one of the restricted paths.
|
|
|
|
|
|
|
|
|
|
path_hash_prefixes:
|
2014-01-22 17:52:55 +00:00
|
|
|
A list of hash prefixes in 'tuf.formats.PATH_HASH_PREFIXES_SCHEMA'
|
|
|
|
|
format, used in hashed bin delegations. Targets may be located and
|
|
|
|
|
stored in hashed bins by calculating the target path's hash prefix.
|
2013-10-22 18:02:01 +00:00
|
|
|
|
|
|
|
|
<Exceptions>
|
2013-11-05 13:22:21 +00:00
|
|
|
tuf.FormatError, if any of the arguments are improperly formatted.
|
2013-10-22 18:02:01 +00:00
|
|
|
|
2013-12-18 16:49:28 +00:00
|
|
|
tuf.Error, if the delegated role already exists or if any of the arguments
|
|
|
|
|
is an invalid path (i.e., not under the repository's targets directory).
|
2013-11-25 01:23:12 +00:00
|
|
|
|
2013-10-22 18:02:01 +00:00
|
|
|
<Side Effects>
|
2013-11-05 13:22:21 +00:00
|
|
|
A new Target object is created for 'rolename' that is accessible to the
|
|
|
|
|
caller (i.e., targets.unclaimed.<rolename>). The 'tuf.keydb.py' and
|
|
|
|
|
'tuf.roledb.py' stores are updated with 'public_keys'.
|
2013-10-22 18:02:01 +00:00
|
|
|
|
|
|
|
|
<Returns>
|
2013-11-05 13:22:21 +00:00
|
|
|
None.
|
2013-10-22 18:02:01 +00:00
|
|
|
"""
|
2013-11-05 13:22:21 +00:00
|
|
|
|
|
|
|
|
# Do the arguments have the correct format?
|
2013-12-18 16:49:28 +00:00
|
|
|
# Ensure the arguments have the appropriate number of objects and object
|
|
|
|
|
# types, and that all dict keys are properly named.
|
|
|
|
|
# Raise 'tuf.FormatError' if there is a mismatch.
|
2013-11-05 13:22:21 +00:00
|
|
|
tuf.formats.ROLENAME_SCHEMA.check_match(rolename)
|
|
|
|
|
tuf.formats.ANYKEYLIST_SCHEMA.check_match(public_keys)
|
|
|
|
|
tuf.formats.RELPATHS_SCHEMA.check_match(list_of_targets)
|
2013-11-12 20:00:26 +00:00
|
|
|
tuf.formats.THRESHOLD_SCHEMA.check_match(threshold)
|
2014-06-08 00:29:18 +00:00
|
|
|
tuf.formats.BOOLEAN_SCHEMA.check_match(backtrack)
|
|
|
|
|
|
2013-11-12 20:00:26 +00:00
|
|
|
if restricted_paths is not None:
|
|
|
|
|
tuf.formats.RELPATHS_SCHEMA.check_match(restricted_paths)
|
2014-06-08 00:29:18 +00:00
|
|
|
|
2013-11-14 19:24:07 +00:00
|
|
|
if path_hash_prefixes is not None:
|
|
|
|
|
tuf.formats.PATH_HASH_PREFIXES_SCHEMA.check_match(path_hash_prefixes)
|
2014-06-08 00:29:18 +00:00
|
|
|
|
|
|
|
|
|
2013-12-18 16:49:28 +00:00
|
|
|
# Check if 'rolename' is not already a delegation. 'tuf.roledb' expects the
|
|
|
|
|
# full rolename.
|
2014-06-08 00:29:18 +00:00
|
|
|
full_rolename = self._rolename + '/' + rolename
|
2013-11-25 01:23:12 +00:00
|
|
|
|
|
|
|
|
if tuf.roledb.role_exists(full_rolename):
|
2015-09-22 14:02:40 +00:00
|
|
|
raise tuf.Error(repr(rolename) + ' already delegated.')
|
2013-11-25 01:23:12 +00:00
|
|
|
|
2013-12-18 16:49:28 +00:00
|
|
|
# Keep track of the valid keyids (added to the new Targets object) and their
|
|
|
|
|
# keydicts (added to this Targets delegations).
|
2013-11-05 13:22:21 +00:00
|
|
|
keyids = []
|
2013-11-12 20:00:26 +00:00
|
|
|
keydict = {}
|
|
|
|
|
|
2013-12-18 16:49:28 +00:00
|
|
|
# Add all the keys of 'public_keys' to tuf.keydb.
|
2013-11-05 13:22:21 +00:00
|
|
|
for key in public_keys:
|
|
|
|
|
keyid = key['keyid']
|
2013-11-12 20:00:26 +00:00
|
|
|
key_metadata_format = tuf.keys.format_keyval_to_metadata(key['keytype'],
|
|
|
|
|
key['keyval'])
|
2013-12-18 16:49:28 +00:00
|
|
|
# Update 'keyids' and 'keydict'.
|
2013-11-25 01:23:12 +00:00
|
|
|
new_keydict = {keyid: key_metadata_format}
|
|
|
|
|
keydict.update(new_keydict)
|
2013-11-05 13:22:21 +00:00
|
|
|
keyids.append(keyid)
|
|
|
|
|
|
2013-12-18 16:49:28 +00:00
|
|
|
# Ensure the paths of 'list_of_targets' all fall under the repository's
|
|
|
|
|
# targets.
|
2014-06-23 17:33:01 +00:00
|
|
|
relative_targetpaths = {}
|
2013-11-12 20:00:26 +00:00
|
|
|
targets_directory_length = len(self._targets_directory)
|
|
|
|
|
|
|
|
|
|
for target in list_of_targets:
|
|
|
|
|
target = os.path.abspath(target)
|
2014-01-25 21:40:53 +00:00
|
|
|
if not target.startswith(self._targets_directory+os.sep):
|
2016-01-28 20:22:20 +00:00
|
|
|
raise tuf.Error(repr(target) + ' is not under the Repository\'s'
|
|
|
|
|
' targets directory: ' + repr(self._targets_directory))
|
2013-11-12 20:00:26 +00:00
|
|
|
|
2014-06-23 17:33:01 +00:00
|
|
|
relative_targetpaths.update({target[targets_directory_length:]: {}})
|
2013-11-12 20:00:26 +00:00
|
|
|
|
2013-12-18 16:49:28 +00:00
|
|
|
# Ensure the paths of 'restricted_paths' all fall under the repository's
|
|
|
|
|
# targets.
|
2014-06-23 17:33:01 +00:00
|
|
|
relative_restricted_paths = []
|
2013-11-12 20:00:26 +00:00
|
|
|
|
|
|
|
|
if restricted_paths is not None:
|
2014-01-22 17:52:55 +00:00
|
|
|
for path in restricted_paths:
|
2014-06-23 17:33:01 +00:00
|
|
|
path = os.path.abspath(path) + os.sep
|
|
|
|
|
if not path.startswith(self._targets_directory + os.sep):
|
2016-01-28 20:22:20 +00:00
|
|
|
raise tuf.Error(repr(path) + ' is not under the Repository\'s'
|
|
|
|
|
' targets directory: ' +repr(self._targets_directory))
|
2014-01-22 17:52:55 +00:00
|
|
|
|
2014-01-25 21:40:53 +00:00
|
|
|
# Append a trailing path separator with os.path.join(path, '').
|
2014-01-22 17:52:55 +00:00
|
|
|
path = os.path.join(path, '')
|
2014-01-25 21:40:53 +00:00
|
|
|
relative_restricted_paths.append(path[targets_directory_length:])
|
2013-11-12 20:00:26 +00:00
|
|
|
|
2013-12-18 16:49:28 +00:00
|
|
|
# Create a new Targets object for the 'rolename' delegation. An initial
|
|
|
|
|
# expiration is set (3 months from the current time).
|
2014-04-19 18:27:53 +00:00
|
|
|
expiration = \
|
|
|
|
|
tuf.formats.unix_timestamp_to_datetime(int(time.time() + TARGETS_EXPIRATION))
|
|
|
|
|
expiration = expiration.isoformat() + 'Z'
|
2014-04-17 16:27:28 +00:00
|
|
|
|
2013-11-25 20:01:27 +00:00
|
|
|
roleinfo = {'name': full_rolename, 'keyids': keyids, 'signing_keyids': [],
|
2013-12-11 19:14:16 +00:00
|
|
|
'threshold': threshold, 'version': 0, 'compressions': [''],
|
2014-01-14 15:01:17 +00:00
|
|
|
'expires': expiration, 'signatures': [], 'partial_loaded': False,
|
2013-11-25 20:01:27 +00:00
|
|
|
'paths': relative_targetpaths, 'delegations': {'keys': {},
|
|
|
|
|
'roles': []}}
|
|
|
|
|
|
2013-12-18 16:49:28 +00:00
|
|
|
# The new targets object is added as an attribute to this Targets object.
|
2013-11-12 20:00:26 +00:00
|
|
|
new_targets_object = Targets(self._targets_directory, full_rolename,
|
2013-11-22 16:13:11 +00:00
|
|
|
roleinfo)
|
2013-11-05 13:22:21 +00:00
|
|
|
|
2013-11-12 20:00:26 +00:00
|
|
|
# Update the 'delegations' field of the current role.
|
|
|
|
|
current_roleinfo = tuf.roledb.get_roleinfo(self.rolename)
|
|
|
|
|
current_roleinfo['delegations']['keys'].update(keydict)
|
|
|
|
|
|
2013-12-18 16:49:28 +00:00
|
|
|
# Update the roleinfo of this role. A ROLE_SCHEMA object requires only
|
|
|
|
|
# 'keyids', 'threshold', and 'paths'.
|
2013-11-12 20:00:26 +00:00
|
|
|
roleinfo = {'name': full_rolename,
|
|
|
|
|
'keyids': roleinfo['keyids'],
|
|
|
|
|
'threshold': roleinfo['threshold'],
|
2014-06-08 00:29:18 +00:00
|
|
|
'backtrack': backtrack,
|
2014-06-23 17:33:01 +00:00
|
|
|
'paths': list(roleinfo['paths'].keys())}
|
2014-06-08 00:29:18 +00:00
|
|
|
|
2013-11-12 20:00:26 +00:00
|
|
|
if restricted_paths is not None:
|
|
|
|
|
roleinfo['paths'] = relative_restricted_paths
|
2014-06-08 00:29:18 +00:00
|
|
|
|
2013-11-14 19:24:07 +00:00
|
|
|
if path_hash_prefixes is not None:
|
|
|
|
|
roleinfo['path_hash_prefixes'] = path_hash_prefixes
|
2014-01-22 17:52:55 +00:00
|
|
|
# A role in a delegations must list either 'path_hash_prefixes'
|
|
|
|
|
# or 'paths'.
|
|
|
|
|
del roleinfo['paths']
|
2013-11-12 20:00:26 +00:00
|
|
|
|
|
|
|
|
current_roleinfo['delegations']['roles'].append(roleinfo)
|
|
|
|
|
tuf.roledb.update_roleinfo(self.rolename, current_roleinfo)
|
2013-11-05 13:22:21 +00:00
|
|
|
|
2013-12-18 16:49:28 +00:00
|
|
|
# Update the public keys of 'new_targets_object'.
|
2013-11-05 13:22:21 +00:00
|
|
|
for key in public_keys:
|
2014-01-29 16:26:56 +00:00
|
|
|
new_targets_object.add_verification_key(key)
|
2013-11-05 13:22:21 +00:00
|
|
|
|
Address Issues #165, #158, and #147.
Issue 147: Finalize conversion of all written metadata behavior. This commit ensures that compressed and uncompressed metadata is also written as outlined in the issue.
Issue 158: As requested, updater.refresh() may now unsafely fetch (i.e., unknown file size and hash) Root metadata if valid top-level metadata cannot be downloaded successfully (e.g., top-level keys may have been revoked). The repository must also sign the new Root file (at least until all clients have updated) with any revoked keys so that clients may successfully update. After unsafely updating Root, the top-level metadata is updated again as normal (and only once to avoid an infinite loop). By default, refresh() unsafely updates Root if only invalid top-level metadata can be downloaded, although this behavior may be overriden by the caller if they wish. Changed default behavior: refresh(self, unsafely_update_root_if_necessary=True)
Issue 165: Delegated roles are no longer added as attributes of a Targets object by libtuf.py (e.g., repository.targets.delegated_role). The previous bahavior restricted rolenames to Python identifiers (i.e., can only include letters, numbers, the underscore character, and must start with a nonnumeric character). Now, delegated roles may be referenced as strings (e.g., repository.targets('recently-claimed')) and include characters other than '_'. In addition, methods have been added to return all the delegated rolesnames of a target (e.g., repository.targets.get_delegated_rolenames()) and the immediate delegated Target objects of a role. Previous behavior: repository.targets.unclaimed.django.version = 8
Current behavior: repository.targets('unclaimed')('django').version = 8.
2014-01-02 17:18:44 +00:00
|
|
|
# Add the new delegation to this Targets object. For example, 'django' is
|
2014-01-22 17:52:55 +00:00
|
|
|
# added to 'repository.targets' (i.e., repository.targets('django')).
|
Address Issues #165, #158, and #147.
Issue 147: Finalize conversion of all written metadata behavior. This commit ensures that compressed and uncompressed metadata is also written as outlined in the issue.
Issue 158: As requested, updater.refresh() may now unsafely fetch (i.e., unknown file size and hash) Root metadata if valid top-level metadata cannot be downloaded successfully (e.g., top-level keys may have been revoked). The repository must also sign the new Root file (at least until all clients have updated) with any revoked keys so that clients may successfully update. After unsafely updating Root, the top-level metadata is updated again as normal (and only once to avoid an infinite loop). By default, refresh() unsafely updates Root if only invalid top-level metadata can be downloaded, although this behavior may be overriden by the caller if they wish. Changed default behavior: refresh(self, unsafely_update_root_if_necessary=True)
Issue 165: Delegated roles are no longer added as attributes of a Targets object by libtuf.py (e.g., repository.targets.delegated_role). The previous bahavior restricted rolenames to Python identifiers (i.e., can only include letters, numbers, the underscore character, and must start with a nonnumeric character). Now, delegated roles may be referenced as strings (e.g., repository.targets('recently-claimed')) and include characters other than '_'. In addition, methods have been added to return all the delegated rolesnames of a target (e.g., repository.targets.get_delegated_rolenames()) and the immediate delegated Target objects of a role. Previous behavior: repository.targets.unclaimed.django.version = 8
Current behavior: repository.targets('unclaimed')('django').version = 8.
2014-01-02 17:18:44 +00:00
|
|
|
self._delegated_roles[rolename] = new_targets_object
|
2013-11-15 01:36:28 +00:00
|
|
|
|
|
|
|
|
|
2014-01-21 19:42:28 +00:00
|
|
|
|
2013-10-22 18:02:01 +00:00
|
|
|
def revoke(self, rolename):
|
|
|
|
|
"""
|
|
|
|
|
<Purpose>
|
2013-12-18 16:49:28 +00:00
|
|
|
Revoke this Targets' 'rolename' delegation. Its 'rolename' attribute is
|
|
|
|
|
deleted, including the entries in its 'delegations' field and in
|
|
|
|
|
'tuf.roledb'.
|
|
|
|
|
|
|
|
|
|
Actual metadata files are not updated, only when repository.write() or
|
|
|
|
|
repository.write_partial() is called.
|
|
|
|
|
|
2013-10-22 18:02:01 +00:00
|
|
|
>>>
|
|
|
|
|
>>>
|
|
|
|
|
>>>
|
|
|
|
|
|
|
|
|
|
<Arguments>
|
|
|
|
|
rolename:
|
2013-12-18 16:49:28 +00:00
|
|
|
The rolename (e.g., 'Django' in 'targets/unclaimed/Django') of
|
|
|
|
|
the child delegation the parent role (this role) wants to revoke.
|
2013-10-22 18:02:01 +00:00
|
|
|
|
|
|
|
|
<Exceptions>
|
2013-11-05 13:22:21 +00:00
|
|
|
tuf.FormatError, if 'rolename' is improperly formatted.
|
2013-10-22 18:02:01 +00:00
|
|
|
|
|
|
|
|
<Side Effects>
|
2013-12-18 16:49:28 +00:00
|
|
|
The delegations dictionary of 'rolename' is modified, and its 'tuf.roledb'
|
|
|
|
|
entry is updated. This Targets' 'rolename' delegation attribute is also
|
|
|
|
|
deleted.
|
2013-10-22 18:02:01 +00:00
|
|
|
|
|
|
|
|
<Returns>
|
2013-11-05 13:22:21 +00:00
|
|
|
None.
|
2013-10-22 18:02:01 +00:00
|
|
|
"""
|
2013-11-05 13:22:21 +00:00
|
|
|
|
2013-12-18 16:49:28 +00:00
|
|
|
# Does 'rolename' have the correct format?
|
|
|
|
|
# Ensure the arguments have the appropriate number of objects and object
|
|
|
|
|
# types, and that all dict keys are properly named.
|
|
|
|
|
# Raise 'tuf.FormatError' if there is a mismatch.
|
2013-11-05 13:22:21 +00:00
|
|
|
tuf.formats.ROLENAME_SCHEMA.check_match(rolename)
|
|
|
|
|
|
2013-12-18 16:49:28 +00:00
|
|
|
# Remove 'rolename' from this Target's delegations dict.
|
|
|
|
|
# The child delegation's full rolename is required to locate in the parent's
|
|
|
|
|
# delegations list.
|
2013-11-12 20:00:26 +00:00
|
|
|
roleinfo = tuf.roledb.get_roleinfo(self.rolename)
|
2014-06-23 00:01:28 +00:00
|
|
|
full_rolename = self.rolename + '/' + rolename
|
2013-11-12 21:55:51 +00:00
|
|
|
|
|
|
|
|
for role in roleinfo['delegations']['roles']:
|
|
|
|
|
if role['name'] == full_rolename:
|
|
|
|
|
roleinfo['delegations']['roles'].remove(role)
|
2013-11-14 19:24:07 +00:00
|
|
|
|
|
|
|
|
tuf.roledb.update_roleinfo(self.rolename, roleinfo)
|
2013-11-12 21:55:51 +00:00
|
|
|
|
2013-12-18 16:49:28 +00:00
|
|
|
# Remove 'rolename' from 'tuf.roledb.py'. The delegations of 'rolename' are
|
|
|
|
|
# also removed.
|
2013-11-12 20:00:26 +00:00
|
|
|
tuf.roledb.remove_role(full_rolename)
|
2013-11-14 19:24:07 +00:00
|
|
|
|
Address Issues #165, #158, and #147.
Issue 147: Finalize conversion of all written metadata behavior. This commit ensures that compressed and uncompressed metadata is also written as outlined in the issue.
Issue 158: As requested, updater.refresh() may now unsafely fetch (i.e., unknown file size and hash) Root metadata if valid top-level metadata cannot be downloaded successfully (e.g., top-level keys may have been revoked). The repository must also sign the new Root file (at least until all clients have updated) with any revoked keys so that clients may successfully update. After unsafely updating Root, the top-level metadata is updated again as normal (and only once to avoid an infinite loop). By default, refresh() unsafely updates Root if only invalid top-level metadata can be downloaded, although this behavior may be overriden by the caller if they wish. Changed default behavior: refresh(self, unsafely_update_root_if_necessary=True)
Issue 165: Delegated roles are no longer added as attributes of a Targets object by libtuf.py (e.g., repository.targets.delegated_role). The previous bahavior restricted rolenames to Python identifiers (i.e., can only include letters, numbers, the underscore character, and must start with a nonnumeric character). Now, delegated roles may be referenced as strings (e.g., repository.targets('recently-claimed')) and include characters other than '_'. In addition, methods have been added to return all the delegated rolesnames of a target (e.g., repository.targets.get_delegated_rolenames()) and the immediate delegated Target objects of a role. Previous behavior: repository.targets.unclaimed.django.version = 8
Current behavior: repository.targets('unclaimed')('django').version = 8.
2014-01-02 17:18:44 +00:00
|
|
|
# Remove the rolename delegation from the current role. For example, the
|
|
|
|
|
# 'django' role is removed from 'repository.targets('unclaimed')('django').
|
|
|
|
|
del self._delegated_roles[rolename]
|
2013-10-22 18:02:01 +00:00
|
|
|
|
|
|
|
|
|
2013-12-18 16:49:28 +00:00
|
|
|
|
2014-01-21 19:42:28 +00:00
|
|
|
def delegate_hashed_bins(self, list_of_targets, keys_of_hashed_bins,
|
|
|
|
|
number_of_bins=1024):
|
|
|
|
|
"""
|
|
|
|
|
<Purpose>
|
2014-04-04 22:03:27 +00:00
|
|
|
Distribute a large number of target files over multiple delegated roles
|
2014-01-23 17:03:31 +00:00
|
|
|
(hashed bins). The metadata files of delegated roles will be nearly equal
|
|
|
|
|
in size (i.e., 'list_of_targets' is uniformly distributed by calculating
|
|
|
|
|
the target filepath's hash and determing which bin it should reside in.
|
|
|
|
|
The updater client will use "lazy bin walk" to find a target file's hashed
|
|
|
|
|
bin destination. The parent role lists a range of path hash prefixes each
|
|
|
|
|
hashed bin contains. This method is intended for repositories with a
|
|
|
|
|
large number of target files, a way of easily distributing and managing
|
|
|
|
|
the metadata that lists the targets, and minimizing the number of metadata
|
|
|
|
|
files (and their size) downloaded by the client. See tuf-spec.txt and the
|
|
|
|
|
following link for more information:
|
2014-01-21 19:42:28 +00:00
|
|
|
http://www.python.org/dev/peps/pep-0458/#metadata-scalability
|
|
|
|
|
|
|
|
|
|
>>>
|
|
|
|
|
>>>
|
|
|
|
|
>>>
|
|
|
|
|
|
|
|
|
|
<Arguments>
|
|
|
|
|
list_of_targets:
|
2014-01-23 17:03:31 +00:00
|
|
|
The target filepaths of the targets that should be stored in hashed
|
|
|
|
|
bins created (i.e., delegated roles). A repository object's
|
|
|
|
|
get_filepaths_in_directory() can generate a list of valid target
|
|
|
|
|
paths.
|
2014-01-21 19:42:28 +00:00
|
|
|
|
|
|
|
|
keys_of_hashed_bins:
|
2014-01-23 17:03:31 +00:00
|
|
|
The initial public keys of the delegated roles. Public keys may be
|
|
|
|
|
later added or removed by calling the usual methods of the delegated
|
|
|
|
|
Targets object. For example:
|
2014-01-29 16:26:56 +00:00
|
|
|
repository.targets('unclaimed')('000-003').add_verification_key()
|
2014-01-21 19:42:28 +00:00
|
|
|
|
|
|
|
|
number_of_bins:
|
2014-01-23 17:03:31 +00:00
|
|
|
The number of delegated roles, or hashed bins, that should be generated
|
|
|
|
|
and contain the target file attributes listed in 'list_of_targets'.
|
2014-04-04 22:03:27 +00:00
|
|
|
'number_of_bins' must be a power of 2. Each bin may contain a
|
2014-01-23 17:03:31 +00:00
|
|
|
range of path hash prefixes (e.g., target filepath digests that range
|
|
|
|
|
from [000]... - [003]..., where the series of digits in brackets is
|
|
|
|
|
considered the hash prefix).
|
2014-01-21 19:42:28 +00:00
|
|
|
|
|
|
|
|
<Exceptions>
|
2014-04-10 15:34:40 +00:00
|
|
|
tuf.FormatError, if the arguments are improperly formatted.
|
|
|
|
|
|
|
|
|
|
tuf.Error, if 'number_of_bins' is not a power of 2, or one of the targets
|
2014-01-21 19:42:28 +00:00
|
|
|
in 'list_of_targets' is not located under the repository's targets
|
|
|
|
|
directory.
|
|
|
|
|
|
|
|
|
|
<Side Effects>
|
2014-01-23 17:03:31 +00:00
|
|
|
Delegates multiple target roles from the current parent role.
|
2014-01-21 19:42:28 +00:00
|
|
|
|
|
|
|
|
<Returns>
|
|
|
|
|
None.
|
|
|
|
|
"""
|
|
|
|
|
|
2014-01-22 17:52:55 +00:00
|
|
|
# Do the arguments have the correct format?
|
2014-01-21 19:42:28 +00:00
|
|
|
# Ensure the arguments have the appropriate number of objects and object
|
|
|
|
|
# types, and that all dict keys are properly named.
|
|
|
|
|
# Raise 'tuf.FormatError' if there is a mismatch.
|
|
|
|
|
tuf.formats.PATHS_SCHEMA.check_match(list_of_targets)
|
|
|
|
|
tuf.formats.ANYKEYLIST_SCHEMA.check_match(keys_of_hashed_bins)
|
|
|
|
|
tuf.formats.NUMBINS_SCHEMA.check_match(number_of_bins)
|
|
|
|
|
|
2014-04-04 22:03:27 +00:00
|
|
|
# Convert 'number_of_bins' to hexadecimal and determine the number of
|
|
|
|
|
# hexadecimal digits needed by each hash prefix. Calculate the total number
|
|
|
|
|
# of hash prefixes (e.g., 000 - FFF total values) to be spread over
|
|
|
|
|
# 'number_of_bins' and strip the first two characters ('0x') from Python's
|
|
|
|
|
# representation of hexadecimal values (so that they are not used in
|
|
|
|
|
# the calculation of the prefix length.)
|
|
|
|
|
# Example: number_of_bins = 32, total_hash_prefixes = 256, and each hashed
|
|
|
|
|
# bin is responsible for 8 hash prefixes.
|
|
|
|
|
# Hashed bin roles created = 00-07.json, 08-0f.json, ..., f8-ff.json.
|
2014-01-21 19:42:28 +00:00
|
|
|
prefix_length = len(hex(number_of_bins - 1)[2:])
|
2014-04-04 22:03:27 +00:00
|
|
|
total_hash_prefixes = 16 ** prefix_length
|
2014-01-21 19:42:28 +00:00
|
|
|
|
2014-04-04 22:03:27 +00:00
|
|
|
# For simplicity, ensure that 'total_hash_prefixes' (16 ^ n) can be evenly
|
|
|
|
|
# distributed over 'number_of_bins' (must be 2 ^ n). Each bin will contain
|
|
|
|
|
# (total_hash_prefixes / number_of_bins) hash prefixes.
|
|
|
|
|
if total_hash_prefixes % number_of_bins != 0:
|
2016-01-28 20:22:20 +00:00
|
|
|
raise tuf.Error('The "number_of_bins" argument must be a power of 2.')
|
2014-01-21 19:42:28 +00:00
|
|
|
|
2014-04-04 22:03:27 +00:00
|
|
|
logger.info('Creating hashed bin delegations.')
|
|
|
|
|
logger.info(repr(len(list_of_targets)) + ' total targets.')
|
|
|
|
|
logger.info(repr(number_of_bins) + ' hashed bins.')
|
|
|
|
|
logger.info(repr(total_hash_prefixes) + ' total hash prefixes.')
|
2014-01-21 19:42:28 +00:00
|
|
|
|
2014-01-23 17:03:31 +00:00
|
|
|
# Store the target paths that fall into each bin. The digest of the
|
|
|
|
|
# target path, reduced to the first 'prefix_length' hex digits, is
|
|
|
|
|
# calculated to determine which 'bin_index' is should go.
|
2014-01-21 19:42:28 +00:00
|
|
|
target_paths_in_bin = {}
|
2014-04-22 19:03:42 +00:00
|
|
|
for bin_index in six.moves.xrange(total_hash_prefixes):
|
2014-01-21 19:42:28 +00:00
|
|
|
target_paths_in_bin[bin_index] = []
|
2014-01-22 17:52:55 +00:00
|
|
|
|
2014-01-21 19:42:28 +00:00
|
|
|
# Assign every path to its bin. Ensure every target is located under the
|
|
|
|
|
# repository's targets directory.
|
|
|
|
|
for target_path in list_of_targets:
|
2014-01-22 17:52:55 +00:00
|
|
|
target_path = os.path.abspath(target_path)
|
2014-01-25 21:40:53 +00:00
|
|
|
if not target_path.startswith(self._targets_directory+os.sep):
|
2016-01-28 20:22:20 +00:00
|
|
|
raise tuf.Error('A path in the list of targets argument is not'
|
|
|
|
|
' under the repository\'s targets directory: ' + repr(target_path))
|
2014-01-21 19:42:28 +00:00
|
|
|
|
|
|
|
|
# Determine the hash prefix of 'target_path' by computing the digest of
|
|
|
|
|
# its path relative to the targets directory. Example:
|
|
|
|
|
# '{repository_root}/targets/file1.txt' -> 'file1.txt'.
|
2014-01-25 21:40:53 +00:00
|
|
|
relative_path = target_path[len(self._targets_directory):]
|
2014-01-21 19:42:28 +00:00
|
|
|
digest_object = tuf.hash.digest(algorithm=HASH_FUNCTION)
|
2014-05-27 17:55:48 +00:00
|
|
|
digest_object.update(relative_path.encode('utf-8'))
|
2014-01-22 17:52:55 +00:00
|
|
|
relative_path_hash = digest_object.hexdigest()
|
2014-01-21 19:42:28 +00:00
|
|
|
relative_path_hash_prefix = relative_path_hash[:prefix_length]
|
|
|
|
|
|
|
|
|
|
# 'target_paths_in_bin' store bin indices in base-10, so convert the
|
|
|
|
|
# 'relative_path_hash_prefix' base-16 (hex) number to a base-10 (dec)
|
|
|
|
|
# number.
|
|
|
|
|
bin_index = int(relative_path_hash_prefix, 16)
|
|
|
|
|
|
2014-01-23 17:03:31 +00:00
|
|
|
# Add the 'target_path' (absolute) to the bin. These target paths are
|
|
|
|
|
# later added to the targets of the 'bin_index' role.
|
2014-01-22 17:52:55 +00:00
|
|
|
target_paths_in_bin[bin_index].append(target_path)
|
2014-01-21 19:42:28 +00:00
|
|
|
|
2014-04-04 22:03:27 +00:00
|
|
|
# Calculate the path hash prefixes of each 'bin_offset' stored in the parent
|
2014-01-23 17:03:31 +00:00
|
|
|
# role. For example: 'targets/unclaimed/000-003' may list the path hash
|
2014-01-21 19:42:28 +00:00
|
|
|
# prefixes "000", "001", "002", "003" in the delegations dict of
|
|
|
|
|
# 'targets/unclaimed'.
|
2014-04-04 22:03:27 +00:00
|
|
|
bin_offset = total_hash_prefixes // number_of_bins
|
|
|
|
|
|
|
|
|
|
logger.info('Each bin ranges over ' + repr(bin_offset) + ' hash prefixes.')
|
|
|
|
|
|
2014-01-21 19:42:28 +00:00
|
|
|
# The parent roles will list bin roles starting from "0" to
|
2014-04-04 22:03:27 +00:00
|
|
|
# 'total_hash_prefixes' in 'bin_offset' increments. The skipped bin roles
|
2014-01-21 19:42:28 +00:00
|
|
|
# are listed in 'path_hash_prefixes' of 'outer_bin_index.
|
2014-04-22 19:03:42 +00:00
|
|
|
for outer_bin_index in six.moves.xrange(0, total_hash_prefixes, bin_offset):
|
2014-01-23 17:03:31 +00:00
|
|
|
# The bin index is hex padded from the left with zeroes for up to the
|
|
|
|
|
# 'prefix_length' (e.g., 'targets/unclaimed/000-003'). Ensure the correct
|
|
|
|
|
# hash bin name is generated if a prefix range is unneeded.
|
2014-01-22 17:52:55 +00:00
|
|
|
start_bin = hex(outer_bin_index)[2:].zfill(prefix_length)
|
|
|
|
|
end_bin = hex(outer_bin_index+bin_offset-1)[2:].zfill(prefix_length)
|
|
|
|
|
if start_bin == end_bin:
|
|
|
|
|
bin_rolename = start_bin
|
|
|
|
|
else:
|
|
|
|
|
bin_rolename = start_bin + '-' + end_bin
|
|
|
|
|
|
2014-01-23 17:03:31 +00:00
|
|
|
# 'bin_rolename' may contain a range of target paths, from 'start_bin' to
|
|
|
|
|
# 'end_bin'. Determine the total target paths that should be included.
|
2014-01-21 19:42:28 +00:00
|
|
|
path_hash_prefixes = []
|
2014-01-22 17:52:55 +00:00
|
|
|
bin_rolename_targets = []
|
2014-01-21 19:42:28 +00:00
|
|
|
|
2014-04-22 19:03:42 +00:00
|
|
|
for inner_bin_index in six.moves.xrange(outer_bin_index, outer_bin_index+bin_offset):
|
2014-01-23 17:03:31 +00:00
|
|
|
# 'inner_bin_rolename' needed in padded hex. For example, "00b".
|
2014-01-21 19:42:28 +00:00
|
|
|
inner_bin_rolename = hex(inner_bin_index)[2:].zfill(prefix_length)
|
|
|
|
|
path_hash_prefixes.append(inner_bin_rolename)
|
2014-01-22 17:52:55 +00:00
|
|
|
bin_rolename_targets.extend(target_paths_in_bin[inner_bin_index])
|
2014-01-21 19:42:28 +00:00
|
|
|
|
|
|
|
|
# Delegate from the "unclaimed" targets role to each 'bin_rolename'
|
|
|
|
|
# (i.e., outer_bin_index).
|
|
|
|
|
self.delegate(bin_rolename, keys_of_hashed_bins,
|
|
|
|
|
list_of_targets=bin_rolename_targets,
|
|
|
|
|
path_hash_prefixes=path_hash_prefixes)
|
2016-01-28 20:22:20 +00:00
|
|
|
logger.debug('Delegated from ' + repr(self.rolename) + ' to ' + repr(bin_rolename))
|
2014-01-21 19:42:28 +00:00
|
|
|
|
|
|
|
|
|
2014-05-05 07:22:55 +00:00
|
|
|
|
2014-06-18 16:39:01 +00:00
|
|
|
def add_target_to_bin(self, target_filepath):
|
2014-04-30 16:46:37 +00:00
|
|
|
"""
|
|
|
|
|
<Purpose>
|
2014-06-18 16:39:01 +00:00
|
|
|
Add the fileinfo of 'target_filepath' to the expected hashed bin, if
|
2014-04-30 16:46:37 +00:00
|
|
|
the bin is available. The hashed bin should have been created by
|
|
|
|
|
{targets_role}.delegate_hashed_bins(). Assuming the target filepath
|
|
|
|
|
falls under the repository's targets directory, determine the filepath's
|
|
|
|
|
hash prefix, locate the expected bin (if any), and then add the fileinfo
|
|
|
|
|
to the expected bin. Example: 'targets/foo.tar.gz' may be added to
|
|
|
|
|
the 'targets/unclaimed/58-5f.json' role's list of targets by calling this
|
|
|
|
|
method.
|
|
|
|
|
|
|
|
|
|
<Arguments>
|
|
|
|
|
target_filepath:
|
|
|
|
|
The filepath of the target to be added to a hashed bin. The filepath
|
|
|
|
|
must fall under repository's targets directory.
|
|
|
|
|
|
|
|
|
|
<Exceptions>
|
|
|
|
|
tuf.FormatError, if 'target_filepath' is improperly formatted.
|
|
|
|
|
|
|
|
|
|
tuf.Error, if 'target_filepath' cannot be added to a hashed bin
|
|
|
|
|
(e.g., an invalid target filepath, or the expected hashed bin does not
|
|
|
|
|
exist.)
|
|
|
|
|
|
|
|
|
|
<Side Effects>
|
|
|
|
|
The fileinfo of 'target_filepath' is added to a hashed bin of this Targets
|
|
|
|
|
object.
|
|
|
|
|
|
|
|
|
|
<Returns>
|
|
|
|
|
None.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
# Do the arguments have the correct format?
|
|
|
|
|
# Ensure the arguments have the appropriate number of objects and object
|
|
|
|
|
# types, and that all dict keys are properly named.
|
|
|
|
|
# Raise 'tuf.FormatError' if there is a mismatch.
|
|
|
|
|
tuf.formats.PATH_SCHEMA.check_match(target_filepath)
|
2014-06-18 16:39:01 +00:00
|
|
|
|
|
|
|
|
return self._locate_and_update_target_in_bin(target_filepath, 'add_target')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def remove_target_from_bin(self, target_filepath):
|
|
|
|
|
"""
|
|
|
|
|
<Purpose>
|
|
|
|
|
Remove the fileinfo of 'target_filepath' from the expected hashed bin, if
|
|
|
|
|
the bin is available. The hashed bin should have been created by
|
|
|
|
|
{targets_role}.delegate_hashed_bins(). Assuming the target filepath
|
|
|
|
|
falls under the repository's targets directory, determine the filepath's
|
|
|
|
|
hash prefix, locate the expected bin (if any), and then remove the
|
|
|
|
|
fileinfo from the expected bin. Example: 'targets/foo.tar.gz' may be
|
|
|
|
|
removed from the 'targets/unclaimed/58-5f.json' role's list of targets by
|
|
|
|
|
calling this method.
|
|
|
|
|
|
|
|
|
|
<Arguments>
|
|
|
|
|
target_filepath:
|
|
|
|
|
The filepath of the target to be added to a hashed bin. The filepath
|
|
|
|
|
must fall under repository's targets directory.
|
|
|
|
|
|
|
|
|
|
<Exceptions>
|
|
|
|
|
tuf.FormatError, if 'target_filepath' is improperly formatted.
|
|
|
|
|
|
|
|
|
|
tuf.Error, if 'target_filepath' cannot be removed from a hashed bin
|
|
|
|
|
(e.g., an invalid target filepath, or the expected hashed bin does not
|
|
|
|
|
exist.)
|
|
|
|
|
|
|
|
|
|
<Side Effects>
|
|
|
|
|
The fileinfo of 'target_filepath' is removed from a hashed bin of this
|
|
|
|
|
Targets object.
|
|
|
|
|
|
|
|
|
|
<Returns>
|
|
|
|
|
None.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
# Do the arguments have the correct format?
|
|
|
|
|
# Ensure the arguments have the appropriate number of objects and object
|
|
|
|
|
# types, and that all dict keys are properly named.
|
|
|
|
|
# Raise 'tuf.FormatError' if there is a mismatch.
|
|
|
|
|
tuf.formats.PATH_SCHEMA.check_match(target_filepath)
|
|
|
|
|
|
|
|
|
|
return self._locate_and_update_target_in_bin(target_filepath, 'remove_target')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _locate_and_update_target_in_bin(self, target_filepath, method_name):
|
|
|
|
|
"""
|
|
|
|
|
<Purpose>
|
|
|
|
|
Assuming the target filepath falls under the repository's targets
|
|
|
|
|
directory, determine the filepath's hash prefix, locate the expected bin
|
|
|
|
|
(if any), and then call the 'method_name' method of the expected hashed
|
|
|
|
|
bin role.
|
|
|
|
|
|
|
|
|
|
<Arguments>
|
|
|
|
|
target_filepath:
|
|
|
|
|
The filepath of the target that may be specified in one of the hashed
|
|
|
|
|
bins. 'target_filepath' must fall under repository's targets directory.
|
|
|
|
|
|
|
|
|
|
method_name:
|
|
|
|
|
A supported method, in string format, of the Targets() class. For
|
2014-06-18 19:17:38 +00:00
|
|
|
example, 'add_target' and 'remove_target'. If 'target_filepath' were
|
|
|
|
|
to be manually added or removed from a bin:
|
2014-06-18 16:39:01 +00:00
|
|
|
|
|
|
|
|
repository.targets('unclaimed')('58-f7).add_target(target_filepath)
|
|
|
|
|
repository.targets('unclaimed')('000-007).remove_target(target_filepath)
|
|
|
|
|
|
|
|
|
|
<Exceptions>
|
|
|
|
|
tuf.Error, if 'target_filepath' cannot be updated (e.g., an invalid target
|
|
|
|
|
filepath, or the expected hashed bin does not exist.)
|
|
|
|
|
|
|
|
|
|
<Side Effects>
|
|
|
|
|
The fileinfo of 'target_filepath' is added to a hashed bin of this Targets
|
|
|
|
|
object.
|
|
|
|
|
|
|
|
|
|
<Returns>
|
|
|
|
|
None.
|
|
|
|
|
"""
|
2014-04-30 16:46:37 +00:00
|
|
|
|
|
|
|
|
# Determine the prefix length of any one of the hashed bins. The prefix
|
|
|
|
|
# length is not stored in the roledb, so it must be determined here by
|
|
|
|
|
# inspecting one of path hash prefixes listed.
|
|
|
|
|
roleinfo = tuf.roledb.get_roleinfo(self.rolename)
|
|
|
|
|
prefix_length = 0
|
|
|
|
|
delegation = None
|
|
|
|
|
|
|
|
|
|
# Set 'delegation' if this Targets role has performed any delegations.
|
|
|
|
|
if len(roleinfo['delegations']['roles']):
|
|
|
|
|
delegation = roleinfo['delegations']['roles'][0]
|
|
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
raise tuf.Error(self.rolename + ' has not delegated to any roles.')
|
|
|
|
|
|
|
|
|
|
# Set 'prefix_length' if this Targets object has delegated to hashed bins,
|
|
|
|
|
# otherwise raise an exception.
|
|
|
|
|
if 'path_hash_prefixes' in delegation and len(delegation['path_hash_prefixes']):
|
|
|
|
|
prefix_length = len(delegation['path_hash_prefixes'][0])
|
|
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
raise tuf.Error(self.rolename + ' has not delegated to hashed bins.')
|
|
|
|
|
|
|
|
|
|
# Ensure the filepath falls under the repository's targets directory.
|
|
|
|
|
filepath = os.path.abspath(target_filepath)
|
|
|
|
|
if not filepath.startswith(self._targets_directory + os.sep):
|
2016-01-28 20:22:20 +00:00
|
|
|
raise tuf.Error(repr(filepath) + ' is not under the Repository\'s'
|
|
|
|
|
' targets directory: ' + repr(self._targets_directory))
|
2014-04-30 16:46:37 +00:00
|
|
|
|
|
|
|
|
# Determine the hash prefix of 'target_path' by computing the digest of
|
|
|
|
|
# its path relative to the targets directory. Example:
|
|
|
|
|
# '{repository_root}/targets/file1.txt' -> '/file1.txt'.
|
|
|
|
|
relative_path = filepath[len(self._targets_directory):]
|
|
|
|
|
digest_object = tuf.hash.digest(algorithm=HASH_FUNCTION)
|
2014-05-27 17:55:48 +00:00
|
|
|
digest_object.update(relative_path.encode('utf-8'))
|
2014-04-30 16:46:37 +00:00
|
|
|
path_hash = digest_object.hexdigest()
|
|
|
|
|
path_hash_prefix = path_hash[:prefix_length]
|
|
|
|
|
|
|
|
|
|
# Search for 'path_hash_prefix', and if found, extract the hashed bin's
|
|
|
|
|
# rolename. The hashed bin name is needed so that 'target_filepath' can be
|
|
|
|
|
# added to the Targets object of the hashed bin.
|
|
|
|
|
hashed_bin_name = None
|
|
|
|
|
for delegation in roleinfo['delegations']['roles']:
|
|
|
|
|
if path_hash_prefix in delegation['path_hash_prefixes']:
|
|
|
|
|
hashed_bin_name = delegation['name']
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
# 'self._delegated_roles' is keyed by relative rolenames, so update
|
|
|
|
|
# 'hashed_bin_name'.
|
|
|
|
|
if hashed_bin_name is not None:
|
2014-06-18 16:39:01 +00:00
|
|
|
hashed_bin_name = hashed_bin_name[len(self.rolename) + 1:]
|
|
|
|
|
|
|
|
|
|
# 'method_name' should be one of the supported methods of the Targets()
|
|
|
|
|
# class.
|
|
|
|
|
getattr(self._delegated_roles[hashed_bin_name], method_name)(target_filepath)
|
2014-04-30 16:46:37 +00:00
|
|
|
|
|
|
|
|
else:
|
2014-06-18 16:39:01 +00:00
|
|
|
raise tuf.Error(target_filepath + ' not found in any of the bins.')
|
2014-04-30 16:46:37 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2013-11-15 01:36:28 +00:00
|
|
|
@property
|
|
|
|
|
def delegations(self):
|
|
|
|
|
"""
|
2013-12-18 16:49:28 +00:00
|
|
|
<Purpose>
|
|
|
|
|
A getter method that returns the delegations made by this Targets role.
|
2013-11-15 01:36:28 +00:00
|
|
|
|
2013-12-18 16:49:28 +00:00
|
|
|
>>>
|
|
|
|
|
>>>
|
|
|
|
|
>>>
|
|
|
|
|
|
|
|
|
|
<Arguments>
|
|
|
|
|
None.
|
|
|
|
|
|
|
|
|
|
<Exceptions>
|
|
|
|
|
tuf.UnknownRoleError, if this Targets' rolename does not exist in
|
|
|
|
|
'tuf.roledb'.
|
|
|
|
|
|
|
|
|
|
<Side Effects>
|
|
|
|
|
None.
|
|
|
|
|
|
|
|
|
|
<Returns>
|
2014-01-22 17:52:55 +00:00
|
|
|
A list containing the Targets objects of this Targets' delegations.
|
2013-12-18 16:49:28 +00:00
|
|
|
"""
|
2013-11-15 01:36:28 +00:00
|
|
|
|
2014-05-27 17:55:48 +00:00
|
|
|
return list(self._delegated_roles.values())
|
2013-11-15 01:36:28 +00:00
|
|
|
|
|
|
|
|
|
2013-10-22 18:02:01 +00:00
|
|
|
|
2013-11-25 17:04:21 +00:00
|
|
|
|
|
|
|
|
|
2013-10-22 18:02:01 +00:00
|
|
|
def create_new_repository(repository_directory):
|
|
|
|
|
"""
|
|
|
|
|
<Purpose>
|
2013-12-16 19:20:09 +00:00
|
|
|
Create a new repository, instantiate barebones metadata for the top-level
|
|
|
|
|
roles, and return a Repository object. On disk, create_new_repository()
|
|
|
|
|
only creates the directories needed to hold the metadata and targets files.
|
|
|
|
|
The repository object returned may be modified to update the newly created
|
|
|
|
|
repository. The methods of the returned object may be called to create
|
|
|
|
|
actual repository files (e.g., repository.write()).
|
2013-10-22 18:02:01 +00:00
|
|
|
|
|
|
|
|
<Arguments>
|
|
|
|
|
repository_directory:
|
2013-12-16 19:20:09 +00:00
|
|
|
The directory that will eventually hold the metadata and target files of
|
|
|
|
|
the TUF repository.
|
2013-10-22 18:02:01 +00:00
|
|
|
|
|
|
|
|
<Exceptions>
|
2013-12-16 19:20:09 +00:00
|
|
|
tuf.FormatError, if the arguments are improperly formatted.
|
2013-10-22 18:02:01 +00:00
|
|
|
|
|
|
|
|
<Side Effects>
|
2013-12-16 19:20:09 +00:00
|
|
|
The 'repository_directory' is created if it does not exist, including its
|
|
|
|
|
metadata and targets sub-directories.
|
2013-10-22 18:02:01 +00:00
|
|
|
|
|
|
|
|
<Returns>
|
2014-01-23 17:03:31 +00:00
|
|
|
A 'tuf.repository_tool.Repository' object.
|
2013-10-22 18:02:01 +00:00
|
|
|
"""
|
2013-11-05 13:22:21 +00:00
|
|
|
|
2013-12-16 19:20:09 +00:00
|
|
|
# Does 'repository_directory' have the correct format?
|
|
|
|
|
# Ensure the arguments have the appropriate number of objects and object
|
|
|
|
|
# types, and that all dict keys are properly named.
|
|
|
|
|
# Raise 'tuf.FormatError' if there is a mismatch.
|
2013-11-05 13:22:21 +00:00
|
|
|
tuf.formats.PATH_SCHEMA.check_match(repository_directory)
|
|
|
|
|
|
2013-12-16 19:20:09 +00:00
|
|
|
# Set the repository, metadata, and targets directories. These directories
|
|
|
|
|
# are created if they do not exist.
|
2013-10-29 19:23:26 +00:00
|
|
|
repository_directory = os.path.abspath(repository_directory)
|
2013-10-22 18:02:01 +00:00
|
|
|
metadata_directory = None
|
|
|
|
|
targets_directory = None
|
|
|
|
|
|
2013-10-29 19:23:26 +00:00
|
|
|
# Try to create 'repository_directory' if it does not exist.
|
2013-10-22 18:02:01 +00:00
|
|
|
try:
|
2016-01-28 20:22:20 +00:00
|
|
|
logger.info('Creating ' + repr(repository_directory))
|
2013-10-29 19:23:26 +00:00
|
|
|
os.makedirs(repository_directory)
|
2013-12-16 19:20:09 +00:00
|
|
|
|
2013-10-29 19:23:26 +00:00
|
|
|
# 'OSError' raised if the leaf directory already exists or cannot be created.
|
2013-12-16 19:20:09 +00:00
|
|
|
# Check for case where 'repository_directory' has already been created.
|
2014-04-22 19:03:42 +00:00
|
|
|
except OSError as e:
|
2013-10-22 18:02:01 +00:00
|
|
|
if e.errno == errno.EEXIST:
|
2013-10-29 19:23:26 +00:00
|
|
|
pass
|
2013-10-22 18:02:01 +00:00
|
|
|
else:
|
|
|
|
|
raise
|
2013-11-05 13:22:21 +00:00
|
|
|
|
2013-12-16 19:20:09 +00:00
|
|
|
# Set the metadata and targets directories. The metadata directory is a
|
|
|
|
|
# staged one so that the "live" repository is not affected. The
|
|
|
|
|
# staged metadata changes may be moved over to "live" after all updated
|
|
|
|
|
# have been completed.
|
2013-10-29 19:23:26 +00:00
|
|
|
metadata_directory = \
|
2013-11-22 16:13:11 +00:00
|
|
|
os.path.join(repository_directory, METADATA_STAGED_DIRECTORY_NAME)
|
2013-10-29 19:23:26 +00:00
|
|
|
targets_directory = \
|
|
|
|
|
os.path.join(repository_directory, TARGETS_DIRECTORY_NAME)
|
2013-10-22 18:02:01 +00:00
|
|
|
|
2013-10-29 19:23:26 +00:00
|
|
|
# Try to create the metadata directory that will hold all of the metadata
|
2014-01-29 16:26:56 +00:00
|
|
|
# files, such as 'root.json' and 'snapshot.json'.
|
2013-10-22 18:02:01 +00:00
|
|
|
try:
|
2016-01-28 20:22:20 +00:00
|
|
|
logger.info('Creating ' + repr(metadata_directory))
|
2013-10-22 18:02:01 +00:00
|
|
|
os.mkdir(metadata_directory)
|
2013-12-16 19:20:09 +00:00
|
|
|
|
|
|
|
|
# 'OSError' raised if the leaf directory already exists or cannot be created.
|
2014-04-22 19:03:42 +00:00
|
|
|
except OSError as e:
|
2013-10-22 18:02:01 +00:00
|
|
|
if e.errno == errno.EEXIST:
|
|
|
|
|
pass
|
|
|
|
|
else:
|
|
|
|
|
raise
|
2013-10-29 19:23:26 +00:00
|
|
|
|
|
|
|
|
# Try to create the targets directory that will hold all of the target files.
|
|
|
|
|
try:
|
2016-01-28 20:22:20 +00:00
|
|
|
logger.info('Creating ' + repr(targets_directory))
|
2013-10-29 19:23:26 +00:00
|
|
|
os.mkdir(targets_directory)
|
2014-02-13 15:03:25 +00:00
|
|
|
|
2014-04-22 19:03:42 +00:00
|
|
|
except OSError as e:
|
2013-10-29 19:23:26 +00:00
|
|
|
if e.errno == errno.EEXIST:
|
|
|
|
|
pass
|
|
|
|
|
else:
|
|
|
|
|
raise
|
2013-12-16 19:20:09 +00:00
|
|
|
|
|
|
|
|
# Create the bare bones repository object, where only the top-level roles
|
|
|
|
|
# have been set and contain default values (e.g., Root roles has a threshold
|
|
|
|
|
# of 1, expires 1 year into the future, etc.)
|
2013-10-29 19:23:26 +00:00
|
|
|
repository = Repository(repository_directory, metadata_directory,
|
|
|
|
|
targets_directory)
|
|
|
|
|
|
|
|
|
|
return repository
|
2013-10-22 18:02:01 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2014-06-03 18:32:44 +00:00
|
|
|
|
|
|
|
|
|
2013-11-12 20:00:26 +00:00
|
|
|
def load_repository(repository_directory):
|
2013-10-22 18:02:01 +00:00
|
|
|
"""
|
|
|
|
|
<Purpose>
|
2013-11-12 20:00:26 +00:00
|
|
|
Return a repository object containing the contents of metadata files loaded
|
|
|
|
|
from the repository.
|
2013-10-22 18:02:01 +00:00
|
|
|
|
|
|
|
|
<Arguments>
|
2013-11-05 13:22:21 +00:00
|
|
|
repository_directory:
|
|
|
|
|
|
2013-10-22 18:02:01 +00:00
|
|
|
<Exceptions>
|
2013-11-12 20:00:26 +00:00
|
|
|
tuf.FormatError, if 'repository_directory' or any of the metadata files
|
2014-04-08 18:56:39 +00:00
|
|
|
are improperly formatted.
|
2013-10-22 18:02:01 +00:00
|
|
|
|
2014-04-08 18:56:39 +00:00
|
|
|
tuf.RepositoryError, if the Root role cannot be found. At a minimum,
|
|
|
|
|
a repository must contain 'root.json'
|
|
|
|
|
|
2013-10-22 18:02:01 +00:00
|
|
|
<Side Effects>
|
2013-11-12 20:00:26 +00:00
|
|
|
All the metadata files found in the repository are loaded and their contents
|
2014-01-23 17:03:31 +00:00
|
|
|
stored in a repository_tool.Repository object.
|
2013-10-22 18:02:01 +00:00
|
|
|
|
|
|
|
|
<Returns>
|
2014-01-23 17:03:31 +00:00
|
|
|
repository_tool.Repository object.
|
2013-10-22 18:02:01 +00:00
|
|
|
"""
|
2013-11-12 20:00:26 +00:00
|
|
|
|
|
|
|
|
# Does 'repository_directory' have the correct format?
|
|
|
|
|
# Raise 'tuf.FormatError' if there is a mismatch.
|
|
|
|
|
tuf.formats.PATH_SCHEMA.check_match(repository_directory)
|
|
|
|
|
|
|
|
|
|
# Load top-level metadata.
|
|
|
|
|
repository_directory = os.path.abspath(repository_directory)
|
|
|
|
|
metadata_directory = os.path.join(repository_directory,
|
2013-11-22 16:13:11 +00:00
|
|
|
METADATA_STAGED_DIRECTORY_NAME)
|
2013-11-12 20:00:26 +00:00
|
|
|
targets_directory = os.path.join(repository_directory,
|
|
|
|
|
TARGETS_DIRECTORY_NAME)
|
2013-11-14 19:24:07 +00:00
|
|
|
|
Finish initial implementation of Issue #151 and reading consistent snapshots.
Support multiple hash algorithms, where the generated digests of metadata and
target files is included in metadata (and filenames if 'consistent_snapshots'
is True). Previously, only a single hash algorithm was supported, and it was
set by default to 'sha256' in code. Repository maintainers may now choose any,
and/or multiple, hash algorithms from those supported by TUF. By default,
'sha256' is used when generating digests.
Support the recent change to the TUF specification, where writing consistent
snapshots may include N versions of identical metadata and targets, if N hash
algorithms is used by the repository when generating metadata.
Update code affected by the recent changes to the specification, such as
targets that may include digests in their filename.
Support consistent snapshots of compressed metadata, including repositories
that provide multiple versions of metadata with different digests included
in their filenames.
The repository tools can now load repositories that include consistent snapshots
of metadata and targets, including those with multiple (i.e., multiple digests
prepended to filenames) consistent snapshots of files.
The client code may now read repositories with 'consistent_snapshots': true in
Root metadata, and properly request and update files with digests included.
2014-01-17 16:05:40 +00:00
|
|
|
# The Repository() object loaded (i.e., containing all the metadata roles
|
|
|
|
|
# found) and returned.
|
2013-11-12 20:00:26 +00:00
|
|
|
repository = Repository(repository_directory, metadata_directory,
|
|
|
|
|
targets_directory)
|
|
|
|
|
|
2014-06-03 18:32:44 +00:00
|
|
|
filenames = repo_lib.get_metadata_filenames(metadata_directory)
|
2013-11-12 20:00:26 +00:00
|
|
|
|
2015-10-27 21:49:25 +00:00
|
|
|
# The Root file is always available without a version number (a consistent
|
|
|
|
|
# snapshot) attached to the filename. Store the 'consistent_snapshot' value
|
|
|
|
|
# and read the loaded Root file so that other metadata files may be located.
|
2014-01-30 13:11:35 +00:00
|
|
|
consistent_snapshot = False
|
2013-11-12 20:00:26 +00:00
|
|
|
|
Finish initial implementation of Issue #151 and reading consistent snapshots.
Support multiple hash algorithms, where the generated digests of metadata and
target files is included in metadata (and filenames if 'consistent_snapshots'
is True). Previously, only a single hash algorithm was supported, and it was
set by default to 'sha256' in code. Repository maintainers may now choose any,
and/or multiple, hash algorithms from those supported by TUF. By default,
'sha256' is used when generating digests.
Support the recent change to the TUF specification, where writing consistent
snapshots may include N versions of identical metadata and targets, if N hash
algorithms is used by the repository when generating metadata.
Update code affected by the recent changes to the specification, such as
targets that may include digests in their filename.
Support consistent snapshots of compressed metadata, including repositories
that provide multiple versions of metadata with different digests included
in their filenames.
The repository tools can now load repositories that include consistent snapshots
of metadata and targets, including those with multiple (i.e., multiple digests
prepended to filenames) consistent snapshots of files.
The client code may now read repositories with 'consistent_snapshots': true in
Root metadata, and properly request and update files with digests included.
2014-01-17 16:05:40 +00:00
|
|
|
# Load the metadata of the top-level roles (i.e., Root, Timestamp, Targets,
|
2014-01-29 16:26:56 +00:00
|
|
|
# and Snapshot).
|
2014-06-03 18:32:44 +00:00
|
|
|
repository, consistent_snapshot = repo_lib._load_top_level_metadata(repository,
|
|
|
|
|
filenames)
|
2013-11-12 20:00:26 +00:00
|
|
|
|
2016-02-19 21:07:19 +00:00
|
|
|
# Load delegated targets metadata.
|
|
|
|
|
# Walk the 'targets/' directory and generate the fileinfo of all the files
|
|
|
|
|
# listed. This information is stored in the 'meta' field of the snapshot
|
|
|
|
|
# metadata object.
|
2013-11-12 20:00:26 +00:00
|
|
|
targets_objects = {}
|
Finish initial implementation of Issue #151 and reading consistent snapshots.
Support multiple hash algorithms, where the generated digests of metadata and
target files is included in metadata (and filenames if 'consistent_snapshots'
is True). Previously, only a single hash algorithm was supported, and it was
set by default to 'sha256' in code. Repository maintainers may now choose any,
and/or multiple, hash algorithms from those supported by TUF. By default,
'sha256' is used when generating digests.
Support the recent change to the TUF specification, where writing consistent
snapshots may include N versions of identical metadata and targets, if N hash
algorithms is used by the repository when generating metadata.
Update code affected by the recent changes to the specification, such as
targets that may include digests in their filename.
Support consistent snapshots of compressed metadata, including repositories
that provide multiple versions of metadata with different digests included
in their filenames.
The repository tools can now load repositories that include consistent snapshots
of metadata and targets, including those with multiple (i.e., multiple digests
prepended to filenames) consistent snapshots of files.
The client code may now read repositories with 'consistent_snapshots': true in
Root metadata, and properly request and update files with digests included.
2014-01-17 16:05:40 +00:00
|
|
|
loaded_metadata = []
|
2013-11-12 20:00:26 +00:00
|
|
|
targets_objects['targets'] = repository.targets
|
2016-02-19 21:07:19 +00:00
|
|
|
targets_metadata_directory = os.path.join(metadata_directory,
|
|
|
|
|
TARGETS_DIRECTORY_NAME)
|
|
|
|
|
if os.path.exists(targets_metadata_directory) and \
|
|
|
|
|
os.path.isdir(targets_metadata_directory):
|
|
|
|
|
for root, directories, files in os.walk(targets_metadata_directory):
|
|
|
|
|
|
|
|
|
|
# 'files' here is a list of target file names.
|
|
|
|
|
for basename in files:
|
|
|
|
|
metadata_path = os.path.join(root, basename)
|
|
|
|
|
metadata_name = \
|
|
|
|
|
metadata_path[len(metadata_directory):].lstrip(os.path.sep)
|
|
|
|
|
|
|
|
|
|
# Strip the version number if 'consistent_snapshot' is True.
|
|
|
|
|
# Example: 'targets/unclaimed/10.django.json' -->
|
|
|
|
|
# 'targets/unclaimed/django.json'
|
2015-10-20 13:16:39 +00:00
|
|
|
metadata_name, version_number_junk = \
|
|
|
|
|
repo_lib._strip_consistent_snapshot_version_number(metadata_name,
|
2014-06-03 18:32:44 +00:00
|
|
|
consistent_snapshot)
|
Finish initial implementation of Issue #151 and reading consistent snapshots.
Support multiple hash algorithms, where the generated digests of metadata and
target files is included in metadata (and filenames if 'consistent_snapshots'
is True). Previously, only a single hash algorithm was supported, and it was
set by default to 'sha256' in code. Repository maintainers may now choose any,
and/or multiple, hash algorithms from those supported by TUF. By default,
'sha256' is used when generating digests.
Support the recent change to the TUF specification, where writing consistent
snapshots may include N versions of identical metadata and targets, if N hash
algorithms is used by the repository when generating metadata.
Update code affected by the recent changes to the specification, such as
targets that may include digests in their filename.
Support consistent snapshots of compressed metadata, including repositories
that provide multiple versions of metadata with different digests included
in their filenames.
The repository tools can now load repositories that include consistent snapshots
of metadata and targets, including those with multiple (i.e., multiple digests
prepended to filenames) consistent snapshots of files.
The client code may now read repositories with 'consistent_snapshots': true in
Root metadata, and properly request and update files with digests included.
2014-01-17 16:05:40 +00:00
|
|
|
|
2013-11-22 16:13:11 +00:00
|
|
|
if metadata_name.endswith(METADATA_EXTENSION):
|
|
|
|
|
extension_length = len(METADATA_EXTENSION)
|
|
|
|
|
metadata_name = metadata_name[:-extension_length]
|
2014-04-17 16:27:28 +00:00
|
|
|
|
2013-11-22 16:13:11 +00:00
|
|
|
else:
|
|
|
|
|
continue
|
2015-10-27 20:11:11 +00:00
|
|
|
|
Finish initial implementation of Issue #151 and reading consistent snapshots.
Support multiple hash algorithms, where the generated digests of metadata and
target files is included in metadata (and filenames if 'consistent_snapshots'
is True). Previously, only a single hash algorithm was supported, and it was
set by default to 'sha256' in code. Repository maintainers may now choose any,
and/or multiple, hash algorithms from those supported by TUF. By default,
'sha256' is used when generating digests.
Support the recent change to the TUF specification, where writing consistent
snapshots may include N versions of identical metadata and targets, if N hash
algorithms is used by the repository when generating metadata.
Update code affected by the recent changes to the specification, such as
targets that may include digests in their filename.
Support consistent snapshots of compressed metadata, including repositories
that provide multiple versions of metadata with different digests included
in their filenames.
The repository tools can now load repositories that include consistent snapshots
of metadata and targets, including those with multiple (i.e., multiple digests
prepended to filenames) consistent snapshots of files.
The client code may now read repositories with 'consistent_snapshots': true in
Root metadata, and properly request and update files with digests included.
2014-01-17 16:05:40 +00:00
|
|
|
# Keep a store metadata previously loaded metadata to prevent
|
|
|
|
|
# re-loading duplicate versions. Duplicate versions may occur with
|
2015-10-27 20:11:11 +00:00
|
|
|
# 'consistent_snapshot', where the same metadata may be available in
|
Finish initial implementation of Issue #151 and reading consistent snapshots.
Support multiple hash algorithms, where the generated digests of metadata and
target files is included in metadata (and filenames if 'consistent_snapshots'
is True). Previously, only a single hash algorithm was supported, and it was
set by default to 'sha256' in code. Repository maintainers may now choose any,
and/or multiple, hash algorithms from those supported by TUF. By default,
'sha256' is used when generating digests.
Support the recent change to the TUF specification, where writing consistent
snapshots may include N versions of identical metadata and targets, if N hash
algorithms is used by the repository when generating metadata.
Update code affected by the recent changes to the specification, such as
targets that may include digests in their filename.
Support consistent snapshots of compressed metadata, including repositories
that provide multiple versions of metadata with different digests included
in their filenames.
The repository tools can now load repositories that include consistent snapshots
of metadata and targets, including those with multiple (i.e., multiple digests
prepended to filenames) consistent snapshots of files.
The client code may now read repositories with 'consistent_snapshots': true in
Root metadata, and properly request and update files with digests included.
2014-01-17 16:05:40 +00:00
|
|
|
# multiples files (the different hash is included in each filename.
|
|
|
|
|
if metadata_name in loaded_metadata:
|
|
|
|
|
continue
|
|
|
|
|
|
2013-11-12 20:00:26 +00:00
|
|
|
signable = None
|
|
|
|
|
try:
|
|
|
|
|
signable = tuf.util.load_json_file(metadata_path)
|
2014-02-13 15:03:25 +00:00
|
|
|
|
2016-01-28 20:22:20 +00:00
|
|
|
except (ValueError, IOError):
|
2013-11-12 20:00:26 +00:00
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
metadata_object = signable['signed']
|
2014-01-23 17:03:31 +00:00
|
|
|
|
|
|
|
|
# Extract the metadata attributes 'metadata_name' and update its
|
|
|
|
|
# corresponding roleinfo.
|
2013-11-12 20:00:26 +00:00
|
|
|
roleinfo = tuf.roledb.get_roleinfo(metadata_name)
|
|
|
|
|
roleinfo['signatures'].extend(signable['signatures'])
|
|
|
|
|
roleinfo['version'] = metadata_object['version']
|
|
|
|
|
roleinfo['expires'] = metadata_object['expires']
|
2014-06-23 17:33:01 +00:00
|
|
|
for filepath, fileinfo in six.iteritems(metadata_object['targets']):
|
|
|
|
|
roleinfo['paths'].update({filepath: fileinfo.get('custom', {})})
|
2013-11-25 01:23:12 +00:00
|
|
|
roleinfo['delegations'] = metadata_object['delegations']
|
|
|
|
|
|
2014-06-10 12:47:10 +00:00
|
|
|
if os.path.exists(metadata_path + '.gz'):
|
2013-11-14 19:24:07 +00:00
|
|
|
roleinfo['compressions'].append('gz')
|
2014-04-01 00:58:11 +00:00
|
|
|
|
|
|
|
|
# The roleinfo of 'metadata_name' should have been initialized with
|
|
|
|
|
# defaults when it was loaded from its parent role.
|
2014-06-03 18:32:44 +00:00
|
|
|
if repo_lib._metadata_is_partially_loaded(metadata_name, signable, roleinfo):
|
2014-04-01 00:58:11 +00:00
|
|
|
roleinfo['partial_loaded'] = True
|
2013-12-11 19:14:16 +00:00
|
|
|
|
2013-11-12 20:00:26 +00:00
|
|
|
tuf.roledb.update_roleinfo(metadata_name, roleinfo)
|
Finish initial implementation of Issue #151 and reading consistent snapshots.
Support multiple hash algorithms, where the generated digests of metadata and
target files is included in metadata (and filenames if 'consistent_snapshots'
is True). Previously, only a single hash algorithm was supported, and it was
set by default to 'sha256' in code. Repository maintainers may now choose any,
and/or multiple, hash algorithms from those supported by TUF. By default,
'sha256' is used when generating digests.
Support the recent change to the TUF specification, where writing consistent
snapshots may include N versions of identical metadata and targets, if N hash
algorithms is used by the repository when generating metadata.
Update code affected by the recent changes to the specification, such as
targets that may include digests in their filename.
Support consistent snapshots of compressed metadata, including repositories
that provide multiple versions of metadata with different digests included
in their filenames.
The repository tools can now load repositories that include consistent snapshots
of metadata and targets, including those with multiple (i.e., multiple digests
prepended to filenames) consistent snapshots of files.
The client code may now read repositories with 'consistent_snapshots': true in
Root metadata, and properly request and update files with digests included.
2014-01-17 16:05:40 +00:00
|
|
|
loaded_metadata.append(metadata_name)
|
2013-11-12 20:00:26 +00:00
|
|
|
|
2014-01-23 17:03:31 +00:00
|
|
|
# Generate the Targets objects of the delegated roles of
|
|
|
|
|
# 'metadata_name' and update the parent role Targets object.
|
2013-11-12 20:00:26 +00:00
|
|
|
new_targets_object = Targets(targets_directory, metadata_name, roleinfo)
|
2013-11-25 17:04:21 +00:00
|
|
|
targets_object = \
|
|
|
|
|
targets_objects[tuf.roledb.get_parent_rolename(metadata_name)]
|
2013-11-25 01:23:12 +00:00
|
|
|
targets_objects[metadata_name] = new_targets_object
|
Address Issues #165, #158, and #147.
Issue 147: Finalize conversion of all written metadata behavior. This commit ensures that compressed and uncompressed metadata is also written as outlined in the issue.
Issue 158: As requested, updater.refresh() may now unsafely fetch (i.e., unknown file size and hash) Root metadata if valid top-level metadata cannot be downloaded successfully (e.g., top-level keys may have been revoked). The repository must also sign the new Root file (at least until all clients have updated) with any revoked keys so that clients may successfully update. After unsafely updating Root, the top-level metadata is updated again as normal (and only once to avoid an infinite loop). By default, refresh() unsafely updates Root if only invalid top-level metadata can be downloaded, although this behavior may be overriden by the caller if they wish. Changed default behavior: refresh(self, unsafely_update_root_if_necessary=True)
Issue 165: Delegated roles are no longer added as attributes of a Targets object by libtuf.py (e.g., repository.targets.delegated_role). The previous bahavior restricted rolenames to Python identifiers (i.e., can only include letters, numbers, the underscore character, and must start with a nonnumeric character). Now, delegated roles may be referenced as strings (e.g., repository.targets('recently-claimed')) and include characters other than '_'. In addition, methods have been added to return all the delegated rolesnames of a target (e.g., repository.targets.get_delegated_rolenames()) and the immediate delegated Target objects of a role. Previous behavior: repository.targets.unclaimed.django.version = 8
Current behavior: repository.targets('unclaimed')('django').version = 8.
2014-01-02 17:18:44 +00:00
|
|
|
|
2014-01-05 19:40:45 +00:00
|
|
|
targets_object._delegated_roles[(os.path.basename(metadata_name))] = \
|
Address Issues #165, #158, and #147.
Issue 147: Finalize conversion of all written metadata behavior. This commit ensures that compressed and uncompressed metadata is also written as outlined in the issue.
Issue 158: As requested, updater.refresh() may now unsafely fetch (i.e., unknown file size and hash) Root metadata if valid top-level metadata cannot be downloaded successfully (e.g., top-level keys may have been revoked). The repository must also sign the new Root file (at least until all clients have updated) with any revoked keys so that clients may successfully update. After unsafely updating Root, the top-level metadata is updated again as normal (and only once to avoid an infinite loop). By default, refresh() unsafely updates Root if only invalid top-level metadata can be downloaded, although this behavior may be overriden by the caller if they wish. Changed default behavior: refresh(self, unsafely_update_root_if_necessary=True)
Issue 165: Delegated roles are no longer added as attributes of a Targets object by libtuf.py (e.g., repository.targets.delegated_role). The previous bahavior restricted rolenames to Python identifiers (i.e., can only include letters, numbers, the underscore character, and must start with a nonnumeric character). Now, delegated roles may be referenced as strings (e.g., repository.targets('recently-claimed')) and include characters other than '_'. In addition, methods have been added to return all the delegated rolesnames of a target (e.g., repository.targets.get_delegated_rolenames()) and the immediate delegated Target objects of a role. Previous behavior: repository.targets.unclaimed.django.version = 8
Current behavior: repository.targets('unclaimed')('django').version = 8.
2014-01-02 17:18:44 +00:00
|
|
|
new_targets_object
|
2013-11-12 20:00:26 +00:00
|
|
|
|
2014-03-06 00:40:05 +00:00
|
|
|
# Extract the keys specified in the delegations field of the Targets
|
|
|
|
|
# role. Add 'key_object' to the list of recognized keys. Keys may be
|
|
|
|
|
# shared, so do not raise an exception if 'key_object' has already been
|
|
|
|
|
# added. In contrast to the methods that may add duplicate keys, do not
|
|
|
|
|
# log a warning here as there may be many such duplicate key warnings.
|
|
|
|
|
# The repository maintainer should have also been made aware of the
|
2014-03-05 16:38:23 +00:00
|
|
|
# duplicate key when it was added.
|
2014-05-27 17:55:48 +00:00
|
|
|
for key_metadata in six.itervalues(metadata_object['delegations']['keys']):
|
2013-11-12 20:00:26 +00:00
|
|
|
key_object = tuf.keys.format_metadata_to_key(key_metadata)
|
|
|
|
|
try:
|
|
|
|
|
tuf.keydb.add_key(key_object)
|
2014-02-13 15:03:25 +00:00
|
|
|
|
2016-01-28 20:22:20 +00:00
|
|
|
except tuf.KeyAlreadyExistsError:
|
2013-11-12 20:00:26 +00:00
|
|
|
pass
|
2014-01-23 17:03:31 +00:00
|
|
|
|
|
|
|
|
# Add the delegated role's initial roleinfo, to be fully populated
|
|
|
|
|
# when its metadata file is next loaded in the os.walk() iteration.
|
2013-11-12 20:00:26 +00:00
|
|
|
for role in metadata_object['delegations']['roles']:
|
|
|
|
|
rolename = role['name']
|
2013-12-11 19:14:16 +00:00
|
|
|
roleinfo = {'name': role['name'], 'keyids': role['keyids'],
|
2013-12-04 14:14:06 +00:00
|
|
|
'threshold': role['threshold'],
|
2013-12-11 19:14:16 +00:00
|
|
|
'compressions': [''], 'signing_keyids': [],
|
2013-12-16 13:45:40 +00:00
|
|
|
'signatures': [],
|
2014-08-20 13:49:57 +00:00
|
|
|
'paths': {},
|
2013-12-16 13:45:40 +00:00
|
|
|
'partial_loaded': False,
|
|
|
|
|
'delegations': {'keys': {},
|
|
|
|
|
'roles': []}}
|
2013-11-25 01:23:12 +00:00
|
|
|
tuf.roledb.add_role(rolename, roleinfo)
|
|
|
|
|
|
2013-11-12 20:00:26 +00:00
|
|
|
return repository
|
|
|
|
|
|
2013-10-22 18:02:01 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
|
# The interactive sessions of the documentation strings can
|
2014-01-23 17:03:31 +00:00
|
|
|
# be tested by running repository_tool.py as a standalone module:
|
|
|
|
|
# $ python repository_tool.py.
|
2013-10-22 18:02:01 +00:00
|
|
|
import doctest
|
|
|
|
|
doctest.testmod()
|