-
Notifications
You must be signed in to change notification settings - Fork 50
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
Add prompt argument to import RSA private key function #124
Conversation
Add an optional boolean 'prompt' argument to interface.import_rsa_privatekey_from_file and change the behavior of the function like so: If password is passed use passed password for decryption. If prompt is True use entered password for decryption. If no password is passed or entered, or if the entered password is an empty string, omit decryption. Passing and prompting for a passowrd is not possible. See code comments or secure-systems-lab#122 for more details. This commit also adopts the unit tests accordingly.
Add two tests to test failing import of encrypted private key when no password is passed, and failing import of non-encrypted private key when a password is passed.
Add Python mock package as dev and and ci requirement for versions <3.3 (above it's in the standard library) and add interface.import_rsa_privatekey_from_file tests that mock passwords entered on the prompt.
I'll review. It would be good to have a clear sense of what the status quo pre-PR was in the PR description. (My TUF fork in Uptane is still pretty remote, so I think the behavior is somewhat different.) That might be too demanding. ' dunno. (: Edit: yeah, way too demanding. It's right in the issue description, basically. Disregard! |
Summarizing our conversation on the whiteboard:
|
Maybe we should put it up for vote? @vladimir-v-diaz, @SantiagoTorres what do you guys think? |
(I think that keeping prompt and doing (3) is fine.) |
Approach (3) is fine with me. If we make I suppose we can recommend that the CLI tools (that use securesystemslib) handle the option of prompting for a password. In that case, an integrator who uses both in-toto and TUF can expect consistent behavior |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM. I left a few questions and comments. We can merge, or I can make the adjustments mentioned.
README.rst
Outdated
# Import an existing private key. If your private key is encrypted, | ||
# which it should be, you either have to pass a 'password' or enter one | ||
# on the prompt. | ||
>>> private_rsa_key1 = import_rsa_privatekey_from_file("rsa_key1", prompt=True) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider a second example, since you've outlined two options in the comment above:
>>> private_rsa_key1 = import_rsa_privatekey_from_file("rsa_key1", prompt=True)
>>> private_rsa_key1 = import_rsa_privatekey_from_file("rsa_key1", password='some passphrase")
securesystemslib/interface.py
Outdated
If prompt is True use entered password for decryption. | ||
If no password is passed or entered, or if the entered password is an empty | ||
string, omit decryption. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
securesystemslib/interface.py
Outdated
If no password is passed or entered, or if the entered password is an empty | ||
string, omit decryption. | ||
|
||
Passing and prompting for a password is not possible. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would say "is not possible and returns an error." This is made clear in the Exceptions list below, but I think there's no sense not having it here.
|
||
elif prompt: | ||
# Password confirmation disabled here, which should ideally happen only | ||
# when creating encrypted key files (i.e., improve usability). | ||
# 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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Forgive my ignorance: why is it safe to specify the full path of the key in the prompt, but not in exceptions or log files?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AFAIK, Trishank and Vlad made this decision, because in order for the prompt to show the full path, the full path must be known to the caller anyway, whereas an exception/log might reveal that information to someone who normally does not know it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
because in order for the prompt to show the full path, the full path must be known to the caller anyway, whereas an exception/log might reveal that information to someone who normally does not know it.
That's correct. Revealing the full path via an exception or log file should be avoided elsewhere.
We want to avoid vulnerabilities like Information exposure through an error message and full path disclosure.
# Hence, we treat an empty password here, as if no 'password' was passed. | ||
password = get_password('Enter a password for an encrypted RSA' | ||
' file \'' + Fore.RED + filepath + Fore.RESET + '\': ', | ||
confirm=False) or None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cute and succinct, but it took me a moment to process the implication of the or None
, even with the comment, FWIW. ("If get_password returns an empty string, this construction stores None" might help, but ... it was probably just my brain hiccuping.)
# This check will not fail, because a mal-formatted passed password fails | ||
# above and an entered password will always be a string (see get_password) | ||
# However, we include it in case PASSWORD_SCHEMA or get_password changes. | ||
securesystemslib.formats.PASSWORD_SCHEMA.check_match(password) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
|
||
else: | ||
import mock | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(This question in no way interferes with this PR. As a point of general curiosity:
Is it better practice to test Python version numbers this way when determining what to import, or to try the first import and fail over to the second one? What if, say, Python 4.0 had been released before mock was added to unittest in Python 3.3? Should one just see if the duck quacks first?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMHO both are fine. In this case I lean towards the version checking approach, because according to Python docs, mock is "available as unittest.mock in Python 3.3 onwards". So if it fails with e.g. 4.0, it would be a Python (docs) bug.
It is important though that the unittests take all supported Python versions into consideration.
There might be a minimal performance difference between exception handling and tuple comparison, but I think we can disregard that.
@@ -237,11 +237,20 @@ def generate_and_write_rsa_keypair(filepath=None, bits=DEFAULT_RSA_KEY_BITS, | |||
|
|||
|
|||
def import_rsa_privatekey_from_file(filepath, password=None, | |||
scheme='rsassa-pss-sha256'): | |||
scheme='rsassa-pss-sha256', prompt=False): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NBD: Has there been some discussion about whether or not False should be the default prompt value used?
- TUF tutorial text -- and some code, probably -- will need to change.
- (What are the security implications of passing the passphrase in through a function call in the interpreter rather than using the prompt functionality from
getpass
?)
@awwad, thanks for the review! It would be great if you could make the changes you proposed.\ @vladimir-v-diaz, |
That's the approach we used in TUF's CLI tool. |
Covering what a call looks like if you accept a password rather than leaving prompt on. Signed-off-by: Sebastien Awwad <[email protected]>
Clarify behavior if prompt is True and password is provided anyway (raises error), and in other combinations. Also corrects a typo. Signed-off-by: Sebastien Awwad <[email protected]>
Recommendations applied. @vladimir-v-diaz Vlad, does merging this affect you? |
Not now, but I'll have to update the TUF code once a new version of securesystemslib is released. |
Fixes secure-systems-lab#122 together with secure-systems-lab#124 and secure-systems-lab#148 Prepares for fixing in-toto/in-toto#80 TODO (Maybe in separate PRs): - support prompt arg for import_ecdsa_privatekey_from_file (see this commit) - support prompt arg for interface.generate_and_write_*_keypair - support password=None (no encryption) for generate_and_write_*_keypair (is there a way to check if encrypted?) - add import_key_from_file(filepath, password=None, prompt=False, key_type=RSA), for pub/priv, rsa/ecdsa/ed25519 - add import_public_keys_from_file(filepaths, key_types=RSA), for pub/priv, rsa/ecdsa/ed25519 - add import_public_keys_from_gpg(keyids, gpg_home=None) + fix tests/docs/etc...
Fixes issue #:
Fixes RSA part of #122
Description of the changes being introduced by the pull request:
Add an optional boolean
prompt
argument tointerface.import_rsa_privatekey_from_file
and change the behavior of the function like so:password
is passed use passedpassword
for decryption.prompt
isTrue
use entered password for decryption.This PR further:
mock
as dev/ci requirement for supported Python versions <3.3, i.e. 2.7, to mock password prompts, andSee commit messages, code comments or #122 for more details.
Please verify and check that the pull request fulfills the following
requirements: