mirror of
https://github.com/PR0M3TH3AN/SeedPass.git
synced 2025-09-08 23:38:49 +00:00
Merge pull request #163 from PR0M3TH3AN/codex/replace-sys.exit-with-custom-exceptions
Add custom exception handling
This commit is contained in:
@@ -31,6 +31,12 @@ from cryptography.hazmat.backends import default_backend
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class Bip85Error(Exception):
|
||||||
|
"""Exception raised for BIP85-related errors."""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class BIP85:
|
class BIP85:
|
||||||
def __init__(self, seed_bytes: bytes | str):
|
def __init__(self, seed_bytes: bytes | str):
|
||||||
"""Initialize from BIP39 seed bytes or BIP32 xprv string."""
|
"""Initialize from BIP39 seed bytes or BIP32 xprv string."""
|
||||||
@@ -43,7 +49,7 @@ class BIP85:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"Error initializing BIP32 context: {e}", exc_info=True)
|
logging.error(f"Error initializing BIP32 context: {e}", exc_info=True)
|
||||||
print(f"{Fore.RED}Error initializing BIP32 context: {e}")
|
print(f"{Fore.RED}Error initializing BIP32 context: {e}")
|
||||||
sys.exit(1)
|
raise Bip85Error(f"Error initializing BIP32 context: {e}")
|
||||||
|
|
||||||
def derive_entropy(
|
def derive_entropy(
|
||||||
self, index: int, bytes_len: int, app_no: int = 39, words_len: int | None = None
|
self, index: int, bytes_len: int, app_no: int = 39, words_len: int | None = None
|
||||||
@@ -90,21 +96,23 @@ class BIP85:
|
|||||||
print(
|
print(
|
||||||
f"{Fore.RED}Error: Derived entropy length is {len(entropy)} bytes; expected {bytes_len} bytes."
|
f"{Fore.RED}Error: Derived entropy length is {len(entropy)} bytes; expected {bytes_len} bytes."
|
||||||
)
|
)
|
||||||
sys.exit(1)
|
raise Bip85Error(
|
||||||
|
f"Derived entropy length is {len(entropy)} bytes; expected {bytes_len} bytes."
|
||||||
|
)
|
||||||
|
|
||||||
logging.debug(f"Derived entropy: {entropy.hex()}")
|
logging.debug(f"Derived entropy: {entropy.hex()}")
|
||||||
return entropy
|
return entropy
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"Error deriving entropy: {e}", exc_info=True)
|
logging.error(f"Error deriving entropy: {e}", exc_info=True)
|
||||||
print(f"{Fore.RED}Error deriving entropy: {e}")
|
print(f"{Fore.RED}Error deriving entropy: {e}")
|
||||||
sys.exit(1)
|
raise Bip85Error(f"Error deriving entropy: {e}")
|
||||||
|
|
||||||
def derive_mnemonic(self, index: int, words_num: int) -> str:
|
def derive_mnemonic(self, index: int, words_num: int) -> str:
|
||||||
bytes_len = {12: 16, 18: 24, 24: 32}.get(words_num)
|
bytes_len = {12: 16, 18: 24, 24: 32}.get(words_num)
|
||||||
if not bytes_len:
|
if not bytes_len:
|
||||||
logging.error(f"Unsupported number of words: {words_num}")
|
logging.error(f"Unsupported number of words: {words_num}")
|
||||||
print(f"{Fore.RED}Error: Unsupported number of words: {words_num}")
|
print(f"{Fore.RED}Error: Unsupported number of words: {words_num}")
|
||||||
sys.exit(1)
|
raise Bip85Error(f"Unsupported number of words: {words_num}")
|
||||||
|
|
||||||
entropy = self.derive_entropy(
|
entropy = self.derive_entropy(
|
||||||
index=index, bytes_len=bytes_len, app_no=39, words_len=words_num
|
index=index, bytes_len=bytes_len, app_no=39, words_len=words_num
|
||||||
@@ -118,7 +126,7 @@ class BIP85:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"Error generating mnemonic: {e}", exc_info=True)
|
logging.error(f"Error generating mnemonic: {e}", exc_info=True)
|
||||||
print(f"{Fore.RED}Error generating mnemonic: {e}")
|
print(f"{Fore.RED}Error generating mnemonic: {e}")
|
||||||
sys.exit(1)
|
raise Bip85Error(f"Error generating mnemonic: {e}")
|
||||||
|
|
||||||
def derive_symmetric_key(self, index: int = 0, app_no: int = 2) -> bytes:
|
def derive_symmetric_key(self, index: int = 0, app_no: int = 2) -> bytes:
|
||||||
"""Derive 32 bytes of entropy for symmetric key usage."""
|
"""Derive 32 bytes of entropy for symmetric key usage."""
|
||||||
@@ -129,4 +137,4 @@ class BIP85:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"Error deriving symmetric key: {e}", exc_info=True)
|
logging.error(f"Error deriving symmetric key: {e}", exc_info=True)
|
||||||
print(f"{Fore.RED}Error deriving symmetric key: {e}")
|
print(f"{Fore.RED}Error deriving symmetric key: {e}")
|
||||||
sys.exit(1)
|
raise Bip85Error(f"Error deriving symmetric key: {e}")
|
||||||
|
16
src/main.py
16
src/main.py
@@ -17,6 +17,8 @@ import traceback
|
|||||||
from password_manager.manager import PasswordManager
|
from password_manager.manager import PasswordManager
|
||||||
from nostr.client import NostrClient
|
from nostr.client import NostrClient
|
||||||
from constants import INACTIVITY_TIMEOUT
|
from constants import INACTIVITY_TIMEOUT
|
||||||
|
from utils.password_prompt import PasswordPromptError
|
||||||
|
from local_bip85.bip85 import Bip85Error
|
||||||
|
|
||||||
|
|
||||||
colorama_init()
|
colorama_init()
|
||||||
@@ -631,6 +633,10 @@ if __name__ == "__main__":
|
|||||||
try:
|
try:
|
||||||
password_manager = PasswordManager()
|
password_manager = PasswordManager()
|
||||||
logger.info("PasswordManager initialized successfully.")
|
logger.info("PasswordManager initialized successfully.")
|
||||||
|
except (PasswordPromptError, Bip85Error) as e:
|
||||||
|
logger.error(f"Failed to initialize PasswordManager: {e}", exc_info=True)
|
||||||
|
print(colored(f"Error: Failed to initialize PasswordManager: {e}", "red"))
|
||||||
|
sys.exit(1)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Failed to initialize PasswordManager: {e}", exc_info=True)
|
logger.error(f"Failed to initialize PasswordManager: {e}", exc_info=True)
|
||||||
print(colored(f"Error: Failed to initialize PasswordManager: {e}", "red"))
|
print(colored(f"Error: Failed to initialize PasswordManager: {e}", "red"))
|
||||||
@@ -677,6 +683,16 @@ if __name__ == "__main__":
|
|||||||
logging.error(f"Error during shutdown: {e}")
|
logging.error(f"Error during shutdown: {e}")
|
||||||
print(colored(f"Error during shutdown: {e}", "red"))
|
print(colored(f"Error during shutdown: {e}", "red"))
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
except (PasswordPromptError, Bip85Error) as e:
|
||||||
|
logger.error(f"A user-related error occurred: {e}", exc_info=True)
|
||||||
|
print(colored(f"Error: {e}", "red"))
|
||||||
|
try:
|
||||||
|
password_manager.nostr_client.close_client_pool()
|
||||||
|
logging.info("NostrClient closed successfully.")
|
||||||
|
except Exception as close_error:
|
||||||
|
logging.error(f"Error during shutdown: {close_error}")
|
||||||
|
print(colored(f"Error during shutdown: {close_error}", "red"))
|
||||||
|
sys.exit(1)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"An unexpected error occurred: {e}", exc_info=True)
|
logger.error(f"An unexpected error occurred: {e}", exc_info=True)
|
||||||
print(colored(f"Error: An unexpected error occurred: {e}", "red"))
|
print(colored(f"Error: An unexpected error occurred: {e}", "red"))
|
||||||
|
@@ -55,7 +55,7 @@ import gzip
|
|||||||
import bcrypt
|
import bcrypt
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from local_bip85.bip85 import BIP85
|
from local_bip85.bip85 import BIP85, Bip85Error
|
||||||
from bip_utils import Bip39SeedGenerator, Bip39MnemonicGenerator, Bip39Languages
|
from bip_utils import Bip39SeedGenerator, Bip39MnemonicGenerator, Bip39Languages
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
@@ -645,6 +645,10 @@ class PasswordManager:
|
|||||||
bip85 = BIP85(master_seed)
|
bip85 = BIP85(master_seed)
|
||||||
mnemonic = bip85.derive_mnemonic(index=0, words_num=12)
|
mnemonic = bip85.derive_mnemonic(index=0, words_num=12)
|
||||||
return mnemonic
|
return mnemonic
|
||||||
|
except Bip85Error as e:
|
||||||
|
logging.error(f"Failed to generate BIP-85 seed: {e}", exc_info=True)
|
||||||
|
print(colored(f"Error: Failed to generate BIP-85 seed: {e}", "red"))
|
||||||
|
sys.exit(1)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"Failed to generate BIP-85 seed: {e}", exc_info=True)
|
logging.error(f"Failed to generate BIP-85 seed: {e}", exc_info=True)
|
||||||
print(colored(f"Error: Failed to generate BIP-85 seed: {e}", "red"))
|
print(colored(f"Error: Failed to generate BIP-85 seed: {e}", "red"))
|
||||||
|
@@ -4,7 +4,7 @@ import pytest
|
|||||||
|
|
||||||
sys.path.append(str(Path(__file__).resolve().parents[1]))
|
sys.path.append(str(Path(__file__).resolve().parents[1]))
|
||||||
|
|
||||||
from local_bip85.bip85 import BIP85
|
from local_bip85.bip85 import BIP85, Bip85Error
|
||||||
|
|
||||||
MASTER_XPRV = "xprv9s21ZrQH143K2LBWUUQRFXhucrQqBpKdRRxNVq2zBqsx8HVqFk2uYo8kmbaLLHRdqtQpUm98uKfu3vca1LqdGhUtyoFnCNkfmXRyPXLjbKb"
|
MASTER_XPRV = "xprv9s21ZrQH143K2LBWUUQRFXhucrQqBpKdRRxNVq2zBqsx8HVqFk2uYo8kmbaLLHRdqtQpUm98uKfu3vca1LqdGhUtyoFnCNkfmXRyPXLjbKb"
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@ def test_bip85_symmetric_key(bip85):
|
|||||||
|
|
||||||
|
|
||||||
def test_invalid_params(bip85):
|
def test_invalid_params(bip85):
|
||||||
with pytest.raises(SystemExit):
|
with pytest.raises(Bip85Error):
|
||||||
bip85.derive_mnemonic(index=0, words_num=15)
|
bip85.derive_mnemonic(index=0, words_num=15)
|
||||||
with pytest.raises(SystemExit):
|
with pytest.raises(Bip85Error):
|
||||||
bip85.derive_mnemonic(index=-1, words_num=12)
|
bip85.derive_mnemonic(index=-1, words_num=12)
|
||||||
|
@@ -29,6 +29,12 @@ colorama_init()
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class PasswordPromptError(Exception):
|
||||||
|
"""Exception raised for password prompt errors."""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def prompt_new_password() -> str:
|
def prompt_new_password() -> str:
|
||||||
"""
|
"""
|
||||||
Prompts the user to enter and confirm a new password for encrypting the parent seed.
|
Prompts the user to enter and confirm a new password for encrypting the parent seed.
|
||||||
@@ -40,7 +46,7 @@ def prompt_new_password() -> str:
|
|||||||
str: The confirmed password entered by the user.
|
str: The confirmed password entered by the user.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
SystemExit: If the user fails to provide a valid password after multiple attempts.
|
PasswordPromptError: If the user fails to provide a valid password after multiple attempts.
|
||||||
"""
|
"""
|
||||||
max_retries = 5
|
max_retries = 5
|
||||||
attempts = 0
|
attempts = 0
|
||||||
@@ -87,7 +93,7 @@ def prompt_new_password() -> str:
|
|||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
print(colored("\nOperation cancelled by user.", "yellow"))
|
print(colored("\nOperation cancelled by user.", "yellow"))
|
||||||
logging.info("Password prompt interrupted by user.")
|
logging.info("Password prompt interrupted by user.")
|
||||||
sys.exit(0)
|
raise PasswordPromptError("Operation cancelled by user")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(
|
logging.error(
|
||||||
f"Unexpected error during password prompt: {e}", exc_info=True
|
f"Unexpected error during password prompt: {e}", exc_info=True
|
||||||
@@ -97,7 +103,7 @@ def prompt_new_password() -> str:
|
|||||||
|
|
||||||
print(colored("Maximum password attempts exceeded. Exiting.", "red"))
|
print(colored("Maximum password attempts exceeded. Exiting.", "red"))
|
||||||
logging.error("User failed to provide a valid password after multiple attempts.")
|
logging.error("User failed to provide a valid password after multiple attempts.")
|
||||||
sys.exit(1)
|
raise PasswordPromptError("Maximum password attempts exceeded")
|
||||||
|
|
||||||
|
|
||||||
def prompt_existing_password(prompt_message: str = "Enter your password: ") -> str:
|
def prompt_existing_password(prompt_message: str = "Enter your password: ") -> str:
|
||||||
@@ -113,7 +119,7 @@ def prompt_existing_password(prompt_message: str = "Enter your password: ") -> s
|
|||||||
str: The password entered by the user.
|
str: The password entered by the user.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
SystemExit: If the user interrupts the operation.
|
PasswordPromptError: If the user interrupts the operation.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
password = getpass.getpass(prompt=prompt_message).strip()
|
password = getpass.getpass(prompt=prompt_message).strip()
|
||||||
@@ -121,7 +127,7 @@ def prompt_existing_password(prompt_message: str = "Enter your password: ") -> s
|
|||||||
if not password:
|
if not password:
|
||||||
print(colored("Error: Password cannot be empty.", "red"))
|
print(colored("Error: Password cannot be empty.", "red"))
|
||||||
logging.warning("User attempted to enter an empty password.")
|
logging.warning("User attempted to enter an empty password.")
|
||||||
sys.exit(1)
|
raise PasswordPromptError("Password cannot be empty")
|
||||||
|
|
||||||
# Normalize the password to NFKD form
|
# Normalize the password to NFKD form
|
||||||
normalized_password = unicodedata.normalize("NFKD", password)
|
normalized_password = unicodedata.normalize("NFKD", password)
|
||||||
@@ -131,13 +137,13 @@ def prompt_existing_password(prompt_message: str = "Enter your password: ") -> s
|
|||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
print(colored("\nOperation cancelled by user.", "yellow"))
|
print(colored("\nOperation cancelled by user.", "yellow"))
|
||||||
logging.info("Existing password prompt interrupted by user.")
|
logging.info("Existing password prompt interrupted by user.")
|
||||||
sys.exit(0)
|
raise PasswordPromptError("Operation cancelled by user")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(
|
logging.error(
|
||||||
f"Unexpected error during existing password prompt: {e}", exc_info=True
|
f"Unexpected error during existing password prompt: {e}", exc_info=True
|
||||||
)
|
)
|
||||||
print(colored(f"Error: {e}", "red"))
|
print(colored(f"Error: {e}", "red"))
|
||||||
sys.exit(1)
|
raise PasswordPromptError(str(e))
|
||||||
|
|
||||||
|
|
||||||
def confirm_action(
|
def confirm_action(
|
||||||
@@ -154,7 +160,7 @@ def confirm_action(
|
|||||||
bool: True if the user confirms the action, False otherwise.
|
bool: True if the user confirms the action, False otherwise.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
SystemExit: If the user interrupts the operation.
|
PasswordPromptError: If the user interrupts the operation.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
while True:
|
while True:
|
||||||
@@ -171,13 +177,13 @@ def confirm_action(
|
|||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
print(colored("\nOperation cancelled by user.", "yellow"))
|
print(colored("\nOperation cancelled by user.", "yellow"))
|
||||||
logging.info("Action confirmation interrupted by user.")
|
logging.info("Action confirmation interrupted by user.")
|
||||||
sys.exit(0)
|
raise PasswordPromptError("Operation cancelled by user")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(
|
logging.error(
|
||||||
f"Unexpected error during action confirmation: {e}", exc_info=True
|
f"Unexpected error during action confirmation: {e}", exc_info=True
|
||||||
)
|
)
|
||||||
print(colored(f"Error: {e}", "red"))
|
print(colored(f"Error: {e}", "red"))
|
||||||
sys.exit(1)
|
raise PasswordPromptError(str(e))
|
||||||
|
|
||||||
|
|
||||||
def prompt_for_password() -> str:
|
def prompt_for_password() -> str:
|
||||||
|
Reference in New Issue
Block a user