| // 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)); | 
| } |