mirror of
https://github.com/PR0M3TH3AN/SeedPass.git
synced 2025-09-08 07:18:47 +00:00
test: align legacy migration handling
This commit is contained in:
@@ -395,15 +395,13 @@ class EncryptionManager:
|
|||||||
logger.info("Index file from Nostr was processed and saved successfully.")
|
logger.info("Index file from Nostr was processed and saved successfully.")
|
||||||
self.last_migration_performed = is_legacy
|
self.last_migration_performed = is_legacy
|
||||||
return True
|
return True
|
||||||
except InvalidToken as e:
|
except (InvalidToken, LegacyFormatRequiresMigrationError):
|
||||||
try:
|
try:
|
||||||
password = prompt_existing_password(
|
password = prompt_existing_password(
|
||||||
"Enter your master password for legacy decryption: "
|
"Enter your master password for legacy decryption: "
|
||||||
)
|
)
|
||||||
legacy_key = _derive_legacy_key_from_password(password)
|
decrypted_data = self.decrypt_legacy(
|
||||||
legacy_mgr = EncryptionManager(legacy_key, self.fingerprint_dir)
|
encrypted_data, password, context=str(relative_path)
|
||||||
decrypted_data = legacy_mgr.decrypt_data(
|
|
||||||
encrypted_data, context=str(relative_path)
|
|
||||||
)
|
)
|
||||||
data = _process(decrypted_data)
|
data = _process(decrypted_data)
|
||||||
self.save_json_data(data, relative_path)
|
self.save_json_data(data, relative_path)
|
||||||
|
@@ -129,6 +129,7 @@ def import_backup(
|
|||||||
)
|
)
|
||||||
key = _derive_export_key(seed)
|
key = _derive_export_key(seed)
|
||||||
enc_mgr = EncryptionManager(key, vault.fingerprint_dir)
|
enc_mgr = EncryptionManager(key, vault.fingerprint_dir)
|
||||||
|
enc_mgr._legacy_migrate_flag = False
|
||||||
index_bytes = enc_mgr.decrypt_data(payload, context="backup payload")
|
index_bytes = enc_mgr.decrypt_data(payload, context="backup payload")
|
||||||
index = json.loads(index_bytes.decode("utf-8"))
|
index = json.loads(index_bytes.decode("utf-8"))
|
||||||
|
|
||||||
|
@@ -3,7 +3,7 @@ import shutil
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from types import SimpleNamespace
|
from types import SimpleNamespace
|
||||||
|
|
||||||
from tests.helpers import TEST_PASSWORD, TEST_SEED
|
from helpers import TEST_PASSWORD, TEST_SEED
|
||||||
|
|
||||||
import colorama
|
import colorama
|
||||||
import constants
|
import constants
|
||||||
|
@@ -6,7 +6,10 @@ import pytest
|
|||||||
from cryptography.fernet import InvalidToken
|
from cryptography.fernet import InvalidToken
|
||||||
|
|
||||||
from helpers import TEST_PASSWORD, TEST_SEED
|
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
|
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)
|
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:
|
def _fast_legacy_key(password: str, iterations: int = 100_000) -> bytes:
|
||||||
normalized = unicodedata.normalize("NFKD", password).strip().encode("utf-8")
|
normalized = unicodedata.normalize("NFKD", password).strip().encode("utf-8")
|
||||||
key = hashlib.pbkdf2_hmac("sha256", normalized, b"", 1, dklen=32)
|
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(
|
monkeypatch.setattr(
|
||||||
"seedpass.core.encryption._derive_legacy_key_from_password", _fast_legacy_key
|
"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_key = _fast_legacy_key(TEST_PASSWORD)
|
||||||
legacy_mgr = EncryptionManager(legacy_key, tmp_path)
|
legacy_mgr = EncryptionManager(legacy_key, tmp_path)
|
||||||
token = legacy_mgr.fernet.encrypt(b"secret")
|
token = legacy_mgr.fernet.encrypt(b"secret")
|
||||||
|
|
||||||
new_mgr = EncryptionManager(derive_index_key(TEST_SEED), tmp_path)
|
new_mgr = EncryptionManager(derive_index_key(TEST_SEED), tmp_path)
|
||||||
assert new_mgr.decrypt_data(token, context="index") == b"secret"
|
with pytest.raises(LegacyFormatRequiresMigrationError, match="index") as exc:
|
||||||
|
new_mgr.decrypt_data(token, context="index")
|
||||||
out = capsys.readouterr().out
|
assert "index" in str(exc.value)
|
||||||
assert "Failed to decrypt index" in out
|
|
||||||
assert "legacy index" in out
|
|
||||||
|
|
||||||
|
|
||||||
def test_corrupted_data_message(tmp_path):
|
def test_corrupted_data_message(tmp_path):
|
||||||
|
@@ -8,6 +8,7 @@ sys.path.append(str(Path(__file__).resolve().parents[1]))
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
import seedpass.core.encryption as enc_module
|
import seedpass.core.encryption as enc_module
|
||||||
|
import seedpass.core.vault as vault_module
|
||||||
from helpers import TEST_PASSWORD
|
from helpers import TEST_PASSWORD
|
||||||
from seedpass.core.encryption import (
|
from seedpass.core.encryption import (
|
||||||
EncryptionManager,
|
EncryptionManager,
|
||||||
@@ -15,12 +16,13 @@ from seedpass.core.encryption import (
|
|||||||
)
|
)
|
||||||
from seedpass.core.config_manager import ConfigManager
|
from seedpass.core.config_manager import ConfigManager
|
||||||
from seedpass.core.vault import Vault
|
from seedpass.core.vault import Vault
|
||||||
|
from seedpass.core.migrations import LATEST_VERSION
|
||||||
|
|
||||||
|
|
||||||
def _setup_legacy_file(tmp_path: Path, iterations: int) -> None:
|
def _setup_legacy_file(tmp_path: Path, iterations: int) -> None:
|
||||||
legacy_key = _derive_legacy_key_from_password(TEST_PASSWORD, iterations=iterations)
|
legacy_key = _derive_legacy_key_from_password(TEST_PASSWORD, iterations=iterations)
|
||||||
mgr = EncryptionManager(legacy_key, tmp_path)
|
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")
|
json_bytes = json.dumps(data, separators=(",", ":")).encode("utf-8")
|
||||||
legacy_encrypted = mgr.fernet.encrypt(json_bytes)
|
legacy_encrypted = mgr.fernet.encrypt(json_bytes)
|
||||||
(tmp_path / "seedpass_entries_db.json.enc").write_bytes(legacy_encrypted)
|
(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)
|
new_key = base64.urlsafe_b64encode(b"B" * 32)
|
||||||
mgr = EncryptionManager(new_key, tmp_path)
|
mgr = EncryptionManager(new_key, tmp_path)
|
||||||
|
vault = Vault(mgr, tmp_path)
|
||||||
|
|
||||||
prompts: list[int] = []
|
prompts: list[int] = []
|
||||||
|
|
||||||
@@ -40,6 +43,7 @@ def test_migrate_iterations(tmp_path, monkeypatch, iterations):
|
|||||||
return TEST_PASSWORD
|
return TEST_PASSWORD
|
||||||
|
|
||||||
monkeypatch.setattr(enc_module, "prompt_existing_password", fake_prompt)
|
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")
|
monkeypatch.setattr("builtins.input", lambda *_a, **_k: "2")
|
||||||
|
|
||||||
calls: list[int] = []
|
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)
|
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
|
# Loading again should not prompt for password or attempt legacy counts
|
||||||
mgr.load_json_data()
|
vault.load_index()
|
||||||
|
|
||||||
assert prompts == [1]
|
assert prompts == [1]
|
||||||
expected = [50_000] if iterations == 50_000 else [50_000, 100_000]
|
expected = [50_000] if iterations == 50_000 else [50_000, 100_000]
|
||||||
assert calls == expected
|
assert calls == expected
|
||||||
|
|
||||||
cfg = ConfigManager(Vault(mgr, tmp_path), tmp_path)
|
cfg = ConfigManager(vault, tmp_path)
|
||||||
assert cfg.get_kdf_iterations() == iterations
|
assert cfg.get_kdf_iterations() == iterations
|
||||||
|
|
||||||
content = (tmp_path / "seedpass_entries_db.json.enc").read_bytes()
|
content = (tmp_path / "seedpass_entries_db.json.enc").read_bytes()
|
||||||
|
@@ -6,12 +6,15 @@ from seedpass.core.encryption import (
|
|||||||
EncryptionManager,
|
EncryptionManager,
|
||||||
_derive_legacy_key_from_password,
|
_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:
|
def _setup_legacy_file(tmp_path: Path, password: str) -> Path:
|
||||||
legacy_key = _derive_legacy_key_from_password(password, iterations=50_000)
|
legacy_key = _derive_legacy_key_from_password(password, iterations=50_000)
|
||||||
legacy_mgr = EncryptionManager(legacy_key, tmp_path)
|
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")
|
json_bytes = json.dumps(data, separators=(",", ":")).encode("utf-8")
|
||||||
legacy_encrypted = legacy_mgr.fernet.encrypt(json_bytes)
|
legacy_encrypted = legacy_mgr.fernet.encrypt(json_bytes)
|
||||||
file_path = tmp_path / "seedpass_entries_db.json.enc"
|
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)
|
_setup_legacy_file(tmp_path, password)
|
||||||
new_key = base64.urlsafe_b64encode(b"A" * 32)
|
new_key = base64.urlsafe_b64encode(b"A" * 32)
|
||||||
mgr = EncryptionManager(new_key, tmp_path)
|
mgr = EncryptionManager(new_key, tmp_path)
|
||||||
|
vault = Vault(mgr, tmp_path)
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
"seedpass.core.encryption.prompt_existing_password", lambda _: password
|
"seedpass.core.encryption.prompt_existing_password", lambda _: password
|
||||||
)
|
)
|
||||||
|
monkeypatch.setattr(vault_module, "prompt_existing_password", lambda _: password)
|
||||||
monkeypatch.setattr("builtins.input", lambda _: "1")
|
monkeypatch.setattr("builtins.input", lambda _: "1")
|
||||||
mgr.load_json_data()
|
vault.load_index()
|
||||||
content = (tmp_path / "seedpass_entries_db.json.enc").read_bytes()
|
assert vault.encryption_manager.last_migration_performed is False
|
||||||
assert not content.startswith(b"V2:")
|
assert vault.migrated_from_legacy is False
|
||||||
assert mgr.last_migration_performed is False
|
|
||||||
|
|
||||||
|
|
||||||
def test_migrate_legacy_sets_flag(tmp_path, monkeypatch):
|
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)
|
_setup_legacy_file(tmp_path, password)
|
||||||
new_key = base64.urlsafe_b64encode(b"B" * 32)
|
new_key = base64.urlsafe_b64encode(b"B" * 32)
|
||||||
mgr = EncryptionManager(new_key, tmp_path)
|
mgr = EncryptionManager(new_key, tmp_path)
|
||||||
|
vault = Vault(mgr, tmp_path)
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
"seedpass.core.encryption.prompt_existing_password", lambda _: password
|
"seedpass.core.encryption.prompt_existing_password", lambda _: password
|
||||||
)
|
)
|
||||||
|
monkeypatch.setattr(vault_module, "prompt_existing_password", lambda _: password)
|
||||||
monkeypatch.setattr("builtins.input", lambda _: "2")
|
monkeypatch.setattr("builtins.input", lambda _: "2")
|
||||||
mgr.load_json_data()
|
vault.load_index()
|
||||||
content = (tmp_path / "seedpass_entries_db.json.enc").read_bytes()
|
content = (tmp_path / "seedpass_entries_db.json.enc").read_bytes()
|
||||||
assert content.startswith(b"V2:")
|
assert content.startswith(b"V2:")
|
||||||
assert mgr.last_migration_performed is True
|
assert vault.encryption_manager.last_migration_performed is True
|
||||||
|
@@ -7,7 +7,7 @@ import constants
|
|||||||
import seedpass.core.manager as manager_module
|
import seedpass.core.manager as manager_module
|
||||||
from utils.fingerprint_manager import FingerprintManager
|
from utils.fingerprint_manager import FingerprintManager
|
||||||
from seedpass.core.config_manager import ConfigManager
|
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):
|
def test_init_with_password(monkeypatch):
|
||||||
|
@@ -25,7 +25,6 @@ def test_legacy_password_only_fallback(monkeypatch, tmp_path, caplog):
|
|||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
enc_module, "prompt_existing_password", lambda *_a, **_k: TEST_PASSWORD
|
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)
|
vault, enc_mgr = create_vault(tmp_path)
|
||||||
data = {"schema_version": 4, "entries": {}}
|
data = {"schema_version": 4, "entries": {}}
|
||||||
|
@@ -81,7 +81,6 @@ def test_corruption_detection(monkeypatch):
|
|||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
enc_module, "prompt_existing_password", lambda *_a, **_k: PASSWORD
|
enc_module, "prompt_existing_password", lambda *_a, **_k: PASSWORD
|
||||||
)
|
)
|
||||||
monkeypatch.setattr("builtins.input", lambda *_a, **_k: "1")
|
|
||||||
|
|
||||||
with pytest.raises(InvalidToken):
|
with pytest.raises(InvalidToken):
|
||||||
import_backup(vault, backup, path, parent_seed=SEED)
|
import_backup(vault, backup, path, parent_seed=SEED)
|
||||||
|
@@ -8,7 +8,7 @@ from pathlib import Path
|
|||||||
sys.path.append(str(Path(__file__).resolve().parents[1]))
|
sys.path.append(str(Path(__file__).resolve().parents[1]))
|
||||||
import main
|
import main
|
||||||
from utils.fingerprint_manager import FingerprintManager
|
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):
|
def test_profile_deletion_stops_sync(monkeypatch, tmp_path):
|
||||||
|
Reference in New Issue
Block a user