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.
220 lines
5.4 KiB
220 lines
5.4 KiB
// Copyright 2018 Joyent, Inc. |
|
|
|
module.exports = Fingerprint; |
|
|
|
var assert = require('assert-plus'); |
|
var Buffer = require('safer-buffer').Buffer; |
|
var algs = require('./algs'); |
|
var crypto = require('crypto'); |
|
var errs = require('./errors'); |
|
var Key = require('./key'); |
|
var PrivateKey = require('./private-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; |
|
this.hashType = opts.hashType; |
|
} |
|
|
|
Fingerprint.prototype.toString = function (format) { |
|
if (format === undefined) { |
|
if (this.algorithm === 'md5' || this.hashType === 'spki') |
|
format = 'hex'; |
|
else |
|
format = 'base64'; |
|
} |
|
assert.string(format); |
|
|
|
switch (format) { |
|
case 'hex': |
|
if (this.hashType === 'spki') |
|
return (this.hash.toString('hex')); |
|
return (addColons(this.hash.toString('hex'))); |
|
case 'base64': |
|
if (this.hashType === 'spki') |
|
return (this.hash.toString('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' && this.hashType !== 'ssh') { |
|
utils.assertCompatible(other, Key, [1, 7], 'key with spki'); |
|
if (PrivateKey.isPrivateKey(other)) { |
|
utils.assertCompatible(other, PrivateKey, [1, 6], |
|
'privatekey with spki support'); |
|
} |
|
} else 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, this.hashType); |
|
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); |
|
}; |
|
|
|
/*JSSTYLED*/ |
|
var base64RE = /^[A-Za-z0-9+\/=]+$/; |
|
/*JSSTYLED*/ |
|
var hexRE = /^[a-fA-F0-9]+$/; |
|
|
|
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; |
|
if (options.algorithms !== undefined) |
|
enAlgs = options.algorithms; |
|
assert.optionalArrayOfString(enAlgs, 'algorithms'); |
|
|
|
var hashType = 'ssh'; |
|
if (options.hashType !== undefined) |
|
hashType = options.hashType; |
|
assert.string(hashType, 'options.hashType'); |
|
|
|
var parts = fp.split(':'); |
|
if (parts.length == 2) { |
|
alg = parts[0].toLowerCase(); |
|
if (!base64RE.test(parts[1])) |
|
throw (new FingerprintFormatError(fp)); |
|
try { |
|
hash = Buffer.from(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.map(function (p) { |
|
while (p.length < 2) |
|
p = '0' + p; |
|
if (p.length > 2) |
|
throw (new FingerprintFormatError(fp)); |
|
return (p); |
|
}); |
|
parts = parts.join(''); |
|
if (!hexRE.test(parts) || parts.length % 2 !== 0) |
|
throw (new FingerprintFormatError(fp)); |
|
try { |
|
hash = Buffer.from(parts, 'hex'); |
|
} catch (e) { |
|
throw (new FingerprintFormatError(fp)); |
|
} |
|
} else { |
|
if (hexRE.test(fp)) { |
|
hash = Buffer.from(fp, 'hex'); |
|
} else if (base64RE.test(fp)) { |
|
hash = Buffer.from(fp, 'base64'); |
|
} else { |
|
throw (new FingerprintFormatError(fp)); |
|
} |
|
|
|
switch (hash.length) { |
|
case 32: |
|
alg = 'sha256'; |
|
break; |
|
case 16: |
|
alg = 'md5'; |
|
break; |
|
case 20: |
|
alg = 'sha1'; |
|
break; |
|
case 64: |
|
alg = 'sha512'; |
|
break; |
|
default: |
|
throw (new FingerprintFormatError(fp)); |
|
} |
|
|
|
/* Plain hex/base64: guess it's probably SPKI unless told. */ |
|
if (options.hashType === undefined) |
|
hashType = 'spki'; |
|
} |
|
|
|
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', |
|
hashType: hashType |
|
})); |
|
}; |
|
|
|
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 |
|
* [1,2] -- hashType and spki support |
|
*/ |
|
Fingerprint.prototype._sshpkApiVersion = [1, 2]; |
|
|
|
Fingerprint._oldVersionDetect = function (obj) { |
|
assert.func(obj.toString); |
|
assert.func(obj.matches); |
|
return ([1, 0]); |
|
};
|
|
|