Initial commit
[yaffs-website] / node_modules / sshpk / lib / utils.js
1 // Copyright 2015 Joyent, Inc.
2
3 module.exports = {
4         bufferSplit: bufferSplit,
5         addRSAMissing: addRSAMissing,
6         calculateDSAPublic: calculateDSAPublic,
7         mpNormalize: mpNormalize,
8         ecNormalize: ecNormalize,
9         countZeros: countZeros,
10         assertCompatible: assertCompatible,
11         isCompatible: isCompatible,
12         opensslKeyDeriv: opensslKeyDeriv,
13         opensshCipherInfo: opensshCipherInfo
14 };
15
16 var assert = require('assert-plus');
17 var PrivateKey = require('./private-key');
18 var crypto = require('crypto');
19
20 var MAX_CLASS_DEPTH = 3;
21
22 function isCompatible(obj, klass, needVer) {
23         if (obj === null || typeof (obj) !== 'object')
24                 return (false);
25         if (needVer === undefined)
26                 needVer = klass.prototype._sshpkApiVersion;
27         if (obj instanceof klass &&
28             klass.prototype._sshpkApiVersion[0] == needVer[0])
29                 return (true);
30         var proto = Object.getPrototypeOf(obj);
31         var depth = 0;
32         while (proto.constructor.name !== klass.name) {
33                 proto = Object.getPrototypeOf(proto);
34                 if (!proto || ++depth > MAX_CLASS_DEPTH)
35                         return (false);
36         }
37         if (proto.constructor.name !== klass.name)
38                 return (false);
39         var ver = proto._sshpkApiVersion;
40         if (ver === undefined)
41                 ver = klass._oldVersionDetect(obj);
42         if (ver[0] != needVer[0] || ver[1] < needVer[1])
43                 return (false);
44         return (true);
45 }
46
47 function assertCompatible(obj, klass, needVer, name) {
48         if (name === undefined)
49                 name = 'object';
50         assert.ok(obj, name + ' must not be null');
51         assert.object(obj, name + ' must be an object');
52         if (needVer === undefined)
53                 needVer = klass.prototype._sshpkApiVersion;
54         if (obj instanceof klass &&
55             klass.prototype._sshpkApiVersion[0] == needVer[0])
56                 return;
57         var proto = Object.getPrototypeOf(obj);
58         var depth = 0;
59         while (proto.constructor.name !== klass.name) {
60                 proto = Object.getPrototypeOf(proto);
61                 assert.ok(proto && ++depth <= MAX_CLASS_DEPTH,
62                     name + ' must be a ' + klass.name + ' instance');
63         }
64         assert.strictEqual(proto.constructor.name, klass.name,
65             name + ' must be a ' + klass.name + ' instance');
66         var ver = proto._sshpkApiVersion;
67         if (ver === undefined)
68                 ver = klass._oldVersionDetect(obj);
69         assert.ok(ver[0] == needVer[0] && ver[1] >= needVer[1],
70             name + ' must be compatible with ' + klass.name + ' klass ' +
71             'version ' + needVer[0] + '.' + needVer[1]);
72 }
73
74 var CIPHER_LEN = {
75         'des-ede3-cbc': { key: 7, iv: 8 },
76         'aes-128-cbc': { key: 16, iv: 16 }
77 };
78 var PKCS5_SALT_LEN = 8;
79
80 function opensslKeyDeriv(cipher, salt, passphrase, count) {
81         assert.buffer(salt, 'salt');
82         assert.buffer(passphrase, 'passphrase');
83         assert.number(count, 'iteration count');
84
85         var clen = CIPHER_LEN[cipher];
86         assert.object(clen, 'supported cipher');
87
88         salt = salt.slice(0, PKCS5_SALT_LEN);
89
90         var D, D_prev, bufs;
91         var material = new Buffer(0);
92         while (material.length < clen.key + clen.iv) {
93                 bufs = [];
94                 if (D_prev)
95                         bufs.push(D_prev);
96                 bufs.push(passphrase);
97                 bufs.push(salt);
98                 D = Buffer.concat(bufs);
99                 for (var j = 0; j < count; ++j)
100                         D = crypto.createHash('md5').update(D).digest();
101                 material = Buffer.concat([material, D]);
102                 D_prev = D;
103         }
104
105         return ({
106             key: material.slice(0, clen.key),
107             iv: material.slice(clen.key, clen.key + clen.iv)
108         });
109 }
110
111 /* Count leading zero bits on a buffer */
112 function countZeros(buf) {
113         var o = 0, obit = 8;
114         while (o < buf.length) {
115                 var mask = (1 << obit);
116                 if ((buf[o] & mask) === mask)
117                         break;
118                 obit--;
119                 if (obit < 0) {
120                         o++;
121                         obit = 8;
122                 }
123         }
124         return (o*8 + (8 - obit) - 1);
125 }
126
127 function bufferSplit(buf, chr) {
128         assert.buffer(buf);
129         assert.string(chr);
130
131         var parts = [];
132         var lastPart = 0;
133         var matches = 0;
134         for (var i = 0; i < buf.length; ++i) {
135                 if (buf[i] === chr.charCodeAt(matches))
136                         ++matches;
137                 else if (buf[i] === chr.charCodeAt(0))
138                         matches = 1;
139                 else
140                         matches = 0;
141
142                 if (matches >= chr.length) {
143                         var newPart = i + 1;
144                         parts.push(buf.slice(lastPart, newPart - matches));
145                         lastPart = newPart;
146                         matches = 0;
147                 }
148         }
149         if (lastPart <= buf.length)
150                 parts.push(buf.slice(lastPart, buf.length));
151
152         return (parts);
153 }
154
155 function ecNormalize(buf, addZero) {
156         assert.buffer(buf);
157         if (buf[0] === 0x00 && buf[1] === 0x04) {
158                 if (addZero)
159                         return (buf);
160                 return (buf.slice(1));
161         } else if (buf[0] === 0x04) {
162                 if (!addZero)
163                         return (buf);
164         } else {
165                 while (buf[0] === 0x00)
166                         buf = buf.slice(1);
167                 if (buf[0] === 0x02 || buf[0] === 0x03)
168                         throw (new Error('Compressed elliptic curve points ' +
169                             'are not supported'));
170                 if (buf[0] !== 0x04)
171                         throw (new Error('Not a valid elliptic curve point'));
172                 if (!addZero)
173                         return (buf);
174         }
175         var b = new Buffer(buf.length + 1);
176         b[0] = 0x0;
177         buf.copy(b, 1);
178         return (b);
179 }
180
181 function mpNormalize(buf) {
182         assert.buffer(buf);
183         while (buf.length > 1 && buf[0] === 0x00 && (buf[1] & 0x80) === 0x00)
184                 buf = buf.slice(1);
185         if ((buf[0] & 0x80) === 0x80) {
186                 var b = new Buffer(buf.length + 1);
187                 b[0] = 0x00;
188                 buf.copy(b, 1);
189                 buf = b;
190         }
191         return (buf);
192 }
193
194 function bigintToMpBuf(bigint) {
195         var buf = new Buffer(bigint.toByteArray());
196         buf = mpNormalize(buf);
197         return (buf);
198 }
199
200 function calculateDSAPublic(g, p, x) {
201         assert.buffer(g);
202         assert.buffer(p);
203         assert.buffer(x);
204         try {
205                 var bigInt = require('jsbn').BigInteger;
206         } catch (e) {
207                 throw (new Error('To load a PKCS#8 format DSA private key, ' +
208                     'the node jsbn library is required.'));
209         }
210         g = new bigInt(g);
211         p = new bigInt(p);
212         x = new bigInt(x);
213         var y = g.modPow(x, p);
214         var ybuf = bigintToMpBuf(y);
215         return (ybuf);
216 }
217
218 function addRSAMissing(key) {
219         assert.object(key);
220         assertCompatible(key, PrivateKey, [1, 1]);
221         try {
222                 var bigInt = require('jsbn').BigInteger;
223         } catch (e) {
224                 throw (new Error('To write a PEM private key from ' +
225                     'this source, the node jsbn lib is required.'));
226         }
227
228         var d = new bigInt(key.part.d.data);
229         var buf;
230
231         if (!key.part.dmodp) {
232                 var p = new bigInt(key.part.p.data);
233                 var dmodp = d.mod(p.subtract(1));
234
235                 buf = bigintToMpBuf(dmodp);
236                 key.part.dmodp = {name: 'dmodp', data: buf};
237                 key.parts.push(key.part.dmodp);
238         }
239         if (!key.part.dmodq) {
240                 var q = new bigInt(key.part.q.data);
241                 var dmodq = d.mod(q.subtract(1));
242
243                 buf = bigintToMpBuf(dmodq);
244                 key.part.dmodq = {name: 'dmodq', data: buf};
245                 key.parts.push(key.part.dmodq);
246         }
247 }
248
249 function opensshCipherInfo(cipher) {
250         var inf = {};
251         switch (cipher) {
252         case '3des-cbc':
253                 inf.keySize = 24;
254                 inf.blockSize = 8;
255                 inf.opensslName = 'des-ede3-cbc';
256                 break;
257         case 'blowfish-cbc':
258                 inf.keySize = 16;
259                 inf.blockSize = 8;
260                 inf.opensslName = 'bf-cbc';
261                 break;
262         case 'aes128-cbc':
263         case 'aes128-ctr':
264         case 'aes128-gcm@openssh.com':
265                 inf.keySize = 16;
266                 inf.blockSize = 16;
267                 inf.opensslName = 'aes-128-' + cipher.slice(7, 10);
268                 break;
269         case 'aes192-cbc':
270         case 'aes192-ctr':
271         case 'aes192-gcm@openssh.com':
272                 inf.keySize = 24;
273                 inf.blockSize = 16;
274                 inf.opensslName = 'aes-192-' + cipher.slice(7, 10);
275                 break;
276         case 'aes256-cbc':
277         case 'aes256-ctr':
278         case 'aes256-gcm@openssh.com':
279                 inf.keySize = 32;
280                 inf.blockSize = 16;
281                 inf.opensslName = 'aes-256-' + cipher.slice(7, 10);
282                 break;
283         default:
284                 throw (new Error(
285                     'Unsupported openssl cipher "' + cipher + '"'));
286         }
287         return (inf);
288 }