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

@@ -221,12 +221,6 @@ The SeedPass roadmap outlines a structured development plan divided into distinc
- Implement synchronization logic to fetch and update entries from Nostr as needed. - Implement synchronization logic to fetch and update entries from Nostr as needed.
4. **Security Enhancements** 4. **Security Enhancements**
- **Parent Seed Recovery**
- **Description:** Develop a secure method for users to recover their parent seed if lost.
- **Features:**
- **Recovery Phrase:** Allow users to generate and store a recovery phrase or backup file.
- **Multi-Factor Authentication (MFA):** Integrate MFA to enhance the security of the recovery process.
- **Encrypted Storage:** Ensure that recovery data is encrypted and stored securely.
- **"Secret" Mode (Clipboard-Only Password Retrieval)** - **"Secret" Mode (Clipboard-Only Password Retrieval)**
- **Description:** Introduce a "secret" mode where passwords are copied directly to the clipboard rather than displayed on the screen upon retrieval. - **Description:** Introduce a "secret" mode where passwords are copied directly to the clipboard rather than displayed on the screen upon retrieval.
- **Features:** - **Features:**

View File

@@ -65,15 +65,15 @@ The following table provides a quick reference to all available advanced CLI com
| Post encrypted index to Nostr | `post` | `-P` | `--post` | `seedpass post` | | Post encrypted index to Nostr | `post` | `-P` | `--post` | `seedpass post` |
| Retrieve from Nostr | `get-nostr` | `-GN` | `--get-nostr` | `seedpass get-nostr` | | Retrieve from Nostr | `get-nostr` | `-GN` | `--get-nostr` | `seedpass get-nostr` |
| Display Nostr public key | `show-pubkey` | `-K` | `--show-pubkey` | `seedpass show-pubkey` | | Display Nostr public key | `show-pubkey` | `-K` | `--show-pubkey` | `seedpass show-pubkey` |
| **Set Custom Nostr Relays** | `set-relays` | `-SR` | `--set-relays` | `seedpass set-relays --add "wss://relay1.example.com" --add "wss://relay2.example.com"` | | Set Custom Nostr Relays | `set-relays` | `-SR` | `--set-relays` | `seedpass set-relays --add "wss://relay1.example.com" --add "wss://relay2.example.com"` |
| **Enable "Secret" Mode** | `set-secret` | `-SS` | `--set-secret` | `seedpass set-secret --enable` or `seedpass set-secret --disable` | | Enable "Secret" Mode | `set-secret` | `-SS` | `--set-secret` | `seedpass set-secret --enable` or `seedpass set-secret --disable` |
| **Batch Post Index Items to Nostr** | `batch-post` | `-BP` | `--batch-post` | `seedpass batch-post --start 0 --end 9` or `seedpass batch-post --range 10-19` | | Batch Post Index Items to Nostr | `batch-post` | `-BP` | `--batch-post` | `seedpass batch-post --start 0 --end 9` or `seedpass batch-post --range 10-19` |
| **Show All Passwords** | `show-all` | `-SA` | `--show-all` | `seedpass show-all` | | Show All Passwords | `show-all` | `-SA` | `--show-all` | `seedpass show-all` |
| **Add Notes to an Entry** | `add-notes` | `-AN` | `--add-notes` | `seedpass add-notes --index 3 --notes "This is a secured account"` | | Add Notes to an Entry | `add-notes` | `-AN` | `--add-notes` | `seedpass add-notes --index 3 --notes "This is a secured account"` |
| **Add Tags to an Entry** | `add-tags` | `-AT` | `--add-tags` | `seedpass add-tags --index 3 --tags "personal,finance"` | | Add Tags to an Entry | `add-tags` | `-AT` | `--add-tags` | `seedpass add-tags --index 3 --tags "personal,finance"` |
| **Search by Tag or Title** | `search-by` | `-SB` | `--search-by` | `seedpass search-by --tag "work"` or `seedpass search-by --title "GitHub"` | | Search by Tag or Title | `search-by` | `-SB` | `--search-by` | `seedpass search-by --tag "work"` or `seedpass search-by --title "GitHub"` |
| **Automatically Post Index to Nostr After Edit** | `auto-post` | `-AP` | `--auto-post` | `seedpass auto-post --enable` or `seedpass auto-post --disable` | | Automatically Post Index to Nostr After Edit | `auto-post` | `-AP` | `--auto-post` | `seedpass auto-post --enable` or `seedpass auto-post --disable` |
| **Initial Setup Prompt for Seed Generation/Import** | `setup` | `-ST` | `--setup` | `seedpass setup` | | Initial Setup Prompt for Seed Generation/Import | `setup` | `-ST` | `--setup` | `seedpass setup` |
--- ---
@@ -577,7 +577,7 @@ seedpass auto-post --disable
**Long Flag:** `--setup` **Long Flag:** `--setup`
**Description:** **Description:**
Guides users through the initial setup process, allowing them to choose between generating a new seed or importing an existing one. This command also handles the encryption of the seed and the creation of a Nostr profile. Guides users through the initial setup process, allowing them to choose between generating a new seed or importing an existing one. This command also handles the encryption of the seed and the creation of a profile.
**Usage Example:** **Usage Example:**
```bash ```bash
@@ -587,7 +587,7 @@ seedpass setup
**Features to Implement:** **Features to Implement:**
- **Seed Choice Prompt:** Asks users whether they want to generate a new seed or import an existing one. - **Seed Choice Prompt:** Asks users whether they want to generate a new seed or import an existing one.
- **Encryption of Seed:** Uses the user-selected password to encrypt the seed, whether generated or imported. - **Encryption of Seed:** Uses the user-selected password to encrypt the seed, whether generated or imported.
- **Nostr Profile Creation:** Upon first login, automatically generates a Nostr profile and checks for existing index data notes that can be pulled and decrypted. - **Profile Creation:** Upon first login, automatically generates a profile and checks for existing index data notes that can be pulled and decrypted.
--- ---
@@ -604,7 +604,7 @@ seedpass setup
- **Features to Implement:** - **Features to Implement:**
- **Seed Choice Prompt:** Ask users whether they want to generate a new seed or import an existing one. - **Seed Choice Prompt:** Ask users whether they want to generate a new seed or import an existing one.
- **Encryption of Seed:** Use the user-selected password to encrypt the seed, whether generated or imported. - **Encryption of Seed:** Use the user-selected password to encrypt the seed, whether generated or imported.
- **Nostr Profile Creation:** Upon first login, automatically generate a Nostr profile and check for existing index data notes that can be pulled and decrypted. - **Profile Creation:** Upon first login, automatically generate a profile and check for existing index data notes that can be pulled and decrypted.
- **Usage Example:** `seedpass setup` - **Usage Example:** `seedpass setup`
3. **Advanced CLI Enhancements:** 3. **Advanced CLI Enhancements:**
@@ -618,8 +618,8 @@ seedpass setup
- **Description:** When running `seedpass setup`, prompts users to either enter an existing seed or generate a new one, followed by password creation for encryption. - **Description:** When running `seedpass setup`, prompts users to either enter an existing seed or generate a new one, followed by password creation for encryption.
- **Usage Example:** `seedpass setup` - **Usage Example:** `seedpass setup`
- **Automatic Nostr Profile Generation and Index Retrieval:** - **Automatic Profile Generation and Index Retrieval:**
- **Description:** During the initial setup or first login, generates a Nostr profile and attempts to retrieve and decrypt any existing index data from Nostr. - **Description:** During the initial setup or first login, generates a profile and attempts to retrieve and decrypt any existing index data from Nostr.
- **Usage Example:** `seedpass setup` (handles internally) - **Usage Example:** `seedpass setup` (handles internally)
--- ---

