Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 8 additions & 26 deletions tests/test_formats.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,15 @@
import unittest
import datetime
import sys
import os

import tuf
import tuf.formats

import utils

import securesystemslib
import securesystemslib.util
import six


Expand Down Expand Up @@ -778,20 +780,10 @@ def test_parse_base64(self):

def test_make_signable(self):
# Test conditions for expected make_signable() behavior.
root = {'_type': 'root',
'spec_version': '1.0.0',
'version': 8,
'consistent_snapshot': False,
'expires': '1985-10-21T13:20:00Z',
'keys': {'123abc': {'keytype': 'rsa',
'scheme': 'rsassa-pss-sha256',
'keyval': {'public': 'pubkey',
'private': 'privkey'}}},
'roles': {'root': {'keyids': ['123abc'],
'threshold': 1,
'paths': ['path1/', 'path2']}}}

SIGNABLE_SCHEMA = tuf.formats.SIGNABLE_SCHEMA
root_file = os.path.join('repository_data', 'repository', 'metadata',
'root.json')
root = securesystemslib.util.load_json_file(root_file)
self.assertTrue(SIGNABLE_SCHEMA.matches(tuf.formats.make_signable(root)))
signable = tuf.formats.make_signable(root)
self.assertEqual('root', tuf.formats.check_signable_object_format(signable))
Expand Down Expand Up @@ -902,19 +894,9 @@ def test_expected_meta_rolename(self):

def test_check_signable_object_format(self):
# Test condition for a valid argument.
root = {'_type': 'root',
'spec_version': '1.0.0',
'version': 8,
'consistent_snapshot': False,
'expires': '1985-10-21T13:20:00Z',
'keys': {'123abc': {'keytype': 'rsa',
'scheme': 'rsassa-pss-sha256',
'keyval': {'public': 'pubkey',
'private': 'privkey'}}},
'roles': {'root': {'keyids': ['123abc'],
'threshold': 1,
'paths': ['path1/', 'path2']}}}

root_file = os.path.join('repository_data', 'repository', 'metadata',
'root.json')
root = securesystemslib.util.load_json_file(root_file)
root = tuf.formats.make_signable(root)
self.assertEqual('root', tuf.formats.check_signable_object_format(root))

Expand Down
23 changes: 22 additions & 1 deletion tests/test_repository_lib.py
Original file line number Diff line number Diff line change
Expand Up @@ -885,6 +885,8 @@ def test__generate_and_write_metadata(self):
# (specifically 'snapshot') and keys to be available in 'tuf.roledb'.
tuf.roledb.create_roledb_from_root_metadata(root_signable['signed'],
repository_name)
tuf.keydb.create_keydb_from_root_metadata(root_signable['signed'],
repository_name)
temporary_directory = tempfile.mkdtemp(dir=self.temporary_directory)
targets_directory = os.path.join(temporary_directory, 'targets')
os.mkdir(targets_directory)
Expand All @@ -897,6 +899,14 @@ def test__generate_and_write_metadata(self):
securesystemslib.util.ensure_parent_dir(obsolete_metadata)
shutil.copyfile(targets_metadata, obsolete_metadata)

keystore_path = os.path.join('repository_data', 'keystore')
targets_private_keypath = os.path.join(keystore_path, 'targets_key')
targets_private_key = repo_lib.import_ed25519_privatekey_from_file(targets_private_keypath,
'password')
tuf.keydb.remove_key(targets_private_key['keyid'],
repository_name=repository_name)
tuf.keydb.add_key(targets_private_key, repository_name=repository_name)

# Verify that obsolete metadata (a metadata file exists on disk, but the
# role is unavailable in 'tuf.roledb'). First add the obsolete
# role to 'tuf.roledb' so that its metadata file can be written to disk.
Expand All @@ -906,6 +916,7 @@ def test__generate_and_write_metadata(self):
tuf.formats.unix_timestamp_to_datetime(int(time.time() + 86400))
expiration = expiration.isoformat() + 'Z'
targets_roleinfo['expires'] = expiration
targets_roleinfo['signing_keyids'] = targets_roleinfo['keyids']
tuf.roledb.add_role('obsolete_role', targets_roleinfo,
repository_name=repository_name)

Expand Down Expand Up @@ -1004,14 +1015,24 @@ def test__load_top_level_metadata(self):
roleinfo['version'] = 1
tuf.roledb.add_role('role1', roleinfo, repository_name)

keystore_path = os.path.join('repository_data', 'keystore')
root_privkey_path = os.path.join(keystore_path, 'root_key')
targets_privkey_path = os.path.join(keystore_path, 'targets_key')
snapshot_privkey_path = os.path.join(keystore_path, 'snapshot_key')
timestamp_privkey_path = os.path.join(keystore_path, 'timestamp_key')

repository.root.load_signing_key(repo_lib.import_rsa_privatekey_from_file(root_privkey_path, 'password'))
repository.targets.load_signing_key(repo_lib.import_ed25519_privatekey_from_file(targets_privkey_path, 'password'))
repository.snapshot.load_signing_key(repo_lib.import_ed25519_privatekey_from_file(snapshot_privkey_path, 'password'))
repository.timestamp.load_signing_key(repo_lib.import_ed25519_privatekey_from_file(timestamp_privkey_path, 'password'))

# Partially write all top-level roles (we increase the threshold of each
# top-level role so that they are flagged as partially written.
repository.root.threshold = repository.root.threshold + 1
repository.snapshot.threshold = repository.snapshot.threshold + 1
repository.targets.threshold = repository.targets.threshold + 1
repository.timestamp.threshold = repository.timestamp.threshold + 1
repository.write('root', )
repository.write('root')
repository.write('snapshot')
repository.write('targets')
repository.write('timestamp')
Expand Down
11 changes: 7 additions & 4 deletions tests/test_updater_root_rotation_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -357,10 +357,13 @@ def test_root_rotation_max(self):
def test_root_rotation_missing_keys(self):
repository = repo_tool.load_repository(self.repository_directory)

