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.
235 lines • 9.8 kB
JavaScript
;
/**
* Pure JavaScript RSA-SHA2 Signature Wrapper 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.wrapRSAKeyWithSHA2 = wrapRSAKeyWithSHA2;
exports.createRSASHA2SignatureCallback = createRSASHA2SignatureCallback;
exports.shouldUseRSASHA2Wrapper = shouldUseRSASHA2Wrapper;
exports.getRecommendedRSASHA2Algorithm = getRecommendedRSASHA2Algorithm;
// @ts-ignore
const sshpk = __importStar(require("sshpk"));
/**
* Wraps an ssh2-streams RSA key to use RSA-SHA2 signatures via sshpk
* @param originalKey - The original ssh2-streams key object
* @param privateKeyPEM - The raw private key in PEM format
* @param algorithm - RSA signature algorithm ('sha256' or 'sha512')
* @returns Wrapped key object with RSA-SHA2 signatures
*/
function wrapRSAKeyWithSHA2(originalKey, privateKeyPEM, algorithm = 'sha256') {
// Validate that this is an RSA key
if (!originalKey || originalKey.type !== 'ssh-rsa') {
throw new Error('RSA-SHA2 wrapper can only be applied to RSA keys');
}
// Parse key with sshpk
let sshpkKey;
try {
sshpkKey = sshpk.parsePrivateKey(privateKeyPEM, 'auto');
}
catch (error) {
throw new Error(`Failed to parse private key with sshpk: ${error instanceof Error ? error.message : String(error)}`);
}
const wrappedKey = {
type: originalKey.type,
getPublicSSH() {
// Use original key's public SSH format
return originalKey.getPublicSSH();
},
sign(data) {
try {
// Use sshpk for RSA-SHA2 signature
const signer = sshpkKey.createSign(algorithm);
signer.update(data);
const signature = signer.sign();
// Extract raw RSA signature bytes for ssh2-streams compatibility
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');
}
}
catch (error) {
// Fallback to original ssh2-streams signing if sshpk fails
console.warn(`sshpk RSA-SHA2 signing failed, falling back to original: ${error instanceof Error ? error.message : String(error)}`);
return originalKey.sign(data);
}
},
verify(data, signature) {
try {
// Use sshpk for verification
const verifier = sshpkKey.createVerify(algorithm);
verifier.update(data);
// Try to parse signature
let sshpkSig;
try {
sshpkSig = sshpk.parseSignature(signature, 'ssh-rsa', 'ssh');
}
catch (parseError) {
sshpkSig = signature;
}
return verifier.verify(sshpkSig);
}
catch (error) {
// Fallback to original verification
console.warn(`sshpk RSA-SHA2 verification failed, falling back to original: ${error instanceof Error ? error.message : String(error)}`);
return originalKey.verify(data, signature);
}
}
};
return wrappedKey;
}
/**
* Creates an RSA-SHA2 signature callback for ssh2-streams authentication using sshpk only
* @param originalKey - The original ssh2-streams key object
* @param privateKeyPEM - The raw private key in PEM format
* @param passphrase - Optional passphrase for encrypted keys
* @param algorithm - RSA signature algorithm ('sha256' or 'sha512')
* @returns Signature callback function for ssh2-streams authPK
*/
function createRSASHA2SignatureCallback(originalKey, privateKeyPEM, passphrase, algorithm = 'sha256') {
// Validate that this is an RSA key
if (!originalKey || originalKey.type !== 'ssh-rsa') {
// Return original callback for non-RSA keys
return (buf, cb) => {
try {
const signature = originalKey.sign(buf);
cb(signature);
}
catch (error) {
throw new Error(`Original key signing failed: ${error instanceof Error ? error.message : String(error)}`);
}
};
}
// Try to parse key with sshpk
let sshpkKey = null;
try {
const sshpkOptions = {};
if (passphrase) {
sshpkOptions.passphrase = passphrase;
}
sshpkKey = sshpk.parsePrivateKey(privateKeyPEM, 'auto', sshpkOptions);
}
catch (sshpkError) {
console.warn(`Failed to parse key with sshpk for RSA-SHA2, using original signing: ${sshpkError instanceof Error ? sshpkError.message : String(sshpkError)}`);
}
// If we couldn't parse with sshpk, fall back to original signing
if (!sshpkKey) {
return (buf, cb) => {
try {
const signature = originalKey.sign(buf);
cb(signature);
}
catch (signError) {
throw new Error(`Fallback signing failed: ${signError instanceof Error ? signError.message : String(signError)}`);
}
};
}
// Return sshpk RSA-SHA2 signature callback
return (buf, cb) => {
try {
// Generate RSA-SHA2 signature using sshpk
const signer = sshpkKey.createSign(algorithm);
signer.update(buf);
const signature = signer.sign();
// Convert to buffer format - extract raw RSA signature bytes
let signatureBuffer;
if (signature && typeof signature.toBuffer === 'function') {
// Try to get raw signature (not SSH wire format)
try {
signatureBuffer = signature.toBuffer('asn1');
}
catch (e) {
// Fallback to raw buffer
signatureBuffer = signature.toBuffer();
}
}
else if (signature && signature.signature && Buffer.isBuffer(signature.signature)) {
signatureBuffer = signature.signature;
}
else if (Buffer.isBuffer(signature)) {
signatureBuffer = signature;
}
else {
throw new Error('Unable to extract raw signature bytes from sshpk signature object');
}
cb(signatureBuffer);
}
catch (error) {
console.warn(`sshpk RSA-SHA2 signing failed, falling back to original: ${error instanceof Error ? error.message : String(error)}`);
try {
// Fallback to original ssh2-streams signing
const fallbackSignature = originalKey.sign(buf);
cb(fallbackSignature);
}
catch (fallbackError) {
throw new Error(`Both sshpk RSA-SHA2 and fallback signing failed: ${fallbackError instanceof Error ? fallbackError.message : String(fallbackError)}`);
}
}
};
}
/**
* Determines if a key should use the RSA-SHA2 wrapper
* @param keyType - The SSH key type (e.g., 'ssh-rsa', 'ssh-ed25519')
* @returns True if the key should be wrapped for RSA-SHA2
*/
function shouldUseRSASHA2Wrapper(keyType) {
return keyType === 'ssh-rsa';
}
/**
* Gets the appropriate RSA-SHA2 algorithm based on key size
* @param keySize - RSA key size in bits
* @returns Recommended algorithm ('sha256' or 'sha512')
*/
function getRecommendedRSASHA2Algorithm(keySize) {
// Use SHA-512 for larger keys (4096+), SHA-256 for smaller keys
return keySize >= 4096 ? 'sha512' : 'sha256';
}
//# sourceMappingURL=rsa-sha2-wrapper.js.map