Notify and interactively confirm mnemonic generation.

pull/2701/head
Kieran Prasch 2021-06-15 10:41:41 -07:00 committed by Kieran R. Prasch
parent efee48aaba
commit b92d04ab00
3 changed files with 41 additions and 8 deletions

View File

@ -19,7 +19,6 @@
import json
import os
import re
import tempfile
from abc import ABC, abstractmethod
from decimal import Decimal
from pathlib import Path
@ -31,7 +30,6 @@ from constant_sorrow.constants import (
UNINITIALIZED_CONFIGURATION,
NO_KEYSTORE_ATTACHED,
NO_BLOCKCHAIN_CONNECTION,
FEDERATED_ADDRESS,
DEVELOPMENT_CONFIGURATION,
LIVE_CONFIGURATION
)
@ -57,6 +55,7 @@ from nucypher.crypto.powers import CryptoPower, CryptoPowerUp
from nucypher.crypto.umbral_adapter import Signature
from nucypher.network.middleware import RestMiddleware
from nucypher.utilities.logging import Logger
from umbral.signing import Signature
class BaseConfiguration(ABC):
@ -758,7 +757,7 @@ class CharacterConfiguration(BaseConfiguration):
power_ups.append(power_up)
return power_ups
def initialize(self, password: str) -> str:
def initialize(self, password: str, force: bool = False) -> str:
"""Initialize a new configuration and write installation files to disk."""
# Development
@ -769,7 +768,7 @@ class CharacterConfiguration(BaseConfiguration):
# Persistent
else:
self._ensure_config_root_exists()
self.write_keystore(password=password)
self.write_keystore(password=password, force=force)
self._cache_runtime_filepaths()
self.node_storage.initialize()
@ -783,8 +782,8 @@ class CharacterConfiguration(BaseConfiguration):
self.log.debug(message)
return self.config_root
def write_keystore(self, password: str) -> Keystore:
self.__keystore = Keystore.generate(password=password, keystore_dir=self.keystore_dir)
def write_keystore(self, password: str, force: bool = False) -> Keystore:
self.__keystore = Keystore.generate(password=password, keystore_dir=self.keystore_dir, force=force)
return self.keystore
@classmethod

View File

@ -20,13 +20,13 @@ import json
import os
import stat
import string
import tempfile
from json import JSONDecodeError
from os.path import abspath
from pathlib import Path
from secrets import token_bytes
from typing import Callable, ClassVar, Dict, List, Union, Optional, Tuple
import click
import time
from constant_sorrow.constants import KEYSTORE_LOCKED
from cryptography.hazmat.backends import default_backend
@ -36,6 +36,7 @@ from mnemonic.mnemonic import Mnemonic
from nacl.exceptions import CryptoError
from nacl.secret import SecretBox
from nucypher.characters.control.emitters import StdoutEmitter
from nucypher.config.constants import DEFAULT_CONFIG_ROOT
from nucypher.crypto.constants import BLAKE2B
from nucypher.crypto.keypairs import HostingKeypair
@ -340,6 +341,7 @@ class Keystore:
@classmethod
def restore(cls, words: str, password: str, keystore_dir: Optional[Path] = None) -> 'Keystore':
"""Restore a keystore from seed words"""
__mnemonic = Mnemonic(_MNEMONIC_LANGUAGE)
__secret = __mnemonic.to_entropy(words)
path = Keystore.__save(secret=__secret, password=password, keystore_dir=keystore_dir)
@ -347,14 +349,40 @@ class Keystore:
return keystore
@classmethod
def generate(cls, password: str, keystore_dir: Optional[Path] = None) -> 'Keystore':
def generate(cls, password: str, keystore_dir: Optional[Path] = None, force: bool = False) -> 'Keystore':
"""Generate a new nucypher keystore for use with characters"""
mnemonic = Mnemonic(_MNEMONIC_LANGUAGE)
__words = mnemonic.generate(strength=_ENTROPY_BITS)
cls._confirm_generate(__words, force=force)
__secret = mnemonic.to_entropy(__words)
path = Keystore.__save(secret=__secret, password=password, keystore_dir=keystore_dir)
keystore = cls(keystore_path=path)
return keystore
@staticmethod
def _confirm_generate(__words: str, force: bool) -> None:
"""
Inform the caller of new keystore seed words generation the console
and optionally perform interactive confirmation
"""
# notification
emitter = StdoutEmitter()
emitter.message(f'Backup your seed words, you will not be able to view them again.\n')
emitter.message(f'{__words}\n', color='cyan')
# confirmation
if not force:
if not click.confirm("Have you backed up your seed phrase?"):
emitter.message('Keystore generation aborted.', color='red')
raise click.Abort()
click.clear()
__response = click.prompt("Confirm seed words")
if __response != __words:
raise ValueError('Incorrect seed word confirmation. No keystore has been created, try again.')
click.clear()
@property
def id(self) -> str:
return self.__id

View File

@ -1051,3 +1051,9 @@ def stakeholder_configuration_file_location(custom_filepath):
def mock_teacher_nodes(mocker):
mock_nodes = tuple(u.rest_url() for u in MOCK_KNOWN_URSULAS_CACHE.values())[0:2]
mocker.patch.dict(TEACHER_NODES, {TEMPORARY_DOMAIN: mock_nodes}, clear=True)
@pytest.fixture(autouse=True)
def disable_interactive_keystore_generation(mocker):
# Do not notify or confirm mnemonic seed words during tests normally
mocker.patch.object(Keystore, '_confirm_generate')