mirror of
https://github.com/PR0M3TH3AN/SeedPass.git
synced 2025-09-08 07:18:47 +00:00
188 lines
5.8 KiB
Python
188 lines
5.8 KiB
Python
import json
|
|
import base64
|
|
from pathlib import Path
|
|
from tempfile import TemporaryDirectory
|
|
|
|
import pytest
|
|
import sys
|
|
|
|
sys.path.append(str(Path(__file__).resolve().parents[1]))
|
|
|
|
from password_manager.encryption import EncryptionManager
|
|
from password_manager.vault import Vault
|
|
from password_manager.backup import BackupManager
|
|
from password_manager.portable_backup import (
|
|
PortableMode,
|
|
export_backup,
|
|
import_backup,
|
|
)
|
|
from utils.key_derivation import (
|
|
derive_index_key,
|
|
derive_key_from_password,
|
|
EncryptionMode,
|
|
)
|
|
|
|
|
|
SEED = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
|
|
PASSWORD = "passw0rd"
|
|
|
|
|
|
def setup_vault(tmp: Path, mode: EncryptionMode = EncryptionMode.SEED_ONLY):
|
|
seed_key = derive_key_from_password(PASSWORD)
|
|
seed_mgr = EncryptionManager(seed_key, tmp)
|
|
seed_mgr.encrypt_parent_seed(SEED)
|
|
|
|
index_key = derive_index_key(SEED, PASSWORD, mode)
|
|
enc_mgr = EncryptionManager(index_key, tmp)
|
|
vault = Vault(enc_mgr, tmp)
|
|
backup = BackupManager(tmp)
|
|
return vault, backup
|
|
|
|
|
|
def test_round_trip_across_modes(monkeypatch):
|
|
for pmode in [
|
|
PortableMode.SEED_ONLY,
|
|
PortableMode.SEED_PLUS_PW,
|
|
PortableMode.PW_ONLY,
|
|
]:
|
|
with TemporaryDirectory() as td:
|
|
tmp = Path(td)
|
|
vault, backup = setup_vault(tmp)
|
|
data = {"pw": 1}
|
|
vault.save_index(data)
|
|
|
|
monkeypatch.setattr(
|
|
"password_manager.portable_backup.prompt_existing_password",
|
|
lambda *_a, **_k: PASSWORD,
|
|
)
|
|
|
|
path = export_backup(vault, backup, pmode, parent_seed=SEED)
|
|
assert path.exists()
|
|
|
|
vault.save_index({"pw": 0})
|
|
import_backup(vault, backup, path, parent_seed=SEED)
|
|
assert vault.load_index()["pw"] == data["pw"]
|
|
|
|
|
|
from cryptography.fernet import InvalidToken
|
|
|
|
|
|
def test_corruption_detection(monkeypatch):
|
|
with TemporaryDirectory() as td:
|
|
tmp = Path(td)
|
|
vault, backup = setup_vault(tmp)
|
|
vault.save_index({"a": 1})
|
|
|
|
monkeypatch.setattr(
|
|
"password_manager.portable_backup.prompt_existing_password",
|
|
lambda *_a, **_k: PASSWORD,
|
|
)
|
|
path = export_backup(vault, backup, PortableMode.SEED_ONLY, parent_seed=SEED)
|
|
|
|
content = json.loads(path.read_text())
|
|
payload = base64.b64decode(content["payload"])
|
|
payload = b"x" + payload[1:]
|
|
content["payload"] = base64.b64encode(payload).decode()
|
|
path.write_text(json.dumps(content))
|
|
|
|
with pytest.raises(InvalidToken):
|
|
import_backup(vault, backup, path, parent_seed=SEED)
|
|
|
|
|
|
def test_incorrect_credentials(monkeypatch):
|
|
with TemporaryDirectory() as td:
|
|
tmp = Path(td)
|
|
vault, backup = setup_vault(tmp)
|
|
vault.save_index({"a": 2})
|
|
|
|
monkeypatch.setattr(
|
|
"password_manager.portable_backup.prompt_existing_password",
|
|
lambda *_a, **_k: PASSWORD,
|
|
)
|
|
path = export_backup(
|
|
vault,
|
|
backup,
|
|
PortableMode.SEED_PLUS_PW,
|
|
parent_seed=SEED,
|
|
)
|
|
|
|
monkeypatch.setattr(
|
|
"password_manager.portable_backup.prompt_existing_password",
|
|
lambda *_a, **_k: "wrong",
|
|
)
|
|
with pytest.raises(Exception):
|
|
import_backup(vault, backup, path, parent_seed=SEED)
|
|
|
|
|
|
def test_import_over_existing(monkeypatch):
|
|
with TemporaryDirectory() as td:
|
|
tmp = Path(td)
|
|
vault, backup = setup_vault(tmp)
|
|
vault.save_index({"v": 1})
|
|
|
|
monkeypatch.setattr(
|
|
"password_manager.portable_backup.prompt_existing_password",
|
|
lambda *_a, **_k: PASSWORD,
|
|
)
|
|
path = export_backup(vault, backup, PortableMode.SEED_ONLY, parent_seed=SEED)
|
|
|
|
vault.save_index({"v": 2})
|
|
import_backup(vault, backup, path, parent_seed=SEED)
|
|
loaded = vault.load_index()
|
|
assert loaded["v"] == 1
|
|
|
|
|
|
def test_checksum_mismatch_detection(monkeypatch):
|
|
with TemporaryDirectory() as td:
|
|
tmp = Path(td)
|
|
vault, backup = setup_vault(tmp)
|
|
vault.save_index({"a": 1})
|
|
|
|
monkeypatch.setattr(
|
|
"password_manager.portable_backup.prompt_existing_password",
|
|
lambda *_a, **_k: PASSWORD,
|
|
)
|
|
|
|
path = export_backup(
|
|
vault,
|
|
backup,
|
|
PortableMode.SEED_ONLY,
|
|
parent_seed=SEED,
|
|
)
|
|
|
|
wrapper = json.loads(path.read_text())
|
|
payload = base64.b64decode(wrapper["payload"])
|
|
key = derive_index_key(SEED, PASSWORD, EncryptionMode.SEED_ONLY)
|
|
enc_mgr = EncryptionManager(key, tmp)
|
|
data = json.loads(enc_mgr.decrypt_data(payload).decode())
|
|
data["a"] = 2
|
|
mod_canon = json.dumps(data, sort_keys=True, separators=(",", ":"))
|
|
new_payload = enc_mgr.encrypt_data(mod_canon.encode())
|
|
wrapper["payload"] = base64.b64encode(new_payload).decode()
|
|
path.write_text(json.dumps(wrapper))
|
|
|
|
with pytest.raises(ValueError):
|
|
import_backup(vault, backup, path, parent_seed=SEED)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"pmode",
|
|
[PortableMode.SEED_ONLY, PortableMode.SEED_PLUS_PW],
|
|
)
|
|
def test_export_import_seed_encrypted_with_different_key(monkeypatch, pmode):
|
|
"""Ensure backup round trip works when seed is encrypted with another key."""
|
|
with TemporaryDirectory() as td:
|
|
tmp = Path(td)
|
|
vault, backup = setup_vault(tmp)
|
|
vault.save_index({"v": 123})
|
|
|
|
monkeypatch.setattr(
|
|
"password_manager.portable_backup.prompt_existing_password",
|
|
lambda *_a, **_k: PASSWORD,
|
|
)
|
|
|
|
path = export_backup(vault, backup, pmode, parent_seed=SEED)
|
|
vault.save_index({"v": 0})
|
|
import_backup(vault, backup, path, parent_seed=SEED)
|
|
assert vault.load_index()["v"] == 123
|