mirror of
https://github.com/PR0M3TH3AN/SeedPass.git
synced 2025-09-08 07:18:47 +00:00
Merge pull request #709 from PR0M3TH3AN/codex/add-key-validation-module-and-update-entrymanager
Add key validation helpers and enforce in entry management
This commit is contained in:
@@ -177,6 +177,7 @@ def create_entry(
|
|||||||
if etype == "nostr":
|
if etype == "nostr":
|
||||||
index = _pm.entry_manager.add_nostr_key(
|
index = _pm.entry_manager.add_nostr_key(
|
||||||
entry.get("label"),
|
entry.get("label"),
|
||||||
|
_pm.parent_seed,
|
||||||
index=entry.get("index"),
|
index=entry.get("index"),
|
||||||
notes=entry.get("notes", ""),
|
notes=entry.get("notes", ""),
|
||||||
archived=entry.get("archived", False),
|
archived=entry.get("archived", False),
|
||||||
|
@@ -420,6 +420,7 @@ class EntryService:
|
|||||||
with self._lock:
|
with self._lock:
|
||||||
idx = self._manager.entry_manager.add_nostr_key(
|
idx = self._manager.entry_manager.add_nostr_key(
|
||||||
label,
|
label,
|
||||||
|
self._manager.parent_seed,
|
||||||
index=index,
|
index=index,
|
||||||
notes=notes,
|
notes=notes,
|
||||||
)
|
)
|
||||||
|
@@ -37,6 +37,13 @@ from .entry_types import EntryType
|
|||||||
from .totp import TotpManager
|
from .totp import TotpManager
|
||||||
from utils.fingerprint import generate_fingerprint
|
from utils.fingerprint import generate_fingerprint
|
||||||
from utils.checksum import canonical_json_dumps
|
from utils.checksum import canonical_json_dumps
|
||||||
|
from utils.key_validation import (
|
||||||
|
validate_totp_secret,
|
||||||
|
validate_ssh_key_pair,
|
||||||
|
validate_pgp_private_key,
|
||||||
|
validate_nostr_keys,
|
||||||
|
validate_seed_phrase,
|
||||||
|
)
|
||||||
|
|
||||||
from .vault import Vault
|
from .vault import Vault
|
||||||
from .backup import BackupManager
|
from .backup import BackupManager
|
||||||
@@ -266,6 +273,8 @@ class EntryManager:
|
|||||||
if index is None:
|
if index is None:
|
||||||
index = self.get_next_totp_index()
|
index = self.get_next_totp_index()
|
||||||
secret = TotpManager.derive_secret(parent_seed, index)
|
secret = TotpManager.derive_secret(parent_seed, index)
|
||||||
|
if not validate_totp_secret(secret):
|
||||||
|
raise ValueError("Invalid derived TOTP secret")
|
||||||
entry = {
|
entry = {
|
||||||
"type": EntryType.TOTP.value,
|
"type": EntryType.TOTP.value,
|
||||||
"kind": EntryType.TOTP.value,
|
"kind": EntryType.TOTP.value,
|
||||||
@@ -279,6 +288,8 @@ class EntryManager:
|
|||||||
"tags": tags or [],
|
"tags": tags or [],
|
||||||
}
|
}
|
||||||
else:
|
else:
|
||||||
|
if not validate_totp_secret(secret):
|
||||||
|
raise ValueError("Invalid TOTP secret")
|
||||||
entry = {
|
entry = {
|
||||||
"type": EntryType.TOTP.value,
|
"type": EntryType.TOTP.value,
|
||||||
"kind": EntryType.TOTP.value,
|
"kind": EntryType.TOTP.value,
|
||||||
@@ -323,6 +334,12 @@ class EntryManager:
|
|||||||
if index is None:
|
if index is None:
|
||||||
index = self.get_next_index()
|
index = self.get_next_index()
|
||||||
|
|
||||||
|
from .password_generation import derive_ssh_key_pair
|
||||||
|
|
||||||
|
priv_pem, pub_pem = derive_ssh_key_pair(parent_seed, index)
|
||||||
|
if not validate_ssh_key_pair(priv_pem, pub_pem):
|
||||||
|
raise ValueError("Derived SSH key pair failed validation")
|
||||||
|
|
||||||
data = self._load_index()
|
data = self._load_index()
|
||||||
data.setdefault("entries", {})
|
data.setdefault("entries", {})
|
||||||
data["entries"][str(index)] = {
|
data["entries"][str(index)] = {
|
||||||
@@ -370,6 +387,17 @@ class EntryManager:
|
|||||||
if index is None:
|
if index is None:
|
||||||
index = self.get_next_index()
|
index = self.get_next_index()
|
||||||
|
|
||||||
|
from .password_generation import derive_pgp_key
|
||||||
|
from local_bip85.bip85 import BIP85
|
||||||
|
from bip_utils import Bip39SeedGenerator
|
||||||
|
|
||||||
|
seed_bytes = Bip39SeedGenerator(parent_seed).Generate()
|
||||||
|
bip85 = BIP85(seed_bytes)
|
||||||
|
|
||||||
|
priv_key, fp = derive_pgp_key(bip85, index, key_type, user_id)
|
||||||
|
if not validate_pgp_private_key(priv_key, fp):
|
||||||
|
raise ValueError("Derived PGP key failed validation")
|
||||||
|
|
||||||
data = self._load_index()
|
data = self._load_index()
|
||||||
data.setdefault("entries", {})
|
data.setdefault("entries", {})
|
||||||
data["entries"][str(index)] = {
|
data["entries"][str(index)] = {
|
||||||
@@ -413,6 +441,7 @@ class EntryManager:
|
|||||||
def add_nostr_key(
|
def add_nostr_key(
|
||||||
self,
|
self,
|
||||||
label: str,
|
label: str,
|
||||||
|
parent_seed: str,
|
||||||
index: int | None = None,
|
index: int | None = None,
|
||||||
notes: str = "",
|
notes: str = "",
|
||||||
archived: bool = False,
|
archived: bool = False,
|
||||||
@@ -423,6 +452,19 @@ class EntryManager:
|
|||||||
if index is None:
|
if index is None:
|
||||||
index = self.get_next_index()
|
index = self.get_next_index()
|
||||||
|
|
||||||
|
from local_bip85.bip85 import BIP85
|
||||||
|
from bip_utils import Bip39SeedGenerator
|
||||||
|
from nostr.coincurve_keys import Keys
|
||||||
|
|
||||||
|
seed_bytes = Bip39SeedGenerator(parent_seed).Generate()
|
||||||
|
bip85 = BIP85(seed_bytes)
|
||||||
|
entropy = bip85.derive_entropy(index=index, bytes_len=32)
|
||||||
|
keys = Keys(priv_k=entropy.hex())
|
||||||
|
npub = Keys.hex_to_bech32(keys.public_key_hex(), "npub")
|
||||||
|
nsec = Keys.hex_to_bech32(keys.private_key_hex(), "nsec")
|
||||||
|
if not validate_nostr_keys(npub, nsec):
|
||||||
|
raise ValueError("Derived Nostr keys failed validation")
|
||||||
|
|
||||||
data = self._load_index()
|
data = self._load_index()
|
||||||
data.setdefault("entries", {})
|
data.setdefault("entries", {})
|
||||||
data["entries"][str(index)] = {
|
data["entries"][str(index)] = {
|
||||||
@@ -515,6 +557,16 @@ class EntryManager:
|
|||||||
if index is None:
|
if index is None:
|
||||||
index = self.get_next_index()
|
index = self.get_next_index()
|
||||||
|
|
||||||
|
from .password_generation import derive_seed_phrase
|
||||||
|
from local_bip85.bip85 import BIP85
|
||||||
|
from bip_utils import Bip39SeedGenerator
|
||||||
|
|
||||||
|
seed_bytes = Bip39SeedGenerator(parent_seed).Generate()
|
||||||
|
bip85 = BIP85(seed_bytes)
|
||||||
|
phrase = derive_seed_phrase(bip85, index, words_num)
|
||||||
|
if not validate_seed_phrase(phrase):
|
||||||
|
raise ValueError("Derived seed phrase failed validation")
|
||||||
|
|
||||||
data = self._load_index()
|
data = self._load_index()
|
||||||
data.setdefault("entries", {})
|
data.setdefault("entries", {})
|
||||||
data["entries"][str(index)] = {
|
data["entries"][str(index)] = {
|
||||||
@@ -583,6 +635,8 @@ class EntryManager:
|
|||||||
word_count = 12
|
word_count = 12
|
||||||
|
|
||||||
seed_phrase = derive_seed_phrase(bip85, index, word_count)
|
seed_phrase = derive_seed_phrase(bip85, index, word_count)
|
||||||
|
if not validate_seed_phrase(seed_phrase):
|
||||||
|
raise ValueError("Derived managed account seed failed validation")
|
||||||
fingerprint = generate_fingerprint(seed_phrase)
|
fingerprint = generate_fingerprint(seed_phrase)
|
||||||
|
|
||||||
account_dir = self.fingerprint_dir / "accounts" / fingerprint
|
account_dir = self.fingerprint_dir / "accounts" / fingerprint
|
||||||
|
@@ -1970,7 +1970,9 @@ class PasswordManager:
|
|||||||
if tags_input
|
if tags_input
|
||||||
else []
|
else []
|
||||||
)
|
)
|
||||||
index = self.entry_manager.add_nostr_key(label, notes=notes, tags=tags)
|
index = self.entry_manager.add_nostr_key(
|
||||||
|
label, self.parent_seed, notes=notes, tags=tags
|
||||||
|
)
|
||||||
npub, nsec = self.entry_manager.get_nostr_key_pair(index, self.parent_seed)
|
npub, nsec = self.entry_manager.get_nostr_key_pair(index, self.parent_seed)
|
||||||
self.is_dirty = True
|
self.is_dirty = True
|
||||||
self.last_update = time.time()
|
self.last_update = time.time()
|
||||||
|
@@ -24,7 +24,7 @@ class DummyPM:
|
|||||||
add_totp=lambda label, seed, index=None, secret=None, period=30, digits=6: "totp://",
|
add_totp=lambda label, seed, index=None, secret=None, period=30, digits=6: "totp://",
|
||||||
add_ssh_key=lambda label, seed, index=None, notes="": 2,
|
add_ssh_key=lambda label, seed, index=None, notes="": 2,
|
||||||
add_pgp_key=lambda label, seed, index=None, key_type="ed25519", user_id="", notes="": 3,
|
add_pgp_key=lambda label, seed, index=None, key_type="ed25519", user_id="", notes="": 3,
|
||||||
add_nostr_key=lambda label, index=None, notes="": 4,
|
add_nostr_key=lambda label, seed, index=None, notes="": 4,
|
||||||
add_seed=lambda label, seed, index=None, words_num=24, notes="": 5,
|
add_seed=lambda label, seed, index=None, words_num=24, notes="": 5,
|
||||||
add_key_value=lambda label, key, value, notes="": 6,
|
add_key_value=lambda label, key, value, notes="": 6,
|
||||||
add_managed_account=lambda label, seed, index=None, notes="": 7,
|
add_managed_account=lambda label, seed, index=None, notes="": 7,
|
||||||
|
@@ -4,6 +4,7 @@ from typer.testing import CliRunner
|
|||||||
|
|
||||||
from seedpass.cli import app
|
from seedpass.cli import app
|
||||||
from seedpass import cli
|
from seedpass import cli
|
||||||
|
from helpers import TEST_SEED
|
||||||
|
|
||||||
runner = CliRunner()
|
runner = CliRunner()
|
||||||
|
|
||||||
@@ -98,7 +99,7 @@ runner = CliRunner()
|
|||||||
"add-nostr",
|
"add-nostr",
|
||||||
"add_nostr_key",
|
"add_nostr_key",
|
||||||
["Label", "--index", "4", "--notes", "n"],
|
["Label", "--index", "4", "--notes", "n"],
|
||||||
("Label",),
|
("Label", "seed"),
|
||||||
{"index": 4, "notes": "n"},
|
{"index": 4, "notes": "n"},
|
||||||
"5",
|
"5",
|
||||||
),
|
),
|
||||||
|
@@ -116,7 +116,7 @@ def test_legacy_entry_defaults_to_password():
|
|||||||
("add_totp", ("totp", TEST_SEED)),
|
("add_totp", ("totp", TEST_SEED)),
|
||||||
("add_ssh_key", ("ssh", TEST_SEED)),
|
("add_ssh_key", ("ssh", TEST_SEED)),
|
||||||
("add_pgp_key", ("pgp", TEST_SEED)),
|
("add_pgp_key", ("pgp", TEST_SEED)),
|
||||||
("add_nostr_key", ("nostr",)),
|
("add_nostr_key", ("nostr", TEST_SEED)),
|
||||||
("add_seed", ("seed", TEST_SEED)),
|
("add_seed", ("seed", TEST_SEED)),
|
||||||
("add_key_value", ("label", "k1", "val")),
|
("add_key_value", ("label", "k1", "val")),
|
||||||
("add_managed_account", ("acct", TEST_SEED)),
|
("add_managed_account", ("acct", TEST_SEED)),
|
||||||
|
@@ -49,7 +49,7 @@ class FakeEntries:
|
|||||||
self.added.append(("pgp", label))
|
self.added.append(("pgp", label))
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
def add_nostr_key(self, label):
|
def add_nostr_key(self, label, seed=None):
|
||||||
self.added.append(("nostr", label))
|
self.added.append(("nostr", label))
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
66
src/tests/test_key_validation_failures.py
Normal file
66
src/tests/test_key_validation_failures.py
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
import pytest
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from helpers import create_vault, TEST_SEED, TEST_PASSWORD
|
||||||
|
|
||||||
|
from seedpass.core.entry_management import EntryManager
|
||||||
|
from seedpass.core.backup import BackupManager
|
||||||
|
from seedpass.core.config_manager import ConfigManager
|
||||||
|
|
||||||
|
|
||||||
|
def setup_mgr(tmp_path: Path) -> EntryManager:
|
||||||
|
vault, _ = create_vault(tmp_path, TEST_SEED, TEST_PASSWORD)
|
||||||
|
cfg = ConfigManager(vault, tmp_path)
|
||||||
|
backup = BackupManager(tmp_path, cfg)
|
||||||
|
return EntryManager(vault, backup)
|
||||||
|
|
||||||
|
|
||||||
|
def test_add_totp_invalid_secret(tmp_path: Path):
|
||||||
|
mgr = setup_mgr(tmp_path)
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
mgr.add_totp("bad", TEST_SEED, secret="notbase32!")
|
||||||
|
|
||||||
|
|
||||||
|
def test_add_ssh_key_validation_failure(monkeypatch, tmp_path: Path):
|
||||||
|
mgr = setup_mgr(tmp_path)
|
||||||
|
monkeypatch.setattr(
|
||||||
|
"seedpass.core.entry_management.validate_ssh_key_pair", lambda p, q: False
|
||||||
|
)
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
mgr.add_ssh_key("ssh", TEST_SEED)
|
||||||
|
|
||||||
|
|
||||||
|
def test_add_pgp_key_validation_failure(monkeypatch, tmp_path: Path):
|
||||||
|
mgr = setup_mgr(tmp_path)
|
||||||
|
monkeypatch.setattr(
|
||||||
|
"seedpass.core.entry_management.validate_pgp_private_key", lambda p, q: False
|
||||||
|
)
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
mgr.add_pgp_key("pgp", TEST_SEED, user_id="test")
|
||||||
|
|
||||||
|
|
||||||
|
def test_add_nostr_key_validation_failure(monkeypatch, tmp_path: Path):
|
||||||
|
mgr = setup_mgr(tmp_path)
|
||||||
|
monkeypatch.setattr(
|
||||||
|
"seedpass.core.entry_management.validate_nostr_keys", lambda p, q: False
|
||||||
|
)
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
mgr.add_nostr_key("nostr", TEST_SEED)
|
||||||
|
|
||||||
|
|
||||||
|
def test_add_seed_validation_failure(monkeypatch, tmp_path: Path):
|
||||||
|
mgr = setup_mgr(tmp_path)
|
||||||
|
monkeypatch.setattr(
|
||||||
|
"seedpass.core.entry_management.validate_seed_phrase", lambda p: False
|
||||||
|
)
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
mgr.add_seed("seed", TEST_SEED)
|
||||||
|
|
||||||
|
|
||||||
|
def test_add_managed_account_validation_failure(monkeypatch, tmp_path: Path):
|
||||||
|
mgr = setup_mgr(tmp_path)
|
||||||
|
monkeypatch.setattr(
|
||||||
|
"seedpass.core.entry_management.validate_seed_phrase", lambda p: False
|
||||||
|
)
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
mgr.add_managed_account("acct", TEST_SEED)
|
@@ -246,7 +246,7 @@ def test_show_nostr_entry_details(monkeypatch, capsys):
|
|||||||
with TemporaryDirectory() as tmpdir:
|
with TemporaryDirectory() as tmpdir:
|
||||||
tmp_path = Path(tmpdir)
|
tmp_path = Path(tmpdir)
|
||||||
pm, entry_mgr = _setup_manager(tmp_path)
|
pm, entry_mgr = _setup_manager(tmp_path)
|
||||||
idx = entry_mgr.add_nostr_key("nostr")
|
idx = entry_mgr.add_nostr_key("nostr", TEST_SEED)
|
||||||
|
|
||||||
called = _detail_common(monkeypatch, pm)
|
called = _detail_common(monkeypatch, pm)
|
||||||
|
|
||||||
@@ -339,7 +339,7 @@ def test_show_entry_details_sensitive(monkeypatch, capsys, entry_type):
|
|||||||
expected = priv
|
expected = priv
|
||||||
extra = fp
|
extra = fp
|
||||||
elif entry_type == "nostr":
|
elif entry_type == "nostr":
|
||||||
idx = entry_mgr.add_nostr_key("nostr")
|
idx = entry_mgr.add_nostr_key("nostr", TEST_SEED)
|
||||||
_npub, nsec = entry_mgr.get_nostr_key_pair(idx, TEST_SEED)
|
_npub, nsec = entry_mgr.get_nostr_key_pair(idx, TEST_SEED)
|
||||||
expected = nsec
|
expected = nsec
|
||||||
elif entry_type == "totp":
|
elif entry_type == "totp":
|
||||||
|
@@ -22,7 +22,7 @@ def test_nostr_key_determinism():
|
|||||||
backup_mgr = BackupManager(tmp_path, cfg_mgr)
|
backup_mgr = BackupManager(tmp_path, cfg_mgr)
|
||||||
entry_mgr = EntryManager(vault, backup_mgr)
|
entry_mgr = EntryManager(vault, backup_mgr)
|
||||||
|
|
||||||
idx = entry_mgr.add_nostr_key("main")
|
idx = entry_mgr.add_nostr_key("main", TEST_SEED)
|
||||||
entry = entry_mgr.retrieve_entry(idx)
|
entry = entry_mgr.retrieve_entry(idx)
|
||||||
assert entry == {
|
assert entry == {
|
||||||
"type": "nostr",
|
"type": "nostr",
|
||||||
|
@@ -42,7 +42,7 @@ def test_show_qr_for_nostr_keys(monkeypatch):
|
|||||||
pm.is_dirty = False
|
pm.is_dirty = False
|
||||||
pm.secret_mode_enabled = False
|
pm.secret_mode_enabled = False
|
||||||
|
|
||||||
idx = entry_mgr.add_nostr_key("main")
|
idx = entry_mgr.add_nostr_key("main", TEST_SEED)
|
||||||
npub, _ = entry_mgr.get_nostr_key_pair(idx, TEST_SEED)
|
npub, _ = entry_mgr.get_nostr_key_pair(idx, TEST_SEED)
|
||||||
|
|
||||||
inputs = iter([str(idx), "q", "p", ""])
|
inputs = iter([str(idx), "q", "p", ""])
|
||||||
@@ -78,7 +78,7 @@ def test_show_private_key_qr(monkeypatch, capsys):
|
|||||||
pm.is_dirty = False
|
pm.is_dirty = False
|
||||||
pm.secret_mode_enabled = False
|
pm.secret_mode_enabled = False
|
||||||
|
|
||||||
idx = entry_mgr.add_nostr_key("main")
|
idx = entry_mgr.add_nostr_key("main", TEST_SEED)
|
||||||
_, nsec = entry_mgr.get_nostr_key_pair(idx, TEST_SEED)
|
_, nsec = entry_mgr.get_nostr_key_pair(idx, TEST_SEED)
|
||||||
|
|
||||||
inputs = iter([str(idx), "q", "k", ""])
|
inputs = iter([str(idx), "q", "k", ""])
|
||||||
@@ -116,7 +116,7 @@ def test_qr_menu_case_insensitive(monkeypatch):
|
|||||||
pm.is_dirty = False
|
pm.is_dirty = False
|
||||||
pm.secret_mode_enabled = False
|
pm.secret_mode_enabled = False
|
||||||
|
|
||||||
idx = entry_mgr.add_nostr_key("main")
|
idx = entry_mgr.add_nostr_key("main", TEST_SEED)
|
||||||
npub, _ = entry_mgr.get_nostr_key_pair(idx, TEST_SEED)
|
npub, _ = entry_mgr.get_nostr_key_pair(idx, TEST_SEED)
|
||||||
|
|
||||||
# Modify index to use uppercase type/kind
|
# Modify index to use uppercase type/kind
|
||||||
|
@@ -20,7 +20,7 @@ import pytest
|
|||||||
(lambda mgr: mgr.add_seed("seed", TEST_SEED), True),
|
(lambda mgr: mgr.add_seed("seed", TEST_SEED), True),
|
||||||
(lambda mgr: mgr.add_pgp_key("pgp", TEST_SEED, user_id="test"), True),
|
(lambda mgr: mgr.add_pgp_key("pgp", TEST_SEED, user_id="test"), True),
|
||||||
(lambda mgr: mgr.add_ssh_key("ssh", TEST_SEED), True),
|
(lambda mgr: mgr.add_ssh_key("ssh", TEST_SEED), True),
|
||||||
(lambda mgr: mgr.add_nostr_key("nostr"), False),
|
(lambda mgr: mgr.add_nostr_key("nostr", TEST_SEED), False),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_pause_before_entry_actions(monkeypatch, adder, needs_confirm):
|
def test_pause_before_entry_actions(monkeypatch, adder, needs_confirm):
|
||||||
|
69
src/utils/key_validation.py
Normal file
69
src/utils/key_validation.py
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
"""Key validation helper functions."""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from cryptography.hazmat.primitives import serialization
|
||||||
|
from pgpy import PGPKey
|
||||||
|
import pyotp
|
||||||
|
from nostr.coincurve_keys import Keys
|
||||||
|
from mnemonic import Mnemonic
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def validate_totp_secret(secret: str) -> bool:
|
||||||
|
"""Return True if ``secret`` is a valid Base32 TOTP secret."""
|
||||||
|
try:
|
||||||
|
pyotp.TOTP(secret).at(0)
|
||||||
|
return True
|
||||||
|
except Exception as e: # pragma: no cover - pyotp errors vary
|
||||||
|
logger.debug(f"Invalid TOTP secret: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def validate_ssh_key_pair(priv_pem: str, pub_pem: str) -> bool:
|
||||||
|
"""Ensure ``priv_pem`` corresponds to ``pub_pem``."""
|
||||||
|
try:
|
||||||
|
priv = serialization.load_pem_private_key(priv_pem.encode(), password=None)
|
||||||
|
derived = (
|
||||||
|
priv.public_key()
|
||||||
|
.public_bytes(
|
||||||
|
serialization.Encoding.PEM,
|
||||||
|
serialization.PublicFormat.SubjectPublicKeyInfo,
|
||||||
|
)
|
||||||
|
.decode()
|
||||||
|
)
|
||||||
|
return derived == pub_pem
|
||||||
|
except Exception as e: # pragma: no cover - serialization errors vary
|
||||||
|
logger.debug(f"SSH key validation failed: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def validate_pgp_private_key(priv_key: str, fingerprint: str) -> bool:
|
||||||
|
"""Return True if ``priv_key`` matches ``fingerprint``."""
|
||||||
|
try:
|
||||||
|
key, _ = PGPKey.from_blob(priv_key)
|
||||||
|
return key.fingerprint == fingerprint
|
||||||
|
except Exception as e: # pragma: no cover - pgpy errors vary
|
||||||
|
logger.debug(f"PGP key validation failed: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def validate_nostr_keys(npub: str, nsec: str) -> bool:
|
||||||
|
"""Return True if ``nsec`` decodes to ``npub``."""
|
||||||
|
try:
|
||||||
|
priv_hex = Keys.bech32_to_hex(nsec)
|
||||||
|
derived = Keys(priv_k=priv_hex)
|
||||||
|
encoded = Keys.hex_to_bech32(derived.public_key_hex(), "npub")
|
||||||
|
return encoded == npub
|
||||||
|
except Exception as e: # pragma: no cover - nostr errors vary
|
||||||
|
logger.debug(f"Nostr key validation failed: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def validate_seed_phrase(mnemonic: str) -> bool:
|
||||||
|
"""Return True if ``mnemonic`` is a valid BIP-39 seed phrase."""
|
||||||
|
try:
|
||||||
|
return Mnemonic("english").check(mnemonic)
|
||||||
|
except Exception as e: # pragma: no cover - mnemonic errors vary
|
||||||
|
logger.debug(f"Seed phrase validation failed: {e}")
|
||||||
|
return False
|
Reference in New Issue
Block a user