hexo/node_modules/ssh2-streams/lib/keyParser.js

240 lines
8.2 KiB
JavaScript
Raw Normal View History

2023-10-03 11:14:36 +08:00
// TODO:
// * handle multi-line header values (OpenSSH)?
// * more thorough validation?
var utils;
var Ber = require('asn1').Ber;
var semver = require('semver');
var RE_PPK = /^PuTTY-User-Key-File-2: ssh-(rsa|dss)\r?\nEncryption: (aes256-cbc|none)\r?\nComment: ([^\r\n]*)\r?\nPublic-Lines: \d+\r?\n([\s\S]+?)\r?\nPrivate-Lines: \d+\r?\n([\s\S]+?)\r?\nPrivate-MAC: ([^\r\n]+)/;
var RE_HEADER_OPENSSH_PRIV = /^-----BEGIN (RSA|DSA|EC) PRIVATE KEY-----$/i;
var RE_FOOTER_OPENSSH_PRIV = /^-----END (?:RSA|DSA|EC) PRIVATE KEY-----$/i;
var RE_HEADER_OPENSSH_PUB = /^((?:(?:ssh-(rsa|dss))|ecdsa-sha2-nistp(256|384|521))(?:-cert-v0[01]@openssh.com)?) ([A-Z0-9a-z\/+=]+)(?:$|\s+([\S].*)?)$/i;
var RE_HEADER_RFC4716_PUB = /^---- BEGIN SSH2 PUBLIC KEY ----$/i;
var RE_FOOTER_RFC4716_PUB = /^---- END SSH2 PUBLIC KEY ----$/i;
var RE_HEADER_OPENSSH = /^([^:]+):\s*([\S].*)?$/i;
var RE_HEADER_RFC4716 = /^([^:]+): (.*)?$/i;
module.exports = function(data) {
if (Buffer.isBuffer(data))
data = data.toString('utf8');
else if (typeof data !== 'string')
return new Error('Key data must be a Buffer or string');
var ret = {
fulltype: undefined,
type: undefined,
curve: undefined,
extra: undefined,
comment: undefined,
encryption: undefined,
private: undefined,
privateOrig: undefined,
public: undefined,
publicOrig: undefined
};
var m;
var i;
var len;
data = data.trim().split(/\r\n|\n/);
while (!data[0].length)
data.shift();
while (!data.slice(-1)[0].length)
data.pop();
var orig = data.join('\n');
if ((m = RE_HEADER_OPENSSH_PRIV.exec(data[0]))
&& RE_FOOTER_OPENSSH_PRIV.test(data.slice(-1))) {
// OpenSSH private key
var keyType = m[1].toLowerCase();
if (keyType === 'dsa')
keyType = 'dss';
if (keyType === 'ec' && semver.lt(process.version, '5.2.0')) {
return new Error(
'EC private keys are not supported in this version of node'
);
}
if (!RE_HEADER_OPENSSH.test(data[1])) {
// unencrypted, no headers
var privData = new Buffer(data.slice(1, -1).join(''), 'base64');
if (keyType !== 'ec') {
ret.fulltype = 'ssh-' + keyType;
} else {
// ECDSA
var asnReader = new Ber.Reader(privData);
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
ret.fulltype = 'ecdsa-sha2-nistp256';
break;
case '1.3.132.0.34':
// secp384r1
ret.fulltype = 'ecdsa-sha2-nistp384';
break;
case '1.3.132.0.35':
// secp521r1
ret.fulltype = 'ecdsa-sha2-nistp521';
break;
}
}
if (ret.fulltype === undefined)
return new Error('Unsupported EC private key type');
}
ret.private = privData;
} else {
// possibly encrypted, headers
for (i = 1, len = data.length; i < len; ++i) {
m = RE_HEADER_OPENSSH.exec(data[i]);
if (m) {
m[1] = m[1].toLowerCase();
if (m[1] === 'dek-info') {
m[2] = m[2].split(',');
ret.encryption = m[2][0].toLowerCase();
if (m[2].length > 1)
ret.extra = m[2].slice(1);
}
} else if (data[i].length)
break;
}
ret.private = new Buffer(data.slice(i, -1).join(''), 'base64');
}
ret.type = keyType;
ret.privateOrig = new Buffer(orig);
} else if (m = RE_HEADER_OPENSSH_PUB.exec(data[0])) {
// OpenSSH public key
ret.fulltype = m[1];
ret.type = (m[2] || 'ec').toLowerCase();
ret.public = new Buffer(m[4], 'base64');
ret.publicOrig = new Buffer(orig);
ret.comment = m[5];
if (m[3]) // ECDSA only
ret.curve = 'nistp' + m[3];
} else if (RE_HEADER_RFC4716_PUB.test(data[0])
&& RE_FOOTER_RFC4716_PUB.test(data.slice(-1))) {
if (data[1].indexOf(': ') === -1) {
// no headers
ret.public = new Buffer(data.slice(1, -1).join(''), 'base64');
} else {
// headers
for (i = 1, len = data.length; i < len; ++i) {
if (data[i].indexOf(': ') === -1) {
if (data[i].length)
break; // start of key data
else
continue; // empty line
}
while (data[i].substr(-1) === '\\') {
if (i + 1 < len) {
data[i] = data[i].slice(0, -1) + data[i + 1];
data.splice(i + 1, 1);
--len;
} else
return new Error('RFC4716 public key missing header continuation line');
}
m = RE_HEADER_RFC4716.exec(data[i]);
if (m) {
m[1] = m[1].toLowerCase();
if (m[1] === 'comment') {
ret.comment = m[2] || '';
if (ret.comment[0] === '"' && ret.comment.substr(-1) === '"')
ret.comment = ret.comment.slice(1, -1);
}
} else
return new Error('RFC4716 public key invalid header line');
}
ret.public = new Buffer(data.slice(i, -1).join(''), 'base64');
}
len = ret.public.readUInt32BE(0, true);
var fulltype = ret.public.toString('ascii', 4, 4 + len);
ret.fulltype = fulltype;
if (fulltype === 'ssh-dss')
ret.type = 'dss';
else if (fulltype === 'ssh-rsa')
ret.type = 'rsa';
else
return new Error('Unsupported RFC4716 public key type: ' + fulltype);
ret.public = ret.public.slice(11);
ret.publicOrig = new Buffer(orig);
} else if (m = RE_PPK.exec(orig)) {
// m[1] = short type
// m[2] = encryption type
// m[3] = comment
// m[4] = base64-encoded public key data:
// for "ssh-rsa":
// string "ssh-rsa"
// mpint e (public exponent)
// mpint n (modulus)
// for "ssh-dss":
// string "ssh-dss"
// mpint p (modulus)
// mpint q (prime)
// mpint g (base number)
// mpint y (public key parameter: g^x mod p)
// m[5] = base64-encoded private key data:
// for "ssh-rsa":
// mpint d (private exponent)
// mpint p (prime 1)
// mpint q (prime 2)
// mpint iqmp ([inverse of q] mod p)
// for "ssh-dss":
// mpint x (private key parameter)
// m[6] = SHA1 HMAC over:
// string name of algorithm ("ssh-dss", "ssh-rsa")
// string encryption type
// string comment
// string public key data
// string private-plaintext (including the final padding)
// avoid cyclic require by requiring on first use
if (!utils)
utils = require('./utils');
ret.ppk = true;
ret.type = m[1];
ret.fulltype = 'ssh-' + m[1];
if (m[2] !== 'none')
ret.encryption = m[2];
ret.comment = m[3];
ret.public = new Buffer(m[4].replace(/\r?\n/g, ''), 'base64');
var privateKey = new Buffer(m[5].replace(/\r?\n/g, ''), 'base64');
ret.privateMAC = m[6].replace(/\r?\n/g, '');
// automatically verify private key MAC if we don't need to wait for
// decryption
if (!ret.encryption) {
var valid = utils.verifyPPKMAC(ret, undefined, privateKey);
if (!valid)
throw new Error('PPK MAC mismatch');
}
// generate a PEM encoded version of the public key
var pubkey = utils.genPublicKey(ret);
ret.public = pubkey.public;
ret.publicOrig = pubkey.publicOrig;
ret.private = privateKey;
// automatically convert private key data to OpenSSL format (including PEM)
// if we don't need to wait for decryption
if (!ret.encryption)
utils.convertPPKPrivate(ret);
} else
return new Error('Unsupported key format');
return ret;
};