# A partially written root.json (threshold = 1, and not signed in this
# case) causes an invalid root chain later.
# A partially written root.json (threshold = 2, and signed with only 1 key)
# causes an invalid root chain later.
repository.root.threshold = 2
repository.root.load_signing_key(self.role_keys['root']['private'])
repository.snapshot.load_signing_key(self.role_keys['snapshot']['private'])
repository.timestamp.load_signing_key(self.role_keys['timestamp']['private'])

repository.write('root')
repository.write('snapshot')
repository.write('timestamp')
Expand All @@ -371,9 +374,9 @@ def test_root_rotation_missing_keys(self):
os.path.join(self.repository_directory, 'metadata'))

# Create a new, valid root.json.
repository.root.threshold = 2
# Still not valid, because it is not written with a threshold of 2
# previous keys
repository.root.add_verification_key(self.role_keys['role1']['public'])
repository.root.load_signing_key(self.role_keys['root']['private'])
repository.root.load_signing_key(self.role_keys['role1']['private'])

repository.writeall()
Expand Down
7 changes: 6 additions & 1 deletion tuf/developer_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -855,7 +855,12 @@ def load_project(project_directory, prefix='', new_targets_location=None,
targets_metadata_path = os.path.join(project_directory, metadata_directory,
project_filename)
signable = securesystemslib.util.load_json_file(targets_metadata_path)
tuf.formats.check_signable_object_format(signable)
try:
tuf.formats.check_signable_object_format(signable)
except tuf.exceptions.UnsignedMetadataError:
# Downgrade the error to a warning because a use case exists where
# metadata may be generated unsigned on one machine and signed on another.
logger.warning('Unsigned metadata object: ' + repr(signable))
targets_metadata = signable['signed']

# Remove the prefix from the metadata.
Expand Down
7 changes: 7 additions & 0 deletions tuf/formats.py
Original file line number Diff line number Diff line change
Expand Up @@ -938,6 +938,9 @@ def check_signable_object_format(signable):
securesystemslib.exceptions.FormatError, if 'signable' does not have the
correct format.

tuf.exceptions.UnsignedMetadataError, if 'signable' does not have any
signatures

<Side Effects>
None.

Expand Down Expand Up @@ -965,6 +968,10 @@ def check_signable_object_format(signable):
six.raise_from(securesystemslib.exceptions.FormatError(
'Unrecognized type ' + repr(role_type)), error)

if not signable['signatures']:
raise tuf.exceptions.UnsignedMetadataError('Signable object of type ' +
repr(role_type) + ' has no signatures ', signable)

# 'securesystemslib.exceptions.FormatError' raised if 'signable' does not
# have a properly formatted role schema.
schema.check_match(signable['signed'])
Expand Down
32 changes: 28 additions & 4 deletions tuf/repository_lib.py
Original file line number Diff line number Diff line change
Expand Up @@ -505,7 +505,13 @@ def _load_top_level_metadata(repository, top_level_filenames, repository_name):
try:
# Initialize the key and role metadata of the top-level roles.
signable = securesystemslib.util.load_json_file(root_filename)
tuf.formats.check_signable_object_format(signable)
try:
tuf.formats.check_signable_object_format(signable)
except tuf.exceptions.UnsignedMetadataError:
# Downgrade the error to a warning because a use case exists where
# metadata may be generated unsigned on one machine and signed on another.
logger.warning('Unsigned metadata object: ' + repr(signable))

root_metadata = signable['signed']
tuf.keydb.create_keydb_from_root_metadata(root_metadata, repository_name)
tuf.roledb.create_roledb_from_root_metadata(root_metadata, repository_name)
Expand Down Expand Up @@ -586,7 +592,13 @@ def _load_top_level_metadata(repository, top_level_filenames, repository_name):

try:
signable = securesystemslib.util.load_json_file(snapshot_filename)
tuf.formats.check_signable_object_format(signable)
try:
tuf.formats.check_signable_object_format(signable)
except tuf.exceptions.UnsignedMetadataError:
# Downgrade the error to a warning because a use case exists where
# metadata may be generated unsigned on one machine and signed on another.
logger.warning('Unsigned metadata object: ' + repr(signable))

snapshot_metadata = signable['signed']

for signature in signable['signatures']:
Expand Down Expand Up @@ -622,7 +634,13 @@ def _load_top_level_metadata(repository, top_level_filenames, repository_name):

try:
signable = securesystemslib.util.load_json_file(targets_filename)
tuf.formats.check_signable_object_format(signable)
try:
tuf.formats.check_signable_object_format(signable)
except tuf.exceptions.UnsignedMetadataError:
# Downgrade the error to a warning because a use case exists where
# metadata may be generated unsigned on one machine and signed on another.
logger.warning('Unsigned metadata object: ' + repr(signable))

targets_metadata = signable['signed']

for signature in signable['signatures']:
Expand Down Expand Up @@ -1862,7 +1880,13 @@ def sign_metadata(metadata_object, keyids, filename, repository_name):

# Raise 'securesystemslib.exceptions.FormatError' if the resulting 'signable'
# is not formatted correctly.
tuf.formats.check_signable_object_format(signable)
try:
tuf.formats.check_signable_object_format(signable)
except tuf.exceptions.UnsignedMetadataError:
# Downgrade the error to a warning because a use case exists where
# metadata may be generated unsigned on one machine and signed on another.
logger.warning('Unsigned metadata object: ' + repr(signable))


return signable

Expand Down