From 5575c2c1358ffce4cba9bf4a3a41b2dbf68924ca Mon Sep 17 00:00:00 2001 From: "Kieran R. Prasch" Date: Thu, 22 Apr 2021 23:01:38 -0700 Subject: [PATCH] Improve keyring creation prompts and warnings --- nucypher/cli/actions/auth.py | 10 ++++++---- nucypher/cli/commands/alice.py | 2 +- nucypher/cli/commands/bob.py | 2 +- nucypher/cli/commands/felix.py | 8 ++++---- nucypher/cli/commands/ursula.py | 4 +++- nucypher/cli/literature.py | 6 ++++++ nucypher/cli/painting/help.py | 13 ++++++++++++- nucypher/cli/utils.py | 2 +- tests/integration/cli/actions/test_auth_actions.py | 3 ++- 9 files changed, 36 insertions(+), 14 deletions(-) diff --git a/nucypher/cli/actions/auth.py b/nucypher/cli/actions/auth.py index dffac5e34..d870b5c9e 100644 --- a/nucypher/cli/actions/auth.py +++ b/nucypher/cli/actions/auth.py @@ -21,18 +21,19 @@ import os from constant_sorrow.constants import NO_PASSWORD from nacl.exceptions import CryptoError -from nucypher.blockchain.eth.signers.software import ClefSigner from nucypher.blockchain.eth.decorators import validate_checksum_address +from nucypher.blockchain.eth.signers.software import ClefSigner from nucypher.characters.control.emitters import StdoutEmitter from nucypher.cli.literature import ( COLLECT_ETH_PASSWORD, COLLECT_NUCYPHER_PASSWORD, DECRYPTING_CHARACTER_KEYRING, - GENERIC_PASSWORD_PROMPT + GENERIC_PASSWORD_PROMPT, + PASSWORD_COLLECTION_NOTICE ) +from nucypher.config.base import CharacterConfiguration from nucypher.config.constants import NUCYPHER_ENVVAR_KEYRING_PASSWORD from nucypher.config.keyring import NucypherKeyring -from nucypher.config.base import CharacterConfiguration def get_password_from_prompt(prompt: str = GENERIC_PASSWORD_PROMPT, envvar: str = None, confirm: bool = False) -> str: @@ -77,11 +78,12 @@ def unlock_signer_account(config: CharacterConfiguration, json_ipc: bool) -> Non config.signer.unlock_account(account=config.checksum_address, password=__password) -def get_nucypher_password(confirm: bool = False, envvar=NUCYPHER_ENVVAR_KEYRING_PASSWORD) -> str: +def get_nucypher_password(emitter, confirm: bool = False, envvar=NUCYPHER_ENVVAR_KEYRING_PASSWORD) -> str: """Interactively collect a nucypher password""" prompt = COLLECT_NUCYPHER_PASSWORD if confirm: from nucypher.config.keyring import NucypherKeyring + emitter.message(PASSWORD_COLLECTION_NOTICE) prompt += f" ({NucypherKeyring.MINIMUM_PASSWORD_LENGTH} character minimum)" keyring_password = get_password_from_prompt(prompt=prompt, confirm=confirm, envvar=envvar) return keyring_password diff --git a/nucypher/cli/commands/alice.py b/nucypher/cli/commands/alice.py index b781b6e93..dbf03bfdc 100644 --- a/nucypher/cli/commands/alice.py +++ b/nucypher/cli/commands/alice.py @@ -206,7 +206,7 @@ class AliceFullConfigOptions: network=opts.domain) return AliceConfiguration.generate( - password=get_nucypher_password(confirm=True), + password=get_nucypher_password(emitter=emitter, confirm=True), config_root=config_root, checksum_address=pay_with, domain=opts.domain, diff --git a/nucypher/cli/commands/bob.py b/nucypher/cli/commands/bob.py index f313dce97..c1908df8d 100644 --- a/nucypher/cli/commands/bob.py +++ b/nucypher/cli/commands/bob.py @@ -141,7 +141,7 @@ class BobConfigOptions: provider_uri=self.provider_uri) # TODO: See #1888 return BobConfiguration.generate( - password=get_nucypher_password(confirm=True), + password=get_nucypher_password(emitter=emitter, confirm=True), config_root=config_root, checksum_address=checksum_address, domain=self.domain, diff --git a/nucypher/cli/commands/felix.py b/nucypher/cli/commands/felix.py index be37fe528..26ea1c836 100644 --- a/nucypher/cli/commands/felix.py +++ b/nucypher/cli/commands/felix.py @@ -16,10 +16,10 @@ along with nucypher. If not, see . """ +import click import os -import click - +from nucypher.characters.control.emitters import StdoutEmitter from nucypher.cli.actions.auth import ( get_client_password, get_nucypher_password, @@ -116,7 +116,7 @@ class FelixConfigOptions: def generate_config(self, config_root, discovery_port): return FelixConfiguration.generate( - password=get_nucypher_password(confirm=True), + password=get_nucypher_password(emitter=StdoutEmitter(), confirm=True), config_root=config_root, rest_host=self.host, rest_port=discovery_port, @@ -163,7 +163,7 @@ class FelixCharacterOptions: # Authenticate unlock_nucypher_keyring(emitter, character_configuration=felix_config, - password=get_nucypher_password(confirm=False)) + password=get_nucypher_password(emitter=emitter, confirm=False)) client_password = get_client_password(checksum_address=felix_config.checksum_address, envvar=NUCYPHER_ENVVAR_WORKER_ETH_PASSWORD) diff --git a/nucypher/cli/commands/ursula.py b/nucypher/cli/commands/ursula.py index 2bc6d27fb..7b36d3c76 100644 --- a/nucypher/cli/commands/ursula.py +++ b/nucypher/cli/commands/ursula.py @@ -179,7 +179,7 @@ class UrsulaConfigOptions: if not self.rest_host: self.rest_host = collect_worker_ip_address(emitter, network=self.domain, force=force) - return UrsulaConfiguration.generate(password=get_nucypher_password(confirm=True), + return UrsulaConfiguration.generate(password=get_nucypher_password(emitter=emitter, confirm=True), config_root=config_root, rest_host=self.rest_host, rest_port=self.rest_port, @@ -301,6 +301,8 @@ def init(general_config, config_options, force, config_root): _pre_launch_warnings(emitter, dev=None, force=force) if not config_root: config_root = general_config.config_root + if not config_options.federated_only and not config_options.provider_uri: + raise click.BadOptionUsage('--provider', message="--provider is required to initialize a new ursula.") if not config_options.federated_only and not config_options.domain: config_options.domain = select_network(emitter) ursula_config = config_options.generate_config(emitter, config_root, force) diff --git a/nucypher/cli/literature.py b/nucypher/cli/literature.py index 4103a6fc0..32a34436f 100644 --- a/nucypher/cli/literature.py +++ b/nucypher/cli/literature.py @@ -210,6 +210,7 @@ SUCCESSFUL_DISABLE_RESTAKING = 'Successfully disabled re-staking for {staking_ad # # Snapshots # + SNAPSHOTS_DISABLING_AGREEMENT = """ By disabling snapshots, staker {staking_address} will be excluded from all future DAO validations until snapshots are enabled. @@ -380,6 +381,11 @@ DEFAULT_TO_LONE_CONFIG_FILE = "Defaulting to {config_class} configuration file: # Authentication # +PASSWORD_COLLECTION_NOTICE = f""" +Please provide a password to lock Worker keys. +Do not forget this password, and ideally store it using a password manager. +""" + COLLECT_ETH_PASSWORD = "Enter ethereum account password ({checksum_address})" COLLECT_NUCYPHER_PASSWORD = 'Enter nucypher keyring password' diff --git a/nucypher/cli/painting/help.py b/nucypher/cli/painting/help.py index 5075aac1b..f27b73a4b 100644 --- a/nucypher/cli/painting/help.py +++ b/nucypher/cli/painting/help.py @@ -55,7 +55,18 @@ def paint_new_installation_help(emitter, new_configuration, filepath): character_config_class = new_configuration.__class__ character_name = character_config_class.NAME.lower() - emitter.message(f"Generated keyring {new_configuration.keyring_root}", color='green') + emitter.message(f"Generated keyring", color='green') + emitter.message(f""" + +Public key (stamp): {bytes(new_configuration.keyring.signing_public_key).hex()} +Path to keyring: {new_configuration.keyring_root} + +- You can share your public key with anyone. Others need it to interact with you. +- Never share secret keys with anyone! Character keys are required to interact with the network! +- Backup your keyring! Without the keyring you wil not be able to use existing network policies. +- Remember your password! Without the password, it's impossible to decrypt the key! + +""") default_config_filepath = True if new_configuration.default_filepath() != filepath: diff --git a/nucypher/cli/utils.py b/nucypher/cli/utils.py index ec0338a63..f8c4e68bc 100644 --- a/nucypher/cli/utils.py +++ b/nucypher/cli/utils.py @@ -84,7 +84,7 @@ def make_cli_character(character_config, if unlock_keyring: unlock_nucypher_keyring(emitter, character_configuration=character_config, - password=get_nucypher_password(confirm=False)) + password=get_nucypher_password(emitter=emitter, confirm=False)) # Handle Signer/Wallet if unlock_signer: diff --git a/tests/integration/cli/actions/test_auth_actions.py b/tests/integration/cli/actions/test_auth_actions.py index 5ac65ce07..d25a73524 100644 --- a/tests/integration/cli/actions/test_auth_actions.py +++ b/tests/integration/cli/actions/test_auth_actions.py @@ -21,6 +21,7 @@ from constant_sorrow.constants import NO_PASSWORD from nacl.exceptions import CryptoError from nucypher.blockchain.eth.decorators import InvalidChecksumAddress +from nucypher.characters.control.emitters import StdoutEmitter from nucypher.cli.actions.auth import ( get_client_password, get_nucypher_password, @@ -90,7 +91,7 @@ def test_get_client_password(mock_stdin, mock_account, confirm, capsys): @pytest.mark.parametrize('confirm', (True, False)) def test_get_nucypher_password(mock_stdin, mock_account, confirm, capsys): mock_stdin.password(INSECURE_DEVELOPMENT_PASSWORD, confirm=confirm) - result = get_nucypher_password(confirm=confirm) + result = get_nucypher_password(emitter=StdoutEmitter(), confirm=confirm) assert result == INSECURE_DEVELOPMENT_PASSWORD assert mock_stdin.empty() captured = capsys.readouterr()