feat: support FIDO2 security keys through ssh agent

This commit is contained in:
LukeGus committed 2026-10-19 19:55:35 -05:00
1 parent 901a235de4
commit 3b3fd7f135
6 files changed
+324 -1

No files matched your search

@@ -10,6 +10,7 @@ import {
isPrivateKeyPassphraseError,
getFriendlyKeyTypeName,
validateKeyPair,
isSecurityKeyFile,
} from "../../utils/ssh-key-utils.js";
const { parsePrivateKey } = sshpk;
@@ -224,3 +225,43 @@ describe("PKCS#8 runtime key normalization", () => {
).toThrow();
});
});
describe("security key files", () => {
const str = (v: string | Buffer) => {
const b = Buffer.from(v);
const l = Buffer.alloc(4);
l.writeUInt32BE(b.length);
return Buffer.concat([l, b]);
};
const skFile = (type: string) => {
const body = Buffer.concat([
Buffer.from("openssh-key-v1\u0000"),
str("none"),
str("none"),
str(""),
Buffer.from([0, 0, 0, 1]),
str(Buffer.concat([str(type), str(Buffer.alloc(32, 1)), str("ssh:")])),
str(Buffer.alloc(16)),
]).toString("base64");
return `-----BEGIN OPENSSH PRIVATE KEY-----
${body}
-----END OPENSSH PRIVATE KEY-----`;
};
it("detects sk key files", () => {
expect(isSecurityKeyFile(skFile("sk-ssh-ed25519@openssh.com"))).toBe(true);
expect(
isSecurityKeyFile(skFile("sk-ecdsa-sha2-nistp256@openssh.com")),
).toBe(true);
});
it("ignores normal keys", () => {
expect(isSecurityKeyFile(ED25519_PRIVATE)).toBe(false);
});
it("explains how to use a security key instead of failing to decode", () => {
expect(() =>
preparePrivateKeyForSSH2(skFile("sk-ssh-ed25519@openssh.com")),
).toThrow("ssh-add");
});
});