1 // Copyright 2015 Joyent, Inc.
5 var assert = require('assert-plus');
6 var algs = require('./algs');
7 var crypto = require('crypto');
8 var Fingerprint = require('./fingerprint');
9 var Signature = require('./signature');
10 var DiffieHellman = require('./dhe');
11 var errs = require('./errors');
12 var utils = require('./utils');
13 var PrivateKey = require('./private-key');
17 edCompat = require('./ed-compat');
19 /* Just continue through, and bail out if we try to use it. */
22 var InvalidAlgorithmError = errs.InvalidAlgorithmError;
23 var KeyParseError = errs.KeyParseError;
26 formats['auto'] = require('./formats/auto');
27 formats['pem'] = require('./formats/pem');
28 formats['pkcs1'] = require('./formats/pkcs1');
29 formats['pkcs8'] = require('./formats/pkcs8');
30 formats['rfc4253'] = require('./formats/rfc4253');
31 formats['ssh'] = require('./formats/ssh');
32 formats['ssh-private'] = require('./formats/ssh-private');
33 formats['openssh'] = formats['ssh-private'];
36 assert.object(opts, 'options');
37 assert.arrayOfObject(opts.parts, 'options.parts');
38 assert.string(opts.type, 'options.type');
39 assert.optionalString(opts.comment, 'options.comment');
41 var algInfo = algs.info[opts.type];
42 if (typeof (algInfo) !== 'object')
43 throw (new InvalidAlgorithmError(opts.type));
46 for (var i = 0; i < opts.parts.length; ++i) {
47 var part = opts.parts[i];
48 partLookup[part.name] = part;
51 this.type = opts.type;
52 this.parts = opts.parts;
53 this.part = partLookup;
54 this.comment = undefined;
55 this.source = opts.source;
57 /* for speeding up hashing/fingerprint operations */
58 this._rfc4253Cache = opts._rfc4253Cache;
62 this.curve = undefined;
63 if (this.type === 'ecdsa') {
64 var curve = this.part.curve.data.toString();
66 sz = algs.curves[curve].size;
67 } else if (this.type === 'ed25519') {
69 this.curve = 'curve25519';
71 var szPart = this.part[algInfo.sizePart];
72 sz = szPart.data.length;
73 sz = sz * 8 - utils.countZeros(szPart.data);
78 Key.formats = formats;
80 Key.prototype.toBuffer = function (format, options) {
81 if (format === undefined)
83 assert.string(format, 'format');
84 assert.object(formats[format], 'formats[format]');
85 assert.optionalObject(options, 'options');
87 if (format === 'rfc4253') {
88 if (this._rfc4253Cache === undefined)
89 this._rfc4253Cache = formats['rfc4253'].write(this);
90 return (this._rfc4253Cache);
93 return (formats[format].write(this, options));
96 Key.prototype.toString = function (format, options) {
97 return (this.toBuffer(format, options).toString());
100 Key.prototype.hash = function (algo) {
101 assert.string(algo, 'algorithm');
102 algo = algo.toLowerCase();
103 if (algs.hashAlgs[algo] === undefined)
104 throw (new InvalidAlgorithmError(algo));
106 if (this._hashCache[algo])
107 return (this._hashCache[algo]);
109 var hash = crypto.createHash(algo).
110 update(this.toBuffer('rfc4253')).digest();
111 this._hashCache[algo] = hash;
115 Key.prototype.fingerprint = function (algo) {
116 if (algo === undefined)
118 assert.string(algo, 'algorithm');
121 hash: this.hash(algo),
124 return (new Fingerprint(opts));
127 Key.prototype.defaultHashAlgorithm = function () {
128 var hashAlgo = 'sha1';
129 if (this.type === 'rsa')
131 if (this.type === 'dsa' && this.size > 1024)
133 if (this.type === 'ed25519')
135 if (this.type === 'ecdsa') {
136 if (this.size <= 256)
138 else if (this.size <= 384)
146 Key.prototype.createVerify = function (hashAlgo) {
147 if (hashAlgo === undefined)
148 hashAlgo = this.defaultHashAlgorithm();
149 assert.string(hashAlgo, 'hash algorithm');
151 /* ED25519 is not supported by OpenSSL, use a javascript impl. */
152 if (this.type === 'ed25519' && edCompat !== undefined)
153 return (new edCompat.Verifier(this, hashAlgo));
154 if (this.type === 'curve25519')
155 throw (new Error('Curve25519 keys are not suitable for ' +
156 'signing or verification'));
160 nm = hashAlgo.toUpperCase();
161 v = crypto.createVerify(nm);
165 if (v === undefined || (err instanceof Error &&
166 err.message.match(/Unknown message digest/))) {
168 nm += hashAlgo.toUpperCase();
169 v = crypto.createVerify(nm);
171 assert.ok(v, 'failed to create verifier');
172 var oldVerify = v.verify.bind(v);
173 var key = this.toBuffer('pkcs8');
175 v.verify = function (signature, fmt) {
176 if (Signature.isSignature(signature, [2, 0])) {
177 if (signature.type !== self.type)
179 if (signature.hashAlgorithm &&
180 signature.hashAlgorithm !== hashAlgo)
182 return (oldVerify(key, signature.toBuffer('asn1')));
184 } else if (typeof (signature) === 'string' ||
185 Buffer.isBuffer(signature)) {
186 return (oldVerify(key, signature, fmt));
189 * Avoid doing this on valid arguments, walking the prototype
190 * chain can be quite slow.
192 } else if (Signature.isSignature(signature, [1, 0])) {
193 throw (new Error('signature was created by too old ' +
194 'a version of sshpk and cannot be verified'));
197 throw (new TypeError('signature must be a string, ' +
198 'Buffer, or Signature object'));
204 Key.prototype.createDiffieHellman = function () {
205 if (this.type === 'rsa')
206 throw (new Error('RSA keys do not support Diffie-Hellman'));
208 return (new DiffieHellman(this));
210 Key.prototype.createDH = Key.prototype.createDiffieHellman;
212 Key.parse = function (data, format, options) {
213 if (typeof (data) !== 'string')
214 assert.buffer(data, 'data');
215 if (format === undefined)
217 assert.string(format, 'format');
218 if (typeof (options) === 'string')
219 options = { filename: options };
220 assert.optionalObject(options, 'options');
221 if (options === undefined)
223 assert.optionalString(options.filename, 'options.filename');
224 if (options.filename === undefined)
225 options.filename = '(unnamed)';
227 assert.object(formats[format], 'formats[format]');
230 var k = formats[format].read(data, options);
231 if (k instanceof PrivateKey)
234 k.comment = options.filename;
237 if (e.name === 'KeyEncryptedError')
239 throw (new KeyParseError(options.filename, format, e));
243 Key.isKey = function (obj, ver) {
244 return (utils.isCompatible(obj, Key, ver));
248 * API versions for Key:
249 * [1,0] -- initial ver, may take Signature for createVerify or may not
250 * [1,1] -- added pkcs1, pkcs8 formats
251 * [1,2] -- added auto, ssh-private, openssh formats
252 * [1,3] -- added defaultHashAlgorithm
253 * [1,4] -- added ed support, createDH
254 * [1,5] -- first explicitly tagged version
256 Key.prototype._sshpkApiVersion = [1, 5];
258 Key._oldVersionDetect = function (obj) {
259 assert.func(obj.toBuffer);
260 assert.func(obj.fingerprint);
263 if (obj.defaultHashAlgorithm)
265 if (obj.formats['auto'])
267 if (obj.formats['pkcs1'])