You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
254 lines
6.5 KiB
254 lines
6.5 KiB
// Copyright 2017 Joyent, Inc. |
|
|
|
module.exports = PrivateKey; |
|
|
|
var assert = require('assert-plus'); |
|
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']; |
|
|
|
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.r.data; |
|
if (priv[0] === 0x00) |
|
priv = priv.slice(1); |
|
priv = priv.slice(0, 32); |
|
|
|
pair = nacl.box.keyPair.fromSecretKey(new Uint8Array(priv)); |
|
pub = new Buffer(pair.publicKey); |
|
priv = Buffer.concat([priv, pub]); |
|
|
|
return (new PrivateKey({ |
|
type: 'curve25519', |
|
parts: [ |
|
{ name: 'R', data: utils.mpNormalize(pub) }, |
|
{ name: 'r', data: priv } |
|
] |
|
})); |
|
} else if (this.type === 'curve25519' && newType === 'ed25519') { |
|
if (nacl === undefined) |
|
nacl = require('tweetnacl'); |
|
|
|
priv = this.part.r.data; |
|
if (priv[0] === 0x00) |
|
priv = priv.slice(1); |
|
priv = priv.slice(0, 32); |
|
|
|
pair = nacl.sign.keyPair.fromSeed(new Uint8Array(priv)); |
|
pub = new Buffer(pair.publicKey); |
|
priv = Buffer.concat([priv, pub]); |
|
|
|
return (new PrivateKey({ |
|
type: 'ed25519', |
|
parts: [ |
|
{ name: 'R', data: utils.mpNormalize(pub) }, |
|
{ name: 'r', data: 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 = new Buffer(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 |
|
*/ |
|
PrivateKey.prototype._sshpkApiVersion = [1, 4]; |
|
|
|
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]); |
|
};
|
|
|