Skip to content

Commit

Permalink
Move obtain_current_passphrase next to KeyringWrapper.
Browse files Browse the repository at this point in the history
  • Loading branch information
AmineKhaldi committed Oct 29, 2024
1 parent 6dee78a commit 2267bb1
Show file tree
Hide file tree
Showing 6 changed files with 72 additions and 65 deletions.
8 changes: 5 additions & 3 deletions chia/_tests/core/util/test_keyring_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@
from chia.simulator.keyring import TempKeyring
from chia.util.errors import KeychainFingerprintNotFound, KeychainLabelError, KeychainLabelExists, KeychainLabelInvalid
from chia.util.file_keyring import Key
from chia.util.keyring_wrapper import DEFAULT_PASSPHRASE_IF_NO_MASTER_PASSPHRASE, KeyringWrapper
from chia.util.keyring_wrapper import (
DEFAULT_PASSPHRASE_IF_NO_MASTER_PASSPHRASE,
KeyringWrapper,
obtain_current_passphrase,
)

log = logging.getLogger(__name__)

Expand Down Expand Up @@ -259,8 +263,6 @@ def test_delete_key(self, empty_temp_file_keyring: TempKeyring):
assert KeyringWrapper.get_shared_instance().keyring.get_key("some service", "some user") is None

# Check that metadata is properly deleted
from chia.cmds.passphrase_funcs import obtain_current_passphrase

