letsencrypt.js 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. const ACME = require('acme')
  2. const Keypairs = require('@root/keypairs')
  3. const _ = require('lodash')
  4. const moment = require('moment')
  5. const CSR = require('@root/csr')
  6. const PEM = require('@root/pem')
  7. const punycode = require('punycode/')
  8. /* global WIKI */
  9. module.exports = {
  10. apiDirectory: WIKI.dev ? 'https://acme-staging-v02.api.letsencrypt.org/directory' : 'https://acme-v02.api.letsencrypt.org/directory',
  11. acme: null,
  12. async init () {
  13. if (!_.get(WIKI.config, 'letsencrypt.payload', false)) {
  14. await this.requestCertificate()
  15. } else if (WIKI.config.letsencrypt.domain !== WIKI.config.ssl.domain) {
  16. WIKI.logger.info(`(LETSENCRYPT) Domain has changed. Requesting new certificates...`)
  17. await this.requestCertificate()
  18. } else if (moment(WIKI.config.letsencrypt.payload.expires).isSameOrBefore(moment().add(5, 'days'))) {
  19. WIKI.logger.info(`(LETSENCRYPT) Certificate is about to or has expired, requesting a new one...`)
  20. await this.requestCertificate()
  21. } else {
  22. WIKI.logger.info(`(LETSENCRYPT) Using existing certificate for ${WIKI.config.ssl.domain}, expires on ${WIKI.config.letsencrypt.payload.expires}: [ OK ]`)
  23. }
  24. WIKI.config.ssl.format = 'pem'
  25. WIKI.config.ssl.inline = true
  26. WIKI.config.ssl.key = WIKI.config.letsencrypt.serverKey
  27. WIKI.config.ssl.cert = WIKI.config.letsencrypt.payload.cert + '\n' + WIKI.config.letsencrypt.payload.chain
  28. WIKI.config.ssl.passphrase = null
  29. WIKI.config.ssl.dhparam = null
  30. },
  31. async requestCertificate () {
  32. try {
  33. WIKI.logger.info(`(LETSENCRYPT) Initializing Let's Encrypt client...`)
  34. this.acme = ACME.create({
  35. maintainerEmail: WIKI.config.maintainerEmail,
  36. packageAgent: `wikijs/${WIKI.version}`,
  37. notify: (ev, msg) => {
  38. if (_.includes(['warning', 'error'], ev)) {
  39. WIKI.logger.warn(`${ev}: ${msg}`)
  40. } else {
  41. WIKI.logger.debug(`${ev}: ${JSON.stringify(msg)}`)
  42. }
  43. }
  44. })
  45. await this.acme.init(this.apiDirectory)
  46. // -> Create ACME Subscriber account
  47. if (!_.get(WIKI.config, 'letsencrypt.account', false)) {
  48. WIKI.logger.info(`(LETSENCRYPT) Setting up account for the first time...`)
  49. const accountKeypair = await Keypairs.generate({ kty: 'EC', format: 'jwk' })
  50. const account = await this.acme.accounts.create({
  51. subscriberEmail: WIKI.config.ssl.subscriberEmail,
  52. agreeToTerms: true,
  53. accountKey: accountKeypair.private
  54. })
  55. WIKI.config.letsencrypt = {
  56. accountKeypair: accountKeypair,
  57. account: account,
  58. domain: WIKI.config.ssl.domain
  59. }
  60. await WIKI.configSvc.saveToDb(['letsencrypt'])
  61. WIKI.logger.info(`(LETSENCRYPT) Account was setup successfully [ OK ]`)
  62. }
  63. // -> Create Server Keypair
  64. if (!WIKI.config.letsencrypt.serverKey) {
  65. WIKI.logger.info(`(LETSENCRYPT) Generating server keypairs...`)
  66. const serverKeypair = await Keypairs.generate({ kty: 'RSA', format: 'jwk' })
  67. WIKI.config.letsencrypt.serverKey = await Keypairs.export({ jwk: serverKeypair.private })
  68. WIKI.logger.info(`(LETSENCRYPT) Server keypairs generated successfully [ OK ]`)
  69. }
  70. // -> Create CSR
  71. WIKI.logger.info(`(LETSENCRYPT) Generating certificate signing request (CSR)...`)
  72. const domains = [ punycode.toASCII(WIKI.config.ssl.domain) ]
  73. const serverKey = await Keypairs.import({ pem: WIKI.config.letsencrypt.serverKey })
  74. const csrDer = await CSR.csr({ jwk: serverKey, domains, encoding: 'der' })
  75. const csr = PEM.packBlock({ type: 'CERTIFICATE REQUEST', bytes: csrDer })
  76. WIKI.logger.info(`(LETSENCRYPT) CSR generated successfully [ OK ]`)
  77. // -> Verify Domain + Get Certificate
  78. WIKI.logger.info(`(LETSENCRYPT) Requesting certificate from Let's Encrypt...`)
  79. const certResp = await this.acme.certificates.create({
  80. account: WIKI.config.letsencrypt.account,
  81. accountKey: WIKI.config.letsencrypt.accountKeypair.private,
  82. csr,
  83. domains,
  84. challenges: {
  85. 'http-01': {
  86. init () {},
  87. set (data) {
  88. WIKI.logger.info(`(LETSENCRYPT) Setting HTTP challenge for ${data.challenge.hostname}: [ READY ]`)
  89. WIKI.config.letsencrypt.challenge = data.challenge
  90. WIKI.logger.info(`(LETSENCRYPT) Waiting for challenge to complete...`)
  91. return null // <- this is needed, cannot be undefined
  92. },
  93. get (data) {
  94. return WIKI.config.letsencrypt.challenge
  95. },
  96. async remove (data) {
  97. WIKI.logger.info(`(LETSENCRYPT) Removing HTTP challenge: [ OK ]`)
  98. WIKI.config.letsencrypt.challenge = null
  99. return null // <- this is needed, cannot be undefined
  100. }
  101. }
  102. }
  103. })
  104. WIKI.logger.info(`(LETSENCRYPT) New certifiate received successfully: [ COMPLETED ]`)
  105. WIKI.config.letsencrypt.payload = certResp
  106. WIKI.config.letsencrypt.domain = WIKI.config.ssl.domain
  107. await WIKI.configSvc.saveToDb(['letsencrypt'])
  108. } catch (err) {
  109. WIKI.logger.warn(`(LETSENCRYPT) ${err}`)
  110. throw err
  111. }
  112. }
  113. }