// Copyright 2015 Joyent, Inc.
module.exports = Fingerprint;
var assert = require('assert-plus'); var algs = require('./algs'); var crypto = require('crypto'); var errs = require('./errors'); var Key = require('./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;
}
Fingerprint.prototype.toString = function (format) {
if (format === undefined) { if (this.algorithm === 'md5') format = 'hex'; else format = 'base64'; } assert.string(format); switch (format) { case 'hex': return (addColons(this.hash.toString('hex'))); case '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') { utils.assertCompatible(other, Key, [1, 0], 'key'); } else { utils.assertCompatible(other, Certificate, [1, 0], 'certificate'); } var theirHash = other.hash(this.algorithm); 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);
};
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; assert.optionalArrayOfString(enAlgs, 'algorithms'); var parts = fp.split(':'); if (parts.length == 2) { alg = parts[0].toLowerCase(); /*JSSTYLED*/ var base64RE = /^[A-Za-z0-9+\/=]+$/; if (!base64RE.test(parts[1])) throw (new FingerprintFormatError(fp)); try { hash = new Buffer(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.join(''); /*JSSTYLED*/ var md5RE = /^[a-fA-F0-9]+$/; if (!md5RE.test(parts)) throw (new FingerprintFormatError(fp)); try { hash = new Buffer(parts, 'hex'); } catch (e) { throw (new FingerprintFormatError(fp)); } } 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' }));
};
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 */
Fingerprint.prototype._sshpkApiVersion = [1, 1];
Fingerprint._oldVersionDetect = function (obj) {
assert.func(obj.toString); assert.func(obj.matches); return ([1, 0]);
};