passphrase = obtain_current_passphrase(use_passphrase_cache=True)
assert KeyringWrapper.get_shared_instance().keyring.cached_file_content.get_decrypted_data_dict(passphrase) == {
"keys": {},
Expand Down
3 changes: 1 addition & 2 deletions chia/cmds/keys_funcs.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@

from chia_rs import AugSchemeMPL, G1Element, G2Element, PrivateKey

from chia.cmds.passphrase_funcs import obtain_current_passphrase
from chia.consensus.coinbase import create_puzzlehash_for_pk
from chia.types.signing_mode import SigningMode
from chia.util.bech32m import bech32_encode, convertbits, encode_puzzle_hash
Expand All @@ -25,7 +24,7 @@
generate_mnemonic,
mnemonic_to_seed,
)
from chia.util.keyring_wrapper import KeyringWrapper
from chia.util.keyring_wrapper import KeyringWrapper, obtain_current_passphrase
from chia.wallet.derive_keys import (
master_pk_to_wallet_pk_unhardened,
master_sk_to_farmer_sk,
Expand Down
56 changes: 1 addition & 55 deletions chia/cmds/passphrase_funcs.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import os
import sys
import time
from getpass import getpass
from io import TextIOWrapper
from pathlib import Path
Expand All @@ -12,15 +11,9 @@

from chia.cmds.cmds_util import prompt_yes_no
from chia.daemon.client import acquire_connection_to_daemon
from chia.util.errors import KeychainMaxUnlockAttempts
from chia.util.keychain import Keychain, supports_os_passphrase_storage
from chia.util.keyring_wrapper import DEFAULT_PASSPHRASE_IF_NO_MASTER_PASSPHRASE, KeyringWrapper
from chia.util.keyring_wrapper import DEFAULT_PASSPHRASE_IF_NO_MASTER_PASSPHRASE, obtain_current_passphrase

DEFAULT_PASSPHRASE_PROMPT = (
colorama.Fore.YELLOW + colorama.Style.BRIGHT + "(Unlock Keyring)" + colorama.Style.RESET_ALL + " Passphrase: "
) # noqa: E501
FAILED_ATTEMPT_DELAY = 0.5
MAX_RETRIES = 3
SAVE_MASTER_PASSPHRASE_WARNING = (
colorama.Fore.YELLOW
+ colorama.Style.BRIGHT
Expand All @@ -31,46 +24,6 @@
)


def obtain_current_passphrase(prompt: str = DEFAULT_PASSPHRASE_PROMPT, use_passphrase_cache: bool = False) -> str:
"""
Obtains the master passphrase for the keyring, optionally using the cached
value (if previously set). If the passphrase isn't already cached, the user is
prompted interactively to enter their passphrase a max of MAX_RETRIES times
before failing.
"""

if use_passphrase_cache:
passphrase, validated = KeyringWrapper.get_shared_instance().get_cached_master_passphrase()
if passphrase:
# If the cached passphrase was previously validated, we assume it's... valid
if validated:
return passphrase

# Cached passphrase needs to be validated
if KeyringWrapper.get_shared_instance().master_passphrase_is_valid(passphrase):
KeyringWrapper.get_shared_instance().set_cached_master_passphrase(passphrase, validated=True)
return passphrase
else:
# Cached passphrase is bad, clear the cache
KeyringWrapper.get_shared_instance().set_cached_master_passphrase(None)

# Prompt interactively with up to MAX_RETRIES attempts
for i in range(MAX_RETRIES):
colorama.init()

passphrase = prompt_for_passphrase(prompt)

if KeyringWrapper.get_shared_instance().master_passphrase_is_valid(passphrase):
# If using the passphrase cache, and the user inputted a passphrase, update the cache
if use_passphrase_cache:
KeyringWrapper.get_shared_instance().set_cached_master_passphrase(passphrase, validated=True)
return passphrase

time.sleep(FAILED_ATTEMPT_DELAY)
print("Incorrect passphrase\n")
raise KeychainMaxUnlockAttempts()


def verify_passphrase_meets_requirements(
new_passphrase: str, confirmation_passphrase: str
) -> tuple[bool, Optional[str]]:
Expand All @@ -88,13 +41,6 @@ def verify_passphrase_meets_requirements(
raise Exception("Unexpected passphrase verification case")


def prompt_for_passphrase(prompt: str) -> str:
if sys.platform == "win32" or sys.platform == "cygwin":
print(prompt, end="", flush=True)
prompt = ""
return getpass(prompt)


def prompt_to_save_passphrase() -> bool:
save: bool = False

Expand Down
4 changes: 2 additions & 2 deletions chia/util/dump_keyring.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@
import yaml
from cryptography.exceptions import InvalidTag

from chia.cmds.passphrase_funcs import prompt_for_passphrase, read_passphrase_from_file
from chia.cmds.passphrase_funcs import read_passphrase_from_file
from chia.util.default_root import DEFAULT_KEYS_ROOT_PATH
from chia.util.file_keyring import FileKeyringContent
from chia.util.keyring_wrapper import DEFAULT_PASSPHRASE_IF_NO_MASTER_PASSPHRASE, KeyringWrapper
from chia.util.keyring_wrapper import DEFAULT_PASSPHRASE_IF_NO_MASTER_PASSPHRASE, KeyringWrapper, prompt_for_passphrase

DEFAULT_KEYRING_YAML = DEFAULT_KEYS_ROOT_PATH / "keyring.yaml"

Expand Down
5 changes: 2 additions & 3 deletions chia/util/file_keyring.py
Original file line number Diff line number Diff line change
Expand Up @@ -406,7 +406,7 @@ def check_passphrase(self, passphrase: str, force_reload: bool = False) -> bool:
return False

def load_keyring(self, passphrase: Optional[str] = None) -> None:
from chia.cmds.passphrase_funcs import obtain_current_passphrase
from chia.util.keyring_wrapper import obtain_current_passphrase

with self.load_keyring_lock:
self.needs_load_keyring = False
Expand All @@ -425,8 +425,7 @@ def load_keyring(self, passphrase: Optional[str] = None) -> None:
)

def write_keyring(self, fresh_salt: bool = False) -> None:
from chia.cmds.passphrase_funcs import obtain_current_passphrase
from chia.util.keyring_wrapper import KeyringWrapper
from chia.util.keyring_wrapper import KeyringWrapper, obtain_current_passphrase

# Merge in other properties like "passphrase_hint"
if "passphrase_hint" in self.file_content_properties_for_next_write:
Expand Down
61 changes: 61 additions & 0 deletions chia/util/keyring_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,21 @@

from __future__ import annotations

import sys
import time
from getpass import getpass
from pathlib import Path
from sys import platform
from typing import ClassVar, Optional, Union, overload

import colorama
from keyring.backends.macOS import Keyring as MacKeyring
from keyring.backends.Windows import WinVaultKeyring as WinKeyring
from keyring.errors import KeyringError, PasswordDeleteError
from typing_extensions import Literal

from chia.util.default_root import DEFAULT_KEYS_ROOT_PATH
from chia.util.errors import KeychainMaxUnlockAttempts
from chia.util.file_keyring import FileKeyring

# We want to protect the keyring, even if a user-specified master passphrase isn't provided
Expand Down Expand Up @@ -54,6 +59,62 @@ def warn_if_macos_errSecInteractionNotAllowed(error: KeyringError) -> bool:
return False


DEFAULT_PASSPHRASE_PROMPT = (
colorama.Fore.YELLOW + colorama.Style.BRIGHT + "(Unlock Keyring)" + colorama.Style.RESET_ALL + " Passphrase: "
) # noqa: E501
FAILED_ATTEMPT_DELAY = 0.5
MAX_RETRIES = 3


def prompt_for_passphrase(prompt: str) -> str:
if sys.platform == "win32" or sys.platform == "cygwin":
print(prompt, end="", flush=True)

Check failure

Code scanning / CodeQL

Clear-text logging of sensitive information High

This expression logs
sensitive data (password)
as clear text.
This expression logs
sensitive data (password)
as clear text.
prompt = ""
return getpass(prompt)


def obtain_current_passphrase(prompt: str = DEFAULT_PASSPHRASE_PROMPT, use_passphrase_cache: bool = False) -> str:
from chia.util.keyring_wrapper import KeyringWrapper

"""
Obtains the master passphrase for the keyring, optionally using the cached
value (if previously set). If the passphrase isn't already cached, the user is
prompted interactively to enter their passphrase a max of MAX_RETRIES times
before failing.
"""

if use_passphrase_cache:
passphrase, validated = KeyringWrapper.get_shared_instance().get_cached_master_passphrase()
if passphrase:
# If the cached passphrase was previously validated, we assume it's... valid
if validated:
return passphrase

# Cached passphrase needs to be validated
if KeyringWrapper.get_shared_instance().master_passphrase_is_valid(passphrase):
KeyringWrapper.get_shared_instance().set_cached_master_passphrase(passphrase, validated=True)
return passphrase
else:
# Cached passphrase is bad, clear the cache
KeyringWrapper.get_shared_instance().set_cached_master_passphrase(None)

# Prompt interactively with up to MAX_RETRIES attempts
for i in range(MAX_RETRIES):
colorama.init()

passphrase = prompt_for_passphrase(prompt)

if KeyringWrapper.get_shared_instance().master_passphrase_is_valid(passphrase):
# If using the passphrase cache, and the user inputted a passphrase, update the cache
if use_passphrase_cache:
KeyringWrapper.get_shared_instance().set_cached_master_passphrase(passphrase, validated=True)
return passphrase

time.sleep(FAILED_ATTEMPT_DELAY)
print("Incorrect passphrase\n")
raise KeychainMaxUnlockAttempts()


class KeyringWrapper:
"""
KeyringWrapper provides an abstraction that the Keychain class can use
Expand Down

0 comments on commit 2267bb1

Please sign in to comment.