twitst4tz

twitter statistics web application
Log | Files | Refs | README | LICENSE

fingerprint.js (5530B)


      1 // Copyright 2018 Joyent, Inc.
      2 
      3 module.exports = Fingerprint;
      4 
      5 var assert = require('assert-plus');
      6 var Buffer = require('safer-buffer').Buffer;
      7 var algs = require('./algs');
      8 var crypto = require('crypto');
      9 var errs = require('./errors');
     10 var Key = require('./key');
     11 var PrivateKey = require('./private-key');
     12 var Certificate = require('./certificate');
     13 var utils = require('./utils');
     14 
     15 var FingerprintFormatError = errs.FingerprintFormatError;
     16 var InvalidAlgorithmError = errs.InvalidAlgorithmError;
     17 
     18 function Fingerprint(opts) {
     19 	assert.object(opts, 'options');
     20 	assert.string(opts.type, 'options.type');
     21 	assert.buffer(opts.hash, 'options.hash');
     22 	assert.string(opts.algorithm, 'options.algorithm');
     23 
     24 	this.algorithm = opts.algorithm.toLowerCase();
     25 	if (algs.hashAlgs[this.algorithm] !== true)
     26 		throw (new InvalidAlgorithmError(this.algorithm));
     27 
     28 	this.hash = opts.hash;
     29 	this.type = opts.type;
     30 	this.hashType = opts.hashType;
     31 }
     32 
     33 Fingerprint.prototype.toString = function (format) {
     34 	if (format === undefined) {
     35 		if (this.algorithm === 'md5' || this.hashType === 'spki')
     36 			format = 'hex';
     37 		else
     38 			format = 'base64';
     39 	}
     40 	assert.string(format);
     41 
     42 	switch (format) {
     43 	case 'hex':
     44 		if (this.hashType === 'spki')
     45 			return (this.hash.toString('hex'));
     46 		return (addColons(this.hash.toString('hex')));
     47 	case 'base64':
     48 		if (this.hashType === 'spki')
     49 			return (this.hash.toString('base64'));
     50 		return (sshBase64Format(this.algorithm,
     51 		    this.hash.toString('base64')));
     52 	default:
     53 		throw (new FingerprintFormatError(undefined, format));
     54 	}
     55 };
     56 
     57 Fingerprint.prototype.matches = function (other) {
     58 	assert.object(other, 'key or certificate');
     59 	if (this.type === 'key' && this.hashType !== 'ssh') {
     60 		utils.assertCompatible(other, Key, [1, 7], 'key with spki');
     61 		if (PrivateKey.isPrivateKey(other)) {
     62 			utils.assertCompatible(other, PrivateKey, [1, 6],
     63 			    'privatekey with spki support');
     64 		}
     65 	} else if (this.type === 'key') {
     66 		utils.assertCompatible(other, Key, [1, 0], 'key');
     67 	} else {
     68 		utils.assertCompatible(other, Certificate, [1, 0],
     69 		    'certificate');
     70 	}
     71 
     72 	var theirHash = other.hash(this.algorithm, this.hashType);
     73 	var theirHash2 = crypto.createHash(this.algorithm).
     74 	    update(theirHash).digest('base64');
     75 
     76 	if (this.hash2 === undefined)
     77 		this.hash2 = crypto.createHash(this.algorithm).
     78 		    update(this.hash).digest('base64');
     79 
     80 	return (this.hash2 === theirHash2);
     81 };
     82 
     83 /*JSSTYLED*/
     84 var base64RE = /^[A-Za-z0-9+\/=]+$/;
     85 /*JSSTYLED*/
     86 var hexRE = /^[a-fA-F0-9]+$/;
     87 
     88 Fingerprint.parse = function (fp, options) {
     89 	assert.string(fp, 'fingerprint');
     90 
     91 	var alg, hash, enAlgs;
     92 	if (Array.isArray(options)) {
     93 		enAlgs = options;
     94 		options = {};
     95 	}
     96 	assert.optionalObject(options, 'options');
     97 	if (options === undefined)
     98 		options = {};
     99 	if (options.enAlgs !== undefined)
    100 		enAlgs = options.enAlgs;
    101 	if (options.algorithms !== undefined)
    102 		enAlgs = options.algorithms;
    103 	assert.optionalArrayOfString(enAlgs, 'algorithms');
    104 
    105 	var hashType = 'ssh';
    106 	if (options.hashType !== undefined)
    107 		hashType = options.hashType;
    108 	assert.string(hashType, 'options.hashType');
    109 
    110 	var parts = fp.split(':');
    111 	if (parts.length == 2) {
    112 		alg = parts[0].toLowerCase();
    113 		if (!base64RE.test(parts[1]))
    114 			throw (new FingerprintFormatError(fp));
    115 		try {
    116 			hash = Buffer.from(parts[1], 'base64');
    117 		} catch (e) {
    118 			throw (new FingerprintFormatError(fp));
    119 		}
    120 	} else if (parts.length > 2) {
    121 		alg = 'md5';
    122 		if (parts[0].toLowerCase() === 'md5')
    123 			parts = parts.slice(1);
    124 		parts = parts.map(function (p) {
    125 			while (p.length < 2)
    126 				p = '0' + p;
    127 			if (p.length > 2)
    128 				throw (new FingerprintFormatError(fp));
    129 			return (p);
    130 		});
    131 		parts = parts.join('');
    132 		if (!hexRE.test(parts) || parts.length % 2 !== 0)
    133 			throw (new FingerprintFormatError(fp));
    134 		try {
    135 			hash = Buffer.from(parts, 'hex');
    136 		} catch (e) {
    137 			throw (new FingerprintFormatError(fp));
    138 		}
    139 	} else {
    140 		if (hexRE.test(fp)) {
    141 			hash = Buffer.from(fp, 'hex');
    142 		} else if (base64RE.test(fp)) {
    143 			hash = Buffer.from(fp, 'base64');
    144 		} else {
    145 			throw (new FingerprintFormatError(fp));
    146 		}
    147 
    148 		switch (hash.length) {
    149 		case 32:
    150 			alg = 'sha256';
    151 			break;
    152 		case 16:
    153 			alg = 'md5';
    154 			break;
    155 		case 20:
    156 			alg = 'sha1';
    157 			break;
    158 		case 64:
    159 			alg = 'sha512';
    160 			break;
    161 		default:
    162 			throw (new FingerprintFormatError(fp));
    163 		}
    164 
    165 		/* Plain hex/base64: guess it's probably SPKI unless told. */
    166 		if (options.hashType === undefined)
    167 			hashType = 'spki';
    168 	}
    169 
    170 	if (alg === undefined)
    171 		throw (new FingerprintFormatError(fp));
    172 
    173 	if (algs.hashAlgs[alg] === undefined)
    174 		throw (new InvalidAlgorithmError(alg));
    175 
    176 	if (enAlgs !== undefined) {
    177 		enAlgs = enAlgs.map(function (a) { return a.toLowerCase(); });
    178 		if (enAlgs.indexOf(alg) === -1)
    179 			throw (new InvalidAlgorithmError(alg));
    180 	}
    181 
    182 	return (new Fingerprint({
    183 		algorithm: alg,
    184 		hash: hash,
    185 		type: options.type || 'key',
    186 		hashType: hashType
    187 	}));
    188 };
    189 
    190 function addColons(s) {
    191 	/*JSSTYLED*/
    192 	return (s.replace(/(.{2})(?=.)/g, '$1:'));
    193 }
    194 
    195 function base64Strip(s) {
    196 	/*JSSTYLED*/
    197 	return (s.replace(/=*$/, ''));
    198 }
    199 
    200 function sshBase64Format(alg, h) {
    201 	return (alg.toUpperCase() + ':' + base64Strip(h));
    202 }
    203 
    204 Fingerprint.isFingerprint = function (obj, ver) {
    205 	return (utils.isCompatible(obj, Fingerprint, ver));
    206 };
    207 
    208 /*
    209  * API versions for Fingerprint:
    210  * [1,0] -- initial ver
    211  * [1,1] -- first tagged ver
    212  * [1,2] -- hashType and spki support
    213  */
    214 Fingerprint.prototype._sshpkApiVersion = [1, 2];
    215 
    216 Fingerprint._oldVersionDetect = function (obj) {
    217 	assert.func(obj.toString);
    218 	assert.func(obj.matches);
    219 	return ([1, 0]);
    220 };