mirror of
https://github.com/PR0M3TH3AN/SeedPass.git
synced 2025-09-09 15:58:48 +00:00
Merge pull request #189 from PR0M3TH3AN/codex/harden-in-memory-secrets
Add in-memory secret encryption
This commit is contained in:
@@ -40,6 +40,7 @@ from utils.password_prompt import (
|
|||||||
prompt_existing_password,
|
prompt_existing_password,
|
||||||
confirm_action,
|
confirm_action,
|
||||||
)
|
)
|
||||||
|
from utils.memory_protection import InMemorySecret
|
||||||
from constants import MIN_HEALTHY_RELAYS
|
from constants import MIN_HEALTHY_RELAYS
|
||||||
|
|
||||||
from constants import (
|
from constants import (
|
||||||
@@ -93,7 +94,7 @@ class PasswordManager:
|
|||||||
self.backup_manager: Optional[BackupManager] = None
|
self.backup_manager: Optional[BackupManager] = None
|
||||||
self.vault: Optional[Vault] = None
|
self.vault: Optional[Vault] = None
|
||||||
self.fingerprint_manager: Optional[FingerprintManager] = None
|
self.fingerprint_manager: Optional[FingerprintManager] = None
|
||||||
self.parent_seed: Optional[str] = None
|
self._parent_seed_secret: Optional[InMemorySecret] = None
|
||||||
self.bip85: Optional[BIP85] = None
|
self.bip85: Optional[BIP85] = None
|
||||||
self.nostr_client: Optional[NostrClient] = None
|
self.nostr_client: Optional[NostrClient] = None
|
||||||
self.config_manager: Optional[ConfigManager] = None
|
self.config_manager: Optional[ConfigManager] = None
|
||||||
@@ -114,6 +115,22 @@ class PasswordManager:
|
|||||||
# Set the current fingerprint directory
|
# Set the current fingerprint directory
|
||||||
self.fingerprint_dir = self.fingerprint_manager.get_current_fingerprint_dir()
|
self.fingerprint_dir = self.fingerprint_manager.get_current_fingerprint_dir()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def parent_seed(self) -> Optional[str]:
|
||||||
|
"""Return the decrypted parent seed if set."""
|
||||||
|
if self._parent_seed_secret is None:
|
||||||
|
return None
|
||||||
|
return self._parent_seed_secret.get_str()
|
||||||
|
|
||||||
|
@parent_seed.setter
|
||||||
|
def parent_seed(self, value: Optional[str]) -> None:
|
||||||
|
if value is None:
|
||||||
|
if self._parent_seed_secret:
|
||||||
|
self._parent_seed_secret.wipe()
|
||||||
|
self._parent_seed_secret = None
|
||||||
|
else:
|
||||||
|
self._parent_seed_secret = InMemorySecret(value.encode("utf-8"))
|
||||||
|
|
||||||
def update_activity(self) -> None:
|
def update_activity(self) -> None:
|
||||||
"""Record the current time as the last user activity."""
|
"""Record the current time as the last user activity."""
|
||||||
self.last_activity = time.time()
|
self.last_activity = time.time()
|
||||||
|
@@ -24,6 +24,7 @@ try:
|
|||||||
)
|
)
|
||||||
from .password_prompt import prompt_for_password
|
from .password_prompt import prompt_for_password
|
||||||
from .input_utils import timed_input
|
from .input_utils import timed_input
|
||||||
|
from .memory_protection import InMemorySecret
|
||||||
|
|
||||||
if logger.isEnabledFor(logging.DEBUG):
|
if logger.isEnabledFor(logging.DEBUG):
|
||||||
logger.info("Modules imported successfully.")
|
logger.info("Modules imported successfully.")
|
||||||
@@ -47,4 +48,5 @@ __all__ = [
|
|||||||
"shared_lock",
|
"shared_lock",
|
||||||
"prompt_for_password",
|
"prompt_for_password",
|
||||||
"timed_input",
|
"timed_input",
|
||||||
|
"InMemorySecret",
|
||||||
]
|
]
|
||||||
|
31
src/utils/memory_protection.py
Normal file
31
src/utils/memory_protection.py
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
|
||||||
|
|
||||||
|
|
||||||
|
class InMemorySecret:
|
||||||
|
"""Store sensitive data encrypted in RAM using AES-GCM."""
|
||||||
|
|
||||||
|
def __init__(self, data: bytes) -> None:
|
||||||
|
if not isinstance(data, (bytes, bytearray)):
|
||||||
|
raise TypeError("data must be bytes")
|
||||||
|
self._key = AESGCM.generate_key(bit_length=128)
|
||||||
|
self._nonce = os.urandom(12)
|
||||||
|
self._cipher = AESGCM(self._key)
|
||||||
|
self._encrypted = self._cipher.encrypt(self._nonce, bytes(data), None)
|
||||||
|
|
||||||
|
def get_bytes(self) -> bytes:
|
||||||
|
"""Decrypt and return the plaintext bytes."""
|
||||||
|
return self._cipher.decrypt(self._nonce, self._encrypted, None)
|
||||||
|
|
||||||
|
def wipe(self) -> None:
|
||||||
|
"""Zero out internal data."""
|
||||||
|
self._key = None
|
||||||
|
self._nonce = None
|
||||||
|
self._cipher = None
|
||||||
|
self._encrypted = None
|
||||||
|
|
||||||
|
def get_str(self) -> str:
|
||||||
|
"""Return the decrypted plaintext as a UTF-8 string."""
|
||||||
|
return self.get_bytes().decode("utf-8")
|
Reference in New Issue
Block a user