mirror of
https://github.com/PR0M3TH3AN/SeedPass.git
synced 2026-11-06 21:02:04 +00:00
Complete security item 3 hardening and redaction sweep
This commit is contained in:
1 parent
fcbe2b2943
commit
56b68bd0ac
11 files changed
+201
-55
No files matched your search
@@ -96,11 +96,10 @@ class BIP85:
|
||||
try:
|
||||
child_key = self.bip32_ctx.DerivePath(path)
|
||||
k = child_key.PrivateKey().Raw().ToBytes()
|
||||
logging.debug(f"Derived child key at path {path}: {k.hex()}")
|
||||
logging.debug("Derived child key at path %s", path)
|
||||
|
||||
hmac_key = b"bip-entropy-from-k"
|
||||
hmac_result = hmac.new(hmac_key, k, hashlib.sha512).digest()
|
||||
logging.debug(f"HMAC-SHA512 result: {hmac_result.hex()}")
|
||||
|
||||
entropy = hmac_result[:entropy_bytes]
|
||||
|
||||
@@ -115,7 +114,7 @@ class BIP85:
|
||||
f"Derived entropy length is {len(entropy)} bytes; expected {entropy_bytes} bytes."
|
||||
)
|
||||
|
||||
logging.debug(f"Derived entropy: {entropy.hex()}")
|
||||
logging.debug("Derived entropy successfully.")
|
||||
return entropy
|
||||
except Exception as e:
|
||||
logging.error(f"Error deriving entropy: {e}", exc_info=True)
|
||||
@@ -139,7 +138,7 @@ class BIP85:
|
||||
mnemonic = Bip39MnemonicGenerator(Bip39Languages.ENGLISH).FromEntropy(
|
||||
entropy
|
||||
)
|
||||
logging.debug(f"Derived mnemonic: {mnemonic}")
|
||||
logging.debug("Derived mnemonic successfully.")
|
||||
return mnemonic.ToStr()
|
||||
except Exception as e:
|
||||
logging.error(f"Error generating mnemonic: {e}", exc_info=True)
|
||||
@@ -150,7 +149,7 @@ class BIP85:
|
||||
"""Derive 32 bytes of entropy for symmetric key usage."""
|
||||
try:
|
||||
key = self.derive_entropy(index=index, entropy_bytes=32, app_no=app_no)
|
||||
logging.debug(f"Derived symmetric key: {key.hex()}")
|
||||
logging.debug("Derived symmetric key successfully.")
|
||||
return key
|
||||
except Exception as e:
|
||||
logging.error(f"Error deriving symmetric key: {e}", exc_info=True)
|
||||
|
||||
@@ -659,6 +659,7 @@ async def import_vault(
|
||||
with tempfile.NamedTemporaryFile(delete=False) as tmp:
|
||||
tmp.write(data)
|
||||
tmp_path = Path(tmp.name)
|
||||
os.chmod(tmp_path, 0o600)
|
||||
try:
|
||||
pm.handle_import_database(tmp_path)
|
||||
finally:
|
||||
|
||||
@@ -1042,10 +1042,7 @@ class PasswordManager:
|
||||
self.encryption_manager = EncryptionManager(key_b64, fingerprint_dir)
|
||||
self.vault = Vault(self.encryption_manager, fingerprint_dir)
|
||||
|
||||
# Log the type and content of parent_seed
|
||||
logger.debug(
|
||||
f"Decrypted parent_seed: {self.parent_seed} (type: {type(self.parent_seed)})"
|
||||
)
|
||||
logger.debug("Parent seed decrypted successfully.")
|
||||
|
||||
# Validate the decrypted seed
|
||||
if not self.validate_bip85_seed(self.parent_seed):
|
||||
@@ -1222,9 +1219,7 @@ class PasswordManager:
|
||||
logging.info("User password hashed and stored successfully.")
|
||||
|
||||
self.parent_seed = parent_seed
|
||||
logger.debug(
|
||||
f"parent_seed set to: {self.parent_seed} (type: {type(self.parent_seed)})"
|
||||
)
|
||||
logger.debug("Parent seed initialized successfully.")
|
||||
|
||||
self.initialize_bip85()
|
||||
self.initialize_managers()
|
||||
@@ -1422,9 +1417,7 @@ class PasswordManager:
|
||||
logging.info("Parent seed encrypted and saved successfully.")
|
||||
|
||||
self.parent_seed = seed # Ensure this is a string
|
||||
logger.debug(
|
||||
f"parent_seed set to: {self.parent_seed} (type: {type(self.parent_seed)})"
|
||||
)
|
||||
logger.debug("Parent seed initialized successfully.")
|
||||
|
||||
self.initialize_bip85()
|
||||
self.initialize_managers()
|
||||
|
||||
@@ -284,6 +284,78 @@ async def test_vault_import_via_upload(client, tmp_path):
|
||||
assert called.get("sync") is True
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_vault_import_upload_sets_secure_temp_permissions(client, monkeypatch):
|
||||
cl, token = client
|
||||
called = {}
|
||||
chmod_calls = []
|
||||
unlink_calls = []
|
||||
|
||||
def import_db(path):
|
||||
called["path"] = path
|
||||
|
||||
api.app.state.pm.handle_import_database = import_db
|
||||
api.app.state.pm.sync_vault = lambda: called.setdefault("sync", True)
|
||||
|
||||
real_chmod = api.os.chmod
|
||||
real_unlink = api.os.unlink
|
||||
|
||||
def tracking_chmod(path, mode):
|
||||
chmod_calls.append((Path(path), mode))
|
||||
return real_chmod(path, mode)
|
||||
|
||||
def tracking_unlink(path):
|
||||
unlink_calls.append(Path(path))
|
||||
return real_unlink(path)
|
||||
|
||||
monkeypatch.setattr(api.os, "chmod", tracking_chmod)
|
||||
monkeypatch.setattr(api.os, "unlink", tracking_unlink)
|
||||
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
res = await cl.post(
|
||||
"/api/v1/vault/import",
|
||||
files={"file": ("c.json.enc", b"{}")},
|
||||
headers=headers,
|
||||
)
|
||||
assert res.status_code == 200
|
||||
assert res.json() == {"status": "ok"}
|
||||
tmp_path_used = called["path"]
|
||||
assert any(path == tmp_path_used and mode == 0o600 for path, mode in chmod_calls)
|
||||
assert tmp_path_used in unlink_calls
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_vault_import_upload_temp_file_removed_on_failure(client, monkeypatch):
|
||||
cl, token = client
|
||||
called = {}
|
||||
unlink_calls = []
|
||||
|
||||
def import_db(path):
|
||||
called["path"] = path
|
||||
raise ValueError("boom")
|
||||
|
||||
api.app.state.pm.handle_import_database = import_db
|
||||
api.app.state.pm.sync_vault = lambda: called.setdefault("sync", True)
|
||||
|
||||
real_unlink = api.os.unlink
|
||||
|
||||
def tracking_unlink(path):
|
||||
unlink_calls.append(Path(path))
|
||||
return real_unlink(path)
|
||||
|
||||
monkeypatch.setattr(api.os, "unlink", tracking_unlink)
|
||||
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
with pytest.raises(ValueError):
|
||||
await cl.post(
|
||||
"/api/v1/vault/import",
|
||||
files={"file": ("c.json.enc", b"{}")},
|
||||
headers=headers,
|
||||
)
|
||||
assert called["path"] in unlink_calls
|
||||
assert not called.get("sync", False)
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_vault_import_upload_too_large(client):
|
||||
cl, token = client
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import logging
|
||||
|
||||
from local_bip85.bip85 import BIP85
|
||||
|
||||
|
||||
@@ -50,3 +52,15 @@ def test_default_word_count_from_entropy_bytes():
|
||||
bip85.derive_entropy(index=5, entropy_bytes=20, app_no=39)
|
||||
|
||||
assert ctx.last_path == "m/83696968'/39'/0'/20'/5'"
|
||||
|
||||
|
||||
def test_derive_entropy_logs_do_not_expose_key_material(caplog):
|
||||
bip85 = BIP85(b"\x00" * 64)
|
||||
ctx = DummyCtx()
|
||||
bip85.bip32_ctx = ctx
|
||||
|
||||
sensitive_hex = "00" * 32
|
||||
with caplog.at_level(logging.DEBUG):
|
||||
bip85.derive_entropy(index=0, entropy_bytes=16, app_no=39, word_count=12)
|
||||
|
||||
assert sensitive_hex not in caplog.text
|
||||
@@ -1,4 +1,5 @@
|
||||
import hashlib
|
||||
import logging
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from tempfile import TemporaryDirectory
|
||||
@@ -23,6 +24,13 @@ def test_generate_fingerprint_deterministic():
|
||||
assert fp1 == fp2
|
||||
|
||||
|
||||
def test_generate_fingerprint_does_not_log_seed_phrase(caplog):
|
||||
seed = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
|
||||
with caplog.at_level(logging.DEBUG):
|
||||
generate_fingerprint(seed)
|
||||
assert seed not in caplog.text
|
||||
|
||||
|
||||
def test_encryption_round_trip():
|
||||
with TemporaryDirectory() as tmpdir:
|
||||
key = base64.urlsafe_b64encode(os.urandom(32))
|
||||
|
||||
@@ -30,15 +30,13 @@ def generate_fingerprint(seed_phrase: str, length: int = 16) -> Optional[str]:
|
||||
try:
|
||||
# Normalize the seed phrase
|
||||
normalized_seed = seed_phrase.strip().lower()
|
||||
logger.debug(f"Normalized seed: {normalized_seed}")
|
||||
|
||||
# Compute SHA-256 hash
|
||||
sha256_hash = hashlib.sha256(normalized_seed.encode("utf-8")).hexdigest()
|
||||
logger.debug(f"SHA-256 Hash: {sha256_hash}")
|
||||
|
||||
# Truncate to desired length
|
||||
fingerprint = sha256_hash[:length].upper()
|
||||
logger.debug(f"Generated Fingerprint: {fingerprint}")
|
||||
logger.debug("Generated seed fingerprint successfully.")
|
||||
|
||||
return fingerprint
|
||||
except Exception as e:
|
||||
|
||||
Reference in new issue
Block a user