AccessTokenEncryptor.js 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. // TODO: This file was created by bulk-decaffeinate.
  2. // Sanity-check the conversion and remove this comment.
  3. /*
  4. * decaffeinate suggestions:
  5. * DS101: Remove unnecessary use of Array.from
  6. * DS102: Remove unnecessary code created because of implicit returns
  7. * DS207: Consider shorter variations of null checks
  8. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  9. */
  10. const crypto = require('crypto');
  11. const Buffer = require('buffer').Buffer;
  12. const ALGORITHM = 'aes-256-ctr';
  13. const keyFn = (password, salt, callback)=> crypto.pbkdf2(password, salt, 10000, 64, 'sha1', callback);
  14. const keyFn32 = (password, salt, keyLength, callback)=> crypto.pbkdf2(password, salt, 10000, 32, 'sha1', callback);
  15. class AccessTokenEncryptor {
  16. constructor(settings) {
  17. this.settings = settings;
  18. this.cipherLabel = this.settings.cipherLabel;
  19. if (this.cipherLabel != null ? this.cipherLabel.match(/:/) : undefined) { throw Error("cipherLabel must not contain a colon (:)"); }
  20. this.cipherPassword = this.settings.cipherPasswords[this.cipherLabel];
  21. if ((this.cipherPassword == null)) { throw Error("cipherPassword not set"); }
  22. if (this.cipherPassword.length < 16) { throw Error("cipherPassword too short"); }
  23. }
  24. encryptJson(json, callback) {
  25. const string = JSON.stringify(json);
  26. return crypto.randomBytes(32, (err, bytes) => {
  27. if (err) { return callback(err); }
  28. const salt = bytes.slice(0, 16);
  29. const iv = bytes.slice(16, 32);
  30. return keyFn32(this.cipherPassword, salt, 32, (err, key) => {
  31. if (err != null) {
  32. logger.err({err}, "error getting Fn key");
  33. return callback(err);
  34. }
  35. const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
  36. const crypted = cipher.update(string, 'utf8', 'base64') + cipher.final('base64');
  37. return callback(null, `${this.cipherLabel}:${salt.toString('hex')}:${crypted}:${iv.toString('hex')}`);
  38. });
  39. });
  40. }
  41. decryptToJson(encryptedJson, callback) {
  42. const [label, salt, cipherText, iv] = Array.from(encryptedJson.split(':', 4));
  43. const password = this.settings.cipherPasswords[label];
  44. if ((password == null) || (password.length < 16)) { return callback(new Error("invalid password")); }
  45. if (iv) {
  46. return this.decryptToJsonV2(password, salt, cipherText, iv, callback);
  47. } else {
  48. return this.decryptToJsonV1(password, salt, cipherText, callback);
  49. }
  50. }
  51. decryptToJsonV1(password, salt, cipherText, callback) {
  52. return keyFn(password, Buffer.from(salt, 'hex'), (err, key) => {
  53. let json;
  54. if (err != null) {
  55. logger.err({err}, "error getting Fn key");
  56. return callback(err);
  57. }
  58. const decipher = crypto.createDecipher(ALGORITHM, key);
  59. const dec = decipher.update(cipherText, 'base64', 'utf8') + decipher.final('utf8');
  60. try {
  61. json = JSON.parse(dec);
  62. } catch (e) {
  63. return callback(new Error("error decrypting token"));
  64. }
  65. return callback(null, json, true);
  66. });
  67. }
  68. decryptToJsonV2(password, salt, cipherText, iv, callback) {
  69. return keyFn32(password, Buffer.from(salt, 'hex'), 32, (err, key) => {
  70. let json;
  71. if (err != null) {
  72. logger.err({err}, "error getting Fn key");
  73. return callback(err);
  74. }
  75. const decipher = crypto.createDecipheriv(ALGORITHM, key, Buffer.from(iv, 'hex'));
  76. const dec = decipher.update(cipherText, 'base64', 'utf8') + decipher.final('utf8');
  77. try {
  78. json = JSON.parse(dec);
  79. } catch (e) {
  80. return callback(new Error("error decrypting token"));
  81. }
  82. return callback(null, json);
  83. });
  84. }
  85. }
  86. module.exports = AccessTokenEncryptor;