Merge pull request #637 from vladimir-v-diaz/support_non-ecdsa_keys_signing

CLI --sign: Support non-ECDSA keys
This commit is contained in:
Vladimir Diaz 2018-03-02 16:42:29 -05:00 committed by GitHub
commit 62dc96e65b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 75 additions and 29 deletions

View file

@ -80,10 +80,10 @@ $ repo.py --key <keytype> --path </path/to/repo_dir> --pw [my_password], --filen
## Sign metadata ##
Sign, using the specified key argument, the metadata of the role indicated by
--role. If no key argument or --role is given, the Targets role or its key is
used. The Snapshot and Timestamp role are also automatically signed, if
possible.
Sign, using the specified key, the metadata of the role indicated by --role
(must be Targets or a delegated role). If no key argument or --role is given,
the Targets role or its key is used. The Snapshot and Timestamp role are also
automatically signed, if possible.
```Bash
$ repo.py --sign
$ repo.py --sign </path/to/key>
@ -97,8 +97,7 @@ $ repo.py --sign /path/to/timestamp_key --role timestamp
```
Note: In the future, the user might have the option of disabling automatic
signing of Snapshot and Timestamp metadata. Only ECDSA keys are
presently supported with `--sign`, but other key types will be added.
signing of Snapshot and Timestamp metadata.

View file

