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.
		
		
		
		
		
			
		
			
				
					221 lines
				
				5.4 KiB
			
		
		
			
		
	
	
					221 lines
				
				5.4 KiB
			| 
								 
											3 years ago
										 
									 | 
							
								// Copyright 2018 Joyent, Inc.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								module.exports = Fingerprint;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								var assert = require('assert-plus');
							 | 
						||
| 
								 | 
							
								var Buffer = require('safer-buffer').Buffer;
							 | 
						||
| 
								 | 
							
								var algs = require('./algs');
							 | 
						||
| 
								 | 
							
								var crypto = require('crypto');
							 | 
						||
| 
								 | 
							
								var errs = require('./errors');
							 | 
						||
| 
								 | 
							
								var Key = require('./key');
							 | 
						||
| 
								 | 
							
								var PrivateKey = require('./private-key');
							 | 
						||
| 
								 | 
							
								var Certificate = require('./certificate');
							 | 
						||
| 
								 | 
							
								var utils = require('./utils');
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								var FingerprintFormatError = errs.FingerprintFormatError;
							 | 
						||
| 
								 | 
							
								var InvalidAlgorithmError = errs.InvalidAlgorithmError;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function Fingerprint(opts) {
							 | 
						||
| 
								 | 
							
									assert.object(opts, 'options');
							 | 
						||
| 
								 | 
							
									assert.string(opts.type, 'options.type');
							 | 
						||
| 
								 | 
							
									assert.buffer(opts.hash, 'options.hash');
							 | 
						||
| 
								 | 
							
									assert.string(opts.algorithm, 'options.algorithm');
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									this.algorithm = opts.algorithm.toLowerCase();
							 | 
						||
| 
								 | 
							
									if (algs.hashAlgs[this.algorithm] !== true)
							 | 
						||
| 
								 | 
							
										throw (new InvalidAlgorithmError(this.algorithm));
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									this.hash = opts.hash;
							 | 
						||
| 
								 | 
							
									this.type = opts.type;
							 | 
						||
| 
								 | 
							
									this.hashType = opts.hashType;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Fingerprint.prototype.toString = function (format) {
							 | 
						||
| 
								 | 
							
									if (format === undefined) {
							 | 
						||
| 
								 | 
							
										if (this.algorithm === 'md5' || this.hashType === 'spki')
							 | 
						||
| 
								 | 
							
											format = 'hex';
							 | 
						||
| 
								 | 
							
										else
							 | 
						||
| 
								 | 
							
											format = 'base64';
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									assert.string(format);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									switch (format) {
							 | 
						||
| 
								 | 
							
									case 'hex':
							 | 
						||
| 
								 | 
							
										if (this.hashType === 'spki')
							 | 
						||
| 
								 | 
							
											return (this.hash.toString('hex'));
							 | 
						||
| 
								 | 
							
										return (addColons(this.hash.toString('hex')));
							 | 
						||
| 
								 | 
							
									case 'base64':
							 | 
						||
| 
								 | 
							
										if (this.hashType === 'spki')
							 | 
						||
| 
								 | 
							
											return (this.hash.toString('base64'));
							 | 
						||
| 
								 | 
							
										return (sshBase64Format(this.algorithm,
							 | 
						||
| 
								 | 
							
										    this.hash.toString('base64')));
							 | 
						||
| 
								 | 
							
									default:
							 | 
						||
| 
								 | 
							
										throw (new FingerprintFormatError(undefined, format));
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Fingerprint.prototype.matches = function (other) {
							 | 
						||
| 
								 | 
							
									assert.object(other, 'key or certificate');
							 | 
						||
| 
								 | 
							
									if (this.type === 'key' && this.hashType !== 'ssh') {
							 | 
						||
| 
								 | 
							
										utils.assertCompatible(other, Key, [1, 7], 'key with spki');
							 | 
						||
| 
								 | 
							
										if (PrivateKey.isPrivateKey(other)) {
							 | 
						||
| 
								 | 
							
											utils.assertCompatible(other, PrivateKey, [1, 6],
							 | 
						||
| 
								 | 
							
											    'privatekey with spki support');
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
									} else if (this.type === 'key') {
							 | 
						||
| 
								 | 
							
										utils.assertCompatible(other, Key, [1, 0], 'key');
							 | 
						||
| 
								 | 
							
									} else {
							 | 
						||
| 
								 | 
							
										utils.assertCompatible(other, Certificate, [1, 0],
							 | 
						||
| 
								 | 
							
										    'certificate');
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									var theirHash = other.hash(this.algorithm, this.hashType);
							 | 
						||
| 
								 | 
							
									var theirHash2 = crypto.createHash(this.algorithm).
							 | 
						||
| 
								 | 
							
									    update(theirHash).digest('base64');
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									if (this.hash2 === undefined)
							 | 
						||
| 
								 | 
							
										this.hash2 = crypto.createHash(this.algorithm).
							 | 
						||
| 
								 | 
							
										    update(this.hash).digest('base64');
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									return (this.hash2 === theirHash2);
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/*JSSTYLED*/
							 | 
						||
| 
								 | 
							
								var base64RE = /^[A-Za-z0-9+\/=]+$/;
							 | 
						||
| 
								 | 
							
								/*JSSTYLED*/
							 | 
						||
| 
								 | 
							
								var hexRE = /^[a-fA-F0-9]+$/;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Fingerprint.parse = function (fp, options) {
							 | 
						||
| 
								 | 
							
									assert.string(fp, 'fingerprint');
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									var alg, hash, enAlgs;
							 | 
						||
| 
								 | 
							
									if (Array.isArray(options)) {
							 | 
						||
| 
								 | 
							
										enAlgs = options;
							 | 
						||
| 
								 | 
							
										options = {};
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									assert.optionalObject(options, 'options');
							 | 
						||
| 
								 | 
							
									if (options === undefined)
							 | 
						||
| 
								 | 
							
										options = {};
							 | 
						||
| 
								 | 
							
									if (options.enAlgs !== undefined)
							 | 
						||
| 
								 | 
							
										enAlgs = options.enAlgs;
							 | 
						||
| 
								 | 
							
									if (options.algorithms !== undefined)
							 | 
						||
| 
								 | 
							
										enAlgs = options.algorithms;
							 | 
						||
| 
								 | 
							
									assert.optionalArrayOfString(enAlgs, 'algorithms');
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									var hashType = 'ssh';
							 | 
						||
| 
								 | 
							
									if (options.hashType !== undefined)
							 | 
						||
| 
								 | 
							
										hashType = options.hashType;
							 | 
						||
| 
								 | 
							
									assert.string(hashType, 'options.hashType');
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									var parts = fp.split(':');
							 | 
						||
| 
								 | 
							
									if (parts.length == 2) {
							 | 
						||
| 
								 | 
							
										alg = parts[0].toLowerCase();
							 | 
						||
| 
								 | 
							
										if (!base64RE.test(parts[1]))
							 | 
						||
| 
								 | 
							
											throw (new FingerprintFormatError(fp));
							 | 
						||
| 
								 | 
							
										try {
							 | 
						||
| 
								 | 
							
											hash = Buffer.from(parts[1], 'base64');
							 | 
						||
| 
								 | 
							
										} catch (e) {
							 | 
						||
| 
								 | 
							
											throw (new FingerprintFormatError(fp));
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
									} else if (parts.length > 2) {
							 | 
						||
| 
								 | 
							
										alg = 'md5';
							 | 
						||
| 
								 | 
							
										if (parts[0].toLowerCase() === 'md5')
							 | 
						||
| 
								 | 
							
											parts = parts.slice(1);
							 | 
						||
| 
								 | 
							
										parts = parts.map(function (p) {
							 | 
						||
| 
								 | 
							
											while (p.length < 2)
							 | 
						||
| 
								 | 
							
												p = '0' + p;
							 | 
						||
| 
								 | 
							
											if (p.length > 2)
							 | 
						||
| 
								 | 
							
												throw (new FingerprintFormatError(fp));
							 | 
						||
| 
								 | 
							
											return (p);
							 | 
						||
| 
								 | 
							
										});
							 | 
						||
| 
								 | 
							
										parts = parts.join('');
							 | 
						||
| 
								 | 
							
										if (!hexRE.test(parts) || parts.length % 2 !== 0)
							 | 
						||
| 
								 | 
							
											throw (new FingerprintFormatError(fp));
							 | 
						||
| 
								 | 
							
										try {
							 | 
						||
| 
								 | 
							
											hash = Buffer.from(parts, 'hex');
							 | 
						||
| 
								 | 
							
										} catch (e) {
							 | 
						||
| 
								 | 
							
											throw (new FingerprintFormatError(fp));
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
									} else {
							 | 
						||
| 
								 | 
							
										if (hexRE.test(fp)) {
							 | 
						||
| 
								 | 
							
											hash = Buffer.from(fp, 'hex');
							 | 
						||
| 
								 | 
							
										} else if (base64RE.test(fp)) {
							 | 
						||
| 
								 | 
							
											hash = Buffer.from(fp, 'base64');
							 | 
						||
| 
								 | 
							
										} else {
							 | 
						||
| 
								 | 
							
											throw (new FingerprintFormatError(fp));
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										switch (hash.length) {
							 | 
						||
| 
								 | 
							
										case 32:
							 | 
						||
| 
								 | 
							
											alg = 'sha256';
							 | 
						||
| 
								 | 
							
											break;
							 | 
						||
| 
								 | 
							
										case 16:
							 | 
						||
| 
								 | 
							
											alg = 'md5';
							 | 
						||
| 
								 | 
							
											break;
							 | 
						||
| 
								 | 
							
										case 20:
							 | 
						||
| 
								 | 
							
											alg = 'sha1';
							 | 
						||
| 
								 | 
							
											break;
							 | 
						||
| 
								 | 
							
										case 64:
							 | 
						||
| 
								 | 
							
											alg = 'sha512';
							 | 
						||
| 
								 | 
							
											break;
							 | 
						||
| 
								 | 
							
										default:
							 | 
						||
| 
								 | 
							
											throw (new FingerprintFormatError(fp));
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										/* Plain hex/base64: guess it's probably SPKI unless told. */
							 | 
						||
| 
								 | 
							
										if (options.hashType === undefined)
							 | 
						||
| 
								 | 
							
											hashType = 'spki';
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									if (alg === undefined)
							 | 
						||
| 
								 | 
							
										throw (new FingerprintFormatError(fp));
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									if (algs.hashAlgs[alg] === undefined)
							 | 
						||
| 
								 | 
							
										throw (new InvalidAlgorithmError(alg));
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									if (enAlgs !== undefined) {
							 | 
						||
| 
								 | 
							
										enAlgs = enAlgs.map(function (a) { return a.toLowerCase(); });
							 | 
						||
| 
								 | 
							
										if (enAlgs.indexOf(alg) === -1)
							 | 
						||
| 
								 | 
							
											throw (new InvalidAlgorithmError(alg));
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									return (new Fingerprint({
							 | 
						||
| 
								 | 
							
										algorithm: alg,
							 | 
						||
| 
								 | 
							
										hash: hash,
							 | 
						||
| 
								 | 
							
										type: options.type || 'key',
							 | 
						||
| 
								 | 
							
										hashType: hashType
							 | 
						||
| 
								 | 
							
									}));
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function addColons(s) {
							 | 
						||
| 
								 | 
							
									/*JSSTYLED*/
							 | 
						||
| 
								 | 
							
									return (s.replace(/(.{2})(?=.)/g, '$1:'));
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function base64Strip(s) {
							 | 
						||
| 
								 | 
							
									/*JSSTYLED*/
							 | 
						||
| 
								 | 
							
									return (s.replace(/=*$/, ''));
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function sshBase64Format(alg, h) {
							 | 
						||
| 
								 | 
							
									return (alg.toUpperCase() + ':' + base64Strip(h));
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Fingerprint.isFingerprint = function (obj, ver) {
							 | 
						||
| 
								 | 
							
									return (utils.isCompatible(obj, Fingerprint, ver));
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/*
							 | 
						||
| 
								 | 
							
								 * API versions for Fingerprint:
							 | 
						||
| 
								 | 
							
								 * [1,0] -- initial ver
							 | 
						||
| 
								 | 
							
								 * [1,1] -- first tagged ver
							 | 
						||
| 
								 | 
							
								 * [1,2] -- hashType and spki support
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								Fingerprint.prototype._sshpkApiVersion = [1, 2];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Fingerprint._oldVersionDetect = function (obj) {
							 | 
						||
| 
								 | 
							
									assert.func(obj.toString);
							 | 
						||
| 
								 | 
							
									assert.func(obj.matches);
							 | 
						||
| 
								 | 
							
									return ([1, 0]);
							 | 
						||
| 
								 | 
							
								};
							 |