From 28f552313fa67a940220d3bec200a091a3aee5e4 Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Mon, 11 Aug 2025 19:35:03 -0400 Subject: [PATCH] test: align legacy migration handling --- src/seedpass/core/encryption.py | 8 +++----- src/seedpass/core/portable_backup.py | 1 + src/tests/test_cli_integration.py | 2 +- src/tests/test_decrypt_messages.py | 20 ++++++++----------- src/tests/test_legacy_migration_iterations.py | 12 +++++++---- src/tests/test_legacy_migration_prompt.py | 20 ++++++++++++------- src/tests/test_noninteractive_init_unlock.py | 2 +- .../test_nostr_legacy_decrypt_fallback.py | 1 - src/tests/test_portable_backup.py | 1 - src/tests/test_profile_deletion_sync.py | 2 +- 10 files changed, 36 insertions(+), 33 deletions(-) diff --git a/src/seedpass/core/encryption.py b/src/seedpass/core/encryption.py index d315e75..f685841 100644 --- a/src/seedpass/core/encryption.py +++ b/src/seedpass/core/encryption.py @@ -395,15 +395,13 @@ class EncryptionManager: logger.info("Index file from Nostr was processed and saved successfully.") self.last_migration_performed = is_legacy return True - except InvalidToken as e: + except (InvalidToken, LegacyFormatRequiresMigrationError): try: password = prompt_existing_password( "Enter your master password for legacy decryption: " ) - legacy_key = _derive_legacy_key_from_password(password) - legacy_mgr = EncryptionManager(legacy_key, self.fingerprint_dir) - decrypted_data = legacy_mgr.decrypt_data( - encrypted_data, context=str(relative_path) + decrypted_data = self.decrypt_legacy( + encrypted_data, password, context=str(relative_path) ) data = _process(decrypted_data) self.save_json_data(data, relative_path) diff --git a/src/seedpass/core/portable_backup.py b/src/seedpass/core/portable_backup.py index a8e4af5..35cb2c2 100644 --- a/src/seedpass/core/portable_backup.py +++ b/src/seedpass/core/portable_backup.py @@ -129,6 +129,7 @@ def import_backup( ) key = _derive_export_key(seed) enc_mgr = EncryptionManager(key, vault.fingerprint_dir) + enc_mgr._legacy_migrate_flag = False index_bytes = enc_mgr.decrypt_data(payload, context="backup payload") index = json.loads(index_bytes.decode("utf-8")) diff --git a/src/tests/test_cli_integration.py b/src/tests/test_cli_integration.py index f3affca..a7279cd 100644 --- a/src/tests/test_cli_integration.py +++ b/src/tests/test_cli_integration.py @@ -3,7 +3,7 @@ import shutil from pathlib import Path from types import SimpleNamespace -from tests.helpers import TEST_PASSWORD, TEST_SEED +from helpers import TEST_PASSWORD, TEST_SEED import colorama import constants diff --git a/src/tests/test_decrypt_messages.py b/src/tests/test_decrypt_messages.py index ff0f955..5d595a8 100644 --- a/src/tests/test_decrypt_messages.py +++ b/src/tests/test_decrypt_messages.py @@ -6,7 +6,10 @@ import pytest from cryptography.fernet import InvalidToken from helpers import TEST_PASSWORD, TEST_SEED -from seedpass.core.encryption import EncryptionManager +from seedpass.core.encryption import ( + EncryptionManager, + LegacyFormatRequiresMigrationError, +) from utils.key_derivation import derive_index_key @@ -24,7 +27,7 @@ def test_wrong_password_message(tmp_path): assert "index" in str(exc.value) -def test_legacy_file_requires_migration_message(tmp_path, monkeypatch, capsys): +def test_legacy_file_requires_migration_message(tmp_path, monkeypatch): def _fast_legacy_key(password: str, iterations: int = 100_000) -> bytes: normalized = unicodedata.normalize("NFKD", password).strip().encode("utf-8") key = hashlib.pbkdf2_hmac("sha256", normalized, b"", 1, dklen=32) @@ -33,22 +36,15 @@ def test_legacy_file_requires_migration_message(tmp_path, monkeypatch, capsys): monkeypatch.setattr( "seedpass.core.encryption._derive_legacy_key_from_password", _fast_legacy_key ) - monkeypatch.setattr( - "seedpass.core.encryption.prompt_existing_password", - lambda *_a, **_k: TEST_PASSWORD, - ) - monkeypatch.setattr("builtins.input", lambda *_a, **_k: "1") legacy_key = _fast_legacy_key(TEST_PASSWORD) legacy_mgr = EncryptionManager(legacy_key, tmp_path) token = legacy_mgr.fernet.encrypt(b"secret") new_mgr = EncryptionManager(derive_index_key(TEST_SEED), tmp_path) - assert new_mgr.decrypt_data(token, context="index") == b"secret" - - out = capsys.readouterr().out - assert "Failed to decrypt index" in out - assert "legacy index" in out + with pytest.raises(LegacyFormatRequiresMigrationError, match="index") as exc: + new_mgr.decrypt_data(token, context="index") + assert "index" in str(exc.value) def test_corrupted_data_message(tmp_path): diff --git a/src/tests/test_legacy_migration_iterations.py b/src/tests/test_legacy_migration_iterations.py index 478270d..ad6087f 100644 --- a/src/tests/test_legacy_migration_iterations.py +++ b/src/tests/test_legacy_migration_iterations.py @@ -8,6 +8,7 @@ sys.path.append(str(Path(__file__).resolve().parents[1])) import pytest import seedpass.core.encryption as enc_module +import seedpass.core.vault as vault_module from helpers import TEST_PASSWORD from seedpass.core.encryption import ( EncryptionManager, @@ -15,12 +16,13 @@ from seedpass.core.encryption import ( ) from seedpass.core.config_manager import ConfigManager from seedpass.core.vault import Vault +from seedpass.core.migrations import LATEST_VERSION def _setup_legacy_file(tmp_path: Path, iterations: int) -> None: legacy_key = _derive_legacy_key_from_password(TEST_PASSWORD, iterations=iterations) mgr = EncryptionManager(legacy_key, tmp_path) - data = {"entries": {"0": {"kind": "test"}}} + data = {"schema_version": LATEST_VERSION, "entries": {"0": {"kind": "test"}}} json_bytes = json.dumps(data, separators=(",", ":")).encode("utf-8") legacy_encrypted = mgr.fernet.encrypt(json_bytes) (tmp_path / "seedpass_entries_db.json.enc").write_bytes(legacy_encrypted) @@ -32,6 +34,7 @@ def test_migrate_iterations(tmp_path, monkeypatch, iterations): new_key = base64.urlsafe_b64encode(b"B" * 32) mgr = EncryptionManager(new_key, tmp_path) + vault = Vault(mgr, tmp_path) prompts: list[int] = [] @@ -40,6 +43,7 @@ def test_migrate_iterations(tmp_path, monkeypatch, iterations): return TEST_PASSWORD monkeypatch.setattr(enc_module, "prompt_existing_password", fake_prompt) + monkeypatch.setattr(vault_module, "prompt_existing_password", fake_prompt) monkeypatch.setattr("builtins.input", lambda *_a, **_k: "2") calls: list[int] = [] @@ -51,15 +55,15 @@ def test_migrate_iterations(tmp_path, monkeypatch, iterations): monkeypatch.setattr(enc_module, "_derive_legacy_key_from_password", tracking_derive) - mgr.load_json_data() + vault.load_index() # Loading again should not prompt for password or attempt legacy counts - mgr.load_json_data() + vault.load_index() assert prompts == [1] expected = [50_000] if iterations == 50_000 else [50_000, 100_000] assert calls == expected - cfg = ConfigManager(Vault(mgr, tmp_path), tmp_path) + cfg = ConfigManager(vault, tmp_path) assert cfg.get_kdf_iterations() == iterations content = (tmp_path / "seedpass_entries_db.json.enc").read_bytes() diff --git a/src/tests/test_legacy_migration_prompt.py b/src/tests/test_legacy_migration_prompt.py index d0a18d3..e7ac7ef 100644 --- a/src/tests/test_legacy_migration_prompt.py +++ b/src/tests/test_legacy_migration_prompt.py @@ -6,12 +6,15 @@ from seedpass.core.encryption import ( EncryptionManager, _derive_legacy_key_from_password, ) +from seedpass.core.vault import Vault +import seedpass.core.vault as vault_module +from seedpass.core.migrations import LATEST_VERSION def _setup_legacy_file(tmp_path: Path, password: str) -> Path: legacy_key = _derive_legacy_key_from_password(password, iterations=50_000) legacy_mgr = EncryptionManager(legacy_key, tmp_path) - data = {"entries": {"0": {"kind": "test"}}} + data = {"schema_version": LATEST_VERSION, "entries": {"0": {"kind": "test"}}} json_bytes = json.dumps(data, separators=(",", ":")).encode("utf-8") legacy_encrypted = legacy_mgr.fernet.encrypt(json_bytes) file_path = tmp_path / "seedpass_entries_db.json.enc" @@ -24,14 +27,15 @@ def test_open_legacy_without_migrating(tmp_path, monkeypatch): _setup_legacy_file(tmp_path, password) new_key = base64.urlsafe_b64encode(b"A" * 32) mgr = EncryptionManager(new_key, tmp_path) + vault = Vault(mgr, tmp_path) monkeypatch.setattr( "seedpass.core.encryption.prompt_existing_password", lambda _: password ) + monkeypatch.setattr(vault_module, "prompt_existing_password", lambda _: password) monkeypatch.setattr("builtins.input", lambda _: "1") - mgr.load_json_data() - content = (tmp_path / "seedpass_entries_db.json.enc").read_bytes() - assert not content.startswith(b"V2:") - assert mgr.last_migration_performed is False + vault.load_index() + assert vault.encryption_manager.last_migration_performed is False + assert vault.migrated_from_legacy is False def test_migrate_legacy_sets_flag(tmp_path, monkeypatch): @@ -39,11 +43,13 @@ def test_migrate_legacy_sets_flag(tmp_path, monkeypatch): _setup_legacy_file(tmp_path, password) new_key = base64.urlsafe_b64encode(b"B" * 32) mgr = EncryptionManager(new_key, tmp_path) + vault = Vault(mgr, tmp_path) monkeypatch.setattr( "seedpass.core.encryption.prompt_existing_password", lambda _: password ) + monkeypatch.setattr(vault_module, "prompt_existing_password", lambda _: password) monkeypatch.setattr("builtins.input", lambda _: "2") - mgr.load_json_data() + vault.load_index() content = (tmp_path / "seedpass_entries_db.json.enc").read_bytes() assert content.startswith(b"V2:") - assert mgr.last_migration_performed is True + assert vault.encryption_manager.last_migration_performed is True diff --git a/src/tests/test_noninteractive_init_unlock.py b/src/tests/test_noninteractive_init_unlock.py index b696905..5b16d8d 100644 --- a/src/tests/test_noninteractive_init_unlock.py +++ b/src/tests/test_noninteractive_init_unlock.py @@ -7,7 +7,7 @@ import constants import seedpass.core.manager as manager_module from utils.fingerprint_manager import FingerprintManager from seedpass.core.config_manager import ConfigManager -from tests.helpers import TEST_SEED, TEST_PASSWORD, create_vault +from helpers import TEST_SEED, TEST_PASSWORD, create_vault def test_init_with_password(monkeypatch): diff --git a/src/tests/test_nostr_legacy_decrypt_fallback.py b/src/tests/test_nostr_legacy_decrypt_fallback.py index 185206c..c130230 100644 --- a/src/tests/test_nostr_legacy_decrypt_fallback.py +++ b/src/tests/test_nostr_legacy_decrypt_fallback.py @@ -25,7 +25,6 @@ def test_legacy_password_only_fallback(monkeypatch, tmp_path, caplog): monkeypatch.setattr( enc_module, "prompt_existing_password", lambda *_a, **_k: TEST_PASSWORD ) - monkeypatch.setattr("builtins.input", lambda *_a, **_k: "2") vault, enc_mgr = create_vault(tmp_path) data = {"schema_version": 4, "entries": {}} diff --git a/src/tests/test_portable_backup.py b/src/tests/test_portable_backup.py index 408e1f4..edd4cfd 100644 --- a/src/tests/test_portable_backup.py +++ b/src/tests/test_portable_backup.py @@ -81,7 +81,6 @@ def test_corruption_detection(monkeypatch): monkeypatch.setattr( enc_module, "prompt_existing_password", lambda *_a, **_k: PASSWORD ) - monkeypatch.setattr("builtins.input", lambda *_a, **_k: "1") with pytest.raises(InvalidToken): import_backup(vault, backup, path, parent_seed=SEED) diff --git a/src/tests/test_profile_deletion_sync.py b/src/tests/test_profile_deletion_sync.py index 4e136a1..2bc3f0f 100644 --- a/src/tests/test_profile_deletion_sync.py +++ b/src/tests/test_profile_deletion_sync.py @@ -8,7 +8,7 @@ from pathlib import Path sys.path.append(str(Path(__file__).resolve().parents[1])) import main from utils.fingerprint_manager import FingerprintManager -from tests.helpers import TEST_SEED +from helpers import TEST_SEED def test_profile_deletion_stops_sync(monkeypatch, tmp_path):