fingerprint.js 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. // Copyright 2015 Joyent, Inc.
  2. module.exports = Fingerprint;
  3. var assert = require('assert-plus');
  4. var Buffer = require('safer-buffer').Buffer;
  5. var algs = require('./algs');
  6. var crypto = require('crypto');
  7. var errs = require('./errors');
  8. var Key = require('./key');
  9. var Certificate = require('./certificate');
  10. var utils = require('./utils');
  11. var FingerprintFormatError = errs.FingerprintFormatError;
  12. var InvalidAlgorithmError = errs.InvalidAlgorithmError;
  13. function Fingerprint(opts) {
  14. assert.object(opts, 'options');
  15. assert.string(opts.type, 'options.type');
  16. assert.buffer(opts.hash, 'options.hash');
  17. assert.string(opts.algorithm, 'options.algorithm');
  18. this.algorithm = opts.algorithm.toLowerCase();
  19. if (algs.hashAlgs[this.algorithm] !== true)
  20. throw (new InvalidAlgorithmError(this.algorithm));
  21. this.hash = opts.hash;
  22. this.type = opts.type;
  23. }
  24. Fingerprint.prototype.toString = function (format) {
  25. if (format === undefined) {
  26. if (this.algorithm === 'md5')
  27. format = 'hex';
  28. else
  29. format = 'base64';
  30. }
  31. assert.string(format);
  32. switch (format) {
  33. case 'hex':
  34. return (addColons(this.hash.toString('hex')));
  35. case 'base64':
  36. return (sshBase64Format(this.algorithm,
  37. this.hash.toString('base64')));
  38. default:
  39. throw (new FingerprintFormatError(undefined, format));
  40. }
  41. };
  42. Fingerprint.prototype.matches = function (other) {
  43. assert.object(other, 'key or certificate');
  44. if (this.type === 'key') {
  45. utils.assertCompatible(other, Key, [1, 0], 'key');
  46. } else {
  47. utils.assertCompatible(other, Certificate, [1, 0],
  48. 'certificate');
  49. }
  50. var theirHash = other.hash(this.algorithm);
  51. var theirHash2 = crypto.createHash(this.algorithm).
  52. update(theirHash).digest('base64');
  53. if (this.hash2 === undefined)
  54. this.hash2 = crypto.createHash(this.algorithm).
  55. update(this.hash).digest('base64');
  56. return (this.hash2 === theirHash2);
  57. };
  58. Fingerprint.parse = function (fp, options) {
  59. assert.string(fp, 'fingerprint');
  60. var alg, hash, enAlgs;
  61. if (Array.isArray(options)) {
  62. enAlgs = options;
  63. options = {};
  64. }
  65. assert.optionalObject(options, 'options');
  66. if (options === undefined)
  67. options = {};
  68. if (options.enAlgs !== undefined)
  69. enAlgs = options.enAlgs;
  70. assert.optionalArrayOfString(enAlgs, 'algorithms');
  71. var parts = fp.split(':');
  72. if (parts.length == 2) {
  73. alg = parts[0].toLowerCase();
  74. /*JSSTYLED*/
  75. var base64RE = /^[A-Za-z0-9+\/=]+$/;
  76. if (!base64RE.test(parts[1]))
  77. throw (new FingerprintFormatError(fp));
  78. try {
  79. hash = Buffer.from(parts[1], 'base64');
  80. } catch (e) {
  81. throw (new FingerprintFormatError(fp));
  82. }
  83. } else if (parts.length > 2) {
  84. alg = 'md5';
  85. if (parts[0].toLowerCase() === 'md5')
  86. parts = parts.slice(1);
  87. parts = parts.map(function (p) {
  88. while (p.length < 2)
  89. p = '0' + p;
  90. if (p.length > 2)
  91. throw (new FingerprintFormatError(fp));
  92. return (p);
  93. });
  94. parts = parts.join('');
  95. /*JSSTYLED*/
  96. var md5RE = /^[a-fA-F0-9]+$/;
  97. if (!md5RE.test(parts) || parts.length % 2 !== 0)
  98. throw (new FingerprintFormatError(fp));
  99. try {
  100. hash = Buffer.from(parts, 'hex');
  101. } catch (e) {
  102. throw (new FingerprintFormatError(fp));
  103. }
  104. }
  105. if (alg === undefined)
  106. throw (new FingerprintFormatError(fp));
  107. if (algs.hashAlgs[alg] === undefined)
  108. throw (new InvalidAlgorithmError(alg));
  109. if (enAlgs !== undefined) {
  110. enAlgs = enAlgs.map(function (a) { return a.toLowerCase(); });
  111. if (enAlgs.indexOf(alg) === -1)
  112. throw (new InvalidAlgorithmError(alg));
  113. }
  114. return (new Fingerprint({
  115. algorithm: alg,
  116. hash: hash,
  117. type: options.type || 'key'
  118. }));
  119. };
  120. function addColons(s) {
  121. /*JSSTYLED*/
  122. return (s.replace(/(.{2})(?=.)/g, '$1:'));
  123. }
  124. function base64Strip(s) {
  125. /*JSSTYLED*/
  126. return (s.replace(/=*$/, ''));
  127. }
  128. function sshBase64Format(alg, h) {
  129. return (alg.toUpperCase() + ':' + base64Strip(h));
  130. }
  131. Fingerprint.isFingerprint = function (obj, ver) {
  132. return (utils.isCompatible(obj, Fingerprint, ver));
  133. };
  134. /*
  135. * API versions for Fingerprint:
  136. * [1,0] -- initial ver
  137. * [1,1] -- first tagged ver
  138. */
  139. Fingerprint.prototype._sshpkApiVersion = [1, 1];
  140. Fingerprint._oldVersionDetect = function (obj) {
  141. assert.func(obj.toString);
  142. assert.func(obj.matches);
  143. return ([1, 0]);
  144. };