@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.)
76 lines (66 loc) • 2.91 kB
JavaScript
////////////////////////////////////////////////////////////////////////////////
//
// Nonce
//
// In order to protect ACME resources from any possible replay attacks,
// ACME POST requests have a mandatory anti-replay mechanism. This
// mechanism is based on the server maintaining a list of nonces that it
// has issued, and requiring any signed request from the client to carry
// such a nonce. – RFC 8555 § 6.5
//
// Before sending a POST request to the server, an ACME client needs to
// have a fresh anti-replay nonce to put in the "nonce" header of the
// JWS. In most cases, the client will have gotten a nonce from a
// previous request. However, the client might sometimes need to get a
// new nonce, e.g., on its first request to the server or if an existing
// nonce is no longer valid. – RFC 8555 § 7.2
//
// Copyright © 2020 Aral Balkan, Small Technology Foundation.
// License: AGPLv3 or later.
//
////////////////////////////////////////////////////////////////////////////////
import prepareRequest from 'bent'
import log from './util/log.js'
import Throws from './util/Throws.js'
const throws = new Throws()
export default class Nonce {
constructor (directory = throws.ifMissing()) {
this.#directory = directory
}
set (freshNonce) {
if (freshNonce === undefined || freshNonce === null) {
log(' ⚠ ❨auto-encrypt❩ nonce.set called with undefined/null. Not saving nonce. No effect on functionality. ')
return
}
if (freshNonce.match(/^[A-Za-z0-9_-]+$/) === null) {
log (' ⚠ ❨auto-encrypt❩ nonce.set with non-Base64-encoded nonce. Not saving nonce. No effect on functionality.')
return
}
this.#freshNonce = freshNonce
}
async get () {
let freshNonce
if (this.#freshNonce !== null) {
// If there is a manually-set unused Nonce available (retrieved from
// a previous API call) then unset and return that.
freshNonce = this.#freshNonce
this.#freshNonce = null
} else {
// To get a fresh nonce, the client sends a HEAD request to the newNonce
// resource on the server. The server's response MUST include a Replay-
// Nonce header field containing a fresh nonce and SHOULD have status
// code 200 (OK). The server MUST also respond to GET requests for this
// resource, returning an empty body (while still providing a Replay-
// Nonce header) with a status code of 204 (No Content). – RFC 8555 § 7.2
const newNonceRequest = prepareRequest('HEAD', this.#directory.newNonceUrl)
const newNonceResponse = await newNonceRequest()
freshNonce = newNonceResponse.headers['replay-nonce']
// Note: we do not persist the freshNonce in this.#freshNonce as we
// ===== are returning it and thus we consider it used.
}
return freshNonce
}
// Private
#directory = null
#freshNonce = null
}