first commit
This commit is contained in:
817
build/node_modules/ssh2-streams/lib/utils.js
generated
vendored
Normal file
817
build/node_modules/ssh2-streams/lib/utils.js
generated
vendored
Normal file
@@ -0,0 +1,817 @@
|
||||
var crypto = require('crypto');
|
||||
|
||||
var Ber = require('asn1').Ber;
|
||||
var BigInteger = require('./jsbn'); // only for converting PPK -> OpenSSL format
|
||||
|
||||
var SSH_TO_OPENSSL = require('./constants').SSH_TO_OPENSSL;
|
||||
|
||||
var RE_STREAM = /^arcfour/i;
|
||||
var RE_KEY_LEN = /(.{64})/g;
|
||||
// XXX the value of 2400 from dropbear is only for certain strings, not all
|
||||
// strings. for example the list strings used during handshakes
|
||||
var MAX_STRING_LEN = Infinity;//2400; // taken from dropbear
|
||||
var PPK_IV = new Buffer([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
|
||||
|
||||
module.exports = {
|
||||
iv_inc: iv_inc,
|
||||
isStreamCipher: isStreamCipher,
|
||||
readInt: readInt,
|
||||
readString: readString,
|
||||
parseKey: require('./keyParser'),
|
||||
genPublicKey: genPublicKey,
|
||||
convertPPKPrivate: convertPPKPrivate,
|
||||
verifyPPKMAC: verifyPPKMAC,
|
||||
decryptKey: decryptKey,
|
||||
DSASigBERToBare: DSASigBERToBare,
|
||||
DSASigBareToBER: DSASigBareToBER,
|
||||
ECDSASigASN1ToSSH: ECDSASigASN1ToSSH,
|
||||
ECDSASigSSHToASN1: ECDSASigSSHToASN1,
|
||||
RSAKeySSHToASN1: RSAKeySSHToASN1,
|
||||
DSAKeySSHToASN1: DSAKeySSHToASN1,
|
||||
ECDSAKeySSHToASN1: ECDSAKeySSHToASN1
|
||||
};
|
||||
|
||||
function iv_inc(iv) {
|
||||
var n = 12;
|
||||
var c = 0;
|
||||
do {
|
||||
--n;
|
||||
c = iv[n];
|
||||
if (c === 255)
|
||||
iv[n] = 0;
|
||||
else {
|
||||
iv[n] = ++c;
|
||||
return;
|
||||
}
|
||||
} while (n > 4);
|
||||
}
|
||||
|
||||
function isStreamCipher(name) {
|
||||
return RE_STREAM.test(name);
|
||||
}
|
||||
|
||||
function readInt(buffer, start, stream, cb) {
|
||||
var bufferLen = buffer.length;
|
||||
if (start < 0 || start >= bufferLen || (bufferLen - start) < 4) {
|
||||
stream && stream._cleanup(cb);
|
||||
return false;
|
||||
}
|
||||
|
||||
return buffer.readUInt32BE(start, true);
|
||||
}
|
||||
|
||||
function DSASigBERToBare(signature) {
|
||||
if (signature.length <= 40)
|
||||
return signature;
|
||||
// This is a quick and dirty way to get from BER encoded r and s that
|
||||
// OpenSSL gives us, to just the bare values back to back (40 bytes
|
||||
// total) like OpenSSH (and possibly others) are expecting
|
||||
var asnReader = new Ber.Reader(signature);
|
||||
asnReader.readSequence();
|
||||
var r = asnReader.readString(Ber.Integer, true);
|
||||
var s = asnReader.readString(Ber.Integer, true);
|
||||
var rOffset = 0;
|
||||
var sOffset = 0;
|
||||
if (r.length < 20) {
|
||||
var rNew = new Buffer(20);
|
||||
r.copy(rNew, 1);
|
||||
r = rNew;
|
||||
r[0] = 0;
|
||||
}
|
||||
if (s.length < 20) {
|
||||
var sNew = new Buffer(20);
|
||||
s.copy(sNew, 1);
|
||||
s = sNew;
|
||||
s[0] = 0;
|
||||
}
|
||||
if (r.length > 20 && r[0] === 0x00)
|
||||
rOffset = 1;
|
||||
if (s.length > 20 && s[0] === 0x00)
|
||||
sOffset = 1;
|
||||
var newSig = new Buffer((r.length - rOffset) + (s.length - sOffset));
|
||||
r.copy(newSig, 0, rOffset);
|
||||
s.copy(newSig, r.length - rOffset, sOffset);
|
||||
return newSig;
|
||||
}
|
||||
|
||||
function DSASigBareToBER(signature) {
|
||||
if (signature.length > 40)
|
||||
return signature;
|
||||
// Change bare signature r and s values to ASN.1 BER values for OpenSSL
|
||||
var asnWriter = new Ber.Writer();
|
||||
asnWriter.startSequence();
|
||||
var r = signature.slice(0, 20);
|
||||
var s = signature.slice(20);
|
||||
if (r[0] & 0x80) {
|
||||
var rNew = new Buffer(21);
|
||||
rNew[0] = 0x00;
|
||||
r.copy(rNew, 1);
|
||||
r = rNew;
|
||||
} else if (r[0] === 0x00 && !(r[1] & 0x80)) {
|
||||
r = r.slice(1);
|
||||
}
|
||||
if (s[0] & 0x80) {
|
||||
var sNew = new Buffer(21);
|
||||
sNew[0] = 0x00;
|
||||
s.copy(sNew, 1);
|
||||
s = sNew;
|
||||
} else if (s[0] === 0x00 && !(s[1] & 0x80)) {
|
||||
s = s.slice(1);
|
||||
}
|
||||
asnWriter.writeBuffer(r, Ber.Integer);
|
||||
asnWriter.writeBuffer(s, Ber.Integer);
|
||||
asnWriter.endSequence();
|
||||
return asnWriter.buffer;
|
||||
}
|
||||
|
||||
function ECDSASigASN1ToSSH(signature) {
|
||||
if (signature[0] === 0x00)
|
||||
return signature;
|
||||
// Convert SSH signature parameters to ASN.1 BER values for OpenSSL
|
||||
var asnReader = new Ber.Reader(signature);
|
||||
asnReader.readSequence();
|
||||
var r = asnReader.readString(Ber.Integer, true);
|
||||
var s = asnReader.readString(Ber.Integer, true);
|
||||
if (r === null || s === null)
|
||||
throw new Error('Invalid signature');
|
||||
var newSig = new Buffer(4 + r.length + 4 + s.length);
|
||||
newSig.writeUInt32BE(r.length, 0, true);
|
||||
r.copy(newSig, 4);
|
||||
newSig.writeUInt32BE(s.length, 4 + r.length, true);
|
||||
s.copy(newSig, 4 + 4 + r.length);
|
||||
return newSig;
|
||||
}
|
||||
|
||||
function ECDSASigSSHToASN1(signature, self, callback) {
|
||||
// Convert SSH signature parameters to ASN.1 BER values for OpenSSL
|
||||
var r = readString(signature, 0, self, callback);
|
||||
if (r === false)
|
||||
return false;
|
||||
var s = readString(signature, signature._pos, self, callback);
|
||||
if (s === false)
|
||||
return false;
|
||||
|
||||
var asnWriter = new Ber.Writer();
|
||||
asnWriter.startSequence();
|
||||
asnWriter.writeBuffer(r, Ber.Integer);
|
||||
asnWriter.writeBuffer(s, Ber.Integer);
|
||||
asnWriter.endSequence();
|
||||
return asnWriter.buffer;
|
||||
}
|
||||
|
||||
function RSAKeySSHToASN1(key, self, callback) {
|
||||
// Convert SSH key parameters to ASN.1 BER values for OpenSSL
|
||||
var e = readString(key, key._pos, self, callback);
|
||||
if (e === false)
|
||||
return false;
|
||||
var n = readString(key, key._pos, self, callback);
|
||||
if (n === false)
|
||||
return false;
|
||||
|
||||
var asnWriter = new Ber.Writer();
|
||||
asnWriter.startSequence();
|
||||
// algorithm
|
||||
asnWriter.startSequence();
|
||||
asnWriter.writeOID('1.2.840.113549.1.1.1'); // rsaEncryption
|
||||
// algorithm parameters (RSA has none)
|
||||
asnWriter.writeNull();
|
||||
asnWriter.endSequence();
|
||||
|
||||
// subjectPublicKey
|
||||
asnWriter.startSequence(Ber.BitString);
|
||||
asnWriter.writeByte(0x00);
|
||||
asnWriter.startSequence();
|
||||
asnWriter.writeBuffer(n, Ber.Integer);
|
||||
asnWriter.writeBuffer(e, Ber.Integer);
|
||||
asnWriter.endSequence();
|
||||
asnWriter.endSequence();
|
||||
asnWriter.endSequence();
|
||||
return asnWriter.buffer;
|
||||
}
|
||||
|
||||
function DSAKeySSHToASN1(key, self, callback) {
|
||||
// Convert SSH key parameters to ASN.1 BER values for OpenSSL
|
||||
var p = readString(key, key._pos, self, callback);
|
||||
if (p === false)
|
||||
return false;
|
||||
var q = readString(key, key._pos, self, callback);
|
||||
if (q === false)
|
||||
return false;
|
||||
var g = readString(key, key._pos, self, callback);
|
||||
if (g === false)
|
||||
return false;
|
||||
var y = readString(key, key._pos, self, callback);
|
||||
if (y === false)
|
||||
return false;
|
||||
|
||||
var asnWriter = new Ber.Writer();
|
||||
asnWriter.startSequence();
|
||||
// algorithm
|
||||
asnWriter.startSequence();
|
||||
asnWriter.writeOID('1.2.840.10040.4.1'); // id-dsa
|
||||
// algorithm parameters
|
||||
asnWriter.startSequence();
|
||||
asnWriter.writeBuffer(p, Ber.Integer);
|
||||
asnWriter.writeBuffer(q, Ber.Integer);
|
||||
asnWriter.writeBuffer(g, Ber.Integer);
|
||||
asnWriter.endSequence();
|
||||
asnWriter.endSequence();
|
||||
|
||||
// subjectPublicKey
|
||||
asnWriter.startSequence(Ber.BitString);
|
||||
asnWriter.writeByte(0x00);
|
||||
asnWriter.writeBuffer(y, Ber.Integer);
|
||||
asnWriter.endSequence();
|
||||
asnWriter.endSequence();
|
||||
return asnWriter.buffer;
|
||||
}
|
||||
|
||||
function ECDSAKeySSHToASN1(key, self, callback) {
|
||||
// Convert SSH key parameters to ASN.1 BER values for OpenSSL
|
||||
var curve = readString(key, key._pos, self, callback);
|
||||
if (curve === false)
|
||||
return false;
|
||||
var Q = readString(key, key._pos, self, callback);
|
||||
if (Q === false)
|
||||
return false;
|
||||
|
||||
var ecCurveOID;
|
||||
switch (curve.toString('ascii')) {
|
||||
case 'nistp256':
|
||||
// prime256v1/secp256r1
|
||||
ecCurveOID = '1.2.840.10045.3.1.7';
|
||||
break;
|
||||
case 'nistp384':
|
||||
// secp384r1
|
||||
ecCurveOID = '1.3.132.0.34';
|
||||
break;
|
||||
case 'nistp521':
|
||||
// secp521r1
|
||||
ecCurveOID = '1.3.132.0.35';
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
var asnWriter = new Ber.Writer();
|
||||
asnWriter.startSequence();
|
||||
// algorithm
|
||||
asnWriter.startSequence();
|
||||
asnWriter.writeOID('1.2.840.10045.2.1'); // id-ecPublicKey
|
||||
// algorithm parameters (namedCurve)
|
||||
asnWriter.writeOID(ecCurveOID);
|
||||
asnWriter.endSequence();
|
||||
|
||||
// subjectPublicKey
|
||||
asnWriter.startSequence(Ber.BitString);
|
||||
asnWriter.writeByte(0x00);
|
||||
// XXX: hack to write a raw buffer without a tag -- yuck
|
||||
asnWriter._ensure(Q.length);
|
||||
Q.copy(asnWriter._buf, asnWriter._offset, 0, Q.length);
|
||||
asnWriter._offset += Q.length;
|
||||
// end hack
|
||||
asnWriter.endSequence();
|
||||
asnWriter.endSequence();
|
||||
return asnWriter.buffer;
|
||||
}
|
||||
|
||||
function decryptKey(keyInfo, passphrase) {
|
||||
if (keyInfo._decrypted || !keyInfo.encryption)
|
||||
return;
|
||||
|
||||
var keylen = 0;
|
||||
var key;
|
||||
var iv;
|
||||
var dc;
|
||||
|
||||
keyInfo.encryption = (SSH_TO_OPENSSL[keyInfo.encryption]
|
||||
|| keyInfo.encryption);
|
||||
switch (keyInfo.encryption) {
|
||||
case 'aes-256-cbc':
|
||||
case 'aes-256-ctr':
|
||||
keylen = 32;
|
||||
break;
|
||||
case 'des-ede3-cbc':
|
||||
case 'des-ede3':
|
||||
case 'aes-192-cbc':
|
||||
case 'aes-192-ctr':
|
||||
keylen = 24;
|
||||
break;
|
||||
case 'aes-128-cbc':
|
||||
case 'aes-128-ctr':
|
||||
case 'cast-cbc':
|
||||
case 'bf-cbc':
|
||||
keylen = 16;
|
||||
break;
|
||||
default:
|
||||
throw new Error('Unsupported cipher for encrypted key: '
|
||||
+ keyInfo.encryption);
|
||||
}
|
||||
|
||||
if (keyInfo.ppk) {
|
||||
iv = PPK_IV;
|
||||
|
||||
key = Buffer.concat([
|
||||
crypto.createHash('sha1')
|
||||
.update('\x00\x00\x00\x00' + passphrase, 'utf8')
|
||||
.digest(),
|
||||
crypto.createHash('sha1')
|
||||
.update('\x00\x00\x00\x01' + passphrase, 'utf8')
|
||||
.digest()
|
||||
]);
|
||||
key = key.slice(0, keylen);
|
||||
} else {
|
||||
iv = new Buffer(keyInfo.extra[0], 'hex');
|
||||
|
||||
key = crypto.createHash('md5')
|
||||
.update(passphrase, 'utf8')
|
||||
.update(iv.slice(0, 8))
|
||||
.digest();
|
||||
|
||||
while (keylen > key.length) {
|
||||
key = Buffer.concat([
|
||||
key,
|
||||
(crypto.createHash('md5')
|
||||
.update(key)
|
||||
.update(passphrase, 'utf8')
|
||||
.update(iv)
|
||||
.digest()).slice(0, 8)
|
||||
]);
|
||||
}
|
||||
if (key.length > keylen)
|
||||
key = key.slice(0, keylen);
|
||||
}
|
||||
|
||||
dc = crypto.createDecipheriv(keyInfo.encryption, key, iv);
|
||||
dc.setAutoPadding(false);
|
||||
keyInfo.private = Buffer.concat([ dc.update(keyInfo.private), dc.final() ]);
|
||||
|
||||
keyInfo._decrypted = true;
|
||||
|
||||
if (keyInfo.privateOrig) {
|
||||
// Update our original base64-encoded version of the private key
|
||||
var orig = keyInfo.privateOrig.toString('utf8');
|
||||
var newOrig = /^(.+(?:\r\n|\n))/.exec(orig)[1];
|
||||
var b64key = keyInfo.private.toString('base64');
|
||||
|
||||
newOrig += b64key.match(/.{1,70}/g).join('\n');
|
||||
newOrig += /((?:\r\n|\n).+)$/.exec(orig)[1];
|
||||
|
||||
keyInfo.privateOrig = newOrig;
|
||||
} else if (keyInfo.ppk) {
|
||||
var valid = verifyPPKMAC(keyInfo, passphrase, keyInfo.private);
|
||||
if (!valid)
|
||||
throw new Error('PPK MAC mismatch');
|
||||
// Automatically convert private key data to OpenSSL format
|
||||
// (including PEM)
|
||||
convertPPKPrivate(keyInfo);
|
||||
}
|
||||
|
||||
// Fill in full key type
|
||||
// TODO: make DRY, we do this also in keyParser
|
||||
if (keyInfo.type !== 'ec') {
|
||||
keyInfo.fulltype = 'ssh-' + keyInfo.type;
|
||||
} else {
|
||||
// ECDSA
|
||||
var asnReader = new Ber.Reader(keyInfo.private);
|
||||
asnReader.readSequence();
|
||||
asnReader.readInt();
|
||||
asnReader.readString(Ber.OctetString, true);
|
||||
asnReader.readByte(); // Skip "complex" context type byte
|
||||
var offset = asnReader.readLength(); // Skip context length
|
||||
if (offset !== null) {
|
||||
asnReader._offset = offset;
|
||||
switch (asnReader.readOID()) {
|
||||
case '1.2.840.10045.3.1.7':
|
||||
// prime256v1/secp256r1
|
||||
keyInfo.fulltype = 'ecdsa-sha2-nistp256';
|
||||
break;
|
||||
case '1.3.132.0.34':
|
||||
// secp384r1
|
||||
keyInfo.fulltype = 'ecdsa-sha2-nistp384';
|
||||
break;
|
||||
case '1.3.132.0.35':
|
||||
// secp521r1
|
||||
keyInfo.fulltype = 'ecdsa-sha2-nistp521';
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (keyInfo.fulltype === undefined)
|
||||
return new Error('Unsupported EC private key type');
|
||||
}
|
||||
}
|
||||
|
||||
function genPublicKey(keyInfo) {
|
||||
var publicKey;
|
||||
var i;
|
||||
|
||||
// RSA
|
||||
var n;
|
||||
var e;
|
||||
|
||||
// DSA
|
||||
var p;
|
||||
var q;
|
||||
var g;
|
||||
var y;
|
||||
|
||||
// ECDSA
|
||||
var d;
|
||||
var Q;
|
||||
var ecCurveOID;
|
||||
var ecCurveName;
|
||||
|
||||
if (keyInfo.private) {
|
||||
// parsing private key in ASN.1 format in order to generate a public key
|
||||
var privKey = keyInfo.private;
|
||||
var asnReader = new Ber.Reader(privKey);
|
||||
var errMsg;
|
||||
|
||||
if (asnReader.readSequence() === null) {
|
||||
errMsg = 'Malformed private key (expected sequence)';
|
||||
if (keyInfo._decrypted)
|
||||
errMsg += '. Bad passphrase?';
|
||||
throw new Error(errMsg);
|
||||
}
|
||||
|
||||
// version (ignored)
|
||||
if (asnReader.readInt() === null) {
|
||||
errMsg = 'Malformed private key (expected version)';
|
||||
if (keyInfo._decrypted)
|
||||
errMsg += '. Bad passphrase?';
|
||||
throw new Error(errMsg);
|
||||
}
|
||||
|
||||
if (keyInfo.type === 'rsa') {
|
||||
// modulus (n) -- integer
|
||||
n = asnReader.readString(Ber.Integer, true);
|
||||
if (n === null) {
|
||||
errMsg = 'Malformed private key (expected RSA n value)';
|
||||
if (keyInfo._decrypted)
|
||||
errMsg += '. Bad passphrase?';
|
||||
throw new Error(errMsg);
|
||||
}
|
||||
|
||||
// public exponent (e) -- integer
|
||||
e = asnReader.readString(Ber.Integer, true);
|
||||
if (e === null) {
|
||||
errMsg = 'Malformed private key (expected RSA e value)';
|
||||
if (keyInfo._decrypted)
|
||||
errMsg += '. Bad passphrase?';
|
||||
throw new Error(errMsg);
|
||||
}
|
||||
|
||||
publicKey = new Buffer(4 + 7 // ssh-rsa
|
||||
+ 4 + n.length
|
||||
+ 4 + e.length);
|
||||
|
||||
publicKey.writeUInt32BE(7, 0, true);
|
||||
publicKey.write('ssh-rsa', 4, 7, 'ascii');
|
||||
|
||||
i = 4 + 7;
|
||||
publicKey.writeUInt32BE(e.length, i, true);
|
||||
e.copy(publicKey, i += 4);
|
||||
|
||||
publicKey.writeUInt32BE(n.length, i += e.length, true);
|
||||
n.copy(publicKey, i += 4);
|
||||
} else if (keyInfo.type === 'dss') { // DSA
|
||||
// prime (p) -- integer
|
||||
p = asnReader.readString(Ber.Integer, true);
|
||||
if (p === null) {
|
||||
errMsg = 'Malformed private key (expected DSA p value)';
|
||||
if (keyInfo._decrypted)
|
||||
errMsg += '. Bad passphrase?';
|
||||
throw new Error(errMsg);
|
||||
}
|
||||
|
||||
// group order (q) -- integer
|
||||
q = asnReader.readString(Ber.Integer, true);
|
||||
if (q === null) {
|
||||
errMsg = 'Malformed private key (expected DSA q value)';
|
||||
if (keyInfo._decrypted)
|
||||
errMsg += '. Bad passphrase?';
|
||||
throw new Error(errMsg);
|
||||
}
|
||||
|
||||
// group generator (g) -- integer
|
||||
g = asnReader.readString(Ber.Integer, true);
|
||||
if (g === null) {
|
||||
errMsg = 'Malformed private key (expected DSA g value)';
|
||||
if (keyInfo._decrypted)
|
||||
errMsg += '. Bad passphrase?';
|
||||
throw new Error(errMsg);
|
||||
}
|
||||
|
||||
// public key value (y) -- integer
|
||||
y = asnReader.readString(Ber.Integer, true);
|
||||
if (y === null) {
|
||||
errMsg = 'Malformed private key (expected DSA y value)';
|
||||
if (keyInfo._decrypted)
|
||||
errMsg += '. Bad passphrase?';
|
||||
throw new Error(errMsg);
|
||||
}
|
||||
|
||||
publicKey = new Buffer(4 + 7 // ssh-dss
|
||||
+ 4 + p.length
|
||||
+ 4 + q.length
|
||||
+ 4 + g.length
|
||||
+ 4 + y.length);
|
||||
|
||||
publicKey.writeUInt32BE(7, 0, true);
|
||||
publicKey.write('ssh-dss', 4, 7, 'ascii');
|
||||
|
||||
i = 4 + 7;
|
||||
publicKey.writeUInt32BE(p.length, i, true);
|
||||
p.copy(publicKey, i += 4);
|
||||
|
||||
publicKey.writeUInt32BE(q.length, i += p.length, true);
|
||||
q.copy(publicKey, i += 4);
|
||||
|
||||
publicKey.writeUInt32BE(g.length, i += q.length, true);
|
||||
g.copy(publicKey, i += 4);
|
||||
|
||||
publicKey.writeUInt32BE(y.length, i += g.length, true);
|
||||
y.copy(publicKey, i += 4);
|
||||
} else { // ECDSA
|
||||
d = asnReader.readString(Ber.OctetString, true);
|
||||
if (d === null)
|
||||
throw new Error('Malformed private key (expected ECDSA private key)');
|
||||
asnReader.readByte(); // Skip "complex" context type byte
|
||||
var offset = asnReader.readLength(); // Skip context length
|
||||
if (offset === null)
|
||||
throw new Error('Malformed private key (expected ECDSA context value)');
|
||||
asnReader._offset = offset;
|
||||
ecCurveOID = asnReader.readOID();
|
||||
if (ecCurveOID === null)
|
||||
throw new Error('Malformed private key (expected ECDSA curve)');
|
||||
var tempECDH;
|
||||
switch (ecCurveOID) {
|
||||
case '1.2.840.10045.3.1.7':
|
||||
// prime256v1/secp256r1
|
||||
keyInfo.curve = ecCurveName = 'nistp256';
|
||||
tempECDH = crypto.createECDH('prime256v1');
|
||||
break;
|
||||
case '1.3.132.0.34':
|
||||
// secp384r1
|
||||
keyInfo.curve = ecCurveName = 'nistp384';
|
||||
tempECDH = crypto.createECDH('secp384r1');
|
||||
break;
|
||||
case '1.3.132.0.35':
|
||||
// secp521r1
|
||||
keyInfo.curve = ecCurveName = 'nistp521';
|
||||
tempECDH = crypto.createECDH('secp521r1');
|
||||
break;
|
||||
default:
|
||||
throw new Error('Malformed private key (unsupported EC curve)');
|
||||
}
|
||||
tempECDH.setPrivateKey(d);
|
||||
Q = tempECDH.getPublicKey();
|
||||
|
||||
publicKey = new Buffer(4 + 19 // ecdsa-sha2-<curve name>
|
||||
+ 4 + 8 // <curve name>
|
||||
+ 4 + Q.length);
|
||||
|
||||
publicKey.writeUInt32BE(19, 0, true);
|
||||
publicKey.write('ecdsa-sha2-' + ecCurveName, 4, 19, 'ascii');
|
||||
|
||||
publicKey.writeUInt32BE(8, 23, true);
|
||||
publicKey.write(ecCurveName, 27, 8, 'ascii');
|
||||
|
||||
publicKey.writeUInt32BE(Q.length, 35, true);
|
||||
Q.copy(publicKey, 39);
|
||||
}
|
||||
} else if (keyInfo.public) {
|
||||
publicKey = keyInfo.public;
|
||||
if (keyInfo.type === 'ec') {
|
||||
// TODO: support adding ecdsa-* prefix
|
||||
ecCurveName = keyInfo.curve;
|
||||
} else if (publicKey[0] !== 0
|
||||
// check for missing ssh-{dsa,rsa} prefix
|
||||
|| publicKey[1] !== 0
|
||||
|| publicKey[2] !== 0
|
||||
|| publicKey[3] !== 7
|
||||
|| publicKey[4] !== 115
|
||||
|| publicKey[5] !== 115
|
||||
|| publicKey[6] !== 104
|
||||
|| publicKey[7] !== 45
|
||||
|| ((publicKey[8] !== 114
|
||||
|| publicKey[9] !== 115
|
||||
|| publicKey[10] !== 97)
|
||||
&&
|
||||
((publicKey[8] !== 100
|
||||
|| publicKey[9] !== 115
|
||||
|| publicKey[10] !== 115)))) {
|
||||
var newPK = new Buffer(4 + 7 + publicKey.length);
|
||||
publicKey.copy(newPK, 11);
|
||||
newPK.writeUInt32BE(7, 0, true);
|
||||
if (keyInfo.type === 'rsa')
|
||||
newPK.write('ssh-rsa', 4, 7, 'ascii');
|
||||
else
|
||||
newPK.write('ssh-dss', 4, 7, 'ascii');
|
||||
publicKey = newPK;
|
||||
}
|
||||
} else
|
||||
throw new Error('Missing data generated by parseKey()');
|
||||
|
||||
// generate a public key format for use with OpenSSL
|
||||
|
||||
i = 4 + 7;
|
||||
|
||||
var fulltype;
|
||||
var asn1KeyBuf;
|
||||
if (keyInfo.type === 'rsa') {
|
||||
fulltype = 'ssh-rsa';
|
||||
asn1KeyBuf = RSAKeySSHToASN1(publicKey.slice(4 + 7));
|
||||
} else if (keyInfo.type === 'dss') {
|
||||
fulltype = 'ssh-dss';
|
||||
asn1KeyBuf = DSAKeySSHToASN1(publicKey.slice(4 + 7));
|
||||
} else { // ECDSA
|
||||
fulltype = 'ecdsa-sha2-' + ecCurveName;
|
||||
asn1KeyBuf = ECDSAKeySSHToASN1(publicKey.slice(4 + 19));
|
||||
}
|
||||
|
||||
if (!asn1KeyBuf)
|
||||
throw new Error('Invalid SSH-formatted public key');
|
||||
|
||||
var b64key = asn1KeyBuf.toString('base64').replace(RE_KEY_LEN, '$1\n');
|
||||
var fullkey = '-----BEGIN PUBLIC KEY-----\n'
|
||||
+ b64key
|
||||
+ (b64key[b64key.length - 1] === '\n' ? '' : '\n')
|
||||
+ '-----END PUBLIC KEY-----';
|
||||
|
||||
return {
|
||||
type: keyInfo.type,
|
||||
fulltype: fulltype,
|
||||
curve: ecCurveName,
|
||||
public: publicKey,
|
||||
publicOrig: new Buffer(fullkey)
|
||||
};
|
||||
}
|
||||
|
||||
function verifyPPKMAC(keyInfo, passphrase, privateKey) {
|
||||
if (keyInfo._macresult !== undefined)
|
||||
return keyInfo._macresult;
|
||||
else if (!keyInfo.ppk)
|
||||
throw new Error("Key isn't a PPK");
|
||||
else if (!keyInfo.privateMAC)
|
||||
throw new Error('Missing MAC');
|
||||
else if (!privateKey)
|
||||
throw new Error('Missing raw private key data');
|
||||
else if (keyInfo.encryption && typeof passphrase !== 'string')
|
||||
throw new Error('Missing passphrase for encrypted PPK');
|
||||
else if (keyInfo.encryption && !keyInfo._decrypted)
|
||||
throw new Error('PPK must be decrypted before verifying MAC');
|
||||
|
||||
var mac = keyInfo.privateMAC;
|
||||
var typelen = keyInfo.fulltype.length;
|
||||
// encryption algorithm is converted at this point for use with OpenSSL,
|
||||
// so we need to use the original value so that the MAC is calculated
|
||||
// correctly
|
||||
var enc = (keyInfo.encryption ? 'aes256-cbc' : 'none');
|
||||
var enclen = enc.length;
|
||||
var commlen = Buffer.byteLength(keyInfo.comment);
|
||||
var pub = keyInfo.public;
|
||||
var publen = pub.length;
|
||||
var privlen = privateKey.length;
|
||||
var macdata = new Buffer(4 + typelen
|
||||
+ 4 + enclen
|
||||
+ 4 + commlen
|
||||
+ 4 + publen
|
||||
+ 4 + privlen);
|
||||
var p = 0;
|
||||
|
||||
macdata.writeUInt32BE(typelen, p, true);
|
||||
macdata.write(keyInfo.fulltype, p += 4, typelen, 'ascii');
|
||||
macdata.writeUInt32BE(enclen, p += typelen, true);
|
||||
macdata.write(enc, p += 4, enclen, 'ascii');
|
||||
macdata.writeUInt32BE(commlen, p += enclen, true);
|
||||
macdata.write(keyInfo.comment, p += 4, commlen, 'utf8');
|
||||
macdata.writeUInt32BE(publen, p += commlen, true);
|
||||
pub.copy(macdata, p += 4);
|
||||
macdata.writeUInt32BE(privlen, p += publen, true);
|
||||
privateKey.copy(macdata, p += 4);
|
||||
|
||||
if (typeof passphrase !== 'string')
|
||||
passphrase = '';
|
||||
|
||||
var mackey = crypto.createHash('sha1')
|
||||
.update('putty-private-key-file-mac-key', 'ascii')
|
||||
.update(passphrase, 'utf8')
|
||||
.digest();
|
||||
|
||||
var calcMAC = crypto.createHmac('sha1', mackey)
|
||||
.update(macdata)
|
||||
.digest('hex');
|
||||
|
||||
return (keyInfo._macresult = (calcMAC === mac));
|
||||
}
|
||||
|
||||
function convertPPKPrivate(keyInfo) {
|
||||
if (!keyInfo.ppk || !keyInfo.public || !keyInfo.private)
|
||||
throw new Error("Key isn't a PPK");
|
||||
else if (keyInfo._converted)
|
||||
return false;
|
||||
|
||||
var pub = keyInfo.public;
|
||||
var priv = keyInfo.private;
|
||||
var asnWriter = new Ber.Writer();
|
||||
var p;
|
||||
var q;
|
||||
|
||||
if (keyInfo.type === 'rsa') {
|
||||
var e = readString(pub, 4 + 7);
|
||||
var n = readString(pub, pub._pos);
|
||||
var d = readString(priv, 0);
|
||||
p = readString(priv, priv._pos);
|
||||
q = readString(priv, priv._pos);
|
||||
var iqmp = readString(priv, priv._pos);
|
||||
var p1 = new BigInteger(p, 256);
|
||||
var q1 = new BigInteger(q, 256);
|
||||
var dmp1 = new BigInteger(d, 256);
|
||||
var dmq1 = new BigInteger(d, 256);
|
||||
|
||||
dmp1 = new Buffer(dmp1.mod(p1.subtract(BigInteger.ONE)).toByteArray());
|
||||
dmq1 = new Buffer(dmq1.mod(q1.subtract(BigInteger.ONE)).toByteArray());
|
||||
|
||||
asnWriter.startSequence();
|
||||
asnWriter.writeInt(0x00, Ber.Integer);
|
||||
asnWriter.writeBuffer(n, Ber.Integer);
|
||||
asnWriter.writeBuffer(e, Ber.Integer);
|
||||
asnWriter.writeBuffer(d, Ber.Integer);
|
||||
asnWriter.writeBuffer(p, Ber.Integer);
|
||||
asnWriter.writeBuffer(q, Ber.Integer);
|
||||
asnWriter.writeBuffer(dmp1, Ber.Integer);
|
||||
asnWriter.writeBuffer(dmq1, Ber.Integer);
|
||||
asnWriter.writeBuffer(iqmp, Ber.Integer);
|
||||
asnWriter.endSequence();
|
||||
} else {
|
||||
p = readString(pub, 4 + 7);
|
||||
q = readString(pub, pub._pos);
|
||||
var g = readString(pub, pub._pos);
|
||||
var y = readString(pub, pub._pos);
|
||||
var x = readString(priv, 0);
|
||||
|
||||
asnWriter.startSequence();
|
||||
asnWriter.writeInt(0x00, Ber.Integer);
|
||||
asnWriter.writeBuffer(p, Ber.Integer);
|
||||
asnWriter.writeBuffer(q, Ber.Integer);
|
||||
asnWriter.writeBuffer(g, Ber.Integer);
|
||||
asnWriter.writeBuffer(y, Ber.Integer);
|
||||
asnWriter.writeBuffer(x, Ber.Integer);
|
||||
asnWriter.endSequence();
|
||||
}
|
||||
|
||||
var b64key = asnWriter.buffer.toString('base64').replace(RE_KEY_LEN, '$1\n');
|
||||
var fullkey = '-----BEGIN '
|
||||
+ (keyInfo.type === 'rsa' ? 'RSA' : 'DSA')
|
||||
+ ' PRIVATE KEY-----\n'
|
||||
+ b64key
|
||||
+ (b64key[b64key.length - 1] === '\n' ? '' : '\n')
|
||||
+ '-----END '
|
||||
+ (keyInfo.type === 'rsa' ? 'RSA' : 'DSA')
|
||||
+ ' PRIVATE KEY-----';
|
||||
|
||||
keyInfo.private = asnWriter.buffer;
|
||||
keyInfo.privateOrig = new Buffer(fullkey);
|
||||
keyInfo._converted = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
function readString(buffer, start, encoding, stream, cb, maxLen) {
|
||||
if (encoding && !Buffer.isBuffer(encoding) && typeof encoding !== 'string') {
|
||||
if (typeof cb === 'number')
|
||||
maxLen = cb;
|
||||
cb = stream;
|
||||
stream = encoding;
|
||||
encoding = undefined;
|
||||
}
|
||||
|
||||
start || (start = 0);
|
||||
var bufferLen = buffer.length;
|
||||
var left = (bufferLen - start);
|
||||
var len;
|
||||
var end;
|
||||
if (start < 0 || start >= bufferLen || left < 4) {
|
||||
stream && stream._cleanup(cb);
|
||||
return false;
|
||||
}
|
||||
|
||||
len = buffer.readUInt32BE(start, true);
|
||||
if (len > (maxLen || MAX_STRING_LEN) || left < (4 + len)) {
|
||||
stream && stream._cleanup(cb);
|
||||
return false;
|
||||
}
|
||||
|
||||
start += 4;
|
||||
end = start + len;
|
||||
buffer._pos = end;
|
||||
|
||||
if (encoding) {
|
||||
if (Buffer.isBuffer(encoding)) {
|
||||
buffer.copy(encoding, 0, start, end);
|
||||
return encoding;
|
||||
} else
|
||||
return buffer.toString(encoding, start, end);
|
||||
} else
|
||||
return buffer.slice(start, end);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user