View File

@@ -1,3 +1,7 @@
Certainly! Below is the updated **SeedPass JSON Entry Management and Extensibility** documentation tailored to align with the new **Fingerprint-Based Backup and Local Storage** approach. This revision ensures that fingerprints are treated as distinct entities within the JSON schema and directory structures, without referencing any SeedSigner devices.
---
# SeedPass JSON Entry Management and Extensibility # SeedPass JSON Entry Management and Extensibility
## Table of Contents ## Table of Contents
@@ -29,7 +33,7 @@
## Introduction ## Introduction
**SeedPass** is a secure password generator and manager leveraging **Bitcoin's BIP-85 standard** and integrating with the **Nostr network** for decentralized synchronization. To enhance modularity, scalability, and security, SeedPass now manages each password or data entry as a separate JSON file. This document outlines the new entry management system, ensuring that new `kind` types can be added seamlessly without disrupting existing functionalities. **SeedPass** is a secure password generator and manager leveraging **Bitcoin's BIP-85 standard** and integrating with the **Nostr network** for decentralized synchronization. To enhance modularity, scalability, and security, SeedPass now manages each password or data entry as a separate JSON file within a **Fingerprint-Based Backup and Local Storage** system. This document outlines the new entry management system, ensuring that new `kind` types can be added seamlessly without disrupting existing functionalities.
--- ---
@@ -43,6 +47,7 @@ Each SeedPass entry is stored as an individual JSON file, promoting isolated man
{ {
"entry_num": 0, "entry_num": 0,
"index_num": 0, "index_num": 0,
"fingerprint": "a1b2c3d4",
"kind": "generated_password", "kind": "generated_password",
"data": { "data": {
// Fields specific to the kind // Fields specific to the kind
@@ -64,6 +69,8 @@ Each SeedPass entry is stored as an individual JSON file, promoting isolated man
- For `generated_password` kind: Starts from 0 and increments sequentially. - For `generated_password` kind: Starts from 0 and increments sequentially.
- For other kinds: A secure random hexadecimal string (e.g., a hash of the content) used as the BIP-85 index. - For other kinds: A secure random hexadecimal string (e.g., a hash of the content) used as the BIP-85 index.
- **fingerprint** (`string`): A unique identifier generated from the seed associated with the entry. This fingerprint ensures that each seed's data is isolated and securely managed.
- **kind** (`string`): Specifies the type of entry. Initial kinds include: - **kind** (`string`): Specifies the type of entry. Initial kinds include:
- `generated_password` - `generated_password`
- `stored_password` - `stored_password`
@@ -89,6 +96,7 @@ Each SeedPass entry is stored as an individual JSON file, promoting isolated man
{ {
"entry_num": 0, "entry_num": 0,
"index_num": 0, "index_num": 0,
"fingerprint": "a1b2c3d4",
"kind": "generated_password", "kind": "generated_password",
"data": { "data": {
"title": "Example Website", "title": "Example Website",
@@ -111,7 +119,8 @@ Each SeedPass entry is stored as an individual JSON file, promoting isolated man
```json ```json
{ {
"entry_num": 1, "entry_num": 1,
"index_num": 1, "index_num": "q1wec4d426fs",
"fingerprint": "a1b2c3d4",
"kind": "stored_password", "kind": "stored_password",
"data": { "data": {
"title": "Another Service", "title": "Another Service",
@@ -133,6 +142,7 @@ Each SeedPass entry is stored as an individual JSON file, promoting isolated man
{ {
"entry_num": 2, "entry_num": 2,
"index_num": "a1b2c3d4e5f6", "index_num": "a1b2c3d4e5f6",
"fingerprint": "a1b2c3d4",
"kind": "managed_user", "kind": "managed_user",
"data": { "data": {
"users_password": "<encrypted_users_password>" "users_password": "<encrypted_users_password>"
@@ -152,6 +162,7 @@ Each SeedPass entry is stored as an individual JSON file, promoting isolated man
{ {
"entry_num": 3, "entry_num": 3,
"index_num": "f7g8h9i0j1k2", "index_num": "f7g8h9i0j1k2",
"fingerprint": "a1b2c3d4",
"kind": "12_word_seed", "kind": "12_word_seed",
"data": { "data": {
"seed_phrase": "<encrypted_seed_phrase>" "seed_phrase": "<encrypted_seed_phrase>"
@@ -171,6 +182,7 @@ Each SeedPass entry is stored as an individual JSON file, promoting isolated man
{ {
"entry_num": 4, "entry_num": 4,
"index_num": "l3m4n5o6p7q8", "index_num": "l3m4n5o6p7q8",
"fingerprint": "a1b2c3d4",
"kind": "nostr_keys", "kind": "nostr_keys",
"data": { "data": {
"public_key": "<public_key>", "public_key": "<public_key>",
@@ -191,6 +203,7 @@ Each SeedPass entry is stored as an individual JSON file, promoting isolated man
{ {
"entry_num": 5, "entry_num": 5,
"index_num": "r9s0t1u2v3w4", "index_num": "r9s0t1u2v3w4",
"fingerprint": "a1b2c3d4",
"kind": "note", "kind": "note",
"data": { "data": {
"content": "This is a secure note.", "content": "This is a secure note.",
@@ -221,6 +234,7 @@ Each entry is encapsulated in its own JSON file with a standardized structure:
{ {
"entry_num": 0, "entry_num": 0,
"index_num": 0, "index_num": 0,
"fingerprint": "a1b2c3d4",
"kind": "generated_password", "kind": "generated_password",
"data": { "data": {
// Fields specific to the kind // Fields specific to the kind
@@ -272,11 +286,12 @@ To maintain compatibility as new `kind` types are introduced, implement the foll
def process_entry(entry): def process_entry(entry):
kind = entry.get("kind") kind = entry.get("kind")
data = entry.get("data") data = entry.get("data")
fingerprint = entry.get("fingerprint")
if kind == "generated_password": if kind == "generated_password":
handle_generated_password(data) handle_generated_password(data, fingerprint)
elif kind == "stored_password": elif kind == "stored_password":
handle_stored_password(data) handle_stored_password(data, fingerprint)
# ... other known kinds ... # ... other known kinds ...
else: else:
log_warning(f"Unknown kind: {kind}. Skipping entry.") log_warning(f"Unknown kind: {kind}. Skipping entry.")
@@ -314,13 +329,13 @@ To ensure seamless integration of new `kind` types in the future, consider the f
```python ```python
# handlers.py # handlers.py
def handle_generated_password(data): def handle_generated_password(data, fingerprint):
# Implementation # Implementation
def handle_stored_password(data): def handle_stored_password(data, fingerprint):
# Implementation # Implementation
def handle_cryptocurrency_wallet(data): def handle_cryptocurrency_wallet(data, fingerprint):
# Implementation # Implementation
``` ```
@@ -331,7 +346,7 @@ def handle_cryptocurrency_wallet(data):
**Example:** **Example:**
```python ```python
def handle_cryptocurrency_wallet(data): def handle_cryptocurrency_wallet(data, fingerprint):
required_fields = ["wallet_name", "address", "private_key"] required_fields = ["wallet_name", "address", "private_key"]
for field in required_fields: for field in required_fields:
if field not in data: if field not in data:
@@ -360,6 +375,7 @@ Create a JSON file following the standardized structure with the new `kind` valu
{ {
"entry_num": 6, "entry_num": 6,
"index_num": "x1y2z3a4b5c6", "index_num": "x1y2z3a4b5c6",
"fingerprint": "a1b2c3d4",
"kind": "cryptocurrency_wallet", "kind": "cryptocurrency_wallet",
"data": { "data": {
"wallet_name": "My Bitcoin Wallet", "wallet_name": "My Bitcoin Wallet",
@@ -379,7 +395,7 @@ Create a JSON file following the standardized structure with the new `kind` valu
**Implement Handler Function:** **Implement Handler Function:**
```python ```python
def handle_cryptocurrency_wallet(data): def handle_cryptocurrency_wallet(data, fingerprint):
wallet_name = data.get("wallet_name") wallet_name = data.get("wallet_name")
address = data.get("address") address = data.get("address")
private_key = decrypt(data.get("private_key")) private_key = decrypt(data.get("private_key"))
@@ -392,13 +408,14 @@ def handle_cryptocurrency_wallet(data):
def process_entry(entry): def process_entry(entry):
kind = entry.get("kind") kind = entry.get("kind")
data = entry.get("data") data = entry.get("data")
fingerprint = entry.get("fingerprint")
if kind == "generated_password": if kind == "generated_password":
handle_generated_password(data) handle_generated_password(data, fingerprint)
elif kind == "stored_password": elif kind == "stored_password":
handle_stored_password(data) handle_stored_password(data, fingerprint)
elif kind == "cryptocurrency_wallet": elif kind == "cryptocurrency_wallet":
handle_cryptocurrency_wallet(data) handle_cryptocurrency_wallet(data, fingerprint)
# ... other known kinds ... # ... other known kinds ...
else: else:
log_warning(f"Unknown kind: {kind}. Skipping entry.") log_warning(f"Unknown kind: {kind}. Skipping entry.")
@@ -412,32 +429,50 @@ Existing kinds such as `generated_password`, `stored_password`, etc., continue t
## Backup and Rollback Mechanism ## Backup and Rollback Mechanism
To ensure data integrity and provide recovery options, SeedPass implements a robust backup and rollback system. To ensure data integrity and provide recovery options, SeedPass implements a robust backup and rollback system within the **Fingerprint-Based Backup and Local Storage** framework.
### Backup Directory Structure ### Backup Directory Structure
- **Primary Entries Directory:** Stores individual JSON files for each entry. All backups are organized based on fingerprints, ensuring that each seed's data remains isolated and secure.
- **Backups Directory:** Stores previous versions of each entry.
**Example Structure:**
``` ```
SeedPass/ ~/.seedpass/
├── entries/ ├── a1b2c3d4/
│ ├── entry_0.json │ ├── entries/
│ ├── entry_1.json │ ├── entry_0.json
└── ... │ ├── entry_1.json
├── backups/ │ │ └── ...
│ ├── entry_0_v1.json │ ├── backups/
│ ├── entry_0_v2.json │ ├── entry_0_v1.json
│ ├── entry_1_v1.json │ ├── entry_0_v2.json
└── ... │ ├── entry_1_v1.json
│ │ └── ...
│ ├── parent_seed.enc
│ ├── seedpass_passwords_checksum.txt
│ ├── seedpass_passwords_db_checksum.txt
│ └── seedpass_passwords_db.json
├── b5c6d7e8/
│ ├── entries/
│ │ ├── entry_0.json
│ │ ├── entry_1.json
│ │ └── ...
│ ├── backups/
│ │ ├── entry_0_v1.json
│ │ ├── entry_0_v2.json
│ │ ├── entry_1_v1.json
│ │ └── ...
│ ├── parent_seed.enc
│ ├── seedpass_passwords_checksum.txt
│ ├── seedpass_passwords_db_checksum.txt
│ └── seedpass_passwords_db.json
└── ...
``` ```
### Backup Process ### Backup Process
1. **Upon Modifying an Entry:** 1. **Upon Modifying an Entry:**
- The current version of the entry is copied to the `backups/` directory with a version suffix (e.g., `entry_0_v1.json`). - The current version of the entry is copied to the `backups/` directory within the corresponding fingerprint folder with a version suffix (e.g., `entry_0_v1.json`).
- The modified entry is saved in the `entries/` directory. - The modified entry is saved in the `entries/` directory within the same fingerprint folder.
2. **Versioning:** 2. **Versioning:**
- Each backup file includes a version number to track changes over time. - Each backup file includes a version number to track changes over time.
@@ -445,10 +480,32 @@ SeedPass/
### Rollback Functionality ### Rollback Functionality
- **Restoring an Entry:** - **Restoring an Entry:**
- Users can select a backup version from the `backups/` directory. - Users can select a backup version from the `backups/` directory within the specific fingerprint folder.
- The selected backup file is copied back to the `entries/` directory, replacing the current version. - The selected backup file is copied back to the `entries/` directory, replacing the current version.
**Example Command:** **Example Command:**
```bash ```bash
python main.py rollback entry_0_v1.json seedpass rollback --fingerprint a1b2c3d4 --file entry_0_v1.json
``` ```
**Example Directory Structure After Rollback:**
```
~/.seedpass/
├── a1b2c3d4/
│ ├── entries/
│ │ ├── entry_0.json # Restored from entry_0_v1.json
│ │ ├── entry_1.json
│ │ └── ...
│ ├── backups/
│ │ ├── entry_0_v1.json
│ │ ├── entry_0_v2.json
│ │ ├── entry_1_v1.json
│ │ └── ...
│ ├── parent_seed.enc
│ ├── seedpass_passwords_checksum.txt
│ ├── seedpass_passwords_db_checksum.txt
│ └── seedpass_passwords_db.json
├── ...
```
*Note: Restoring a backup overwrites the current entry. Ensure that you intend to revert to the selected backup before proceeding.*

View File

@@ -91,3 +91,5 @@ MAX_PASSWORD_LENGTH = 128 # Maximum allowed password length
# Additional Constants (if any) # Additional Constants (if any)
# ----------------------------------- # -----------------------------------
# Add any other constants here as your project expands # 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 5. Post Encrypted Index to Nostr
6. Retrieve Encrypted Index from Nostr 6. Retrieve Encrypted Index from Nostr
7. Display Nostr Public Key (npub) 7. Display Nostr Public Key (npub)
8. Exit 8. Backup/Reveal Parent Seed
9. Exit
""" """
while True: while True:
print(colored(menu, 'cyan')) 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': if choice == '1':
password_manager.handle_generate_password() password_manager.handle_generate_password()
elif choice == '2': elif choice == '2':
@@ -77,6 +78,8 @@ def display_menu(password_manager: PasswordManager, nostr_client: NostrClient):
elif choice == '7': elif choice == '7':
handle_display_npub(nostr_client) handle_display_npub(nostr_client)
elif choice == '8': elif choice == '8':
password_manager.handle_backup_reveal_parent_seed() # Corrected variable name
elif choice == '9':
logging.info("Exiting the program.") logging.info("Exiting the program.")
print(colored("Exiting the program.", 'green')) print(colored("Exiting the program.", 'green'))
nostr_client.close_client_pool() # Gracefully close the ClientPool 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 os
import json import json
import stat
import hashlib import hashlib
import logging import logging
import traceback import traceback
@@ -90,16 +91,18 @@ class EncryptionManager:
def encrypt_parent_seed(self, parent_seed: str, file_path: Path) -> None: 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 parent_seed: The BIP39 parent seed phrase.
:param file_path: The path to the file where the encrypted parent seed will be saved. :param file_path: The path to the file where the encrypted parent seed will be saved.
""" """
try: try:
# **Do not encrypt the data here** # Encode the parent seed to bytes
data = parent_seed.encode('utf-8') 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) 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}'.") logger.info(f"Parent seed encrypted and saved to '{file_path}'.")
print(colored(f"Parent seed encrypted and saved to '{file_path}'.", 'green')) print(colored(f"Parent seed encrypted and saved to '{file_path}'.", 'green'))
except Exception as e: except Exception as e:
@@ -141,7 +144,7 @@ class EncryptionManager:
return encrypted_data return encrypted_data
except Exception as e: except Exception as e:
logger.error(f"Error encrypting data: {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')) print(colored(f"Error: Failed to encrypt data: {e}", 'red'))
raise raise

View File

@@ -25,7 +25,7 @@ from password_manager.password_generation import PasswordGenerator
from password_manager.backup import BackupManager from password_manager.backup import BackupManager
from utils.key_derivation import derive_key_from_parent_seed, derive_key_from_password from utils.key_derivation import derive_key_from_parent_seed, derive_key_from_password
from utils.checksum import calculate_checksum, verify_checksum 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 ( from constants import (
APP_DIR, APP_DIR,
@@ -35,10 +35,14 @@ from constants import (
SCRIPT_CHECKSUM_FILE, SCRIPT_CHECKSUM_FILE,
MIN_PASSWORD_LENGTH, MIN_PASSWORD_LENGTH,
MAX_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 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 # Configure logging at the start of the module
def configure_logging(): def configure_logging():
@@ -112,7 +116,7 @@ class PasswordManager:
self.encryption_manager = EncryptionManager(key) self.encryption_manager = EncryptionManager(key)
self.parent_seed = self.encryption_manager.decrypt_parent_seed(PARENT_SEED_FILE) 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): if not self.validate_seed_phrase(self.parent_seed):
logging.error("Decrypted seed is invalid. Exiting.") logging.error("Decrypted seed is invalid. Exiting.")
print(colored("Error: Decrypted seed is invalid.", 'red')) print(colored("Error: Decrypted seed is invalid.", 'red'))
@@ -149,6 +153,9 @@ class PasswordManager:
try: try:
self.encryption_manager.encrypt_parent_seed(parent_seed, PARENT_SEED_FILE) self.encryption_manager.encrypt_parent_seed(parent_seed, PARENT_SEED_FILE)
logging.info("Parent seed encrypted and saved successfully.") 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: except Exception as e:
logging.error(f"Failed to encrypt and save parent seed: {e}") logging.error(f"Failed to encrypt and save parent seed: {e}")
logging.error(traceback.format_exc()) logging.error(traceback.format_exc())
@@ -182,7 +189,7 @@ class PasswordManager:
print(colored(f"Error: {e}", 'red')) print(colored(f"Error: {e}", 'red'))
return None 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, Validates the seed phrase using the EncryptionManager if available,
otherwise performs basic validation. otherwise performs basic validation.
@@ -191,26 +198,26 @@ class PasswordManager:
seed_phrase (str): The seed phrase to validate. seed_phrase (str): The seed phrase to validate.
Returns: Returns:
Optional[str]: The validated seed phrase or None if invalid. bool: True if valid, False otherwise.
""" """
try: try:
if self.encryption_manager: if self.encryption_manager:
# Use EncryptionManager to validate seed # 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.") logging.debug("Seed phrase validated successfully using EncryptionManager.")
return seed_phrase
else: else:
logging.error("Invalid seed phrase.") logging.error("Invalid seed phrase.")
print(colored("Error: Invalid seed phrase.", 'red')) print(colored("Error: Invalid seed phrase.", 'red'))
return None return is_valid
else: else:
# Perform basic validation # 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: except Exception as e:
logging.error(f"Error validating seed phrase: {e}") logging.error(f"Error validating seed phrase: {e}")
logging.error(traceback.format_exc()) logging.error(traceback.format_exc())
print(colored(f"Error: Failed to validate seed phrase: {e}", 'red')) print(colored(f"Error: Failed to validate seed phrase: {e}", 'red'))
return None return False
def initialize_managers(self) -> None: def initialize_managers(self) -> None:
""" """
@@ -443,7 +450,101 @@ class PasswordManager:
logging.error(traceback.format_exc()) logging.error(traceback.format_exc())
print(colored(f"Error: Failed to restore backup: {e}", 'red')) 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) # Example usage (this part should be removed or commented out when integrating into the larger application)
if __name__ == "__main__": if __name__ == "__main__":

View File

@@ -6,3 +6,4 @@ bech32==1.2.0
monstr @ git+https://github.com/monty888/monstr.git@master#egg=monstr monstr @ git+https://github.com/monty888/monstr.git@master#egg=monstr
mnemonic mnemonic
aiohttp aiohttp
bcrypt