pure-js-sftp
Version:
A pure JavaScript SFTP client with revolutionary RSA-SHA2 compatibility fixes. Zero native dependencies, built on ssh2-streams with 100% SSH key support.
243 lines • 9.46 kB
JavaScript
;
/**
* Pure JavaScript SSH Key Parser using sshpk only
* Eliminates all Node.js crypto dependencies for maximum VSCode/webpack compatibility
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseKey = parseKey;
// @ts-ignore
const sshpk = __importStar(require("sshpk"));
function parseKey(keyData, passphrase) {
let keyString;
if (Buffer.isBuffer(keyData)) {
keyString = keyData.toString('utf8');
}
else {
keyString = keyData;
}
// Normalize line endings and trim
keyString = keyString.replace(/\r\n/g, '\n').trim();
try {
// Use sshpk exclusively for all key parsing
const options = {};
if (passphrase !== undefined && passphrase !== null && passphrase !== '') {
options.passphrase = passphrase;
}
const sshpkKey = sshpk.parsePrivateKey(keyString, 'auto', options);
if (sshpkKey) {
return createKeyFromSSHPK(sshpkKey);
}
}
catch (sshpkError) {
// If sshpk fails, return null
console.warn(`Key parsing failed: ${sshpkError instanceof Error ? sshpkError.message : String(sshpkError)}`);
}
return null;
}
// Create our ParsedKey interface from sshpk key objects
function createKeyFromSSHPK(sshpkKey) {
// Map sshpk types to our SSH types
const typeMapping = {
'rsa': 'ssh-rsa',
'ed25519': 'ssh-ed25519',
'ecdsa': getECDSAType(sshpkKey)
};
const sshType = typeMapping[sshpkKey.type] || sshpkKey.type;
return {
type: sshType,
comment: sshpkKey.comment || '',
sign(data, algorithm) {
try {
// Determine signing algorithm
let signingAlgo;
if (sshType === 'ssh-rsa') {
if (algorithm === 'ssh-rsa') {
signingAlgo = 'sha1';
}
else if (algorithm === 'rsa-sha2-256') {
signingAlgo = 'sha256';
}
else if (algorithm === 'rsa-sha2-512') {
signingAlgo = 'sha512';
}
else {
// Default to sha256 for RSA-SHA2 compatibility
signingAlgo = 'sha256';
}
}
else {
signingAlgo = getHashAlgorithm(sshType, algorithm);
}
const signer = sshpkKey.createSign(signingAlgo);
signer.update(data);
const signature = signer.sign();
// Extract raw signature bytes properly from sshpk signature object
if (sshType === 'ssh-rsa') {
// For RSA keys, ssh2-streams expects raw signature bytes
if (signature && typeof signature.toBuffer === 'function') {
// Try to get raw signature (not SSH wire format)
try {
return signature.toBuffer('asn1');
}
catch (e) {
// Fallback to raw buffer
return signature.toBuffer();
}
}
else if (signature && signature.signature && Buffer.isBuffer(signature.signature)) {
return signature.signature;
}
else if (Buffer.isBuffer(signature)) {
return signature;
}
else {
throw new Error('Unable to extract raw signature bytes from sshpk signature object');
}
}
else {
// For Ed25519/ECDSA, ssh2-streams expects different format
if (signature && typeof signature.toBuffer === 'function') {
return signature.toBuffer('ssh');
}
else if (signature && signature.signature && Buffer.isBuffer(signature.signature)) {
return signature.signature;
}
else if (Buffer.isBuffer(signature)) {
return signature;
}
else {
throw new Error('Unable to extract signature bytes from sshpk signature object');
}
}
}
catch (error) {
throw new Error(`sshpk signing failed: ${error instanceof Error ? error.message : String(error)}`);
}
},
verify(data, signature, algorithm) {
try {
// Use sshpk's verification capability
const hashAlgo = getHashAlgorithm(sshType, algorithm);
const verifier = sshpkKey.createVerify(hashAlgo);
verifier.update(data);
// Try to parse the signature buffer as SSH format
let sshpkSig;
try {
sshpkSig = sshpk.parseSignature(signature, sshType, 'ssh');
}
catch (parseError) {
// If parsing fails, try with raw signature
sshpkSig = signature;
}
return verifier.verify(sshpkSig);
}
catch (error) {
return false;
}
},
isPrivateKey() {
return sshpkKey.isPrivate();
},
getPrivatePEM() {
try {
return sshpkKey.toBuffer('pem').toString();
}
catch (error) {
throw new Error(`sshpk PEM export failed: ${error instanceof Error ? error.message : String(error)}`);
}
},
getPublicPEM() {
try {
const publicKey = sshpkKey.toPublic();
return publicKey.toBuffer('pem').toString();
}
catch (error) {
throw new Error(`sshpk public PEM export failed: ${error instanceof Error ? error.message : String(error)}`);
}
},
getPublicSSH() {
try {
const publicKey = sshpkKey.toPublic();
return publicKey.toBuffer('ssh');
}
catch (error) {
throw new Error(`sshpk SSH export failed: ${error instanceof Error ? error.message : String(error)}`);
}
},
equals(other) {
try {
return this.type === other.type && this.getPublicSSH().equals(other.getPublicSSH());
}
catch (error) {
return false;
}
}
};
}
function getECDSAType(sshpkKey) {
if (sshpkKey.curve) {
switch (sshpkKey.curve) {
case 'nistp256': return 'ecdsa-sha2-nistp256';
case 'nistp384': return 'ecdsa-sha2-nistp384';
case 'nistp521': return 'ecdsa-sha2-nistp521';
default: return 'ecdsa-sha2-' + sshpkKey.curve;
}
}
return 'ecdsa-sha2-nistp256'; // default
}
function getHashAlgorithm(sshType, algorithm) {
if (algorithm) {
if (algorithm.includes('sha1') || algorithm === 'ssh-rsa') {
return 'sha1';
}
else if (algorithm.includes('sha256') || algorithm === 'rsa-sha2-256') {
return 'sha256';
}
else if (algorithm.includes('sha512') || algorithm === 'rsa-sha2-512') {
return 'sha512';
}
}
// Default hash algorithms based on key type
switch (sshType) {
case 'ssh-rsa': return 'sha256'; // Default to SHA256 for RSA (RSA-SHA2)
case 'ssh-ed25519': return 'sha512'; // Ed25519 uses SHA512
case 'ecdsa-sha2-nistp256': return 'sha256';
case 'ecdsa-sha2-nistp384': return 'sha384';
case 'ecdsa-sha2-nistp521': return 'sha512';
default: return 'sha256';
}
}
//# sourceMappingURL=enhanced-key-parser.js.map