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.
		
		
		
		
		
			
		
			
				
					
					
						
							246 lines
						
					
					
						
							6.6 KiB
						
					
					
				
			
		
		
	
	
							246 lines
						
					
					
						
							6.6 KiB
						
					
					
				// 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 = require('./ed-compat'); | 
						|
var nacl = require('tweetnacl'); | 
						|
 | 
						|
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, type) { | 
						|
	return (this.toPublic().hash(algo, type)); | 
						|
}; | 
						|
 | 
						|
PrivateKey.prototype.fingerprint = function (algo, type) { | 
						|
	return (this.toPublic().fingerprint(algo, type)); | 
						|
}; | 
						|
 | 
						|
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') { | 
						|
		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') { | 
						|
		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 | 
						|
 * [1,6] -- type arguments for hash() and fingerprint() | 
						|
 */ | 
						|
PrivateKey.prototype._sshpkApiVersion = [1, 6]; | 
						|
 | 
						|
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]); | 
						|
};
 | 
						|
 |