@small-tech/auto-encrypt
Version:
Automatically provisions and renews Let’s Encrypt TLS certificates on Node.js https servers (including Kitten, Polka, Express.js, etc.)
88 lines (75 loc) • 2.64 kB
JavaScript
///////////////////////////////////////////////////////////////////////////////
//
// ACME CSR
//
// Given a regular Certification Request in PEM format, returns an
// ACME-formatted CSR (single-line PEM without the header or footer and encoded
// in base64Url instead of base64 format).
//
// See RFC 8555 § 7.4 (Applying for Certificate Issuance).
//
// Copyright © 2020 Aral Balkan, Small Technology Foundation.
// License: AGPLv3 or later.
//
////////////////////////////////////////////////////////////////////////////////
import forge from 'node-forge'
const DNS = 2 // The ANS.1 type for DNS name.
// Returns a valid ACME-formatted (RFC 8555) CSR.
/**
* Return an ACME-formatted (RFC 8555) CSR given a list of domains and a Jose JWK.rsaKey.
*
* @param {String[]} domains
* @param {JWK.rsaKey} key
* @returns {String} An ACME-formatted CSR in PEM format.
*/
export default function (domains, key) { return pemToAcmeCsr(csrAsPem(domains, key)) }
/**
* Create a CSR given a list of domains and a Jose JWK.rsaKey.
*
* @param {String[]} domains
* @param {JWK.rsaKey} key
* @returns {String} A CSR in PEM format.
*/
function csrAsPem (domains, key) {
var csr = forge.pki.createCertificationRequest()
const keys = {
public: forge.pki.publicKeyFromPem(key.toPEM(/* private = */ false)),
private: forge.pki.privateKeyFromPem(key.toPEM(/* private = */ true))
}
csr.publicKey = keys.public
const altNames = domains.map(domain => {
return {type: DNS, value: domain}
})
// According to RFC 8555, we *either* need to specify the subject or the subjectAltName so skip the subject.
csr.setAttributes([{
name: 'extensionRequest',
extensions: [{
name: 'subjectAltName',
altNames
}]
}])
csr.sign(keys.private, forge.md.sha256.create())
const pem = forge.pki.certificationRequestToPem(csr)
return pem
}
function pemToAcmeCsr (csr) {
csr = pemToHeaderlessSingleLinePem(csr)
csr = base64ToBase64Url(csr)
return csr
}
// Strip the PEM headers and covert to a non-newline delimited Base64Url-encoded
// string as required by RFC 8555 (would be nice if this was explicitly-mentioned in the spec).
function pemToHeaderlessSingleLinePem (str) {
return str
.replace('-----BEGIN CERTIFICATE REQUEST-----', '')
.replace('-----END CERTIFICATE REQUEST-----', '')
.replace(/\n/g, '')
}
// Convert a base64-encoded string into a base64Url-encoded string.
function base64ToBase64Url (str) {
return str
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=/g, '')
.replace(/\r/g, '')
}