UNPKG

9.83 kBJavaScriptView Raw
1// Copyright 2015 Joyent, Inc.
2
3module.exports = {
4 bufferSplit: bufferSplit,
5 addRSAMissing: addRSAMissing,
6 calculateDSAPublic: calculateDSAPublic,
7 calculateED25519Public: calculateED25519Public,
8 calculateX25519Public: calculateX25519Public,
9 mpNormalize: mpNormalize,
10 mpDenormalize: mpDenormalize,
11 ecNormalize: ecNormalize,
12 countZeros: countZeros,
13 assertCompatible: assertCompatible,
14 isCompatible: isCompatible,
15 opensslKeyDeriv: opensslKeyDeriv,
16 opensshCipherInfo: opensshCipherInfo,
17 publicFromPrivateECDSA: publicFromPrivateECDSA,
18 zeroPadToLength: zeroPadToLength,
19 writeBitString: writeBitString,
20 readBitString: readBitString,
21 pbkdf2: pbkdf2
22};
23
24var assert = require('assert-plus');
25var Buffer = require('safer-buffer').Buffer;
26var PrivateKey = require('./private-key');
27var Key = require('./key');
28var crypto = require('crypto');
29var algs = require('./algs');
30var asn1 = require('asn1');
31
32var ec = require('ecc-jsbn/lib/ec');
33var jsbn = require('jsbn').BigInteger;
34var nacl = require('tweetnacl');
35
36var MAX_CLASS_DEPTH = 3;
37
38function isCompatible(obj, klass, needVer) {
39 if (obj === null || typeof (obj) !== 'object')
40 return (false);
41 if (needVer === undefined)
42 needVer = klass.prototype._sshpkApiVersion;
43 if (obj instanceof klass &&
44 klass.prototype._sshpkApiVersion[0] == needVer[0])
45 return (true);
46 var proto = Object.getPrototypeOf(obj);
47 var depth = 0;
48 while (proto.constructor.name !== klass.name) {
49 proto = Object.getPrototypeOf(proto);
50 if (!proto || ++depth > MAX_CLASS_DEPTH)
51 return (false);
52 }
53 if (proto.constructor.name !== klass.name)
54 return (false);
55 var ver = proto._sshpkApiVersion;
56 if (ver === undefined)
57 ver = klass._oldVersionDetect(obj);
58 if (ver[0] != needVer[0] || ver[1] < needVer[1])
59 return (false);
60 return (true);
61}
62
63function assertCompatible(obj, klass, needVer, name) {
64 if (name === undefined)
65 name = 'object';
66 assert.ok(obj, name + ' must not be null');
67 assert.object(obj, name + ' must be an object');
68 if (needVer === undefined)
69 needVer = klass.prototype._sshpkApiVersion;
70 if (obj instanceof klass &&
71 klass.prototype._sshpkApiVersion[0] == needVer[0])
72 return;
73 var proto = Object.getPrototypeOf(obj);
74 var depth = 0;
75 while (proto.constructor.name !== klass.name) {
76 proto = Object.getPrototypeOf(proto);
77 assert.ok(proto && ++depth <= MAX_CLASS_DEPTH,
78 name + ' must be a ' + klass.name + ' instance');
79 }
80 assert.strictEqual(proto.constructor.name, klass.name,
81 name + ' must be a ' + klass.name + ' instance');
82 var ver = proto._sshpkApiVersion;
83 if (ver === undefined)
84 ver = klass._oldVersionDetect(obj);
85 assert.ok(ver[0] == needVer[0] && ver[1] >= needVer[1],
86 name + ' must be compatible with ' + klass.name + ' klass ' +
87 'version ' + needVer[0] + '.' + needVer[1]);
88}
89
90var CIPHER_LEN = {
91 'des-ede3-cbc': { key: 24, iv: 8 },
92 'aes-128-cbc': { key: 16, iv: 16 },
93 'aes-256-cbc': { key: 32, iv: 16 }
94};
95var PKCS5_SALT_LEN = 8;
96
97function opensslKeyDeriv(cipher, salt, passphrase, count) {
98 assert.buffer(salt, 'salt');
99 assert.buffer(passphrase, 'passphrase');
100 assert.number(count, 'iteration count');
101
102 var clen = CIPHER_LEN[cipher];
103 assert.object(clen, 'supported cipher');
104
105 salt = salt.slice(0, PKCS5_SALT_LEN);
106
107 var D, D_prev, bufs;
108 var material = Buffer.alloc(0);
109 while (material.length < clen.key + clen.iv) {
110 bufs = [];
111 if (D_prev)
112 bufs.push(D_prev);
113 bufs.push(passphrase);
114 bufs.push(salt);
115 D = Buffer.concat(bufs);
116 for (var j = 0; j < count; ++j)
117 D = crypto.createHash('md5').update(D).digest();
118 material = Buffer.concat([material, D]);
119 D_prev = D;
120 }
121
122 return ({
123 key: material.slice(0, clen.key),
124 iv: material.slice(clen.key, clen.key + clen.iv)
125 });
126}
127
128/* See: RFC2898 */
129function pbkdf2(hashAlg, salt, iterations, size, passphrase) {
130 var hkey = Buffer.alloc(salt.length + 4);
131 salt.copy(hkey);
132
133 var gen = 0, ts = [];
134 var i = 1;
135 while (gen < size) {
136 var t = T(i++);
137 gen += t.length;
138 ts.push(t);
139 }
140 return (Buffer.concat(ts).slice(0, size));
141
142 function T(I) {
143 hkey.writeUInt32BE(I, hkey.length - 4);
144
145 var hmac = crypto.createHmac(hashAlg, passphrase);
146 hmac.update(hkey);
147
148 var Ti = hmac.digest();
149 var Uc = Ti;
150 var c = 1;
151 while (c++ < iterations) {
152 hmac = crypto.createHmac(hashAlg, passphrase);
153 hmac.update(Uc);
154 Uc = hmac.digest();
155 for (var x = 0; x < Ti.length; ++x)
156 Ti[x] ^= Uc[x];
157 }
158 return (Ti);
159 }
160}
161
162/* Count leading zero bits on a buffer */
163function countZeros(buf) {
164 var o = 0, obit = 8;
165 while (o < buf.length) {
166 var mask = (1 << obit);
167 if ((buf[o] & mask) === mask)
168 break;
169 obit--;
170 if (obit < 0) {
171 o++;
172 obit = 8;
173 }
174 }
175 return (o*8 + (8 - obit) - 1);
176}
177
178function bufferSplit(buf, chr) {
179 assert.buffer(buf);
180 assert.string(chr);
181
182 var parts = [];
183 var lastPart = 0;
184 var matches = 0;
185 for (var i = 0; i < buf.length; ++i) {
186 if (buf[i] === chr.charCodeAt(matches))
187 ++matches;
188 else if (buf[i] === chr.charCodeAt(0))
189 matches = 1;
190 else
191 matches = 0;
192
193 if (matches >= chr.length) {
194 var newPart = i + 1;
195 parts.push(buf.slice(lastPart, newPart - matches));
196 lastPart = newPart;
197 matches = 0;
198 }
199 }
200 if (lastPart <= buf.length)
201 parts.push(buf.slice(lastPart, buf.length));
202
203 return (parts);
204}
205
206function ecNormalize(buf, addZero) {
207 assert.buffer(buf);
208 if (buf[0] === 0x00 && buf[1] === 0x04) {
209 if (addZero)
210 return (buf);
211 return (buf.slice(1));
212 } else if (buf[0] === 0x04) {
213 if (!addZero)
214 return (buf);
215 } else {
216 while (buf[0] === 0x00)
217 buf = buf.slice(1);
218 if (buf[0] === 0x02 || buf[0] === 0x03)
219 throw (new Error('Compressed elliptic curve points ' +
220 'are not supported'));
221 if (buf[0] !== 0x04)
222 throw (new Error('Not a valid elliptic curve point'));
223 if (!addZero)
224 return (buf);
225 }
226 var b = Buffer.alloc(buf.length + 1);
227 b[0] = 0x0;
228 buf.copy(b, 1);
229 return (b);
230}
231
232function readBitString(der, tag) {
233 if (tag === undefined)
234 tag = asn1.Ber.BitString;
235 var buf = der.readString(tag, true);
236 assert.strictEqual(buf[0], 0x00, 'bit strings with unused bits are ' +
237 'not supported (0x' + buf[0].toString(16) + ')');
238 return (buf.slice(1));
239}
240
241function writeBitString(der, buf, tag) {
242 if (tag === undefined)
243 tag = asn1.Ber.BitString;
244 var b = Buffer.alloc(buf.length + 1);
245 b[0] = 0x00;
246 buf.copy(b, 1);
247 der.writeBuffer(b, tag);
248}
249
250function mpNormalize(buf) {
251 assert.buffer(buf);
252 while (buf.length > 1 && buf[0] === 0x00 && (buf[1] & 0x80) === 0x00)
253 buf = buf.slice(1);
254 if ((buf[0] & 0x80) === 0x80) {
255 var b = Buffer.alloc(buf.length + 1);
256 b[0] = 0x00;
257 buf.copy(b, 1);
258 buf = b;
259 }
260 return (buf);
261}
262
263function mpDenormalize(buf) {
264 assert.buffer(buf);
265 while (buf.length > 1 && buf[0] === 0x00)
266 buf = buf.slice(1);
267 return (buf);
268}
269
270function zeroPadToLength(buf, len) {
271 assert.buffer(buf);
272 assert.number(len);
273 while (buf.length > len) {
274 assert.equal(buf[0], 0x00);
275 buf = buf.slice(1);
276 }
277 while (buf.length < len) {
278 var b = Buffer.alloc(buf.length + 1);
279 b[0] = 0x00;
280 buf.copy(b, 1);
281 buf = b;
282 }
283 return (buf);
284}
285
286function bigintToMpBuf(bigint) {
287 var buf = Buffer.from(bigint.toByteArray());
288 buf = mpNormalize(buf);
289 return (buf);
290}
291
292function calculateDSAPublic(g, p, x) {
293 assert.buffer(g);
294 assert.buffer(p);
295 assert.buffer(x);
296 g = new jsbn(g);
297 p = new jsbn(p);
298 x = new jsbn(x);
299 var y = g.modPow(x, p);
300 var ybuf = bigintToMpBuf(y);
301 return (ybuf);
302}
303
304function calculateED25519Public(k) {
305 assert.buffer(k);
306
307 var kp = nacl.sign.keyPair.fromSeed(new Uint8Array(k));
308 return (Buffer.from(kp.publicKey));
309}
310
311function calculateX25519Public(k) {
312 assert.buffer(k);
313
314 var kp = nacl.box.keyPair.fromSeed(new Uint8Array(k));
315 return (Buffer.from(kp.publicKey));
316}
317
318function addRSAMissing(key) {
319 assert.object(key);
320 assertCompatible(key, PrivateKey, [1, 1]);
321
322 var d = new jsbn(key.part.d.data);
323 var buf;
324
325 if (!key.part.dmodp) {
326 var p = new jsbn(key.part.p.data);
327 var dmodp = d.mod(p.subtract(1));
328
329 buf = bigintToMpBuf(dmodp);
330 key.part.dmodp = {name: 'dmodp', data: buf};
331 key.parts.push(key.part.dmodp);
332 }
333 if (!key.part.dmodq) {
334 var q = new jsbn(key.part.q.data);
335 var dmodq = d.mod(q.subtract(1));
336
337 buf = bigintToMpBuf(dmodq);
338 key.part.dmodq = {name: 'dmodq', data: buf};
339 key.parts.push(key.part.dmodq);
340 }
341}
342
343function publicFromPrivateECDSA(curveName, priv) {
344 assert.string(curveName, 'curveName');
345 assert.buffer(priv);
346 var params = algs.curves[curveName];
347 var p = new jsbn(params.p);
348 var a = new jsbn(params.a);
349 var b = new jsbn(params.b);
350 var curve = new ec.ECCurveFp(p, a, b);
351 var G = curve.decodePointHex(params.G.toString('hex'));
352
353 var d = new jsbn(mpNormalize(priv));
354 var pub = G.multiply(d);
355 pub = Buffer.from(curve.encodePointHex(pub), 'hex');
356
357 var parts = [];
358 parts.push({name: 'curve', data: Buffer.from(curveName)});
359 parts.push({name: 'Q', data: pub});
360
361 var key = new Key({type: 'ecdsa', curve: curve, parts: parts});
362 return (key);
363}
364
365function opensshCipherInfo(cipher) {
366 var inf = {};
367 switch (cipher) {
368 case '3des-cbc':
369 inf.keySize = 24;
370 inf.blockSize = 8;
371 inf.opensslName = 'des-ede3-cbc';
372 break;
373 case 'blowfish-cbc':
374 inf.keySize = 16;
375 inf.blockSize = 8;
376 inf.opensslName = 'bf-cbc';
377 break;
378 case 'aes128-cbc':
379 case 'aes128-ctr':
380 case 'aes128-gcm@openssh.com':
381 inf.keySize = 16;
382 inf.blockSize = 16;
383 inf.opensslName = 'aes-128-' + cipher.slice(7, 10);
384 break;
385 case 'aes192-cbc':
386 case 'aes192-ctr':
387 case 'aes192-gcm@openssh.com':
388 inf.keySize = 24;
389 inf.blockSize = 16;
390 inf.opensslName = 'aes-192-' + cipher.slice(7, 10);
391 break;
392 case 'aes256-cbc':
393 case 'aes256-ctr':
394 case 'aes256-gcm@openssh.com':
395 inf.keySize = 32;
396 inf.blockSize = 16;
397 inf.opensslName = 'aes-256-' + cipher.slice(7, 10);
398 break;
399 default:
400 throw (new Error(
401 'Unsupported openssl cipher "' + cipher + '"'));
402 }
403 return (inf);
404}
405
\No newline at end of file