1 // Copyright 2015 Joyent, Inc.
5 readSSHPrivate: readSSHPrivate,
9 var assert = require('assert-plus');
10 var asn1 = require('asn1');
11 var algs = require('../algs');
12 var utils = require('../utils');
13 var crypto = require('crypto');
15 var Key = require('../key');
16 var PrivateKey = require('../private-key');
17 var pem = require('./pem');
18 var rfc4253 = require('./rfc4253');
19 var SSHBuffer = require('../ssh-buffer');
20 var errors = require('../errors');
24 function read(buf, options) {
25 return (pem.read(buf, options));
28 var MAGIC = 'openssh-key-v1';
30 function readSSHPrivate(type, buf, options) {
31 buf = new SSHBuffer({buffer: buf});
33 var magic = buf.readCString();
34 assert.strictEqual(magic, MAGIC, 'bad magic string');
36 var cipher = buf.readString();
37 var kdf = buf.readString();
38 var kdfOpts = buf.readBuffer();
40 var nkeys = buf.readInt();
42 throw (new Error('OpenSSH-format key file contains ' +
43 'multiple keys: this is unsupported.'));
46 var pubKey = buf.readBuffer();
48 if (type === 'public') {
49 assert.ok(buf.atEnd(), 'excess bytes left after key');
50 return (rfc4253.read(pubKey));
53 var privKeyBlob = buf.readBuffer();
54 assert.ok(buf.atEnd(), 'excess bytes left after key');
56 var kdfOptsBuf = new SSHBuffer({ buffer: kdfOpts });
59 if (cipher !== 'none') {
60 throw (new Error('OpenSSH-format key uses KDF "none" ' +
61 'but specifies a cipher other than "none"'));
65 var salt = kdfOptsBuf.readBuffer();
66 var rounds = kdfOptsBuf.readInt();
67 var cinf = utils.opensshCipherInfo(cipher);
68 if (bcrypt === undefined) {
69 bcrypt = require('bcrypt-pbkdf');
72 if (typeof (options.passphrase) === 'string') {
73 options.passphrase = new Buffer(options.passphrase,
76 if (!Buffer.isBuffer(options.passphrase)) {
77 throw (new errors.KeyEncryptedError(
78 options.filename, 'OpenSSH'));
81 var pass = new Uint8Array(options.passphrase);
82 var salti = new Uint8Array(salt);
83 /* Use the pbkdf to derive both the key and the IV. */
84 var out = new Uint8Array(cinf.keySize + cinf.blockSize);
85 var res = bcrypt.pbkdf(pass, pass.length, salti, salti.length,
86 out, out.length, rounds);
88 throw (new Error('bcrypt_pbkdf function returned ' +
89 'failure, parameters invalid'));
91 out = new Buffer(out);
92 var ckey = out.slice(0, cinf.keySize);
93 var iv = out.slice(cinf.keySize, cinf.keySize + cinf.blockSize);
94 var cipherStream = crypto.createDecipheriv(cinf.opensslName,
96 cipherStream.setAutoPadding(false);
97 var chunk, chunks = [];
98 cipherStream.once('error', function (e) {
99 if (e.toString().indexOf('bad decrypt') !== -1) {
100 throw (new Error('Incorrect passphrase ' +
101 'supplied, could not decrypt key'));
105 cipherStream.write(privKeyBlob);
107 while ((chunk = cipherStream.read()) !== null)
109 privKeyBlob = Buffer.concat(chunks);
113 'OpenSSH-format key uses unknown KDF "' + kdf + '"'));
116 buf = new SSHBuffer({buffer: privKeyBlob});
118 var checkInt1 = buf.readInt();
119 var checkInt2 = buf.readInt();
120 if (checkInt1 !== checkInt2) {
121 throw (new Error('Incorrect passphrase supplied, could not ' +
126 var key = rfc4253.readInternal(ret, 'private', buf.remainder());
128 buf.skip(ret.consumed);
130 var comment = buf.readString();
131 key.comment = comment;
136 function write(key, options) {
138 if (PrivateKey.isPrivateKey(key))
139 pubKey = key.toPublic();
145 var kdfopts = new Buffer(0);
146 var cinf = { blockSize: 8 };
148 if (options !== undefined) {
149 passphrase = options.passphrase;
150 if (typeof (passphrase) === 'string')
151 passphrase = new Buffer(passphrase, 'utf-8');
152 if (passphrase !== undefined) {
153 assert.buffer(passphrase, 'options.passphrase');
154 assert.optionalString(options.cipher, 'options.cipher');
155 cipher = options.cipher;
156 if (cipher === undefined)
157 cipher = 'aes128-ctr';
158 cinf = utils.opensshCipherInfo(cipher);
164 if (PrivateKey.isPrivateKey(key)) {
165 privBuf = new SSHBuffer({});
166 var checkInt = crypto.randomBytes(4).readUInt32BE(0);
167 privBuf.writeInt(checkInt);
168 privBuf.writeInt(checkInt);
169 privBuf.write(key.toBuffer('rfc4253'));
170 privBuf.writeString(key.comment || '');
173 while (privBuf._offset % cinf.blockSize !== 0)
174 privBuf.writeChar(n++);
175 privBuf = privBuf.toBuffer();
182 var salt = crypto.randomBytes(16);
184 var kdfssh = new SSHBuffer({});
185 kdfssh.writeBuffer(salt);
186 kdfssh.writeInt(rounds);
187 kdfopts = kdfssh.toBuffer();
189 if (bcrypt === undefined) {
190 bcrypt = require('bcrypt-pbkdf');
192 var pass = new Uint8Array(passphrase);
193 var salti = new Uint8Array(salt);
194 /* Use the pbkdf to derive both the key and the IV. */
195 var out = new Uint8Array(cinf.keySize + cinf.blockSize);
196 var res = bcrypt.pbkdf(pass, pass.length, salti, salti.length,
197 out, out.length, rounds);
199 throw (new Error('bcrypt_pbkdf function returned ' +
200 'failure, parameters invalid'));
202 out = new Buffer(out);
203 var ckey = out.slice(0, cinf.keySize);
204 var iv = out.slice(cinf.keySize, cinf.keySize + cinf.blockSize);
206 var cipherStream = crypto.createCipheriv(cinf.opensslName,
208 cipherStream.setAutoPadding(false);
209 var chunk, chunks = [];
210 cipherStream.once('error', function (e) {
213 cipherStream.write(privBuf);
215 while ((chunk = cipherStream.read()) !== null)
217 privBuf = Buffer.concat(chunks);
220 throw (new Error('Unsupported kdf ' + kdf));
223 var buf = new SSHBuffer({});
225 buf.writeCString(MAGIC);
226 buf.writeString(cipher); /* cipher */
227 buf.writeString(kdf); /* kdf */
228 buf.writeBuffer(kdfopts); /* kdfoptions */
230 buf.writeInt(1); /* nkeys */
231 buf.writeBuffer(pubKey.toBuffer('rfc4253'));
234 buf.writeBuffer(privBuf);
236 buf = buf.toBuffer();
239 if (PrivateKey.isPrivateKey(key))
240 header = 'OPENSSH PRIVATE KEY';
242 header = 'OPENSSH PUBLIC KEY';
244 var tmp = buf.toString('base64');
245 var len = tmp.length + (tmp.length / 70) +
246 18 + 16 + header.length*2 + 10;
247 buf = new Buffer(len);
249 o += buf.write('-----BEGIN ' + header + '-----\n', o);
250 for (var i = 0; i < tmp.length; ) {
252 if (limit > tmp.length)
254 o += buf.write(tmp.slice(i, limit), o);
258 o += buf.write('-----END ' + header + '-----\n', o);
260 return (buf.slice(0, o));