diff --git a/docs/TUTORIAL.md b/docs/TUTORIAL.md index 9d217eeb62..ad0b6c1ad7 100644 --- a/docs/TUTORIAL.md +++ b/docs/TUTORIAL.md @@ -96,14 +96,15 @@ text without prepended symbols is the output of a command. # following function creates an RSA key pair, where the private key is saved to # "root_key" and the public key to "root_key.pub" (both saved to the current # working directory). ->>> generate_and_write_rsa_keypair("root_key", bits=2048, password="password") +>>> generate_and_write_rsa_keypair(password="password", filepath="root_key", bits=2048) # If the key length is unspecified, it defaults to 3072 bits. A length of less -# than 2048 bits raises an exception. A password may be supplied as an -# argument, otherwise a user prompt is presented. If an empty password -# is entered, the private key is saved unencrypted. ->>> generate_and_write_rsa_keypair("root_key2") -Enter a password for the RSA key (/path/to/root_key2): +# than 2048 bits raises an exception. A similar function is available to supply +# a password on the prompt. If an empty password is entered, the private key +# is saved unencrypted. +>>> generate_and_write_rsa_keypair_with_prompt(filepath="root_key2") +enter password to encrypt private key file '/path/to/root_key2' +(leave empty if key should not be encrypted): Confirm: ``` The following four key files should now exist: @@ -117,8 +118,9 @@ If a filepath is not given, the KEYID of the generated key is used as the filename. The key files are written to the current working directory. ```python # Continuing from the previous section . . . ->>> generate_and_write_rsa_keypair() -Enter a password for the encrypted RSA key (/path/to/b5b8de8aeda674bce948fbe82cab07e309d6775fc0ec299199d16746dc2bd54c): +>>> generate_and_write_rsa_keypair_with_prompt() +enter password to encrypt private key file '/path/to/KEYID' +(leave empty if key should not be encrypted): Confirm: ``` @@ -132,28 +134,18 @@ Confirm: # Import an existing private key. Importing a private key requires a password, # whereas importing a public key does not. >>> private_root_key = import_rsa_privatekey_from_file("root_key") -Enter a password for the encrypted RSA key (/path/to/root_key): -``` - -`import_rsa_privatekey_from_file()` raises a -`securesystemslib.exceptions.CryptoError` exception if the key / password is -invalid: - -``` -securesystemslib.exceptions.CryptoError: RSA (public, private) tuple cannot be -generated from the encrypted PEM string: Bad decrypt. Incorrect password? +enter password to decrypt private key file '/path/to/root_key' +(leave empty if key not encrypted): ``` ### Create and Import Ed25519 Keys ### ```Python # Continuing from the previous section . . . -# Generate and write an Ed25519 key pair. A 'password' argument may be -# supplied, otherwise a prompt is presented. The private key is saved -# encrypted if a non-empty password is given, and unencrypted if the password -# is empty. ->>> generate_and_write_ed25519_keypair('ed25519_key') -Enter a password for the Ed25519 key (/path/to/ed25519_key): +# The same generation and import functions as for rsa keys exist for ed25519 +>>> generate_and_write_ed25519_keypair_with_prompt(filepath='ed25519_key') +enter password to encrypt private key file '/path/to/ed25519_key' +(leave empty if key should not be encrypted): Confirm: # Import the ed25519 public key just created . . . @@ -161,7 +153,8 @@ Confirm: # and its corresponding private key. >>> private_ed25519_key = import_ed25519_privatekey_from_file('ed25519_key') -Enter a password for the encrypted Ed25519 key (/path/to/ed25519_key): +enter password to decrypt private key file '/path/to/ed25519_key' +(leave empty if key should not be encrypted): ``` Note: Methods are also available to generate and write keys from memory. @@ -259,10 +252,9 @@ secure manner. >>> import datetime # Generate keys for the remaining top-level roles. The root keys have been set above. -# The password argument may be omitted if a password prompt is needed. ->>> generate_and_write_rsa_keypair('targets_key', password='password') ->>> generate_and_write_rsa_keypair('snapshot_key', password='password') ->>> generate_and_write_rsa_keypair('timestamp_key', password='password') +>>> generate_and_write_rsa_keypair(password='password', filepath='targets_key') +>>> generate_and_write_rsa_keypair(password='password', filepath='snapshot_key') +>>> generate_and_write_rsa_keypair(password='password', filepath='timestamp_key') # Add the verification keys of the remaining top-level roles. @@ -270,15 +262,10 @@ secure manner. >>> repository.snapshot.add_verification_key(import_rsa_publickey_from_file('snapshot_key.pub')) >>> repository.timestamp.add_verification_key(import_rsa_publickey_from_file('timestamp_key.pub')) -# Import the signing keys of the remaining top-level roles. Prompt for passwords. ->>> private_targets_key = import_rsa_privatekey_from_file('targets_key') -Enter a password for the encrypted RSA key (/path/to/targets_key): - ->>> private_snapshot_key = import_rsa_privatekey_from_file('snapshot_key') -Enter a password for the encrypted RSA key (/path/to/snapshot_key): - ->>> private_timestamp_key = import_rsa_privatekey_from_file('timestamp_key') -Enter a password for the encrypted RSA key (/path/to/timestamp_key): +# Import the signing keys of the remaining top-level roles. +>>> private_targets_key = import_rsa_privatekey_from_file('targets_key', password='password') +>>> private_snapshot_key = import_rsa_privatekey_from_file('snapshot_key', password='password') +>>> private_timestamp_key = import_rsa_privatekey_from_file('timestamp_key', password='password') # Load the signing keys of the remaining roles so that valid signatures are # generated when repository.writeall() is called. @@ -390,18 +377,21 @@ metadata. `snapshot.json` keys must be loaded and its metadata signed because # The private key of the updated targets metadata must be re-loaded before it # can be signed and written (Note the load_repository() call above). >>> private_targets_key = import_rsa_privatekey_from_file('targets_key') -Enter a password for the encrypted RSA key (/path/to/targets_key): +enter password to decrypt private key file '/path/to/targets_key' +(leave empty if key not encrypted): >>> repository.targets.load_signing_key(private_targets_key) # Due to the load_repository() and new versions of metadata, we must also load # the private keys of Snapshot and Timestamp to generate a valid set of metadata. >>> private_snapshot_key = import_rsa_privatekey_from_file('snapshot_key') -Enter a password for the encrypted RSA key (/path/to/snapshot_key): +enter password to decrypt private key file '/path/to/snapshot_key' +(leave empty if key not encrypted): >>> repository.snapshot.load_signing_key(private_snapshot_key) >>> private_timestamp_key = import_rsa_privatekey_from_file('timestamp_key') -Enter a password for the encrypted RSA key (/path/to/timestamp_key): +enter password to decrypt private key file '/path/to/timestamp_key' +(leave empty if key not encrypted): >>> repository.timestamp.load_signing_key(private_timestamp_key) # Mark roles for metadata update (see #964, #958) @@ -451,7 +441,7 @@ threshold, it needs to be added to `root.json`, e.g. via >>> from securesystemslib.formats import encode_canonical >>> from securesystemslib.keys import create_signature >>> private_ed25519_key = import_ed25519_privatekey_from_file('ed25519_key') -Enter a password for the encrypted Ed25519 key (/path/to/ed25519_key): +enter password to decrypt private key file '/path/to/ed25519_key' >>> signature = create_signature( ... private_ed25519_key, encode_canonical(signable_content).encode()) ``` @@ -489,7 +479,7 @@ targets and generate signed metadata. # Continuing from the previous section . . . # Generate a key for a new delegated role named "unclaimed". ->>> generate_and_write_rsa_keypair('unclaimed_key', bits=2048, password='password') +>>> generate_and_write_rsa_keypair(password='password', filepath='unclaimed_key', bits=2048) >>> public_unclaimed_key = import_rsa_publickey_from_file('unclaimed_key.pub') # Make a delegation (delegate trust of 'myproject/*.txt' files) from "targets" @@ -502,8 +492,7 @@ targets and generate signed metadata. # Load the private key of "unclaimed" so that unclaimed's metadata can be # signed, and valid metadata created. ->>> private_unclaimed_key = import_rsa_privatekey_from_file('unclaimed_key') -Enter a password for the encrypted RSA key (/path/to/unclaimed_key): +>>> private_unclaimed_key = import_rsa_privatekey_from_file('unclaimed_key', password='password') >>> repository.targets("unclaimed").load_signing_key(private_unclaimed_key) diff --git a/requirements-pinned.txt b/requirements-pinned.txt index 8216943d6c..0c1a9b5a77 100644 --- a/requirements-pinned.txt +++ b/requirements-pinned.txt @@ -8,7 +8,7 @@ ipaddress==1.0.23 ; python_version < '3' # via cryptography pycparser==2.20 # via cffi pynacl==1.4.0 # via securesystemslib requests==2.24.0 -securesystemslib[crypto,pynacl]==0.17.0 +securesystemslib[crypto,pynacl]==0.18.0 six==1.15.0 subprocess32==3.5.4 ; python_version < '3' # via securesystemslib urllib3==1.25.11 # via requests diff --git a/setup.py b/setup.py index a9c84d03e1..c87e2292b4 100755 --- a/setup.py +++ b/setup.py @@ -117,7 +117,7 @@ install_requires = [ 'requests>=2.19.1', 'six>=1.11.0', - 'securesystemslib>=0.16.0' + 'securesystemslib>=0.18.0' ], tests_require = [ 'mock; python_version < "3.3"' diff --git a/tests/repository_data/generate.py b/tests/repository_data/generate.py index fc253c2c78..e131329ec9 100755 --- a/tests/repository_data/generate.py +++ b/tests/repository_data/generate.py @@ -59,11 +59,11 @@ # Generate public and private key files for the top-level roles, and two # delegated roles (these number of keys should be sufficient for most of the # unit tests). Unit tests may generate additional keys, if needed. - generate_and_write_rsa_keypair(root_key_file, password='password') - generate_and_write_ed25519_keypair(targets_key_file, password='password') - generate_and_write_ed25519_keypair(snapshot_key_file, password='password') - generate_and_write_ed25519_keypair(timestamp_key_file, password='password') - generate_and_write_ed25519_keypair(delegation_key_file, password='password') + generate_and_write_rsa_keypair(password='password', filepath=root_key_file) + generate_and_write_ed25519_keypair(password='password', filepath=targets_key_file) + generate_and_write_ed25519_keypair(password='password', filepath=snapshot_key_file) + generate_and_write_ed25519_keypair(password='password', filepath=timestamp_key_file) + generate_and_write_ed25519_keypair(password='password', filepath=delegation_key_file) # Import the public keys. These keys are needed so that metadata roles are # assigned verification keys, which clients use to verify the signatures created diff --git a/tests/test_repository_lib.py b/tests/test_repository_lib.py index 40cf6f964b..a30037477c 100755 --- a/tests/test_repository_lib.py +++ b/tests/test_repository_lib.py @@ -143,7 +143,7 @@ def test_import_ed25519_privatekey_from_file(self): temporary_directory = tempfile.mkdtemp(dir=self.temporary_directory) ed25519_keypath = os.path.join(temporary_directory, 'ed25519_key') securesystemslib.interface.generate_and_write_ed25519_keypair( - ed25519_keypath, password='pw') + password='pw', filepath=ed25519_keypath) imported_ed25519_key = \ repo_lib.import_ed25519_privatekey_from_file(ed25519_keypath, 'pw') diff --git a/tests/test_tutorial.py b/tests/test_tutorial.py index 83ee56404a..1296dd1ddb 100755 --- a/tests/test_tutorial.py +++ b/tests/test_tutorial.py @@ -75,11 +75,11 @@ def test_tutorial(self): # ----- Tutorial Section: Keys - generate_and_write_rsa_keypair('root_key', bits=2048, password='password') + generate_and_write_rsa_keypair(password='password', filepath='root_key', bits=2048) # Skipping user entry of password - ## generate_and_write_rsa_keypair('root_key2') - generate_and_write_rsa_keypair('root_key2', password='password') + ## generate_and_write_rsa_keypair_with_prompt('root_key2') + generate_and_write_rsa_keypair(password='password', filepath='root_key2') # Tutorial tells users to expect these files to exist: # ['root_key', 'root_key.pub', 'root_key2', 'root_key2.pub'] @@ -109,8 +109,8 @@ def test_tutorial(self): # ----- Tutorial Section: Create and Import Ed25519 Keys # Skipping user entry of password - ## generate_and_write_ed25519_keypair('ed25519_key') - generate_and_write_ed25519_keypair('ed25519_key', password='password') + ## generate_and_write_ed25519_keypair_with_prompt('ed25519_key') + generate_and_write_ed25519_keypair(password='password', filepath='ed25519_key') public_ed25519_key = import_ed25519_publickey_from_file('ed25519_key.pub') @@ -157,9 +157,9 @@ def test_tutorial(self): repr('targets') + " role contains 0 / 1 signatures." ], [args[0] for args, _ in mock_logger.info.call_args_list]) - generate_and_write_rsa_keypair('targets_key', password='password') - generate_and_write_rsa_keypair('snapshot_key', password='password') - generate_and_write_rsa_keypair('timestamp_key', password='password') + generate_and_write_rsa_keypair(password='password', filepath='targets_key') + generate_and_write_rsa_keypair(password='password', filepath='snapshot_key') + generate_and_write_rsa_keypair(password='password', filepath='timestamp_key') repository.targets.add_verification_key(import_rsa_publickey_from_file( 'targets_key.pub')) @@ -309,7 +309,7 @@ def test_tutorial(self): # ----- Tutorial Section: Delegations generate_and_write_rsa_keypair( - 'unclaimed_key', bits=2048, password='password') + password='password', filepath='unclaimed_key', bits=2048) public_unclaimed_key = import_rsa_publickey_from_file('unclaimed_key.pub') repository.targets.delegate( 'unclaimed', [public_unclaimed_key], ['myproject/*.txt']) diff --git a/tuf/README-developer-tools.md b/tuf/README-developer-tools.md index 3697411390..1b593400a5 100644 --- a/tuf/README-developer-tools.md +++ b/tuf/README-developer-tools.md @@ -53,15 +53,16 @@ is the private key. ``` >>> from tuf.developer_tool import * ->>> generate_and_write_rsa_keypair("path/to/key") -Enter a password for the RSA key: +>>> generate_and_write_rsa_keypair_with_prompt(filepath="path/to/key") +enter password to encrypt private key file 'path/to/key' +(leave empty if key should not be encrypted): Confirm: >>> ``` We can also use the bits parameter to set a different key length (the default -is 3072). We can also provide the password parameter in order to suppress the -password prompt. +is 3072). We can also `generate_and_write_rsa_keypair` with a `password` +parameter if a prompt is not desired. In this example we will be using rsa keys, but ed25519 keys are also supported. @@ -257,7 +258,7 @@ When generating keys, it is possible to specify the length of the key in bits and its password as parameters: ``` ->>> generate_and_write_rsa_keypair("path/to/key", bits=2048, password="pw") +>>> generate_and_write_rsa_keypair(password="pw", filepath="path/to/key", bits=2048) ``` The bits parameter defaults to 3072, and values below 2048 will raise an error. The password parameter is only intended to be used in scripts. diff --git a/tuf/developer_tool.py b/tuf/developer_tool.py index 0d4c3331c6..e3269b088b 100755 --- a/tuf/developer_tool.py +++ b/tuf/developer_tool.py @@ -76,7 +76,14 @@ from securesystemslib.interface import ( generate_and_write_rsa_keypair, + generate_and_write_rsa_keypair_with_prompt, + generate_and_write_unencrypted_rsa_keypair, + generate_and_write_ecdsa_keypair, + generate_and_write_ecdsa_keypair_with_prompt, + generate_and_write_unencrypted_ecdsa_keypair, generate_and_write_ed25519_keypair, + generate_and_write_ed25519_keypair_with_prompt, + generate_and_write_unencrypted_ed25519_keypair, import_rsa_publickey_from_file, import_ed25519_publickey_from_file, import_ed25519_privatekey_from_file) diff --git a/tuf/repository_tool.py b/tuf/repository_tool.py index 59eab5be8a..1fe6a51e83 100755 --- a/tuf/repository_tool.py +++ b/tuf/repository_tool.py @@ -74,8 +74,14 @@ from securesystemslib.interface import ( generate_and_write_rsa_keypair, + generate_and_write_rsa_keypair_with_prompt, + generate_and_write_unencrypted_rsa_keypair, generate_and_write_ecdsa_keypair, + generate_and_write_ecdsa_keypair_with_prompt, + generate_and_write_unencrypted_ecdsa_keypair, generate_and_write_ed25519_keypair, + generate_and_write_ed25519_keypair_with_prompt, + generate_and_write_unencrypted_ed25519_keypair, import_rsa_publickey_from_file, import_ecdsa_publickey_from_file, import_ed25519_publickey_from_file, diff --git a/tuf/scripts/repo.py b/tuf/scripts/repo.py index 93fb93529e..da1664a86e 100755 --- a/tuf/scripts/repo.py +++ b/tuf/scripts/repo.py @@ -189,6 +189,8 @@ # securesystemslib. SUPPORTED_KEY_TYPES = ('ed25519', 'ecdsa-sha2-nistp256', 'rsa') +# pylint: disable=protected-access +# ... to allow use of sslib _generate_and_write_*_keypair convenience methods def process_command_line_arguments(parsed_arguments): """ @@ -379,23 +381,30 @@ def gen_key(parsed_arguments): keypath = None + keygen_kwargs = { + "password": parsed_arguments.pw, + "filepath": parsed_arguments.filename, + "prompt": (not parsed_arguments.pw) # prompt if no default or passed pw + } + if parsed_arguments.key not in SUPPORTED_CLI_KEYTYPES: tuf.exceptions.Error( 'Invalid key type: ' + repr(parsed_arguments.key) + '. Supported' ' key types: ' + repr(SUPPORTED_CLI_KEYTYPES)) elif parsed_arguments.key == ECDSA_KEYTYPE: - keypath = securesystemslib.interface.generate_and_write_ecdsa_keypair( - parsed_arguments.filename, password=parsed_arguments.pw) + keypath = securesystemslib.interface._generate_and_write_ecdsa_keypair( + **keygen_kwargs) elif parsed_arguments.key == ED25519_KEYTYPE: - keypath = securesystemslib.interface.generate_and_write_ed25519_keypair( - parsed_arguments.filename, password=parsed_arguments.pw) + keypath = securesystemslib.interface._generate_and_write_ed25519_keypair( + **keygen_kwargs) # RSA key.. else: - keypath = securesystemslib.interface.generate_and_write_rsa_keypair( - parsed_arguments.filename, password=parsed_arguments.pw) + keypath = securesystemslib.interface._generate_and_write_rsa_keypair( + **keygen_kwargs) + # If a filename is not given, the generated keypair is saved to the current # working directory. By default, the keypair is written to .pub @@ -889,26 +898,27 @@ def set_top_level_keys(repository, parsed_arguments): Generate, write, and set the top-level keys. 'repository' is modified. """ - # Examples of how the --pw command-line option is interpreted: - # repo.py --init': parsed_arguments.pw = 'pw' - # repo.py --init --pw my_pw: parsed_arguments.pw = 'my_pw' - # repo.py --init --pw: The user is prompted for a password, here. - if not parsed_arguments.pw: - parsed_arguments.pw = securesystemslib.interface.get_password( - prompt='Enter a password for the top-level role keys: ', confirm=True) - - repo_tool.generate_and_write_ed25519_keypair( - os.path.join(parsed_arguments.path, KEYSTORE_DIR, - ROOT_KEY_NAME), password=parsed_arguments.root_pw) - repo_tool.generate_and_write_ed25519_keypair( - os.path.join(parsed_arguments.path, KEYSTORE_DIR, - TARGETS_KEY_NAME), password=parsed_arguments.targets_pw) - repo_tool.generate_and_write_ed25519_keypair( - os.path.join(parsed_arguments.path, KEYSTORE_DIR, - SNAPSHOT_KEY_NAME), password=parsed_arguments.snapshot_pw) - repo_tool.generate_and_write_ed25519_keypair( - os.path.join(parsed_arguments.path, KEYSTORE_DIR, - TIMESTAMP_KEY_NAME), password=parsed_arguments.timestamp_pw) + # Examples of how the --*_pw command-line options are interpreted: + # repo.py --init': parsed_arguments.*_pw = 'pw' + # repo.py --init --*_pw my_pw: parsed_arguments.*_pw = 'my_pw' + # repo.py --init --*_pw: The user is prompted for a password. + + securesystemslib.interface._generate_and_write_ed25519_keypair( + password=parsed_arguments.root_pw, + filepath=os.path.join(parsed_arguments.path, KEYSTORE_DIR, ROOT_KEY_NAME), + prompt=(not parsed_arguments.root_pw)) + securesystemslib.interface._generate_and_write_ed25519_keypair( + password=parsed_arguments.targets_pw, + filepath=os.path.join(parsed_arguments.path, KEYSTORE_DIR, TARGETS_KEY_NAME), + prompt=(not parsed_arguments.targets_pw)) + securesystemslib.interface._generate_and_write_ed25519_keypair( + password=parsed_arguments.snapshot_pw, + filepath=os.path.join(parsed_arguments.path, KEYSTORE_DIR, SNAPSHOT_KEY_NAME), + prompt=(not parsed_arguments.snapshot_pw)) + securesystemslib.interface._generate_and_write_ed25519_keypair( + password=parsed_arguments.timestamp_pw, + filepath=os.path.join(parsed_arguments.path, KEYSTORE_DIR, TIMESTAMP_KEY_NAME), + prompt=(not parsed_arguments.timestamp_pw)) # Import the private keys. They are needed to generate the signatures # included in metadata.