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.
162 lines
3.9 KiB
162 lines
3.9 KiB
7 years ago
|
// 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]);
|
||
|
};
|