| // Copyright 2017 Joyent, Inc. |
| |
| module.exports = PrivateKey; |
| |
| var assert = require('assert-plus'); |
| var Buffer = require('safer-buffer').Buffer; |
| var algs = require('./algs'); |
| var crypto = require('crypto'); |
| var Fingerprint = require('./fingerprint'); |
| var Signature = require('./signature'); |
| var errs = require('./errors'); |
| var util = require('util'); |
| var utils = require('./utils'); |
| var dhe = require('./dhe'); |
| var generateECDSA = dhe.generateECDSA; |
| var generateED25519 = dhe.generateED25519; |
| var edCompat; |
| var nacl; |
| |
| try { |
| edCompat = require('./ed-compat'); |
| } catch (e) { |
| /* Just continue through, and bail out if we try to use it. */ |
| } |
| |
| var Key = require('./key'); |
| |
| var InvalidAlgorithmError = errs.InvalidAlgorithmError; |
| var KeyParseError = errs.KeyParseError; |
| var KeyEncryptedError = errs.KeyEncryptedError; |
| |
| 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-private'] = require('./formats/ssh-private'); |
| formats['openssh'] = formats['ssh-private']; |
| formats['ssh'] = formats['ssh-private']; |
| formats['dnssec'] = require('./formats/dnssec'); |
| |
| function PrivateKey(opts) { |
| assert.object(opts, 'options'); |
| Key.call(this, opts); |
| |
| this._pubCache = undefined; |
| } |
| util.inherits(PrivateKey, Key); |
| |
| PrivateKey.formats = formats; |
| |
| PrivateKey.prototype.toBuffer = function (format, options) { |
| if (format === undefined) |
| format = 'pkcs1'; |
| assert.string(format, 'format'); |
| assert.object(formats[format], 'formats[format]'); |
| assert.optionalObject(options, 'options'); |
| |
| return (formats[format].write(this, options)); |
| }; |
| |
| PrivateKey.prototype.hash = function (algo) { |
| return (this.toPublic().hash(algo)); |
| }; |
| |
| PrivateKey.prototype.toPublic = function () { |
| if (this._pubCache) |
| return (this._pubCache); |
| |
| var algInfo = algs.info[this.type]; |
| var pubParts = []; |
| for (var i = 0; i < algInfo.parts.length; ++i) { |
| var p = algInfo.parts[i]; |
| pubParts.push(this.part[p]); |
| } |
| |
| this._pubCache = new Key({ |
| type: this.type, |
| source: this, |
| parts: pubParts |
| }); |
| if (this.comment) |
| this._pubCache.comment = this.comment; |
| return (this._pubCache); |
| }; |
| |
| PrivateKey.prototype.derive = function (newType) { |
| assert.string(newType, 'type'); |
| var priv, pub, pair; |
| |
| if (this.type === 'ed25519' && newType === 'curve25519') { |
| if (nacl === undefined) |
| nacl = require('tweetnacl'); |
| |
| priv = this.part.k.data; |
| if (priv[0] === 0x00) |
| priv = priv.slice(1); |
| |
| pair = nacl.box.keyPair.fromSecretKey(new Uint8Array(priv)); |
| pub = Buffer.from(pair.publicKey); |
| |
| return (new PrivateKey({ |
| type: 'curve25519', |
| parts: [ |
| { name: 'A', data: utils.mpNormalize(pub) }, |
| { name: 'k', data: utils.mpNormalize(priv) } |
| ] |
| })); |
| } else if (this.type === 'curve25519' && newType === 'ed25519') { |
| if (nacl === undefined) |
| nacl = require('tweetnacl'); |
| |
| priv = this.part.k.data; |
| if (priv[0] === 0x00) |
| priv = priv.slice(1); |
| |
| pair = nacl.sign.keyPair.fromSeed(new Uint8Array(priv)); |
| pub = Buffer.from(pair.publicKey); |
| |
| return (new PrivateKey({ |
| type: 'ed25519', |
| parts: [ |
| { name: 'A', data: utils.mpNormalize(pub) }, |
| { name: 'k', data: utils.mpNormalize(priv) } |
| ] |
| })); |
| } |
| throw (new Error('Key derivation not supported from ' + this.type + |
| ' to ' + newType)); |
| }; |
| |
| PrivateKey.prototype.createVerify = function (hashAlgo) { |
| return (this.toPublic().createVerify(hashAlgo)); |
| }; |
| |
| PrivateKey.prototype.createSign = 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.Signer(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.createSign(nm); |
| } catch (e) { |
| err = e; |
| } |
| if (v === undefined || (err instanceof Error && |
| err.message.match(/Unknown message digest/))) { |
| nm = 'RSA-'; |
| nm += hashAlgo.toUpperCase(); |
| v = crypto.createSign(nm); |
| } |
| assert.ok(v, 'failed to create verifier'); |
| var oldSign = v.sign.bind(v); |
| var key = this.toBuffer('pkcs1'); |
| var type = this.type; |
| var curve = this.curve; |
| v.sign = function () { |
| var sig = oldSign(key); |
| if (typeof (sig) === 'string') |
| sig = Buffer.from(sig, 'binary'); |
| sig = Signature.parse(sig, type, 'asn1'); |
| sig.hashAlgorithm = hashAlgo; |
| sig.curve = curve; |
| return (sig); |
| }; |
| return (v); |
| }; |
| |
| PrivateKey.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); |
| assert.ok(k instanceof PrivateKey, 'key is not a private key'); |
| if (!k.comment) |
| k.comment = options.filename; |
| return (k); |
| } catch (e) { |
| if (e.name === 'KeyEncryptedError') |
| throw (e); |
| throw (new KeyParseError(options.filename, format, e)); |
| } |
| }; |
| |
| PrivateKey.isPrivateKey = function (obj, ver) { |
| return (utils.isCompatible(obj, PrivateKey, ver)); |
| }; |
| |
| PrivateKey.generate = function (type, options) { |
| if (options === undefined) |
| options = {}; |
| assert.object(options, 'options'); |
| |
| switch (type) { |
| case 'ecdsa': |
| if (options.curve === undefined) |
| options.curve = 'nistp256'; |
| assert.string(options.curve, 'options.curve'); |
| return (generateECDSA(options.curve)); |
| case 'ed25519': |
| return (generateED25519()); |
| default: |
| throw (new Error('Key generation not supported with key ' + |
| 'type "' + type + '"')); |
| } |
| }; |
| |
| /* |
| * API versions for PrivateKey: |
| * [1,0] -- initial ver |
| * [1,1] -- added auto, pkcs[18], openssh/ssh-private formats |
| * [1,2] -- added defaultHashAlgorithm |
| * [1,3] -- added derive, ed, createDH |
| * [1,4] -- first tagged version |
| * [1,5] -- changed ed25519 part names and format |
| */ |
| PrivateKey.prototype._sshpkApiVersion = [1, 5]; |
| |
| PrivateKey._oldVersionDetect = function (obj) { |
| assert.func(obj.toPublic); |
| assert.func(obj.createSign); |
| if (obj.derive) |
| return ([1, 3]); |
| if (obj.defaultHashAlgorithm) |
| return ([1, 2]); |
| if (obj.formats['auto']) |
| return ([1, 1]); |
| return ([1, 0]); |
| }; |