| // Copyright 2018 Joyent, Inc. | 
|   | 
| module.exports = Key; | 
|   | 
| var assert = require('assert-plus'); | 
| var algs = require('./algs'); | 
| var crypto = require('crypto'); | 
| var Fingerprint = require('./fingerprint'); | 
| var Signature = require('./signature'); | 
| var DiffieHellman = require('./dhe').DiffieHellman; | 
| var errs = require('./errors'); | 
| var utils = require('./utils'); | 
| var PrivateKey = require('./private-key'); | 
| var edCompat; | 
|   | 
| try { | 
|     edCompat = require('./ed-compat'); | 
| } catch (e) { | 
|     /* Just continue through, and bail out if we try to use it. */ | 
| } | 
|   | 
| var InvalidAlgorithmError = errs.InvalidAlgorithmError; | 
| var KeyParseError = errs.KeyParseError; | 
|   | 
| var formats = {}; | 
| formats['auto'] = require('./formats/auto'); | 
| formats['pem'] = require('./formats/pem'); | 
| formats['pkcs1'] = require('./formats/pkcs1'); | 
| formats['pkcs8'] = require('./formats/pkcs8'); | 
| formats['rfc4253'] = require('./formats/rfc4253'); | 
| formats['ssh'] = require('./formats/ssh'); | 
| formats['ssh-private'] = require('./formats/ssh-private'); | 
| formats['openssh'] = formats['ssh-private']; | 
| formats['dnssec'] = require('./formats/dnssec'); | 
| formats['putty'] = require('./formats/putty'); | 
| formats['ppk'] = formats['putty']; | 
|   | 
| function Key(opts) { | 
|     assert.object(opts, 'options'); | 
|     assert.arrayOfObject(opts.parts, 'options.parts'); | 
|     assert.string(opts.type, 'options.type'); | 
|     assert.optionalString(opts.comment, 'options.comment'); | 
|   | 
|     var algInfo = algs.info[opts.type]; | 
|     if (typeof (algInfo) !== 'object') | 
|         throw (new InvalidAlgorithmError(opts.type)); | 
|   | 
|     var partLookup = {}; | 
|     for (var i = 0; i < opts.parts.length; ++i) { | 
|         var part = opts.parts[i]; | 
|         partLookup[part.name] = part; | 
|     } | 
|   | 
|     this.type = opts.type; | 
|     this.parts = opts.parts; | 
|     this.part = partLookup; | 
|     this.comment = undefined; | 
|     this.source = opts.source; | 
|   | 
|     /* for speeding up hashing/fingerprint operations */ | 
|     this._rfc4253Cache = opts._rfc4253Cache; | 
|     this._hashCache = {}; | 
|   | 
|     var sz; | 
|     this.curve = undefined; | 
|     if (this.type === 'ecdsa') { | 
|         var curve = this.part.curve.data.toString(); | 
|         this.curve = curve; | 
|         sz = algs.curves[curve].size; | 
|     } else if (this.type === 'ed25519' || this.type === 'curve25519') { | 
|         sz = 256; | 
|         this.curve = 'curve25519'; | 
|     } else { | 
|         var szPart = this.part[algInfo.sizePart]; | 
|         sz = szPart.data.length; | 
|         sz = sz * 8 - utils.countZeros(szPart.data); | 
|     } | 
|     this.size = sz; | 
| } | 
|   | 
| Key.formats = formats; | 
|   | 
| Key.prototype.toBuffer = function (format, options) { | 
|     if (format === undefined) | 
|         format = 'ssh'; | 
|     assert.string(format, 'format'); | 
|     assert.object(formats[format], 'formats[format]'); | 
|     assert.optionalObject(options, 'options'); | 
|   | 
|     if (format === 'rfc4253') { | 
|         if (this._rfc4253Cache === undefined) | 
|             this._rfc4253Cache = formats['rfc4253'].write(this); | 
|         return (this._rfc4253Cache); | 
|     } | 
|   | 
|     return (formats[format].write(this, options)); | 
| }; | 
|   | 
| Key.prototype.toString = function (format, options) { | 
|     return (this.toBuffer(format, options).toString()); | 
| }; | 
|   | 
| Key.prototype.hash = function (algo, type) { | 
|     assert.string(algo, 'algorithm'); | 
|     assert.optionalString(type, 'type'); | 
|     if (type === undefined) | 
|         type = 'ssh'; | 
|     algo = algo.toLowerCase(); | 
|     if (algs.hashAlgs[algo] === undefined) | 
|         throw (new InvalidAlgorithmError(algo)); | 
|   | 
|     var cacheKey = algo + '||' + type; | 
|     if (this._hashCache[cacheKey]) | 
|         return (this._hashCache[cacheKey]); | 
|   | 
|     var buf; | 
|     if (type === 'ssh') { | 
|         buf = this.toBuffer('rfc4253'); | 
|     } else if (type === 'spki') { | 
|         buf = formats.pkcs8.pkcs8ToBuffer(this); | 
|     } else { | 
|         throw (new Error('Hash type ' + type + ' not supported')); | 
|     } | 
|     var hash = crypto.createHash(algo).update(buf).digest(); | 
|     this._hashCache[cacheKey] = hash; | 
|     return (hash); | 
| }; | 
|   | 
| Key.prototype.fingerprint = function (algo, type) { | 
|     if (algo === undefined) | 
|         algo = 'sha256'; | 
|     if (type === undefined) | 
|         type = 'ssh'; | 
|     assert.string(algo, 'algorithm'); | 
|     assert.string(type, 'type'); | 
|     var opts = { | 
|         type: 'key', | 
|         hash: this.hash(algo, type), | 
|         algorithm: algo, | 
|         hashType: type | 
|     }; | 
|     return (new Fingerprint(opts)); | 
| }; | 
|   | 
| Key.prototype.defaultHashAlgorithm = function () { | 
|     var hashAlgo = 'sha1'; | 
|     if (this.type === 'rsa') | 
|         hashAlgo = 'sha256'; | 
|     if (this.type === 'dsa' && this.size > 1024) | 
|         hashAlgo = 'sha256'; | 
|     if (this.type === 'ed25519') | 
|         hashAlgo = 'sha512'; | 
|     if (this.type === 'ecdsa') { | 
|         if (this.size <= 256) | 
|             hashAlgo = 'sha256'; | 
|         else if (this.size <= 384) | 
|             hashAlgo = 'sha384'; | 
|         else | 
|             hashAlgo = 'sha512'; | 
|     } | 
|     return (hashAlgo); | 
| }; | 
|   | 
| Key.prototype.createVerify = function (hashAlgo) { | 
|     if (hashAlgo === undefined) | 
|         hashAlgo = this.defaultHashAlgorithm(); | 
|     assert.string(hashAlgo, 'hash algorithm'); | 
|   | 
|     /* ED25519 is not supported by OpenSSL, use a javascript impl. */ | 
|     if (this.type === 'ed25519' && edCompat !== undefined) | 
|         return (new edCompat.Verifier(this, hashAlgo)); | 
|     if (this.type === 'curve25519') | 
|         throw (new Error('Curve25519 keys are not suitable for ' + | 
|             'signing or verification')); | 
|   | 
|     var v, nm, err; | 
|     try { | 
|         nm = hashAlgo.toUpperCase(); | 
|         v = crypto.createVerify(nm); | 
|     } catch (e) { | 
|         err = e; | 
|     } | 
|     if (v === undefined || (err instanceof Error && | 
|         err.message.match(/Unknown message digest/))) { | 
|         nm = 'RSA-'; | 
|         nm += hashAlgo.toUpperCase(); | 
|         v = crypto.createVerify(nm); | 
|     } | 
|     assert.ok(v, 'failed to create verifier'); | 
|     var oldVerify = v.verify.bind(v); | 
|     var key = this.toBuffer('pkcs8'); | 
|     var curve = this.curve; | 
|     var self = this; | 
|     v.verify = function (signature, fmt) { | 
|         if (Signature.isSignature(signature, [2, 0])) { | 
|             if (signature.type !== self.type) | 
|                 return (false); | 
|             if (signature.hashAlgorithm && | 
|                 signature.hashAlgorithm !== hashAlgo) | 
|                 return (false); | 
|             if (signature.curve && self.type === 'ecdsa' && | 
|                 signature.curve !== curve) | 
|                 return (false); | 
|             return (oldVerify(key, signature.toBuffer('asn1'))); | 
|   | 
|         } else if (typeof (signature) === 'string' || | 
|             Buffer.isBuffer(signature)) { | 
|             return (oldVerify(key, signature, fmt)); | 
|   | 
|         /* | 
|          * Avoid doing this on valid arguments, walking the prototype | 
|          * chain can be quite slow. | 
|          */ | 
|         } else if (Signature.isSignature(signature, [1, 0])) { | 
|             throw (new Error('signature was created by too old ' + | 
|                 'a version of sshpk and cannot be verified')); | 
|   | 
|         } else { | 
|             throw (new TypeError('signature must be a string, ' + | 
|                 'Buffer, or Signature object')); | 
|         } | 
|     }; | 
|     return (v); | 
| }; | 
|   | 
| Key.prototype.createDiffieHellman = function () { | 
|     if (this.type === 'rsa') | 
|         throw (new Error('RSA keys do not support Diffie-Hellman')); | 
|   | 
|     return (new DiffieHellman(this)); | 
| }; | 
| Key.prototype.createDH = Key.prototype.createDiffieHellman; | 
|   | 
| Key.parse = function (data, format, options) { | 
|     if (typeof (data) !== 'string') | 
|         assert.buffer(data, 'data'); | 
|     if (format === undefined) | 
|         format = 'auto'; | 
|     assert.string(format, 'format'); | 
|     if (typeof (options) === 'string') | 
|         options = { filename: options }; | 
|     assert.optionalObject(options, 'options'); | 
|     if (options === undefined) | 
|         options = {}; | 
|     assert.optionalString(options.filename, 'options.filename'); | 
|     if (options.filename === undefined) | 
|         options.filename = '(unnamed)'; | 
|   | 
|     assert.object(formats[format], 'formats[format]'); | 
|   | 
|     try { | 
|         var k = formats[format].read(data, options); | 
|         if (k instanceof PrivateKey) | 
|             k = k.toPublic(); | 
|         if (!k.comment) | 
|             k.comment = options.filename; | 
|         return (k); | 
|     } catch (e) { | 
|         if (e.name === 'KeyEncryptedError') | 
|             throw (e); | 
|         throw (new KeyParseError(options.filename, format, e)); | 
|     } | 
| }; | 
|   | 
| Key.isKey = function (obj, ver) { | 
|     return (utils.isCompatible(obj, Key, ver)); | 
| }; | 
|   | 
| /* | 
|  * API versions for Key: | 
|  * [1,0] -- initial ver, may take Signature for createVerify or may not | 
|  * [1,1] -- added pkcs1, pkcs8 formats | 
|  * [1,2] -- added auto, ssh-private, openssh formats | 
|  * [1,3] -- added defaultHashAlgorithm | 
|  * [1,4] -- added ed support, createDH | 
|  * [1,5] -- first explicitly tagged version | 
|  * [1,6] -- changed ed25519 part names | 
|  * [1,7] -- spki hash types | 
|  */ | 
| Key.prototype._sshpkApiVersion = [1, 7]; | 
|   | 
| Key._oldVersionDetect = function (obj) { | 
|     assert.func(obj.toBuffer); | 
|     assert.func(obj.fingerprint); | 
|     if (obj.createDH) | 
|         return ([1, 4]); | 
|     if (obj.defaultHashAlgorithm) | 
|         return ([1, 3]); | 
|     if (obj.formats['auto']) | 
|         return ([1, 2]); | 
|     if (obj.formats['pkcs1']) | 
|         return ([1, 1]); | 
|     return ([1, 0]); | 
| }; |