mirror of
https://github.com/PR0M3TH3AN/SeedPass.git
synced 2026-11-03 11:22:03 +00:00
parity: PGP key derivation, byte-for-byte with PGPy
The last unported derivation, and the one the plan flagged as highest risk. It turned out to be fully reproducible: the key material is deterministic, EdDSA signatures are deterministic (RFC 8032), and the packet layout is fixed -- so the armored blocks match exactly, with no OpenPGP dependency. core/derive/pgp.ts emits the packets directly: new-format headers, the secret-key packet (unencrypted MPI plus 16-bit checksum), the user-id packet, and the self-certification signature with PGPy's exact subpacket set and order, wrapped in ASCII armor with a CRC-24 footer. Verified against PGPy across two seeds, three indices and three user-id shapes -- armored private key, armored public key, and fingerprint. RSA is deliberately unsupported: PyCryptodome's seeded prime search is not reproducible, so the TS port refuses the key type rather than silently deriving a different key. Two things worth recording from the build: the v4 fingerprint reproduces from first principles (SHA-1 over the public-key packet), which is what made byte-parity look feasible before committing to it; and the only mismatch in the first run was packet framing -- PGPy uses new-format headers where I had emitted old-format. CLI: `entry add pgp`, reveal (armored private key), `entry pgp-public`. Cross-impl phases A/B now cover a pgp entry in both directions, completing coverage of every creatable entry kind. 193 core + 47 CLI tests green; 30/30 cross-implementation checks green. Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
1 parent
5dcd779a6f
commit
c2d7306bb7
12 files changed
+457
-13
No files matched your search
@@ -183,6 +183,7 @@ def py_create_profile(app_dir: Path, seed: str, password: str) -> str:
|
||||
em.add_managed_account("python-managed", seed)
|
||||
em.add_nostr_key("python-nostr", seed)
|
||||
em.add_ssh_key("python-ssh", seed)
|
||||
em.add_pgp_key("python-pgp", seed, user_id="[email protected]")
|
||||
|
||||
mgr = FingerprintManager(app_dir)
|
||||
if fp not in mgr.fingerprints:
|
||||
@@ -276,6 +277,16 @@ def py_secrets_for(index: dict, seed: str) -> dict[str, str]:
|
||||
)
|
||||
elif kind == "managed_account":
|
||||
out[label] = derive_seed_phrase(bip85, int(entry.get("index", int(idx))), 12)
|
||||
elif kind == "pgp":
|
||||
from seedpass.core.password_generation import derive_pgp_key
|
||||
|
||||
priv, _pub, _fp = derive_pgp_key(
|
||||
bip85,
|
||||
int(entry.get("index", int(idx))),
|
||||
entry.get("key_type", "ed25519"),
|
||||
entry.get("user_id", ""),
|
||||
)
|
||||
out[label] = priv
|
||||
elif kind == "ssh":
|
||||
from seedpass.core.password_generation import derive_ssh_key_pair
|
||||
|
||||
@@ -378,6 +389,10 @@ def phase_b(tmp: Path) -> None:
|
||||
run_cli(app_dir, "entry", "add", "managed-account", "ts-managed", env_extra=env)
|
||||
run_cli(app_dir, "entry", "add", "ssh", "ts-ssh", env_extra=env)
|
||||
run_cli(app_dir, "entry", "add", "nostr", "ts-nostr", env_extra=env)
|
||||
run_cli(
|
||||
app_dir, "entry", "add", "pgp", "ts-pgp", "--user-id", "[email protected]",
|
||||
env_extra=env,
|
||||
)
|
||||
run_cli(app_dir, "entry", "add", "document", "ts-doc", "ts document body", env_extra=env)
|
||||
run_cli(app_dir, "entry", "add", "seed", "ts-seed", "--words", "24", env_extra=env)
|
||||
run_cli(
|
||||
|
||||
@@ -1133,6 +1133,36 @@ def gen_ssh_keys() -> dict:
|
||||
}
|
||||
|
||||
|
||||
def gen_pgp_keys() -> dict:
|
||||
from seedpass.core.password_generation import derive_pgp_key
|
||||
|
||||
cases = []
|
||||
for mid in (PRIMARY, "zoo24"):
|
||||
bip85 = _bip85(MNEMONICS[mid])
|
||||
for index, user_id in ((0, "[email protected]"), (3, ""), (7, "Test User <[email protected]>")):
|
||||
priv, pub, fp = derive_pgp_key(bip85, index, "ed25519", user_id)
|
||||
cases.append(
|
||||
{
|
||||
"mnemonic_id": mid,
|
||||
"index": index,
|
||||
"user_id": user_id,
|
||||
"fingerprint": fp,
|
||||
"private_key_armored": priv,
|
||||
"public_key_armored": pub,
|
||||
}
|
||||
)
|
||||
return {
|
||||
"description": (
|
||||
"Deterministic PGP (Ed25519/EdDSA) keys from BIP-85 app 32, "
|
||||
"created 2000-01-01T00:00:00Z, as PGPy serializes them. EdDSA "
|
||||
"signatures are deterministic, so the armored output is "
|
||||
"byte-reproducible."
|
||||
),
|
||||
"created_at": 946684800,
|
||||
"cases": cases,
|
||||
}
|
||||
|
||||
|
||||
def gen_kdf_metadata() -> dict:
|
||||
return {
|
||||
"description": (
|
||||
@@ -1177,6 +1207,7 @@ def main() -> None:
|
||||
"kdf_metadata.json": gen_kdf_metadata(),
|
||||
"migrations.json": gen_migrations(),
|
||||
"ssh_keys.json": gen_ssh_keys(),
|
||||
"pgp_keys.json": gen_pgp_keys(),
|
||||
"password_kdf.json": gen_password_kdf(),
|
||||
"legacy_payloads.json": gen_legacy_payloads(),
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user