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