This commit is contained in:
thePR0M3TH3AN
2024-10-23 16:35:30 -04:00
parent 4875394da9
commit d8aff057b7
8 changed files with 232 additions and 71 deletions

View File

@@ -91,3 +91,5 @@ MAX_PASSWORD_LENGTH = 128 # Maximum allowed password length
# Additional Constants (if any)
# -----------------------------------
# Add any other constants here as your project expands
HASHED_PASSWORD_FILE = APP_DIR / 'hashed_password.enc'
DEFAULT_SEED_BACKUP_FILENAME = 'parent_seed_backup.enc'

View File

@@ -57,11 +57,12 @@ def display_menu(password_manager: PasswordManager, nostr_client: NostrClient):
5. Post Encrypted Index to Nostr
6. Retrieve Encrypted Index from Nostr
7. Display Nostr Public Key (npub)
8. Exit
8. Backup/Reveal Parent Seed
9. Exit
"""
while True:
print(colored(menu, 'cyan'))
choice = input('Enter your choice (1-8): ').strip()
choice = input('Enter your choice (1-9): ').strip() # Updated to include option 9
if choice == '1':
password_manager.handle_generate_password()
elif choice == '2':
@@ -77,6 +78,8 @@ def display_menu(password_manager: PasswordManager, nostr_client: NostrClient):
elif choice == '7':
handle_display_npub(nostr_client)
elif choice == '8':
password_manager.handle_backup_reveal_parent_seed() # Corrected variable name
elif choice == '9':
logging.info("Exiting the program.")
print(colored("Exiting the program.", 'green'))
nostr_client.close_client_pool() # Gracefully close the ClientPool

View File

@@ -16,6 +16,7 @@ This means it should generate passwords the exact same way every single time. S
import os
import json
import stat
import hashlib
import logging
import traceback
@@ -90,16 +91,18 @@ class EncryptionManager:
def encrypt_parent_seed(self, parent_seed: str, file_path: Path) -> None:
"""
Encrypts and saves the parent seed to the specified file.
Encrypts and securely saves the parent seed to the specified file.
:param parent_seed: The BIP39 parent seed phrase.
:param file_path: The path to the file where the encrypted parent seed will be saved.
"""
try:
# **Do not encrypt the data here**
# Encode the parent seed to bytes
data = parent_seed.encode('utf-8')
# Pass the raw data to encrypt_file, which handles encryption
# Encrypt and write to file using encrypt_file
self.encrypt_file(file_path, data)
# Set file permissions to read/write for the user only
os.chmod(file_path, stat.S_IRUSR | stat.S_IWUSR)
logger.info(f"Parent seed encrypted and saved to '{file_path}'.")
print(colored(f"Parent seed encrypted and saved to '{file_path}'.", 'green'))
except Exception as e:
@@ -141,7 +144,7 @@ class EncryptionManager:
return encrypted_data
except Exception as e:
logger.error(f"Error encrypting data: {e}")
logger.error(traceback.format_exc()) # Log full traceback
logger.error(traceback.format_exc())
print(colored(f"Error: Failed to encrypt data: {e}", 'red'))
raise

View File

@@ -25,7 +25,7 @@ from password_manager.password_generation import PasswordGenerator
from password_manager.backup import BackupManager
from utils.key_derivation import derive_key_from_parent_seed, derive_key_from_password
from utils.checksum import calculate_checksum, verify_checksum
from utils.password_prompt import prompt_for_password
from utils.password_prompt import prompt_for_password, prompt_existing_password, confirm_action
from constants import (
APP_DIR,
@@ -35,10 +35,14 @@ from constants import (
SCRIPT_CHECKSUM_FILE,
MIN_PASSWORD_LENGTH,
MAX_PASSWORD_LENGTH,
DEFAULT_PASSWORD_LENGTH
DEFAULT_PASSWORD_LENGTH,
HASHED_PASSWORD_FILE, # Ensure this constant is defined in constants.py
DEFAULT_SEED_BACKUP_FILENAME
)
import traceback # Added for exception traceback logging
import bcrypt # Ensure bcrypt is installed in your environment
from pathlib import Path # Required for handling file paths
# Configure logging at the start of the module
def configure_logging():
@@ -112,7 +116,7 @@ class PasswordManager:
self.encryption_manager = EncryptionManager(key)
self.parent_seed = self.encryption_manager.decrypt_parent_seed(PARENT_SEED_FILE)
# **Add validation for the decrypted seed**
# Validate the decrypted seed
if not self.validate_seed_phrase(self.parent_seed):
logging.error("Decrypted seed is invalid. Exiting.")
print(colored("Error: Decrypted seed is invalid.", 'red'))
@@ -149,6 +153,9 @@ class PasswordManager:
try:
self.encryption_manager.encrypt_parent_seed(parent_seed, PARENT_SEED_FILE)
logging.info("Parent seed encrypted and saved successfully.")
# Store the hashed password
self.store_hashed_password(password)
logging.info("User password hashed and stored successfully.")
except Exception as e:
logging.error(f"Failed to encrypt and save parent seed: {e}")
logging.error(traceback.format_exc())
@@ -182,7 +189,7 @@ class PasswordManager:
print(colored(f"Error: {e}", 'red'))
return None
def validate_seed_phrase(self, seed_phrase: str) -> Optional[str]:
def validate_seed_phrase(self, seed_phrase: str) -> bool:
"""
Validates the seed phrase using the EncryptionManager if available,
otherwise performs basic validation.
@@ -191,26 +198,26 @@ class PasswordManager:
seed_phrase (str): The seed phrase to validate.
Returns:
Optional[str]: The validated seed phrase or None if invalid.
bool: True if valid, False otherwise.
"""
try:
if self.encryption_manager:
# Use EncryptionManager to validate seed
if self.encryption_manager.validate_seed(seed_phrase):
is_valid = self.encryption_manager.validate_seed(seed_phrase)
if is_valid:
logging.debug("Seed phrase validated successfully using EncryptionManager.")
return seed_phrase
else:
logging.error("Invalid seed phrase.")
print(colored("Error: Invalid seed phrase.", 'red'))
return None
return is_valid
else:
# Perform basic validation
return self.basic_validate_seed_phrase(seed_phrase)
return self.basic_validate_seed_phrase(seed_phrase) is not None
except Exception as e:
logging.error(f"Error validating seed phrase: {e}")
logging.error(traceback.format_exc())
print(colored(f"Error: Failed to validate seed phrase: {e}", 'red'))
return None
return False
def initialize_managers(self) -> None:
"""
@@ -443,7 +450,101 @@ class PasswordManager:
logging.error(traceback.format_exc())
print(colored(f"Error: Failed to restore backup: {e}", 'red'))
# Additional methods can be added here as needed
def handle_backup_reveal_parent_seed(self) -> None:
"""
Handles the backup and reveal of the parent seed.
"""
try:
print(colored("\n=== Backup/Reveal Parent Seed ===", 'yellow'))
print(colored("Warning: Revealing your parent seed is a highly sensitive operation.", 'red'))
print(colored("Ensure you're in a secure, private environment and no one is watching your screen.", 'red'))
# Verify user's identity with secure password verification
password = prompt_existing_password("Enter your master password to continue: ")
if not self.verify_password(password):
print(colored("Incorrect password. Operation aborted.", 'red'))
return
# Double confirmation
if not confirm_action("Are you absolutely sure you want to reveal your parent seed? (Y/N): "):
print(colored("Operation cancelled by user.", 'yellow'))
return
# Reveal the parent seed
print(colored("\n=== Your Parent Seed ===", 'green'))
print(colored(self.parent_seed, 'yellow'))
print(colored("\nPlease write this down and store it securely. Do not share it with anyone.", 'red'))
# Option to save to file with default filename
if confirm_action("Do you want to save this to an encrypted backup file? (Y/N): "):
filename = input(f"Enter filename to save (default: {DEFAULT_SEED_BACKUP_FILENAME}): ").strip()
filename = filename if filename else DEFAULT_SEED_BACKUP_FILENAME
backup_path = Path(APP_DIR) / filename
# Validate filename
if not self.is_valid_filename(filename):
print(colored("Invalid filename. Operation aborted.", 'red'))
return
self.encryption_manager.encrypt_parent_seed(self.parent_seed, backup_path)
print(colored(f"Encrypted seed backup saved to '{backup_path}'. Ensure this file is stored securely.", 'green'))
except Exception as e:
logging.error(f"Error during parent seed backup/reveal: {e}")
logging.error(traceback.format_exc())
print(colored(f"Error: Failed to backup/reveal parent seed: {e}", 'red'))
def verify_password(self, password: str) -> bool:
"""
Verifies the provided password against the stored hashed password.
"""
try:
if not os.path.exists(HASHED_PASSWORD_FILE):
logging.error("Hashed password file not found.")
print(colored("Error: Hashed password file not found.", 'red'))
return False
with open(HASHED_PASSWORD_FILE, 'rb') as f:
stored_hash = f.read()
is_correct = bcrypt.checkpw(password.encode('utf-8'), stored_hash)
if is_correct:
logging.debug("Password verification successful.")
else:
logging.warning("Password verification failed.")
return is_correct
except Exception as e:
logging.error(f"Error verifying password: {e}")
logging.error(traceback.format_exc())
print(colored(f"Error: Failed to verify password: {e}", 'red'))
return False
def is_valid_filename(self, filename: str) -> bool:
"""
Validates the provided filename to prevent directory traversal and invalid characters.
"""
# Basic validation: filename should not contain path separators or be empty
invalid_chars = ['/', '\\', '..']
if any(char in filename for char in invalid_chars) or not filename:
logging.warning(f"Invalid filename attempted: {filename}")
return False
return True
def store_hashed_password(self, password: str) -> None:
"""
Hashes and stores the user's password securely using bcrypt.
This should be called during the initial setup.
"""
try:
hashed = bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt())
with open(HASHED_PASSWORD_FILE, 'wb') as f:
f.write(hashed)
# Set file permissions to read/write for the user only
os.chmod(HASHED_PASSWORD_FILE, 0o600)
logging.info("User password hashed and stored successfully.")
except Exception as e:
logging.error(f"Failed to store hashed password: {e}")
logging.error(traceback.format_exc())
print(colored(f"Error: Failed to store hashed password: {e}", 'red'))
raise
# Example usage (this part should be removed or commented out when integrating into the larger application)
if __name__ == "__main__":

View File

@@ -5,4 +5,5 @@ bip-utils>=2.5.0
bech32==1.2.0
monstr @ git+https://github.com/monty888/monstr.git@master#egg=monstr
mnemonic
aiohttp
aiohttp
bcrypt