1 // Copyright 2015 Joyent, Inc.
3 module.exports = Fingerprint;
5 var assert = require('assert-plus');
6 var algs = require('./algs');
7 var crypto = require('crypto');
8 var errs = require('./errors');
9 var Key = require('./key');
10 var Certificate = require('./certificate');
11 var utils = require('./utils');
13 var FingerprintFormatError = errs.FingerprintFormatError;
14 var InvalidAlgorithmError = errs.InvalidAlgorithmError;
16 function Fingerprint(opts) {
17 assert.object(opts, 'options');
18 assert.string(opts.type, 'options.type');
19 assert.buffer(opts.hash, 'options.hash');
20 assert.string(opts.algorithm, 'options.algorithm');
22 this.algorithm = opts.algorithm.toLowerCase();
23 if (algs.hashAlgs[this.algorithm] !== true)
24 throw (new InvalidAlgorithmError(this.algorithm));
26 this.hash = opts.hash;
27 this.type = opts.type;
30 Fingerprint.prototype.toString = function (format) {
31 if (format === undefined) {
32 if (this.algorithm === 'md5')
37 assert.string(format);
41 return (addColons(this.hash.toString('hex')));
43 return (sshBase64Format(this.algorithm,
44 this.hash.toString('base64')));
46 throw (new FingerprintFormatError(undefined, format));
50 Fingerprint.prototype.matches = function (other) {
51 assert.object(other, 'key or certificate');
52 if (this.type === 'key') {
53 utils.assertCompatible(other, Key, [1, 0], 'key');
55 utils.assertCompatible(other, Certificate, [1, 0],
59 var theirHash = other.hash(this.algorithm);
60 var theirHash2 = crypto.createHash(this.algorithm).
61 update(theirHash).digest('base64');
63 if (this.hash2 === undefined)
64 this.hash2 = crypto.createHash(this.algorithm).
65 update(this.hash).digest('base64');
67 return (this.hash2 === theirHash2);
70 Fingerprint.parse = function (fp, options) {
71 assert.string(fp, 'fingerprint');
73 var alg, hash, enAlgs;
74 if (Array.isArray(options)) {
78 assert.optionalObject(options, 'options');
79 if (options === undefined)
81 if (options.enAlgs !== undefined)
82 enAlgs = options.enAlgs;
83 assert.optionalArrayOfString(enAlgs, 'algorithms');
85 var parts = fp.split(':');
86 if (parts.length == 2) {
87 alg = parts[0].toLowerCase();
89 var base64RE = /^[A-Za-z0-9+\/=]+$/;
90 if (!base64RE.test(parts[1]))
91 throw (new FingerprintFormatError(fp));
93 hash = new Buffer(parts[1], 'base64');
95 throw (new FingerprintFormatError(fp));
97 } else if (parts.length > 2) {
99 if (parts[0].toLowerCase() === 'md5')
100 parts = parts.slice(1);
101 parts = parts.join('');
103 var md5RE = /^[a-fA-F0-9]+$/;
104 if (!md5RE.test(parts))
105 throw (new FingerprintFormatError(fp));
107 hash = new Buffer(parts, 'hex');
109 throw (new FingerprintFormatError(fp));
113 if (alg === undefined)
114 throw (new FingerprintFormatError(fp));
116 if (algs.hashAlgs[alg] === undefined)
117 throw (new InvalidAlgorithmError(alg));
119 if (enAlgs !== undefined) {
120 enAlgs = enAlgs.map(function (a) { return a.toLowerCase(); });
121 if (enAlgs.indexOf(alg) === -1)
122 throw (new InvalidAlgorithmError(alg));
125 return (new Fingerprint({
128 type: options.type || 'key'
132 function addColons(s) {
134 return (s.replace(/(.{2})(?=.)/g, '$1:'));
137 function base64Strip(s) {
139 return (s.replace(/=*$/, ''));
142 function sshBase64Format(alg, h) {
143 return (alg.toUpperCase() + ':' + base64Strip(h));
146 Fingerprint.isFingerprint = function (obj, ver) {
147 return (utils.isCompatible(obj, Fingerprint, ver));
151 * API versions for Fingerprint:
152 * [1,0] -- initial ver
153 * [1,1] -- first tagged ver
155 Fingerprint.prototype._sshpkApiVersion = [1, 1];
157 Fingerprint._oldVersionDetect = function (obj) {
158 assert.func(obj.toString);
159 assert.func(obj.matches);