@ -59,6 +59,7 @@
import tuf.repository_tool as repo_tool
import securesystemslib
from colorama import Fore
# See 'log.py' to learn how logging is handled in TUF.
logger = logging.getLogger('tuf.scripts.repo')
@ -79,7 +80,7 @@
STAGED_METADATA_DIR = 'metadata.staged'
METADATA_DIR = 'metadata'
SUPPORTED_KEY_TYPES = ['ed25519', 'ecdsa-sha2-nistp256', 'rsa']
def process_arguments(parsed_arguments):
"""
@ -159,7 +160,7 @@ def delegate(parsed_arguments):
parsed_arguments.terminating, list_of_targets=None,
path_hash_prefixes=None)
targets_private = repo_tool.import_ecdsa_privatekey_from_file(
targets_private = import_privatekey_from_file(
os.path.join(parsed_arguments.path, KEYSTORE_DIR, TARGETS_KEY_NAME),
parsed_arguments.pw)
@ -174,18 +175,17 @@ def delegate(parsed_arguments):
parsed_arguments.terminating, list_of_targets=None,
path_hash_prefixes=None)
role_privatekey = repo_tool.import_ecdsa_privatekey_from_file(
parsed_arguments.sign)
role_privatekey = import_privatekey_from_file(parsed_arguments.sign)
repository.targets(parsed_arguments.role).load_signing_key(role_privatekey)
# Update the required top-level roles, Snapshot and Timestamp, to make a new
# release.
snapshot_private = repo_tool.import_ecdsa_privatekey_from_file(
snapshot_private = import_privatekey_from_file(
os.path.join(parsed_arguments.path, KEYSTORE_DIR, SNAPSHOT_KEY_NAME),
parsed_arguments.pw)
timestamp_private = repo_tool.import_ecdsa_privatekey_from_file(
timestamp_private = import_privatekey_from_file(
os.path.join(parsed_arguments.path, KEYSTORE_DIR,
TIMESTAMP_KEY_NAME), parsed_arguments.pw)
@ -207,7 +207,7 @@ def revoke(parsed_arguments):
if parsed_arguments.role == 'targets':
repository.targets.revoke(parsed_arguments.delegatee)
targets_private = repo_tool.import_ecdsa_privatekey_from_file(
targets_private = import_privatekey_from_file(
os.path.join(parsed_arguments.path, KEYSTORE_DIR, TARGETS_KEY_NAME),
parsed_arguments.pw)
@ -218,17 +218,16 @@ def revoke(parsed_arguments):
else:
repository.targets(parsed_arguments.role).revoke(parsed_arguments.delegatee)
role_privatekey = repo_tool.import_ecdsa_privatekey_from_file(
parsed_arguments.sign)
role_privatekey = import_privatekey_from_file(parsed_arguments.sign)
repository.targets(parsed_arguments.role).load_signing_key(role_privatekey)
# Update the required top-level roles, Snapshot and Timestamp, to make a new
# release.
snapshot_private = repo_tool.import_ecdsa_privatekey_from_file(
snapshot_private = import_privatekey_from_file(
os.path.join(parsed_arguments.path, KEYSTORE_DIR, SNAPSHOT_KEY_NAME),
parsed_arguments.pw)
timestamp_private = repo_tool.import_ecdsa_privatekey_from_file(
timestamp_private = import_privatekey_from_file(
os.path.join(parsed_arguments.path, KEYSTORE_DIR,
TIMESTAMP_KEY_NAME), parsed_arguments.pw)
@ -279,6 +278,55 @@ def gen_key(parsed_arguments):
def import_privatekey_from_file(keypath, password=None):
# Note: should securesystemslib support this functionality (import any
# privatekey type)?
# If the caller does not provide a password argument, prompt for one.
# Password confirmation is disabled here, which should ideally happen only
# when creating encrypted key files.
if password is None: # pragma: no cover
# It is safe to specify the full path of 'filepath' in the prompt and not
# worry about leaking sensitive information about the key's location.
# However, care should be taken when including the full path in exceptions
# and log files.
password = securesystemslib.interface.get_password('Enter a password for'
' the encrypted key (' + Fore.RED + keypath + Fore.RESET + '): ',
confirm=False)
# Does 'password' have the correct format?
securesystemslib.formats.PASSWORD_SCHEMA.check_match(password)
# Store the encrypted contents of 'filepath' prior to calling the decryption
# routine.
encrypted_key = None
with open(keypath, 'rb') as file_object:
encrypted_key = file_object.read()
# Decrypt the loaded key file, calling the 'cryptography' library to generate
# the derived encryption key from 'password'. Raise
# 'securesystemslib.exceptions.CryptoError' if the decryption fails.
try:
key_object = securesystemslib.keys.decrypt_key(encrypted_key.decode('utf-8'),
password)
except securesystemslib.exceptions.CryptoError:
key_object = securesystemslib.keys.import_rsakey_from_private_pem(
encrypted_key, 'rsassa-pss-sha256', password)
if key_object['keytype'] not in SUPPORTED_KEY_TYPES:
raise tuf.exceptions.Error('Trying to import an unsupported key'
' type: ' + repr(key_object['keytype'] + '.'
' Supported key types: ' + repr(SUPPORTED_KEY_TYPES)))
else:
# Add "keyid_hash_algorithms" so that equal keys with different keyids can
# be associated using supported keyid_hash_algorithms.
key_object['keyid_hash_algorithms'] = securesystemslib.settings.HASH_ALGORITHMS
return key_object
def sign_role(parsed_arguments):
@ -289,11 +337,10 @@ def sign_role(parsed_arguments):
# Was a private key path given with --sign? If so, load the specified
# private key. Otherwise, load the default key path.
if parsed_arguments.sign != '.':
role_privatekey = repo_tool.import_ecdsa_privatekey_from_file(
parsed_arguments.sign)
role_privatekey = import_privatekey_from_file(parsed_arguments.sign)
else:
role_privatekey = repo_tool.import_ecdsa_privatekey_from_file(
role_privatekey = import_privatekey_from_file(
os.path.join(parsed_arguments.path, KEYSTORE_DIR, TARGETS_KEY_NAME),
parsed_arguments.pw)
@ -308,10 +355,10 @@ def sign_role(parsed_arguments):
# Update the required top-level roles, Snapshot and Timestamp, to make a new
# release.
snapshot_private = repo_tool.import_ecdsa_privatekey_from_file(
snapshot_private = import_privatekey_from_file(
os.path.join(parsed_arguments.path, KEYSTORE_DIR, SNAPSHOT_KEY_NAME),
parsed_arguments.pw)
timestamp_private = repo_tool.import_ecdsa_privatekey_from_file(
timestamp_private = import_privatekey_from_file(
os.path.join(parsed_arguments.path, KEYSTORE_DIR,
TIMESTAMP_KEY_NAME), parsed_arguments.pw)
@ -393,13 +440,13 @@ def add_targets(parsed_arguments):
prompt='Enter a password for the top-level role keys: ', confirm=True)
# Load the top-level, non-root, keys to make a new release.
targets_private = repo_tool.import_ecdsa_privatekey_from_file(
targets_private = import_privatekey_from_file(
os.path.join(parsed_arguments.path, KEYSTORE_DIR, TARGETS_KEY_NAME),
parsed_arguments.pw)
snapshot_private = repo_tool.import_ecdsa_privatekey_from_file(
snapshot_private = import_privatekey_from_file(
os.path.join(parsed_arguments.path, KEYSTORE_DIR, SNAPSHOT_KEY_NAME),
parsed_arguments.pw)
timestamp_private = repo_tool.import_ecdsa_privatekey_from_file(
timestamp_private = import_privatekey_from_file(
os.path.join(parsed_arguments.path, KEYSTORE_DIR,
TIMESTAMP_KEY_NAME), parsed_arguments.pw)
@ -491,16 +538,16 @@ def set_top_level_keys(repository):
# Import the private keys. They are needed to generate the signatures
# included in metadata.
root_private = repo_tool.import_ecdsa_privatekey_from_file(
root_private = import_privatekey_from_file(
os.path.join(parsed_arguments.path, KEYSTORE_DIR,
ROOT_KEY_NAME), parsed_arguments.pw)
targets_private = repo_tool.import_ecdsa_privatekey_from_file(
targets_private = import_privatekey_from_file(
os.path.join(parsed_arguments.path, KEYSTORE_DIR,
TARGETS_KEY_NAME), parsed_arguments.pw)
snapshot_private = repo_tool.import_ecdsa_privatekey_from_file(
snapshot_private = import_privatekey_from_file(
os.path.join(parsed_arguments.path, KEYSTORE_DIR,
SNAPSHOT_KEY_NAME), parsed_arguments.pw)
timestamp_private = repo_tool.import_ecdsa_privatekey_from_file(
timestamp_private = import_privatekey_from_file(
os.path.join(parsed_arguments.path, KEYSTORE_DIR,
TIMESTAMP_KEY_NAME), parsed_arguments.pw)