• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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