Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adopt sslib keygen interface encryption changes #1191

Merged
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
79 changes: 34 additions & 45 deletions docs/TUTORIAL.md
Expand Up @@ -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:
Expand All @@ -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:
```

Expand All @@ -132,36 +134,27 @@ 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 . . .
>>> public_ed25519_key = import_ed25519_publickey_from_file('ed25519_key.pub')

# 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.
Expand Down Expand Up @@ -259,26 +252,20 @@ 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.

>>> repository.targets.add_verification_key(import_rsa_publickey_from_file('targets_key.pub'))
>>> 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.
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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())
```
Expand Down Expand Up @@ -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"
Expand All @@ -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)

Expand Down
2 changes: 1 addition & 1 deletion requirements-pinned.txt
Expand Up @@ -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
2 changes: 1 addition & 1 deletion setup.py
Expand Up @@ -116,7 +116,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"'
Expand Down
10 changes: 5 additions & 5 deletions tests/repository_data/generate.py
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion tests/test_repository_lib.py
Expand Up @@ -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')
Expand Down
18 changes: 9 additions & 9 deletions tests/test_tutorial.py
Expand Up @@ -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']
Expand Down Expand Up @@ -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')

Expand Down Expand Up @@ -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'))
Expand Down Expand Up @@ -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'])
Expand Down
11 changes: 6 additions & 5 deletions tuf/README-developer-tools.md
Expand Up @@ -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.

Expand Down Expand Up @@ -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.
Expand Down
7 changes: 7 additions & 0 deletions tuf/developer_tool.py
Expand Up @@ -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)
Expand Down
6 changes: 6 additions & 0 deletions tuf/repository_tool.py
Expand Up @@ -74,8 +74,14 @@

from securesystemslib.interface import (
generate_and_write_rsa_keypair,
generate_and_write_rsa_keypair_with_prompt,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we actually use these new functions here, or are they just being imported w/o being used?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The latter is correct. They are just added to the TUF namespace (see comments above), because a lot of docs suggest to do from repository_tool import * to access them.

I started this in #919 (see 7306446 for details), in order to remove many oneliner function wrappers that served the same purpose but added cruft to the code base and duplicated the maintenance burden above all in regards to docstrings.

A better but also more invasive fix would be to remove these functions from the tuf namespace altogether, and update the docs to directly use securesystemslib akin to what we now do in in-toto (see in-toto/in-toto#402, in-toto/in-toto#408 and https://in-toto-lukpueh.readthedocs.io/en/latest/api.html#key-utilities).

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,
Expand Down