mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-21 11:01:09 +00:00
150 lines
4.5 KiB
JavaScript
150 lines
4.5 KiB
JavaScript
const fs = require("node:fs");
|
|
|
|
const SK_ED = "sk-ssh-ed25519@openssh.com";
|
|
const SK_EC = "sk-ecdsa-sha2-nistp256@openssh.com";
|
|
|
|
function replaceOnce(source, from, to, label) {
|
|
const first = source.indexOf(from);
|
|
if (first === -1 || source.indexOf(from, first + 1) !== -1) {
|
|
throw new Error(`ssh2 changed (${label}); review the security key patch`);
|
|
}
|
|
return source.replace(from, () => to);
|
|
}
|
|
|
|
function patchKeyParserSource(source) {
|
|
if (source.includes("// termix-sk")) return source;
|
|
let out = replaceOnce(
|
|
source,
|
|
` case 'ecdsa-sha2-nistp521':
|
|
return true;
|
|
case 'ssh-ed25519':
|
|
if (eddsaSupported)
|
|
return true;`,
|
|
` case 'ecdsa-sha2-nistp521':
|
|
return true;
|
|
case '${SK_EC}': // termix-sk
|
|
return true;
|
|
case '${SK_ED}':
|
|
case 'ssh-ed25519':
|
|
if (eddsaSupported)
|
|
return true;`,
|
|
"key types",
|
|
);
|
|
out = replaceOnce(
|
|
out,
|
|
` default:
|
|
return new Error(\`Unsupported OpenSSH public key type: \${baseType}\`);
|
|
}
|
|
|
|
return new OpenSSH_Public(`,
|
|
` case '${SK_ED}':
|
|
case '${SK_EC}': {
|
|
// Public blob only; the security key signs, so there is no private half.
|
|
const rest = data.subarray(data._pos || 0);
|
|
const typeLen = Buffer.byteLength(baseType);
|
|
pubSSH = Buffer.allocUnsafe(4 + typeLen + rest.length);
|
|
writeUInt32BE(pubSSH, typeLen, 0);
|
|
pubSSH.utf8Write(baseType, 4, typeLen);
|
|
pubSSH.set(rest, 4 + typeLen);
|
|
algo = baseType === '${SK_ED}' ? null : 'sha256';
|
|
break;
|
|
}
|
|
default:
|
|
return new Error(\`Unsupported OpenSSH public key type: \${baseType}\`);
|
|
}
|
|
|
|
return new OpenSSH_Public(`,
|
|
"parseDER",
|
|
);
|
|
return out;
|
|
}
|
|
|
|
function patchAgentSource(source) {
|
|
if (source.includes("skTrailer")) return source;
|
|
return replaceOnce(
|
|
source,
|
|
` binaryParser.init(signature, 0);
|
|
binaryParser.readString(true);
|
|
signature = binaryParser.readString();
|
|
binaryParser.clear();`,
|
|
` binaryParser.init(signature, 0);
|
|
binaryParser.readString(true);
|
|
signature = binaryParser.readString();
|
|
// Security key signatures end with flags and a counter.
|
|
const skTrailer = signature === undefined
|
|
? undefined
|
|
: binaryParser.readRaw();
|
|
binaryParser.clear();
|
|
if (skTrailer && skTrailer.length)
|
|
signature.skTrailer = Buffer.from(skTrailer);`,
|
|
"agent sign response",
|
|
);
|
|
}
|
|
|
|
function patchProtocolSource(source) {
|
|
if (source.includes("skTrailer")) return source;
|
|
const start = source.indexOf(" authPK(username, pubKey, keyAlgo, cbSign) {");
|
|
const end = source.indexOf(" authHostbased(", start);
|
|
if (start === -1 || end === -1) {
|
|
throw new Error("ssh2 changed (authPK); review the security key patch");
|
|
}
|
|
let region = source.slice(start, end);
|
|
region = replaceOnce(
|
|
region,
|
|
` signature = convertSignature(signature, keyType);`,
|
|
` const skTrailer = signature && signature.skTrailer;
|
|
signature = convertSignature(signature, keyType);`,
|
|
"authPK sign",
|
|
);
|
|
region = replaceOnce(
|
|
region,
|
|
` + 4 + algoLen + 4 + sigLen
|
|
);`,
|
|
` + 4 + algoLen + 4 + sigLen + (skTrailer ? skTrailer.length : 0)
|
|
);`,
|
|
"authPK alloc",
|
|
);
|
|
region = replaceOnce(
|
|
region,
|
|
` writeUInt32BE(packet, 4 + algoLen + 4 + sigLen, p += pubKeyLen);`,
|
|
` writeUInt32BE(
|
|
packet,
|
|
4 + algoLen + 4 + sigLen + (skTrailer ? skTrailer.length : 0),
|
|
p += pubKeyLen
|
|
);`,
|
|
"authPK length",
|
|
);
|
|
region = replaceOnce(
|
|
region,
|
|
` packet.set(signature, p += 4);`,
|
|
` packet.set(signature, p += 4);
|
|
if (skTrailer)
|
|
packet.set(skTrailer, p + sigLen);`,
|
|
"authPK copy",
|
|
);
|
|
return source.slice(0, start) + region + source.slice(end);
|
|
}
|
|
|
|
if (require.main === module) {
|
|
const targets = [
|
|
["ssh2/lib/protocol/keyParser.js", patchKeyParserSource],
|
|
["ssh2/lib/agent.js", patchAgentSource],
|
|
["ssh2/lib/protocol/Protocol.js", patchProtocolSource],
|
|
];
|
|
for (const [id, patch] of targets) {
|
|
const target = require.resolve(id);
|
|
const source = fs.readFileSync(target, "utf8");
|
|
const result = patch(source);
|
|
if (result !== source) fs.writeFileSync(target, result);
|
|
}
|
|
console.log(
|
|
"[patch-ssh2-sk] Security key public keys and signatures enabled",
|
|
);
|
|
}
|
|
|
|
module.exports = {
|
|
patchKeyParserSource,
|
|
patchAgentSource,
|
|
patchProtocolSource,
|
|
};
|