diff --git a/docs/CLI.md b/docs/CLI.md index e41991cb..97dc4ea3 100644 --- a/docs/CLI.md +++ b/docs/CLI.md @@ -1,4 +1,4 @@ -# CLI examples # +# CLI # Note: This is a work in progress and subject to change. @@ -45,6 +45,7 @@ $ repo.py --init --consistent_snapshot + ## Add a target file ## Copy a target file to the repo and add it to the Targets metadata (or the @@ -81,7 +82,7 @@ Remove all target files, that match `foo*.tgz,` from the Targets metadata. $ repo.py --remove "foo*.tgz" ``` -Remove all target files from the `my_role` Targets metadata. +Remove all target files from the `my_role` metadata. ```Bash $ repo.py --remove "*" --role my_role --sign tufkeystore/my_role_key ``` @@ -94,24 +95,46 @@ specific metadata with `--sign`. The supported key types are: `ecdsa`, ```Bash $ repo.py --key $ repo.py --key -$ repo.py --key --path --pw [my_password], --filename +$ repo.py --key [--path --pw [my_password], --filename ] ``` Instead of using some default password, the user can enter one on the command line or be prompted for it via password masking. ```Bash -$ repo.py --key ... --pw my_password +$ repo.py --key ed25519 --pw my_password ``` ```Bash -$ repo.py --key ... --pw +$ repo.py --key rsa --pw Enter a password for the top-level role keys: Confirm: ``` + +## Trust keys ## + +The Root role specifies the trusted keys of the top-level roles, including +itself. The --trust command-line option, in conjunction with --pubkeys and +--role, can be used to indicate the trusted keys of a role. + +```Bash +$ repo.py --trust --pubkeys --role +``` + +For example: +```Bash +$ repo.py --init --bare +$ repo.py --trust --pubkeys keystore/my_key.pub keystore/my_key_too.pub --role root +``` + +Note: This action replaces any previously trusted keys that might have been +specified for --role. + + + ## Sign metadata ## -Sign, using the specified key, the metadata of the role indicated by --role +Sign, using the specified key, the metadata of the role indicated in --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. @@ -120,7 +143,7 @@ $ repo.py --sign $ repo.py --sign [--role , --path ] ``` -For example, to sign new Timestamp metadata: +For example, to sign the delegated `foo` metadata: ```Bash $ repo.py --sign /path/to/foo_key --role foo ``` @@ -130,14 +153,15 @@ signing of Snapshot and Timestamp metadata. -## Delegate trust ## +## Delegation ## -Delegate trust of target files from the targets role (or the one specified +Delegate trust of target files from the Targets role (or the one specified in --role) to some other role (--delegatee). --delegatee is trusted to sign for target files that match the delegated glob patterns. + ```Bash $ repo.py --delegate ... --delegatee --pubkeys -... [--role --terminating --threshold + ... [--role --terminating --threshold --sign ] ``` @@ -149,7 +173,7 @@ $ repo.py --delegate "/foo*.tgz" --delegatee foo --pubkeys ./keystore/foo.pub -## Revoke trust ## +## Revocation ## Revoke trust of target files from a delegated role (--delegatee). The "targets" role performs the revocation if --role is not specified. diff --git a/tuf/scripts/repo.py b/tuf/scripts/repo.py index 6b112fb3..85332eaf 100755 --- a/tuf/scripts/repo.py +++ b/tuf/scripts/repo.py @@ -25,14 +25,25 @@ Note: arguments within brackets are optional. - $ repo.py --init [--consistent_snapshot, --bare, --path] + $ repo.py --init + [--consistent_snapshot, --bare, --path, --root_pw, --targets_pw, + --snapshot_pw, --timestamp_pw] $ repo.py --add ... [--path, --recursive] + $ repo.py --remove + $ repo.py --trust --pubkeys [--role] $ repo.py --sign [--role ] - $ repo.py --key [--filename + $ repo.py --key + [--filename --path , --pw [my_password]] - $ repo.py --delegate ... --role - --delegatee --terminating --threshold - --keys --sign + + $ repo.py --delegate --delegatee + --pubkeys + [role --terminating --threshold + --sign ] + + $ repo.py --revoke --delegatee + [--role --sign ] + $ repo.py --verbose $ repo.py --clean [--path] """ @@ -128,6 +139,9 @@ def process_arguments(parsed_arguments): if parsed_arguments.remove: remove_targets(parsed_arguments) + if parsed_arguments.trust: + add_verification_key(parsed_arguments) + if parsed_arguments.sign: sign_role(parsed_arguments) @@ -158,7 +172,6 @@ def delegate(parsed_arguments): public_keys = [] for public_key in parsed_arguments.pubkeys: - # In the future, any type of key can be imported... imported_pubkey = import_publickey_from_file( public_key) public_keys.append(imported_pubkey) @@ -320,12 +333,22 @@ def import_privatekey_from_file(keypath, password=None): # 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) + try: + logger.debug( + 'Decryption failsed. Attempting to import a private PEM instead.') + key_object = securesystemslib.keys.import_rsakey_from_private_pem( + encrypted_key, 'rsassa-pss-sha256', password) + + except securesystemslib.exceptions.CryptoError as e: + raise tuf.exceptions.Error(repr(keypath) + ' cannot be imported, possibly' + ' because the decryption password is incorrect. Encryption' + ' passwords can be specified via the --root_pw, --targets_pw,' + ' --snapshot_pw, and --timestamp_pw command-line options.') if key_object['keytype'] not in SUPPORTED_KEY_TYPES: raise tuf.exceptions.Error('Trying to import an unsupported key' @@ -356,6 +379,40 @@ def import_publickey_from_file(keypath): +def add_verification_key(parsed_arguments): + if not parsed_arguments.pubkeys: + raise tuf.exception.Error('--pubkeys must be given with --trust.') + + repository = repo_tool.load_repository( + os.path.join(parsed_arguments.path, REPO_DIR)) + + for keypath in parsed_arguments.pubkeys: + imported_pubkey = import_publickey_from_file(keypath) + + if parsed_arguments.role == 'root': + repository.root.add_verification_key(imported_pubkey) + + elif parsed_arguments.role == 'targets': + repository.targets.add_verification_key(imported_pubkey) + + elif parsed_arguments.role == 'snapshot': + repository.snapshot.add_verification_key(imported_pubkey) + + elif parsed_arguments.role == 'timestamp': + repository.timestamp.add_verification_key(imported_pubkey) + + else: + raise tuf.exception.Error('The given --role is not a top-level role.') + + repository.write('root', increment_version_number=False) + + # Move staged metadata directory to "live" metadata directory. + write_to_live_repo() + + + + + def sign_role(parsed_arguments): repository = repo_tool.load_repository( @@ -378,7 +435,7 @@ def sign_role(parsed_arguments): pass else: - # TODO: The repository tool will be refactored to clean up the following + # TODO: repository_tool.py will be refactored to clean up the following # approach, which adds and signs for a non-existent role. if not tuf.roledb.role_exists(parsed_arguments.role): @@ -476,7 +533,7 @@ def add_target_to_repo(target_path, repo_targets_path, repository, custom=None): else: logger.debug('Replacing target: ' + repr(target_path)) - roleinfo['paths'].update({relative_path: custom}) + roleinfo['paths'].update({target_path: custom}) tuf.roledb.update_roleinfo(parsed_arguments.role, roleinfo, mark_role_as_dirty=True, repository_name=repository._repository_name) @@ -796,6 +853,10 @@ def parse_arguments(): metavar='', help='Specify a filename. This option can' ' be used to name a generated key file.') + parser.add_argument('--trust', action='store_true', + help='Indicate the trusted key(s) (via --pubkeys) for the role in --role.' + ' This action modifies Root metadata with the trusted key(s).') + parser.add_argument('--sign', nargs='?', type=str, const='.', default=None, metavar='', help='Sign the "targets"' ' metadata (or the one for --role) with the specified key.')