diff --git a/migrations/20211116080624-regenerate-le-account-keys.js b/migrations/20211116080624-regenerate-le-account-keys.js deleted file mode 100644 index 972643acb..000000000 --- a/migrations/20211116080624-regenerate-le-account-keys.js +++ /dev/null @@ -1,28 +0,0 @@ -'use strict'; - -const child_process = require('child_process'); - -// DO 1-click images ended up having the same ACME key. We regenerate the shared ones. -// https://community.letsencrypt.org/t/receiving-expiration-emails-for-dozens-of-domains/165441 -exports.up = function(db, callback) { - db.all('SELECT SHA2(value, 256) AS sha256key FROM blobs WHERE id = ?', [ 'acme_account_key' ], function (error, result) { - if (error) return callback(error); - if (result.length === 0) return callback(); - - const BAD_KEYS = [ - '37d6c4c4bf8aaceed57cb9097edb38a6641eae4324ca383774d732ea3b81c269', /* 7.0.3 DO */ - '6498621a2ba9106fc1f1d903df802d82ca3570004a59776ca63920ad3a7837a0', /* 6.3.5 Vultr */ - 'd47957b86cbf66279ba9fb09cfd3d96e753ed84ed18060b1f0488e5d4ca4d4ce', /* 6.3.6 Vultr */ - '195a4335b95f3f183b85c995d4d4a1ddaae6ff04d03e1ceb5537b00b062fd6c1', /* 7.0.3 Vultr */ - ]; - - if (!BAD_KEYS.includes(result[0].sha256key)) return callback(); - - const acmeAccountKey = child_process.execSync('openssl genrsa 4096'); - db.runSql('UPDATE blobs SET value = ? WHERE id = ?', [ acmeAccountKey, 'acme_account_key' ], callback); - }); -}; - -exports.down = function(db, callback) { - callback(); -}; diff --git a/src/acme2.js b/src/acme2.js index 89c5b6686..1a2f28c5b 100644 --- a/src/acme2.js +++ b/src/acme2.js @@ -9,6 +9,7 @@ exports = module.exports = { }; const assert = require('assert'), + blobs = require('./blobs.js'), BoxError = require('./boxerror.js'), crypto = require('crypto'), debug = require('debug')('box:cert/acme2'), @@ -31,7 +32,7 @@ const CA_PROD_DIRECTORY_URL = 'https://acme-v02.api.letsencrypt.org/directory', function Acme2(options) { assert.strictEqual(typeof options, 'object'); - this.accountKeyPem = options.accountKeyPem; // Buffer + this.accountKeyPem = null; // Buffer . this.email = options.email; this.keyId = null; this.caDirectory = options.prod ? CA_PROD_DIRECTORY_URL : CA_STAGING_DIRECTORY_URL; @@ -133,18 +134,38 @@ Acme2.prototype.updateContact = async function (registrationUri) { debug(`updateContact: contact of user updated to ${this.email}`); }; -Acme2.prototype.registerUser = async function () { +async function generateAccountKey() { + const acmeAccountKey = safe.child_process.execSync('openssl genrsa 4096'); + if (!acmeAccountKey) throw new BoxError(BoxError.OPENSSL_ERROR, `Could not generate acme account key: ${safe.error.message}`); + return acmeAccountKey; +} + +Acme2.prototype.ensureAccount = async function () { const payload = { termsOfServiceAgreed: true }; - debug('registerUser: registering user'); + debug('ensureAccount: registering user'); + + this.accountKeyPem = await blobs.get(blobs.ACME_ACCOUNT_KEY); + if (!this.accountKeyPem) { + debug('ensureAccount: generating new account keys'); + this.accountKeyPem = await generateAccountKey(); + await blobs.set(blobs.ACME_ACCOUNT_KEY, this.accountKeyPem); + } + + let result = await this.sendSignedRequest(this.directory.newAccount, JSON.stringify(payload)); + if (result.status === 403 && result.body.type === 'urn:ietf:params:acme:error:unauthorized') { + debug(`ensureAccount: key was revoked. ${result.status} ${JSON.stringify(result.body)}. generating new account key`); + this.accountKeyPem = await generateAccountKey(); + await blobs.set(blobs.ACME_ACCOUNT_KEY, this.accountKeyPem); + result = await this.sendSignedRequest(this.directory.newAccount, JSON.stringify(payload)); + } - const result = await this.sendSignedRequest(this.directory.newAccount, JSON.stringify(payload)); // 200 if already exists. 201 for new accounts if (result.status !== 200 && result.status !== 201) throw new BoxError(BoxError.EXTERNAL_ERROR, `Failed to register new account. Expecting 200 or 201, got ${result.status} ${JSON.stringify(result.body)}`); - debug(`registerUser: user registered keyid: ${result.headers.location}`); + debug(`ensureAccount: user registered keyid: ${result.headers.location}`); this.keyId = result.headers.location; @@ -464,7 +485,7 @@ Acme2.prototype.acmeFlow = async function (hostname, domain, paths) { const { certFilePath, keyFilePath, csrFilePath, acmeChallengesDir } = paths; - await this.registerUser(); + await this.ensureAccount(); const { order, orderUrl } = await this.newOrder(hostname); for (let i = 0; i < order.authorizations.length; i++) { diff --git a/src/blobs.js b/src/blobs.js index 056ca40bc..bd6347804 100644 --- a/src/blobs.js +++ b/src/blobs.js @@ -54,10 +54,6 @@ async function clear() { } async function generateSecrets() { - const acmeAccountKey = safe.child_process.execSync('openssl genrsa 4096'); - if (!acmeAccountKey) throw new BoxError(BoxError.OPENSSL_ERROR, `Could not generate acme account key: ${safe.error.message}`); - await set(exports.ACME_ACCOUNT_KEY, acmeAccountKey); - debug('generateSecrets: generating dhparams.pem'); // https://security.stackexchange.com/questions/95178/diffie-hellman-parameters-still-calculating-after-24-hours const dhparams = safe.child_process.execSync('openssl dhparam -dsaparam 2048'); diff --git a/src/reverseproxy.js b/src/reverseproxy.js index be8aa7818..dadb216a6 100644 --- a/src/reverseproxy.js +++ b/src/reverseproxy.js @@ -51,8 +51,7 @@ const acme2 = require('./acme2.js'), shell = require('./shell.js'), sysinfo = require('./sysinfo.js'), users = require('./users.js'), - util = require('util'), - _ = require('underscore'); + util = require('util'); const NGINX_APPCONFIG_EJS = fs.readFileSync(__dirname + '/nginxconfig.ejs', { encoding: 'utf8' }); const RESTART_SERVICE_CMD = path.join(__dirname, 'scripts/restartservice.sh'); @@ -82,11 +81,6 @@ async function getAcmeApi(domainObject) { const [error, owner] = await safe(users.getOwner()); apiOptions.email = (error || !owner) ? 'webmaster@cloudron.io' : owner.email; // can error if not activated yet - const accountKeyPem = await blobs.get(blobs.ACME_ACCOUNT_KEY); - if (!accountKeyPem) throw new BoxError(BoxError.NOT_FOUND, 'acme account key not found'); - - apiOptions.accountKeyPem = accountKeyPem; - return { acmeApi, apiOptions }; } @@ -412,7 +406,7 @@ async function ensureCertificate(vhost, domain, auditSource) { debug(`ensureCertificate: ${vhost} cert does not exist`); } - debug('ensureCertificate: getting certificate for %s with options %j', vhost, _.omit(apiOptions, 'accountKeyPem')); + debug('ensureCertificate: getting certificate for %s with options %j', vhost, apiOptions); const acmePaths = getAcmeCertificatePathSync(vhost, domainObject); let [error] = await safe(acmeApi.getCertificate(vhost, domain, acmePaths, apiOptions));