diff --git a/src/acme2.js b/src/acme2.js index 6e68fc744..6824ad857 100644 --- a/src/acme2.js +++ b/src/acme2.js @@ -37,7 +37,7 @@ function Acme2(fqdn, domainObject, email) { assert.strictEqual(typeof email, 'string'); this.fqdn = fqdn; - this.accountKeyPem = null; // Buffer . + this.accountKey = null; this.email = email; this.keyId = null; const prod = domainObject.tlsConfig.provider.match(/.*-prod/) !== null; // matches 'le-prod' or 'letsencrypt-prod' @@ -64,7 +64,7 @@ function b64(str) { } function getModulus(pem) { - assert(Buffer.isBuffer(pem)); + assert.strictEqual(typeof pem, 'string'); const stdout = safe.child_process.execSync('openssl rsa -modulus -noout', { input: pem, encoding: 'utf8' }); if (!stdout) return null; @@ -76,8 +76,7 @@ function getModulus(pem) { Acme2.prototype.sendSignedRequest = async function (url, payload) { assert.strictEqual(typeof url, 'string'); assert.strictEqual(typeof payload, 'string'); - - assert(Buffer.isBuffer(this.accountKeyPem)); + assert.strictEqual(typeof this.accountKey, 'string'); const that = this; let header = { @@ -92,7 +91,7 @@ Acme2.prototype.sendSignedRequest = async function (url, payload) { header.jwk = { e: b64(Buffer.from([0x01, 0x00, 0x01])), // exponent - 65537 kty: 'RSA', - n: b64(getModulus(this.accountKeyPem)) + n: b64(getModulus(this.accountKey)) }; } @@ -111,7 +110,7 @@ Acme2.prototype.sendSignedRequest = async function (url, payload) { const signer = crypto.createSign('RSA-SHA256'); signer.update(protected64 + '.' + payload64, 'utf8'); - const signature64 = urlBase64Encode(signer.sign(that.accountKeyPem, 'base64')); + const signature64 = urlBase64Encode(signer.sign(that.accountKey, 'base64')); const data = { protected: protected64, @@ -147,7 +146,7 @@ Acme2.prototype.updateContact = async function (registrationUri) { }; async function generateAccountKey() { - const acmeAccountKey = safe.child_process.execSync('openssl genrsa 4096'); + const acmeAccountKey = safe.child_process.execSync('openssl genrsa 4096', { encoding: 'utf8' }); if (!acmeAccountKey) throw new BoxError(BoxError.OPENSSL_ERROR, `Could not generate acme account key: ${safe.error.message}`); return acmeAccountKey; } @@ -159,18 +158,18 @@ Acme2.prototype.ensureAccount = async function () { debug('ensureAccount: registering user'); - this.accountKeyPem = await blobs.get(blobs.ACME_ACCOUNT_KEY); - if (!this.accountKeyPem) { + this.accountKey = await blobs.getString(blobs.ACME_ACCOUNT_KEY); + if (!this.accountKey) { debug('ensureAccount: generating new account keys'); - this.accountKeyPem = await generateAccountKey(); - await blobs.set(blobs.ACME_ACCOUNT_KEY, this.accountKeyPem); + this.accountKey = await generateAccountKey(); + await blobs.setString(blobs.ACME_ACCOUNT_KEY, this.accountKey); } 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); + this.accountKey = await generateAccountKey(); + await blobs.setString(blobs.ACME_ACCOUNT_KEY, this.accountKey); result = await this.sendSignedRequest(this.directory.newAccount, JSON.stringify(payload)); } @@ -232,12 +231,12 @@ Acme2.prototype.waitForOrder = async function (orderUrl) { }; Acme2.prototype.getKeyAuthorization = function (token) { - assert(Buffer.isBuffer(this.accountKeyPem)); + assert(typeof this.accountKey, 'string'); let jwk = { e: b64(Buffer.from([0x01, 0x00, 0x01])), // Exponent - 65537 kty: 'RSA', - n: b64(getModulus(this.accountKeyPem)) + n: b64(getModulus(this.accountKey)) }; let shasum = crypto.createHash('sha256'); @@ -285,9 +284,12 @@ Acme2.prototype.waitForChallenge = async function (challenge) { }; // https://community.letsencrypt.org/t/public-beta-rate-limits/4772 for rate limits -Acme2.prototype.signCertificate = async function (finalizationUrl, csrDer) { +Acme2.prototype.signCertificate = async function (finalizationUrl, csrPem) { assert.strictEqual(typeof finalizationUrl, 'string'); - assert(Buffer.isBuffer(csrDer)); + assert.strictEqual(typeof csrPem, 'string'); + + const csrDer = safe.child_process.execSync('openssl req -inform pem -outform der', { input: csrPem }); + if (!csrDer) throw new BoxError(BoxError.OPENSSL_ERROR, safe.error); const payload = { csr: b64(csrDer) @@ -301,20 +303,20 @@ Acme2.prototype.signCertificate = async function (finalizationUrl, csrDer) { }; Acme2.prototype.ensureKey = async function () { - const key = await blobs.get(`${blobs.CERT_PREFIX}-${this.certName}.key`); + const key = await blobs.getString(`${blobs.CERT_PREFIX}-${this.certName}.key`); if (key) { debug(`ensureKey: reuse existing key for ${this.cn}`); return key; } debug(`ensureKey: generating new key for ${this.cn}`); - const newKey = safe.child_process.execSync('openssl ecparam -genkey -name secp384r1'); // openssl ecparam -list_curves + const newKey = safe.child_process.execSync('openssl ecparam -genkey -name secp384r1', { encoding: 'utf8' }); // openssl ecparam -list_curves if (!newKey) throw new BoxError(BoxError.OPENSSL_ERROR, safe.error); return newKey; }; Acme2.prototype.createCsr = async function (key) { - assert(Buffer.isBuffer(key)); + assert.strictEqual(typeof key, 'string'); const [error, tmpdir] = await safe(fs.promises.mkdtemp(path.join(os.tmpdir(), 'acme-'))); if (error) throw new BoxError(BoxError.FS_ERROR, `Error creating temporary directory for openssl config: ${error.message}`); @@ -335,12 +337,12 @@ Acme2.prototype.createCsr = async function (key) { if (!safe.fs.writeFileSync(opensslConfigFile, conf)) throw new BoxError(BoxError.FS_ERROR, `Failed to write openssl config: ${safe.error.message}`); // while we pass the CN anyways, subjectAltName takes precedence - const csrDer = safe.child_process.execSync(`openssl req -new -key ${keyFilePath} -outform DER -subj /CN=${this.cn} -config ${opensslConfigFile}`); - if (!csrDer) throw new BoxError(BoxError.OPENSSL_ERROR, safe.error); + const csrPem = safe.child_process.execSync(`openssl req -new -key ${keyFilePath} -outform PEM -subj /CN=${this.cn} -config ${opensslConfigFile}`, { encoding: 'utf8' }); + if (!csrPem) throw new BoxError(BoxError.OPENSSL_ERROR, safe.error); await safe(fs.promises.rm(tmpdir, { recursive: true, force: true })); debug(`createCsr: csr file created for ${this.cn}`); - return csrDer; // inspect with openssl req -text -noout -in hostname.csr -inform der + return csrPem; // inspect with openssl req -text -noout -in hostname.csr -inform pem }; Acme2.prototype.downloadCertificate = async function (certUrl) { @@ -353,7 +355,7 @@ Acme2.prototype.downloadCertificate = async function (certUrl) { if (result.statusCode === 202) throw new BoxError(BoxError.ACME_ERROR, 'Retry downloading certificate'); if (result.statusCode !== 200) throw new BoxError(BoxError.ACME_ERROR, `Failed to get cert. Expecting 200, got ${result.statusCode} ${JSON.stringify(result.body)}`); - const fullChainPem = result.body; // buffer + const fullChainPem = result.body.toString('utf8'); // buffer return fullChainPem; }); }; @@ -519,9 +521,9 @@ Acme2.prototype.getCertificate = async function () { debug(`getCertificate: acme flow completed for ${this.cn}. result: ${result.length}`); - await blobs.set(`${blobs.CERT_PREFIX}-${this.certName}.key`, result[0].key); - await blobs.set(`${blobs.CERT_PREFIX}-${this.certName}.cert`, result[0].cert); - await blobs.set(`${blobs.CERT_PREFIX}-${this.certName}.csr`, result[0].csr); + await blobs.setString(`${blobs.CERT_PREFIX}-${this.certName}.key`, result[0].key); + await blobs.setString(`${blobs.CERT_PREFIX}-${this.certName}.cert`, result[0].cert); + await blobs.setString(`${blobs.CERT_PREFIX}-${this.certName}.csr`, result[0].csr); return result[0]; }; diff --git a/src/reverseproxy.js b/src/reverseproxy.js index bd429ab68..d29795469 100644 --- a/src/reverseproxy.js +++ b/src/reverseproxy.js @@ -63,7 +63,7 @@ function nginxLocation(s) { } function getExpiryDateSync(cert) { - assert(Buffer.isBuffer(cert)); + assert.strictEqual(typeof cert, 'string'); const result = safe.child_process.spawnSync('/usr/bin/openssl', [ 'x509', '-enddate', '-noout' ], { input: cert }); if (!result) return null; // some error @@ -91,7 +91,7 @@ async function isOcspEnabled(certFilePath) { // checks if the certificate matches the options provided by user (like wildcard, le-staging etc) function providerMatchesSync(domainObject, cert) { assert.strictEqual(typeof domainObject, 'object'); - assert(Buffer.isBuffer(cert)); + assert.strictEqual(typeof cert, 'string'); const subjectAndIssuer = safe.child_process.execSync('/usr/bin/openssl x509 -noout -subject -issuer', { encoding: 'utf8', input: cert }); if (!subjectAndIssuer) return false; // something bad happenned @@ -238,7 +238,7 @@ function getAcmeCertificateNameSync(fqdn, domainObject) { } function needsRenewalSync(cert) { - assert(Buffer.isBuffer(cert)); + assert.strictEqual(typeof cert, 'string'); const notAfter = getExpiryDateSync(cert); const isExpiring = (notAfter - new Date()) <= (30 * 24 * 60 * 60 * 1000); // expiring in a month @@ -257,8 +257,8 @@ async function getCertificate(location) { if (domainObject.tlsConfig.provider === 'fallback') return domainObject.fallbackCertificate; const certName = getAcmeCertificateNameSync(fqdn, domainObject); - const cert = await blobs.get(`${blobs.CERT_PREFIX}-${certName}.cert`); - const key = await blobs.get(`${blobs.CERT_PREFIX}-${certName}.key`); + const cert = await blobs.getString(`${blobs.CERT_PREFIX}-${certName}.cert`); + const key = await blobs.getString(`${blobs.CERT_PREFIX}-${certName}.key`); if (!key || !cert) return domainObject.fallbackCertificate; return { key, cert }; @@ -330,8 +330,8 @@ async function writeCertificate(location) { } const certName = getAcmeCertificateNameSync(fqdn, domainObject); - let cert = await blobs.get(`${blobs.CERT_PREFIX}-${certName}.cert`); - let key = await blobs.get(`${blobs.CERT_PREFIX}-${certName}.key`); + let cert = await blobs.getString(`${blobs.CERT_PREFIX}-${certName}.cert`); + let key = await blobs.getString(`${blobs.CERT_PREFIX}-${certName}.key`); if (!key || !cert) { // use fallback certs if we didn't manage to get acme certs debug(`writeCertificate: ${fqdn} will use fallback certs because acme is missing`); @@ -367,8 +367,8 @@ async function ensureCertificate(location, auditSource) { } const certName = getAcmeCertificateNameSync(fqdn, domainObject); - const key = await blobs.get(`${blobs.CERT_PREFIX}-${certName}.key`); - const cert = await blobs.get(`${blobs.CERT_PREFIX}-${certName}.cert`); + const key = await blobs.getString(`${blobs.CERT_PREFIX}-${certName}.key`); + const cert = await blobs.getString(`${blobs.CERT_PREFIX}-${certName}.cert`); if (key && cert) { if (providerMatchesSync(domainObject, cert) && !needsRenewalSync(cert)) { @@ -564,7 +564,7 @@ async function cleanupCerts(locations, auditSource, progressCallback) { const certName = certId.match(new RegExp(`${blobs.CERT_PREFIX}-(.*).cert`))[0]; if (certNamesInUse.has(certName)) continue; - const cert = await blobs.get(certId); + const cert = await blobs.getString(certId); const notAfter = getExpiryDateSync(cert); if (!notAfter) continue; // some error