Initial commit
[yaffs-website] / node_modules / sshpk / lib / formats / ssh-private.js
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 algs = require('../algs');
12 var utils = require('../utils');
13 var crypto = require('crypto');
14
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');
21
22 var bcrypt;
23
24 function read(buf, options) {
25         return (pem.read(buf, options));
26 }
27
28 var MAGIC = 'openssh-key-v1';
29
30 function readSSHPrivate(type, buf, options) {
31         buf = new SSHBuffer({buffer: buf});
32
33         var magic = buf.readCString();
34         assert.strictEqual(magic, MAGIC, 'bad magic string');
35
36         var cipher = buf.readString();
37         var kdf = buf.readString();
38         var kdfOpts = buf.readBuffer();
39
40         var nkeys = buf.readInt();
41         if (nkeys !== 1) {
42                 throw (new Error('OpenSSH-format key file contains ' +
43                     'multiple keys: this is unsupported.'));
44         }
45
46         var pubKey = buf.readBuffer();
47
48         if (type === 'public') {
49                 assert.ok(buf.atEnd(), 'excess bytes left after key');
50                 return (rfc4253.read(pubKey));
51         }
52
53         var privKeyBlob = buf.readBuffer();
54         assert.ok(buf.atEnd(), 'excess bytes left after key');
55
56         var kdfOptsBuf = new SSHBuffer({ buffer: kdfOpts });
57         switch (kdf) {
58         case 'none':
59                 if (cipher !== 'none') {
60                         throw (new Error('OpenSSH-format key uses KDF "none" ' +
61                              'but specifies a cipher other than "none"'));
62                 }
63                 break;
64         case 'bcrypt':
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');
70                 }
71
72                 if (typeof (options.passphrase) === 'string') {
73                         options.passphrase = new Buffer(options.passphrase,
74                             'utf-8');
75                 }
76                 if (!Buffer.isBuffer(options.passphrase)) {
77                         throw (new errors.KeyEncryptedError(
78                             options.filename, 'OpenSSH'));
79                 }
80
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);
87                 if (res !== 0) {
88                         throw (new Error('bcrypt_pbkdf function returned ' +
89                             'failure, parameters invalid'));
90                 }
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,
95                     ckey, iv);
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'));
102                         }
103                         throw (e);
104                 });
105                 cipherStream.write(privKeyBlob);
106                 cipherStream.end();
107                 while ((chunk = cipherStream.read()) !== null)
108                         chunks.push(chunk);
109                 privKeyBlob = Buffer.concat(chunks);
110                 break;
111         default:
112                 throw (new Error(
113                     'OpenSSH-format key uses unknown KDF "' + kdf + '"'));
114         }
115
116         buf = new SSHBuffer({buffer: privKeyBlob});
117
118         var checkInt1 = buf.readInt();
119         var checkInt2 = buf.readInt();
120         if (checkInt1 !== checkInt2) {
121                 throw (new Error('Incorrect passphrase supplied, could not ' +
122                     'decrypt key'));
123         }
124
125         var ret = {};
126         var key = rfc4253.readInternal(ret, 'private', buf.remainder());
127
128         buf.skip(ret.consumed);
129
130         var comment = buf.readString();
131         key.comment = comment;
132
133         return (key);
134 }
135
136 function write(key, options) {
137         var pubKey;
138         if (PrivateKey.isPrivateKey(key))
139                 pubKey = key.toPublic();
140         else
141                 pubKey = key;
142
143         var cipher = 'none';
144         var kdf = 'none';
145         var kdfopts = new Buffer(0);
146         var cinf = { blockSize: 8 };
147         var passphrase;
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);
159                         kdf = 'bcrypt';
160                 }
161         }
162
163         var privBuf;
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 || '');
171
172                 var n = 1;
173                 while (privBuf._offset % cinf.blockSize !== 0)
174                         privBuf.writeChar(n++);
175                 privBuf = privBuf.toBuffer();
176         }
177
178         switch (kdf) {
179         case 'none':
180                 break;
181         case 'bcrypt':
182                 var salt = crypto.randomBytes(16);
183                 var rounds = 16;
184                 var kdfssh = new SSHBuffer({});
185                 kdfssh.writeBuffer(salt);
186                 kdfssh.writeInt(rounds);
187                 kdfopts = kdfssh.toBuffer();
188
189                 if (bcrypt === undefined) {
190                         bcrypt = require('bcrypt-pbkdf');
191                 }
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);
198                 if (res !== 0) {
199                         throw (new Error('bcrypt_pbkdf function returned ' +
200                             'failure, parameters invalid'));
201                 }
202                 out = new Buffer(out);
203                 var ckey = out.slice(0, cinf.keySize);
204                 var iv = out.slice(cinf.keySize, cinf.keySize + cinf.blockSize);
205
206                 var cipherStream = crypto.createCipheriv(cinf.opensslName,
207                     ckey, iv);
208                 cipherStream.setAutoPadding(false);
209                 var chunk, chunks = [];
210                 cipherStream.once('error', function (e) {
211                         throw (e);
212                 });
213                 cipherStream.write(privBuf);
214                 cipherStream.end();
215                 while ((chunk = cipherStream.read()) !== null)
216                         chunks.push(chunk);
217                 privBuf = Buffer.concat(chunks);
218                 break;
219         default:
220                 throw (new Error('Unsupported kdf ' + kdf));
221         }
222
223         var buf = new SSHBuffer({});
224
225         buf.writeCString(MAGIC);
226         buf.writeString(cipher);        /* cipher */
227         buf.writeString(kdf);           /* kdf */
228         buf.writeBuffer(kdfopts);       /* kdfoptions */
229
230         buf.writeInt(1);                /* nkeys */
231         buf.writeBuffer(pubKey.toBuffer('rfc4253'));
232
233         if (privBuf)
234                 buf.writeBuffer(privBuf);
235
236         buf = buf.toBuffer();
237
238         var header;
239         if (PrivateKey.isPrivateKey(key))
240                 header = 'OPENSSH PRIVATE KEY';
241         else
242                 header = 'OPENSSH PUBLIC KEY';
243
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);
248         var o = 0;
249         o += buf.write('-----BEGIN ' + header + '-----\n', o);
250         for (var i = 0; i < tmp.length; ) {
251                 var limit = i + 70;
252                 if (limit > tmp.length)
253                         limit = tmp.length;
254                 o += buf.write(tmp.slice(i, limit), o);
255                 buf[o++] = 10;
256                 i = limit;
257         }
258         o += buf.write('-----END ' + header + '-----\n', o);
259
260         return (buf.slice(0, o));
261 }