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

Add prompt argument to import RSA private key function #124

Merged
merged 8 commits into from
Apr 3, 2018

Conversation

lukpueh
Copy link
Member

@lukpueh lukpueh commented Mar 8, 2018

Fixes issue #:
Fixes RSA part of #122

Description of the changes being introduced by the pull request:
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 password is not possible.

This PR further:

  • adds mock as dev/ci requirement for supported Python versions <3.3, i.e. 2.7, to mock password prompts, and
  • adopts and adds related unit tests.

See commit messages, code comments or #122 for more details.

Please verify and check that the pull request fulfills the following
requirements
:

  • The code follows the Code Style Guidelines
  • Tests have been added for the bug fix or new feature
  • Docs have been added for the bug fix or new feature

lukpueh added 6 commits March 8, 2018 11:41
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.
@awwad
Copy link
Contributor

awwad commented Mar 8, 2018

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!

@awwad
Copy link
Contributor

awwad commented Mar 8, 2018

Consider the following alternative (which I don't say is superior or anything -- just worth considering):

optional argument 'encrypt' instead of optional argument 'prompt'

prompt vs encrypt

@lukpueh
Copy link
Member Author

lukpueh commented Mar 8, 2018

Summarizing our conversation on the whiteboard:

  • I'd like to keep prompt because, it should be explicit whether a function expects user input,
  • If prompt is True and a password is passed, we could (1) not prompt and use the passed pw, (2) prompt and override the passed pw with the entered pw, (3) fail with an argument error. Any of these make sense, so we can stick with solution (3).

@lukpueh
Copy link
Member Author

lukpueh commented Mar 9, 2018

Maybe we should put it up for vote? @vladimir-v-diaz, @SantiagoTorres what do you guys think?

@awwad
Copy link
Contributor

awwad commented Mar 13, 2018

(I think that keeping prompt and doing (3) is fine.)

@vladimir-v-diaz
Copy link
Contributor

Approach (3) is fine with me.

If we make prompt optional, how do we deal with guaranteeing consistent behavior across projects? For instance, the TUF project might choose to set prompt=True, whereas in-toto uses prompt=False.

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
by using, for example, $ in-toto --sign --prompt-pw and $ repo.py --sign --prompt-pw.

Copy link
Contributor

@awwad awwad left a 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)
Copy link
Contributor

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")

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.

Copy link
Contributor

Choose a reason for hiding this comment

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

👍

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.
Copy link
Contributor

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.
Copy link
Contributor

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?

Copy link
Member Author

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.

Copy link
Contributor

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
Copy link
Contributor

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)
Copy link
Contributor

Choose a reason for hiding this comment

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

👍


else:
import mock

Copy link
Contributor

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?)

Copy link
Member Author

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):
Copy link
Contributor

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?)

@lukpueh
Copy link
Member Author

lukpueh commented Mar 16, 2018

@awwad, thanks for the review! It would be great if you could make the changes you proposed.\

@vladimir-v-diaz, --prompt-pw makes sense. An alternative that I've often come across is to provide a -p/--password [<password>] option, which can be used to either pass the password as argument or, if the password is not passed, signals the cli tool to prompt for a password.

@vladimir-v-diaz
Copy link
Contributor

@vladimir-v-diaz, --prompt-pw makes sense. An alternative that I've often come across is to provide a -p/--password [] option, which can be used to either pass the password as argument or, if the password is not passed, signals the cli tool to prompt for a password.

That's the approach we used in TUF's CLI tool.
https://github.com/theupdateframework/tuf/blob/develop/docs/CLI.md#cli-examples

awwad added 2 commits April 3, 2018 16:26
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]>
@awwad
Copy link
Contributor

awwad commented Apr 3, 2018

Recommendations applied. @vladimir-v-diaz Vlad, does merging this affect you?

@vladimir-v-diaz
Copy link
Contributor

Not now, but I'll have to update the TUF code once a new version of securesystemslib is released.

@awwad awwad merged commit f01cfbc into secure-systems-lab:master Apr 3, 2018
lukpueh added a commit to lukpueh/securesystemslib that referenced this pull request Mar 12, 2020
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...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants