1 // Copyright 2015 Joyent, Inc.
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
16 var assert = require('assert-plus');
17 var PrivateKey = require('./private-key');
18 var crypto = require('crypto');
20 var MAX_CLASS_DEPTH = 3;
22 function isCompatible(obj, klass, needVer) {
23 if (obj === null || typeof (obj) !== 'object')
25 if (needVer === undefined)
26 needVer = klass.prototype._sshpkApiVersion;
27 if (obj instanceof klass &&
28 klass.prototype._sshpkApiVersion[0] == needVer[0])
30 var proto = Object.getPrototypeOf(obj);
32 while (proto.constructor.name !== klass.name) {
33 proto = Object.getPrototypeOf(proto);
34 if (!proto || ++depth > MAX_CLASS_DEPTH)
37 if (proto.constructor.name !== klass.name)
39 var ver = proto._sshpkApiVersion;
40 if (ver === undefined)
41 ver = klass._oldVersionDetect(obj);
42 if (ver[0] != needVer[0] || ver[1] < needVer[1])
47 function assertCompatible(obj, klass, needVer, name) {
48 if (name === undefined)
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])
57 var proto = Object.getPrototypeOf(obj);
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');
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]);
75 'des-ede3-cbc': { key: 7, iv: 8 },
76 'aes-128-cbc': { key: 16, iv: 16 }
78 var PKCS5_SALT_LEN = 8;
80 function opensslKeyDeriv(cipher, salt, passphrase, count) {
81 assert.buffer(salt, 'salt');
82 assert.buffer(passphrase, 'passphrase');
83 assert.number(count, 'iteration count');
85 var clen = CIPHER_LEN[cipher];
86 assert.object(clen, 'supported cipher');
88 salt = salt.slice(0, PKCS5_SALT_LEN);
91 var material = new Buffer(0);
92 while (material.length < clen.key + clen.iv) {
96 bufs.push(passphrase);
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]);
106 key: material.slice(0, clen.key),
107 iv: material.slice(clen.key, clen.key + clen.iv)
111 /* Count leading zero bits on a buffer */
112 function countZeros(buf) {
114 while (o < buf.length) {
115 var mask = (1 << obit);
116 if ((buf[o] & mask) === mask)
124 return (o*8 + (8 - obit) - 1);
127 function bufferSplit(buf, chr) {
134 for (var i = 0; i < buf.length; ++i) {
135 if (buf[i] === chr.charCodeAt(matches))
137 else if (buf[i] === chr.charCodeAt(0))
142 if (matches >= chr.length) {
144 parts.push(buf.slice(lastPart, newPart - matches));
149 if (lastPart <= buf.length)
150 parts.push(buf.slice(lastPart, buf.length));
155 function ecNormalize(buf, addZero) {
157 if (buf[0] === 0x00 && buf[1] === 0x04) {
160 return (buf.slice(1));
161 } else if (buf[0] === 0x04) {
165 while (buf[0] === 0x00)
167 if (buf[0] === 0x02 || buf[0] === 0x03)
168 throw (new Error('Compressed elliptic curve points ' +
169 'are not supported'));
171 throw (new Error('Not a valid elliptic curve point'));
175 var b = new Buffer(buf.length + 1);
181 function mpNormalize(buf) {
183 while (buf.length > 1 && buf[0] === 0x00 && (buf[1] & 0x80) === 0x00)
185 if ((buf[0] & 0x80) === 0x80) {
186 var b = new Buffer(buf.length + 1);
194 function bigintToMpBuf(bigint) {
195 var buf = new Buffer(bigint.toByteArray());
196 buf = mpNormalize(buf);
200 function calculateDSAPublic(g, p, x) {
205 var bigInt = require('jsbn').BigInteger;
207 throw (new Error('To load a PKCS#8 format DSA private key, ' +
208 'the node jsbn library is required.'));
213 var y = g.modPow(x, p);
214 var ybuf = bigintToMpBuf(y);
218 function addRSAMissing(key) {
220 assertCompatible(key, PrivateKey, [1, 1]);
222 var bigInt = require('jsbn').BigInteger;
224 throw (new Error('To write a PEM private key from ' +
225 'this source, the node jsbn lib is required.'));
228 var d = new bigInt(key.part.d.data);
231 if (!key.part.dmodp) {
232 var p = new bigInt(key.part.p.data);
233 var dmodp = d.mod(p.subtract(1));
235 buf = bigintToMpBuf(dmodp);
236 key.part.dmodp = {name: 'dmodp', data: buf};
237 key.parts.push(key.part.dmodp);
239 if (!key.part.dmodq) {
240 var q = new bigInt(key.part.q.data);
241 var dmodq = d.mod(q.subtract(1));
243 buf = bigintToMpBuf(dmodq);
244 key.part.dmodq = {name: 'dmodq', data: buf};
245 key.parts.push(key.part.dmodq);
249 function opensshCipherInfo(cipher) {
255 inf.opensslName = 'des-ede3-cbc';
260 inf.opensslName = 'bf-cbc';
264 case 'aes128-gcm@openssh.com':
267 inf.opensslName = 'aes-128-' + cipher.slice(7, 10);
271 case 'aes192-gcm@openssh.com':
274 inf.opensslName = 'aes-192-' + cipher.slice(7, 10);
278 case 'aes256-gcm@openssh.com':
281 inf.opensslName = 'aes-256-' + cipher.slice(7, 10);
285 'Unsupported openssl cipher "' + cipher + '"'));