twitst4tz

twitter statistics web application
Log | Files | Refs | README | LICENSE

ssh-private.js (6934B)


      1 // Copyright 2015 Joyent, Inc.
      2 
      3 module.exports = {
      4 	read: read,
      5 	readSSHPrivate: readSSHPrivate,
      6 	write: write
      7 };
      8 
      9 var assert = require('assert-plus');
     10 var asn1 = require('asn1');
     11 var Buffer = require('safer-buffer').Buffer;
     12 var algs = require('../algs');
     13 var utils = require('../utils');
     14 var crypto = require('crypto');
     15 
     16 var Key = require('../key');
     17 var PrivateKey = require('../private-key');
     18 var pem = require('./pem');
     19 var rfc4253 = require('./rfc4253');
     20 var SSHBuffer = require('../ssh-buffer');
     21 var errors = require('../errors');
     22 
     23 var bcrypt;
     24 
     25 function read(buf, options) {
     26 	return (pem.read(buf, options));
     27 }
     28 
     29 var MAGIC = 'openssh-key-v1';
     30 
     31 function readSSHPrivate(type, buf, options) {
     32 	buf = new SSHBuffer({buffer: buf});
     33 
     34 	var magic = buf.readCString();
     35 	assert.strictEqual(magic, MAGIC, 'bad magic string');
     36 
     37 	var cipher = buf.readString();
     38 	var kdf = buf.readString();
     39 	var kdfOpts = buf.readBuffer();
     40 
     41 	var nkeys = buf.readInt();
     42 	if (nkeys !== 1) {
     43 		throw (new Error('OpenSSH-format key file contains ' +
     44 		    'multiple keys: this is unsupported.'));
     45 	}
     46 
     47 	var pubKey = buf.readBuffer();
     48 
     49 	if (type === 'public') {
     50 		assert.ok(buf.atEnd(), 'excess bytes left after key');
     51 		return (rfc4253.read(pubKey));
     52 	}
     53 
     54 	var privKeyBlob = buf.readBuffer();
     55 	assert.ok(buf.atEnd(), 'excess bytes left after key');
     56 
     57 	var kdfOptsBuf = new SSHBuffer({ buffer: kdfOpts });
     58 	switch (kdf) {
     59 	case 'none':
     60 		if (cipher !== 'none') {
     61 			throw (new Error('OpenSSH-format key uses KDF "none" ' +
     62 			     'but specifies a cipher other than "none"'));
     63 		}
     64 		break;
     65 	case 'bcrypt':
     66 		var salt = kdfOptsBuf.readBuffer();
     67 		var rounds = kdfOptsBuf.readInt();
     68 		var cinf = utils.opensshCipherInfo(cipher);
     69 		if (bcrypt === undefined) {
     70 			bcrypt = require('bcrypt-pbkdf');
     71 		}
     72 
     73 		if (typeof (options.passphrase) === 'string') {
     74 			options.passphrase = Buffer.from(options.passphrase,
     75 			    'utf-8');
     76 		}
     77 		if (!Buffer.isBuffer(options.passphrase)) {
     78 			throw (new errors.KeyEncryptedError(
     79 			    options.filename, 'OpenSSH'));
     80 		}
     81 
     82 		var pass = new Uint8Array(options.passphrase);
     83 		var salti = new Uint8Array(salt);
     84 		/* Use the pbkdf to derive both the key and the IV. */
     85 		var out = new Uint8Array(cinf.keySize + cinf.blockSize);
     86 		var res = bcrypt.pbkdf(pass, pass.length, salti, salti.length,
     87 		    out, out.length, rounds);
     88 		if (res !== 0) {
     89 			throw (new Error('bcrypt_pbkdf function returned ' +
     90 			    'failure, parameters invalid'));
     91 		}
     92 		out = Buffer.from(out);
     93 		var ckey = out.slice(0, cinf.keySize);
     94 		var iv = out.slice(cinf.keySize, cinf.keySize + cinf.blockSize);
     95 		var cipherStream = crypto.createDecipheriv(cinf.opensslName,
     96 		    ckey, iv);
     97 		cipherStream.setAutoPadding(false);
     98 		var chunk, chunks = [];
     99 		cipherStream.once('error', function (e) {
    100 			if (e.toString().indexOf('bad decrypt') !== -1) {
    101 				throw (new Error('Incorrect passphrase ' +
    102 				    'supplied, could not decrypt key'));
    103 			}
    104 			throw (e);
    105 		});
    106 		cipherStream.write(privKeyBlob);
    107 		cipherStream.end();
    108 		while ((chunk = cipherStream.read()) !== null)
    109 			chunks.push(chunk);
    110 		privKeyBlob = Buffer.concat(chunks);
    111 		break;
    112 	default:
    113 		throw (new Error(
    114 		    'OpenSSH-format key uses unknown KDF "' + kdf + '"'));
    115 	}
    116 
    117 	buf = new SSHBuffer({buffer: privKeyBlob});
    118 
    119 	var checkInt1 = buf.readInt();
    120 	var checkInt2 = buf.readInt();
    121 	if (checkInt1 !== checkInt2) {
    122 		throw (new Error('Incorrect passphrase supplied, could not ' +
    123 		    'decrypt key'));
    124 	}
    125 
    126 	var ret = {};
    127 	var key = rfc4253.readInternal(ret, 'private', buf.remainder());
    128 
    129 	buf.skip(ret.consumed);
    130 
    131 	var comment = buf.readString();
    132 	key.comment = comment;
    133 
    134 	return (key);
    135 }
    136 
    137 function write(key, options) {
    138 	var pubKey;
    139 	if (PrivateKey.isPrivateKey(key))
    140 		pubKey = key.toPublic();
    141 	else
    142 		pubKey = key;
    143 
    144 	var cipher = 'none';
    145 	var kdf = 'none';
    146 	var kdfopts = Buffer.alloc(0);
    147 	var cinf = { blockSize: 8 };
    148 	var passphrase;
    149 	if (options !== undefined) {
    150 		passphrase = options.passphrase;
    151 		if (typeof (passphrase) === 'string')
    152 			passphrase = Buffer.from(passphrase, 'utf-8');
    153 		if (passphrase !== undefined) {
    154 			assert.buffer(passphrase, 'options.passphrase');
    155 			assert.optionalString(options.cipher, 'options.cipher');
    156 			cipher = options.cipher;
    157 			if (cipher === undefined)
    158 				cipher = 'aes128-ctr';
    159 			cinf = utils.opensshCipherInfo(cipher);
    160 			kdf = 'bcrypt';
    161 		}
    162 	}
    163 
    164 	var privBuf;
    165 	if (PrivateKey.isPrivateKey(key)) {
    166 		privBuf = new SSHBuffer({});
    167 		var checkInt = crypto.randomBytes(4).readUInt32BE(0);
    168 		privBuf.writeInt(checkInt);
    169 		privBuf.writeInt(checkInt);
    170 		privBuf.write(key.toBuffer('rfc4253'));
    171 		privBuf.writeString(key.comment || '');
    172 
    173 		var n = 1;
    174 		while (privBuf._offset % cinf.blockSize !== 0)
    175 			privBuf.writeChar(n++);
    176 		privBuf = privBuf.toBuffer();
    177 	}
    178 
    179 	switch (kdf) {
    180 	case 'none':
    181 		break;
    182 	case 'bcrypt':
    183 		var salt = crypto.randomBytes(16);
    184 		var rounds = 16;
    185 		var kdfssh = new SSHBuffer({});
    186 		kdfssh.writeBuffer(salt);
    187 		kdfssh.writeInt(rounds);
    188 		kdfopts = kdfssh.toBuffer();
    189 
    190 		if (bcrypt === undefined) {
    191 			bcrypt = require('bcrypt-pbkdf');
    192 		}
    193 		var pass = new Uint8Array(passphrase);
    194 		var salti = new Uint8Array(salt);
    195 		/* Use the pbkdf to derive both the key and the IV. */
    196 		var out = new Uint8Array(cinf.keySize + cinf.blockSize);
    197 		var res = bcrypt.pbkdf(pass, pass.length, salti, salti.length,
    198 		    out, out.length, rounds);
    199 		if (res !== 0) {
    200 			throw (new Error('bcrypt_pbkdf function returned ' +
    201 			    'failure, parameters invalid'));
    202 		}
    203 		out = Buffer.from(out);
    204 		var ckey = out.slice(0, cinf.keySize);
    205 		var iv = out.slice(cinf.keySize, cinf.keySize + cinf.blockSize);
    206 
    207 		var cipherStream = crypto.createCipheriv(cinf.opensslName,
    208 		    ckey, iv);
    209 		cipherStream.setAutoPadding(false);
    210 		var chunk, chunks = [];
    211 		cipherStream.once('error', function (e) {
    212 			throw (e);
    213 		});
    214 		cipherStream.write(privBuf);
    215 		cipherStream.end();
    216 		while ((chunk = cipherStream.read()) !== null)
    217 			chunks.push(chunk);
    218 		privBuf = Buffer.concat(chunks);
    219 		break;
    220 	default:
    221 		throw (new Error('Unsupported kdf ' + kdf));
    222 	}
    223 
    224 	var buf = new SSHBuffer({});
    225 
    226 	buf.writeCString(MAGIC);
    227 	buf.writeString(cipher);	/* cipher */
    228 	buf.writeString(kdf);		/* kdf */
    229 	buf.writeBuffer(kdfopts);	/* kdfoptions */
    230 
    231 	buf.writeInt(1);		/* nkeys */
    232 	buf.writeBuffer(pubKey.toBuffer('rfc4253'));
    233 
    234 	if (privBuf)
    235 		buf.writeBuffer(privBuf);
    236 
    237 	buf = buf.toBuffer();
    238 
    239 	var header;
    240 	if (PrivateKey.isPrivateKey(key))
    241 		header = 'OPENSSH PRIVATE KEY';
    242 	else
    243 		header = 'OPENSSH PUBLIC KEY';
    244 
    245 	var tmp = buf.toString('base64');
    246 	var len = tmp.length + (tmp.length / 70) +
    247 	    18 + 16 + header.length*2 + 10;
    248 	buf = Buffer.alloc(len);
    249 	var o = 0;
    250 	o += buf.write('-----BEGIN ' + header + '-----\n', o);
    251 	for (var i = 0; i < tmp.length; ) {
    252 		var limit = i + 70;
    253 		if (limit > tmp.length)
    254 			limit = tmp.length;
    255 		o += buf.write(tmp.slice(i, limit), o);
    256 		buf[o++] = 10;
    257 		i = limit;
    258 	}
    259 	o += buf.write('-----END ' + header + '-----\n', o);
    260 
    261 	return (buf.slice(0, o));
    262 }