AccessTokenEncryptor.js 2.4 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889
  1. const crypto = require('crypto')
  2. const logger = require('@overleaf/logger')
  3. const ALGORITHM = 'aes-256-ctr'
  4. const keyFn32 = (password, salt, keyLength, callback) =>
  5. crypto.pbkdf2(password, salt, 10000, 32, 'sha1', callback)
  6. class AccessTokenEncryptor {
  7. constructor(settings) {
  8. this.settings = settings
  9. this.cipherLabel = this.settings.cipherLabel
  10. if (this.cipherLabel && this.cipherLabel.match(/:/)) {
  11. throw Error('cipherLabel must not contain a colon (:)')
  12. }
  13. this.cipherPassword = this.settings.cipherPasswords[this.cipherLabel]
  14. if (!this.cipherPassword) {
  15. throw Error('cipherPassword not set')
  16. }
  17. if (this.cipherPassword.length < 16) {
  18. throw Error('cipherPassword too short')
  19. }
  20. }
  21. encryptJson(json, callback) {
  22. const string = JSON.stringify(json)
  23. crypto.randomBytes(32, (err, bytes) => {
  24. if (err) {
  25. return callback(err)
  26. }
  27. const salt = bytes.slice(0, 16)
  28. const iv = bytes.slice(16, 32)
  29. keyFn32(this.cipherPassword, salt, 32, (err, key) => {
  30. if (err) {
  31. logger.err({ err }, 'error getting Fn key')
  32. return callback(err)
  33. }
  34. const cipher = crypto.createCipheriv(ALGORITHM, key, iv)
  35. const crypted =
  36. cipher.update(string, 'utf8', 'base64') + cipher.final('base64')
  37. callback(
  38. null,
  39. `${this.cipherLabel}:${salt.toString('hex')}:${crypted}:${iv.toString(
  40. 'hex'
  41. )}`
  42. )
  43. })
  44. })
  45. }
  46. decryptToJson(encryptedJson, callback) {
  47. const [label, salt, cipherText, iv] = encryptedJson.split(':', 4)
  48. const password = this.settings.cipherPasswords[label]
  49. if (!password || password.length < 16) {
  50. return callback(new Error('invalid password'))
  51. }
  52. if (!iv) {
  53. return callback(new Error('token scheme v1 is not supported anymore'))
  54. }
  55. keyFn32(password, Buffer.from(salt, 'hex'), 32, (err, key) => {
  56. let json
  57. if (err) {
  58. logger.err({ err }, 'error getting Fn key')
  59. return callback(err)
  60. }
  61. const decipher = crypto.createDecipheriv(
  62. ALGORITHM,
  63. key,
  64. Buffer.from(iv, 'hex')
  65. )
  66. const dec =
  67. decipher.update(cipherText, 'base64', 'utf8') + decipher.final('utf8')
  68. try {
  69. json = JSON.parse(dec)
  70. } catch (e) {
  71. return callback(new Error('error decrypting token'))
  72. }
  73. callback(null, json)
  74. })
  75. }
  76. }
  77. module.exports = AccessTokenEncryptor