letsencrypt.js 5.1 KB

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