trouble-in-terror-town/node_modules/sshpk/lib/formats/ssh-private.js
Mikolaj 2bbacbea09 did some more work on networking and removed EOS in favor of LRM
did some more work on networking and removed EOS in favor of Light Reflective Mirror
2022-05-31 15:04:31 +02:00

262 lines
6.8 KiB
JavaScript

// Copyright 2015 Joyent, Inc.
module.exports = {
read: read,
readSSHPrivate: readSSHPrivate,
write: write
};
var assert = require('assert-plus');
var asn1 = require('asn1');
var Buffer = require('safer-buffer').Buffer;
var algs = require('../algs');
var utils = require('../utils');
var crypto = require('crypto');
var Key = require('../key');
var PrivateKey = require('../private-key');
var pem = require('./pem');
var rfc4253 = require('./rfc4253');
var SSHBuffer = require('../ssh-buffer');
var errors = require('../errors');
var bcrypt;
function read(buf, options) {
return (pem.read(buf, options));
}
var MAGIC = 'openssh-key-v1';
function readSSHPrivate(type, buf, options) {
buf = new SSHBuffer({buffer: buf});
var magic = buf.readCString();
assert.strictEqual(magic, MAGIC, 'bad magic string');
var cipher = buf.readString();
var kdf = buf.readString();
var kdfOpts = buf.readBuffer();
var nkeys = buf.readInt();
if (nkeys !== 1) {
throw (new Error('OpenSSH-format key file contains ' +
'multiple keys: this is unsupported.'));
}
var pubKey = buf.readBuffer();
if (type === 'public') {
assert.ok(buf.atEnd(), 'excess bytes left after key');
return (rfc4253.read(pubKey));
}
var privKeyBlob = buf.readBuffer();
assert.ok(buf.atEnd(), 'excess bytes left after key');
var kdfOptsBuf = new SSHBuffer({ buffer: kdfOpts });
switch (kdf) {
case 'none':
if (cipher !== 'none') {
throw (new Error('OpenSSH-format key uses KDF "none" ' +
'but specifies a cipher other than "none"'));
}
break;
case 'bcrypt':
var salt = kdfOptsBuf.readBuffer();
var rounds = kdfOptsBuf.readInt();
var cinf = utils.opensshCipherInfo(cipher);
if (bcrypt === undefined) {
bcrypt = require('bcrypt-pbkdf');
}
if (typeof (options.passphrase) === 'string') {
options.passphrase = Buffer.from(options.passphrase,
'utf-8');
}
if (!Buffer.isBuffer(options.passphrase)) {
throw (new errors.KeyEncryptedError(
options.filename, 'OpenSSH'));
}
var pass = new Uint8Array(options.passphrase);
var salti = new Uint8Array(salt);
/* Use the pbkdf to derive both the key and the IV. */
var out = new Uint8Array(cinf.keySize + cinf.blockSize);
var res = bcrypt.pbkdf(pass, pass.length, salti, salti.length,
out, out.length, rounds);
if (res !== 0) {
throw (new Error('bcrypt_pbkdf function returned ' +
'failure, parameters invalid'));
}
out = Buffer.from(out);
var ckey = out.slice(0, cinf.keySize);
var iv = out.slice(cinf.keySize, cinf.keySize + cinf.blockSize);
var cipherStream = crypto.createDecipheriv(cinf.opensslName,
ckey, iv);
cipherStream.setAutoPadding(false);
var chunk, chunks = [];
cipherStream.once('error', function (e) {
if (e.toString().indexOf('bad decrypt') !== -1) {
throw (new Error('Incorrect passphrase ' +
'supplied, could not decrypt key'));
}
throw (e);
});
cipherStream.write(privKeyBlob);
cipherStream.end();
while ((chunk = cipherStream.read()) !== null)
chunks.push(chunk);
privKeyBlob = Buffer.concat(chunks);
break;
default:
throw (new Error(
'OpenSSH-format key uses unknown KDF "' + kdf + '"'));
}
buf = new SSHBuffer({buffer: privKeyBlob});
var checkInt1 = buf.readInt();
var checkInt2 = buf.readInt();
if (checkInt1 !== checkInt2) {
throw (new Error('Incorrect passphrase supplied, could not ' +
'decrypt key'));
}
var ret = {};
var key = rfc4253.readInternal(ret, 'private', buf.remainder());
buf.skip(ret.consumed);
var comment = buf.readString();
key.comment = comment;
return (key);
}
function write(key, options) {
var pubKey;
if (PrivateKey.isPrivateKey(key))
pubKey = key.toPublic();
else
pubKey = key;
var cipher = 'none';
var kdf = 'none';
var kdfopts = Buffer.alloc(0);
var cinf = { blockSize: 8 };
var passphrase;
if (options !== undefined) {
passphrase = options.passphrase;
if (typeof (passphrase) === 'string')
passphrase = Buffer.from(passphrase, 'utf-8');
if (passphrase !== undefined) {
assert.buffer(passphrase, 'options.passphrase');
assert.optionalString(options.cipher, 'options.cipher');
cipher = options.cipher;
if (cipher === undefined)
cipher = 'aes128-ctr';
cinf = utils.opensshCipherInfo(cipher);
kdf = 'bcrypt';
}
}
var privBuf;
if (PrivateKey.isPrivateKey(key)) {
privBuf = new SSHBuffer({});
var checkInt = crypto.randomBytes(4).readUInt32BE(0);
privBuf.writeInt(checkInt);
privBuf.writeInt(checkInt);
privBuf.write(key.toBuffer('rfc4253'));
privBuf.writeString(key.comment || '');
var n = 1;
while (privBuf._offset % cinf.blockSize !== 0)
privBuf.writeChar(n++);
privBuf = privBuf.toBuffer();
}
switch (kdf) {
case 'none':
break;
case 'bcrypt':
var salt = crypto.randomBytes(16);
var rounds = 16;
var kdfssh = new SSHBuffer({});
kdfssh.writeBuffer(salt);
kdfssh.writeInt(rounds);
kdfopts = kdfssh.toBuffer();
if (bcrypt === undefined) {
bcrypt = require('bcrypt-pbkdf');
}
var pass = new Uint8Array(passphrase);
var salti = new Uint8Array(salt);
/* Use the pbkdf to derive both the key and the IV. */
var out = new Uint8Array(cinf.keySize + cinf.blockSize);
var res = bcrypt.pbkdf(pass, pass.length, salti, salti.length,
out, out.length, rounds);
if (res !== 0) {
throw (new Error('bcrypt_pbkdf function returned ' +
'failure, parameters invalid'));
}
out = Buffer.from(out);
var ckey = out.slice(0, cinf.keySize);
var iv = out.slice(cinf.keySize, cinf.keySize + cinf.blockSize);
var cipherStream = crypto.createCipheriv(cinf.opensslName,
ckey, iv);
cipherStream.setAutoPadding(false);
var chunk, chunks = [];
cipherStream.once('error', function (e) {
throw (e);
});
cipherStream.write(privBuf);
cipherStream.end();
while ((chunk = cipherStream.read()) !== null)
chunks.push(chunk);
privBuf = Buffer.concat(chunks);
break;
default:
throw (new Error('Unsupported kdf ' + kdf));
}
var buf = new SSHBuffer({});
buf.writeCString(MAGIC);
buf.writeString(cipher); /* cipher */
buf.writeString(kdf); /* kdf */
buf.writeBuffer(kdfopts); /* kdfoptions */
buf.writeInt(1); /* nkeys */
buf.writeBuffer(pubKey.toBuffer('rfc4253'));
if (privBuf)
buf.writeBuffer(privBuf);
buf = buf.toBuffer();
var header;
if (PrivateKey.isPrivateKey(key))
header = 'OPENSSH PRIVATE KEY';
else
header = 'OPENSSH PUBLIC KEY';
var tmp = buf.toString('base64');
var len = tmp.length + (tmp.length / 70) +
18 + 16 + header.length*2 + 10;
buf = Buffer.alloc(len);
var o = 0;
o += buf.write('-----BEGIN ' + header + '-----\n', o);
for (var i = 0; i < tmp.length; ) {
var limit = i + 70;
if (limit > tmp.length)
limit = tmp.length;
o += buf.write(tmp.slice(i, limit), o);
buf[o++] = 10;
i = limit;
}
o += buf.write('-----END ' + header + '-----\n', o);
return (buf.slice(0, o));
}