1// Copyright 2015 Joyent, Inc. 2 3module.exports = Fingerprint; 4 5var assert = require('assert-plus'); 6var Buffer = require('safer-buffer').Buffer; 7var algs = require('./algs'); 8var crypto = require('crypto'); 9var errs = require('./errors'); 10var Key = require('./key'); 11var Certificate = require('./certificate'); 12var utils = require('./utils'); 13 14var FingerprintFormatError = errs.FingerprintFormatError; 15var InvalidAlgorithmError = errs.InvalidAlgorithmError; 16 17function Fingerprint(opts) { 18 assert.object(opts, 'options'); 19 assert.string(opts.type, 'options.type'); 20 assert.buffer(opts.hash, 'options.hash'); 21 assert.string(opts.algorithm, 'options.algorithm'); 22 23 this.algorithm = opts.algorithm.toLowerCase(); 24 if (algs.hashAlgs[this.algorithm] !== true) 25 throw (new InvalidAlgorithmError(this.algorithm)); 26 27 this.hash = opts.hash; 28 this.type = opts.type; 29} 30 31Fingerprint.prototype.toString = function (format) { 32 if (format === undefined) { 33 if (this.algorithm === 'md5') 34 format = 'hex'; 35 else 36 format = 'base64'; 37 } 38 assert.string(format); 39 40 switch (format) { 41 case 'hex': 42 return (addColons(this.hash.toString('hex'))); 43 case 'base64': 44 return (sshBase64Format(this.algorithm, 45 this.hash.toString('base64'))); 46 default: 47 throw (new FingerprintFormatError(undefined, format)); 48 } 49}; 50 51Fingerprint.prototype.matches = function (other) { 52 assert.object(other, 'key or certificate'); 53 if (this.type === 'key') { 54 utils.assertCompatible(other, Key, [1, 0], 'key'); 55 } else { 56 utils.assertCompatible(other, Certificate, [1, 0], 57 'certificate'); 58 } 59 60 var theirHash = other.hash(this.algorithm); 61 var theirHash2 = crypto.createHash(this.algorithm). 62 update(theirHash).digest('base64'); 63 64 if (this.hash2 === undefined) 65 this.hash2 = crypto.createHash(this.algorithm). 66 update(this.hash).digest('base64'); 67 68 return (this.hash2 === theirHash2); 69}; 70 71Fingerprint.parse = function (fp, options) { 72 assert.string(fp, 'fingerprint'); 73 74 var alg, hash, enAlgs; 75 if (Array.isArray(options)) { 76 enAlgs = options; 77 options = {}; 78 } 79 assert.optionalObject(options, 'options'); 80 if (options === undefined) 81 options = {}; 82 if (options.enAlgs !== undefined) 83 enAlgs = options.enAlgs; 84 assert.optionalArrayOfString(enAlgs, 'algorithms'); 85 86 var parts = fp.split(':'); 87 if (parts.length == 2) { 88 alg = parts[0].toLowerCase(); 89 /*JSSTYLED*/ 90 var base64RE = /^[A-Za-z0-9+\/=]+$/; 91 if (!base64RE.test(parts[1])) 92 throw (new FingerprintFormatError(fp)); 93 try { 94 hash = Buffer.from(parts[1], 'base64'); 95 } catch (e) { 96 throw (new FingerprintFormatError(fp)); 97 } 98 } else if (parts.length > 2) { 99 alg = 'md5'; 100 if (parts[0].toLowerCase() === 'md5') 101 parts = parts.slice(1); 102 parts = parts.join(''); 103 /*JSSTYLED*/ 104 var md5RE = /^[a-fA-F0-9]+$/; 105 if (!md5RE.test(parts)) 106 throw (new FingerprintFormatError(fp)); 107 try { 108 hash = Buffer.from(parts, 'hex'); 109 } catch (e) { 110 throw (new FingerprintFormatError(fp)); 111 } 112 } 113 114 if (alg === undefined) 115 throw (new FingerprintFormatError(fp)); 116 117 if (algs.hashAlgs[alg] === undefined) 118 throw (new InvalidAlgorithmError(alg)); 119 120 if (enAlgs !== undefined) { 121 enAlgs = enAlgs.map(function (a) { return a.toLowerCase(); }); 122 if (enAlgs.indexOf(alg) === -1) 123 throw (new InvalidAlgorithmError(alg)); 124 } 125 126 return (new Fingerprint({ 127 algorithm: alg, 128 hash: hash, 129 type: options.type || 'key' 130 })); 131}; 132 133function addColons(s) { 134 /*JSSTYLED*/ 135 return (s.replace(/(.{2})(?=.)/g, '$1:')); 136} 137 138function base64Strip(s) { 139 /*JSSTYLED*/ 140 return (s.replace(/=*$/, '')); 141} 142 143function sshBase64Format(alg, h) { 144 return (alg.toUpperCase() + ':' + base64Strip(h)); 145} 146 147Fingerprint.isFingerprint = function (obj, ver) { 148 return (utils.isCompatible(obj, Fingerprint, ver)); 149}; 150 151/* 152 * API versions for Fingerprint: 153 * [1,0] -- initial ver 154 * [1,1] -- first tagged ver 155 */ 156Fingerprint.prototype._sshpkApiVersion = [1, 1]; 157 158Fingerprint._oldVersionDetect = function (obj) { 159 assert.func(obj.toString); 160 assert.func(obj.matches); 161 return ([1, 0]); 162}; 163