key.js (8117B)
1 // Copyright 2018 Joyent, Inc. 2 3 module.exports = Key; 4 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').DiffieHellman; 11 var errs = require('./errors'); 12 var utils = require('./utils'); 13 var PrivateKey = require('./private-key'); 14 var edCompat; 15 16 try { 17 edCompat = require('./ed-compat'); 18 } catch (e) { 19 /* Just continue through, and bail out if we try to use it. */ 20 } 21 22 var InvalidAlgorithmError = errs.InvalidAlgorithmError; 23 var KeyParseError = errs.KeyParseError; 24 25 var formats = {}; 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']; 34 formats['dnssec'] = require('./formats/dnssec'); 35 formats['putty'] = require('./formats/putty'); 36 formats['ppk'] = formats['putty']; 37 38 function Key(opts) { 39 assert.object(opts, 'options'); 40 assert.arrayOfObject(opts.parts, 'options.parts'); 41 assert.string(opts.type, 'options.type'); 42 assert.optionalString(opts.comment, 'options.comment'); 43 44 var algInfo = algs.info[opts.type]; 45 if (typeof (algInfo) !== 'object') 46 throw (new InvalidAlgorithmError(opts.type)); 47 48 var partLookup = {}; 49 for (var i = 0; i < opts.parts.length; ++i) { 50 var part = opts.parts[i]; 51 partLookup[part.name] = part; 52 } 53 54 this.type = opts.type; 55 this.parts = opts.parts; 56 this.part = partLookup; 57 this.comment = undefined; 58 this.source = opts.source; 59 60 /* for speeding up hashing/fingerprint operations */ 61 this._rfc4253Cache = opts._rfc4253Cache; 62 this._hashCache = {}; 63 64 var sz; 65 this.curve = undefined; 66 if (this.type === 'ecdsa') { 67 var curve = this.part.curve.data.toString(); 68 this.curve = curve; 69 sz = algs.curves[curve].size; 70 } else if (this.type === 'ed25519' || this.type === 'curve25519') { 71 sz = 256; 72 this.curve = 'curve25519'; 73 } else { 74 var szPart = this.part[algInfo.sizePart]; 75 sz = szPart.data.length; 76 sz = sz * 8 - utils.countZeros(szPart.data); 77 } 78 this.size = sz; 79 } 80 81 Key.formats = formats; 82 83 Key.prototype.toBuffer = function (format, options) { 84 if (format === undefined) 85 format = 'ssh'; 86 assert.string(format, 'format'); 87 assert.object(formats[format], 'formats[format]'); 88 assert.optionalObject(options, 'options'); 89 90 if (format === 'rfc4253') { 91 if (this._rfc4253Cache === undefined) 92 this._rfc4253Cache = formats['rfc4253'].write(this); 93 return (this._rfc4253Cache); 94 } 95 96 return (formats[format].write(this, options)); 97 }; 98 99 Key.prototype.toString = function (format, options) { 100 return (this.toBuffer(format, options).toString()); 101 }; 102 103 Key.prototype.hash = function (algo, type) { 104 assert.string(algo, 'algorithm'); 105 assert.optionalString(type, 'type'); 106 if (type === undefined) 107 type = 'ssh'; 108 algo = algo.toLowerCase(); 109 if (algs.hashAlgs[algo] === undefined) 110 throw (new InvalidAlgorithmError(algo)); 111 112 var cacheKey = algo + '||' + type; 113 if (this._hashCache[cacheKey]) 114 return (this._hashCache[cacheKey]); 115 116 var buf; 117 if (type === 'ssh') { 118 buf = this.toBuffer('rfc4253'); 119 } else if (type === 'spki') { 120 buf = formats.pkcs8.pkcs8ToBuffer(this); 121 } else { 122 throw (new Error('Hash type ' + type + ' not supported')); 123 } 124 var hash = crypto.createHash(algo).update(buf).digest(); 125 this._hashCache[cacheKey] = hash; 126 return (hash); 127 }; 128 129 Key.prototype.fingerprint = function (algo, type) { 130 if (algo === undefined) 131 algo = 'sha256'; 132 if (type === undefined) 133 type = 'ssh'; 134 assert.string(algo, 'algorithm'); 135 assert.string(type, 'type'); 136 var opts = { 137 type: 'key', 138 hash: this.hash(algo, type), 139 algorithm: algo, 140 hashType: type 141 }; 142 return (new Fingerprint(opts)); 143 }; 144 145 Key.prototype.defaultHashAlgorithm = function () { 146 var hashAlgo = 'sha1'; 147 if (this.type === 'rsa') 148 hashAlgo = 'sha256'; 149 if (this.type === 'dsa' && this.size > 1024) 150 hashAlgo = 'sha256'; 151 if (this.type === 'ed25519') 152 hashAlgo = 'sha512'; 153 if (this.type === 'ecdsa') { 154 if (this.size <= 256) 155 hashAlgo = 'sha256'; 156 else if (this.size <= 384) 157 hashAlgo = 'sha384'; 158 else 159 hashAlgo = 'sha512'; 160 } 161 return (hashAlgo); 162 }; 163 164 Key.prototype.createVerify = function (hashAlgo) { 165 if (hashAlgo === undefined) 166 hashAlgo = this.defaultHashAlgorithm(); 167 assert.string(hashAlgo, 'hash algorithm'); 168 169 /* ED25519 is not supported by OpenSSL, use a javascript impl. */ 170 if (this.type === 'ed25519' && edCompat !== undefined) 171 return (new edCompat.Verifier(this, hashAlgo)); 172 if (this.type === 'curve25519') 173 throw (new Error('Curve25519 keys are not suitable for ' + 174 'signing or verification')); 175 176 var v, nm, err; 177 try { 178 nm = hashAlgo.toUpperCase(); 179 v = crypto.createVerify(nm); 180 } catch (e) { 181 err = e; 182 } 183 if (v === undefined || (err instanceof Error && 184 err.message.match(/Unknown message digest/))) { 185 nm = 'RSA-'; 186 nm += hashAlgo.toUpperCase(); 187 v = crypto.createVerify(nm); 188 } 189 assert.ok(v, 'failed to create verifier'); 190 var oldVerify = v.verify.bind(v); 191 var key = this.toBuffer('pkcs8'); 192 var curve = this.curve; 193 var self = this; 194 v.verify = function (signature, fmt) { 195 if (Signature.isSignature(signature, [2, 0])) { 196 if (signature.type !== self.type) 197 return (false); 198 if (signature.hashAlgorithm && 199 signature.hashAlgorithm !== hashAlgo) 200 return (false); 201 if (signature.curve && self.type === 'ecdsa' && 202 signature.curve !== curve) 203 return (false); 204 return (oldVerify(key, signature.toBuffer('asn1'))); 205 206 } else if (typeof (signature) === 'string' || 207 Buffer.isBuffer(signature)) { 208 return (oldVerify(key, signature, fmt)); 209 210 /* 211 * Avoid doing this on valid arguments, walking the prototype 212 * chain can be quite slow. 213 */ 214 } else if (Signature.isSignature(signature, [1, 0])) { 215 throw (new Error('signature was created by too old ' + 216 'a version of sshpk and cannot be verified')); 217 218 } else { 219 throw (new TypeError('signature must be a string, ' + 220 'Buffer, or Signature object')); 221 } 222 }; 223 return (v); 224 }; 225 226 Key.prototype.createDiffieHellman = function () { 227 if (this.type === 'rsa') 228 throw (new Error('RSA keys do not support Diffie-Hellman')); 229 230 return (new DiffieHellman(this)); 231 }; 232 Key.prototype.createDH = Key.prototype.createDiffieHellman; 233 234 Key.parse = function (data, format, options) { 235 if (typeof (data) !== 'string') 236 assert.buffer(data, 'data'); 237 if (format === undefined) 238 format = 'auto'; 239 assert.string(format, 'format'); 240 if (typeof (options) === 'string') 241 options = { filename: options }; 242 assert.optionalObject(options, 'options'); 243 if (options === undefined) 244 options = {}; 245 assert.optionalString(options.filename, 'options.filename'); 246 if (options.filename === undefined) 247 options.filename = '(unnamed)'; 248 249 assert.object(formats[format], 'formats[format]'); 250 251 try { 252 var k = formats[format].read(data, options); 253 if (k instanceof PrivateKey) 254 k = k.toPublic(); 255 if (!k.comment) 256 k.comment = options.filename; 257 return (k); 258 } catch (e) { 259 if (e.name === 'KeyEncryptedError') 260 throw (e); 261 throw (new KeyParseError(options.filename, format, e)); 262 } 263 }; 264 265 Key.isKey = function (obj, ver) { 266 return (utils.isCompatible(obj, Key, ver)); 267 }; 268 269 /* 270 * API versions for Key: 271 * [1,0] -- initial ver, may take Signature for createVerify or may not 272 * [1,1] -- added pkcs1, pkcs8 formats 273 * [1,2] -- added auto, ssh-private, openssh formats 274 * [1,3] -- added defaultHashAlgorithm 275 * [1,4] -- added ed support, createDH 276 * [1,5] -- first explicitly tagged version 277 * [1,6] -- changed ed25519 part names 278 * [1,7] -- spki hash types 279 */ 280 Key.prototype._sshpkApiVersion = [1, 7]; 281 282 Key._oldVersionDetect = function (obj) { 283 assert.func(obj.toBuffer); 284 assert.func(obj.fingerprint); 285 if (obj.createDH) 286 return ([1, 4]); 287 if (obj.defaultHashAlgorithm) 288 return ([1, 3]); 289 if (obj.formats['auto']) 290 return ([1, 2]); 291 if (obj.formats['pkcs1']) 292 return ([1, 1]); 293 return ([1, 0]); 294 };