mirror of
https://github.com/PR0M3TH3AN/SeedPass.git
synced 2026-10-31 01:42:05 +00:00
parity: SSH key derivation (Ed25519, PEM byte-for-byte)
- core/derive/ssh.ts: BIP-85 app 32 entropy used directly as the Ed25519 private key, serialized as PKCS#8 and SubjectPublicKeyInfo PEM. Both DER shapes are fixed for Ed25519, so they are emitted from literal prefixes rather than adding an ASN.1 dependency to the crypto path. - Output matches Python's `cryptography` byte-for-byte across two seeds and three indices, including PEM line wrapping. - CLI: `entry add ssh`, reveal (private key PEM), and `entry ssh-public` which prints the public key without touching secret material. The OpenSSH authorized_keys line is a TS-only convenience -- Python emits only PEM, which ssh(1) cannot consume directly -- and is marked as such in the compatibility matrix. - Cross-impl phases A and B now include an ssh entry in both directions. One harness fix: PEM values carry a trailing newline that the CLI capture strips, so the cross-impl comparison normalizes trailing whitespace. The unit tests still compare exactly. 170 core + 46 CLI tests green; 16/16 cross-implementation checks green. Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
1 parent
7407d65294
commit
1eee053ff0
11 files changed
+271
-7
No files matched your search
@@ -50,6 +50,9 @@ import {
|
||||
addSeedEntry,
|
||||
addManagedAccountEntry,
|
||||
addNostrKeyEntry,
|
||||
addSshKeyEntry,
|
||||
deriveSshKeyPair,
|
||||
sshPublicKeyOpenSsh,
|
||||
} from "@seedpass/core";
|
||||
import { readFile, writeFile } from "node:fs/promises";
|
||||
import { existsSync } from "node:fs";
|
||||
@@ -395,6 +398,31 @@ export function buildProgram(io: ProgramIo = defaultIo): Command {
|
||||
await finishAdd(vault, id);
|
||||
});
|
||||
|
||||
commonAddOptions(
|
||||
add.command("ssh <label>").description("derived Ed25519 SSH key entry"),
|
||||
).action(async (label: string, o: { notes?: string; tags?: string[]; archived?: boolean }) => {
|
||||
const vault = await openFromOptions(program.opts());
|
||||
const id = addSshKeyEntry(vault.index, label, commonOpts(o));
|
||||
await finishAdd(vault, id);
|
||||
});
|
||||
|
||||
entry
|
||||
.command("ssh-public <refOrQuery>")
|
||||
.description("print an SSH entry's public key (not secret material)")
|
||||
.option("--format <fmt>", "pem or openssh", "openssh")
|
||||
.option("--comment <text>", "comment for the openssh format", "")
|
||||
.action(async (refOrQuery: string, o: { format: string; comment: string }) => {
|
||||
const vault = await openFromOptions(program.opts());
|
||||
const hit = resolveEntry(vault.index, refOrQuery);
|
||||
if (hit.entry.kind !== "ssh") throw new Error(`${hit.ref} is not an ssh entry`);
|
||||
const pair = deriveSshKeyPair(vault.mnemonic, hit.entry.index);
|
||||
io.out(
|
||||
o.format === "pem"
|
||||
? pair.publicKeyPem.trimEnd()
|
||||
: sshPublicKeyOpenSsh(pair.publicKey, o.comment || hit.entry.label),
|
||||
);
|
||||
});
|
||||
|
||||
entry
|
||||
.command("modify <refOrQuery>")
|
||||
.description("update entry fields (kind-checked); prints the new metadata")
|
||||
|
||||
Reference in new issue
Block a user