Skip to content

An asynchronous module for interacting with ACME servers for automated SSL/TLS certificate issuance and management.

License

Notifications You must be signed in to change notification settings

FirstTimeEZ/base-acme

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

60 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Automatic Certificate Management Environment (ACME)

A module for interacting with ACME servers for automated SSL/TLS certificate issuance and management.

Exports

import * as bac from 'base-acme-client'; // ES6

newDirectory

Fetches the directory information from an ACME server.

Show jsdoc
/**
 * Fetches the directory information from an ACME server.
 * @async
 * 
 * @param {string} mainDirectoryUrl - The URL of the ACME server's directory endpoint
 * 
 * @returns {Promise<Object>} An object containing the directory information or an error
 * @property {Object|null} get - The parsed directory JSON or null
 * 
 * @property {null|Object} error - The error response if the request was unsuccessful
 */
export async function newDirectory(mainDirectoryUrl) { /*...*/ }

newNonce

Retrieves a new nonce from the ACME server.

Show jsdoc
/**
 * Retrieves a new nonce from the ACME server.
 * @async
 * 
 * @param {string} [newNonceUrl] - ACME Directory URL to fetch a new nonce.
 * 
 * @returns {Promise<Object>} An object containing the nonce or error details
 * @property {string|null} nonce - A new replay nonce for subsequent requests
 * 
 * @property {null|Object} error - The error response if the request was unsuccessful
 */
export async function newNonce(newNonceUrl) { /*...*/ }

createJsonWebKey

Creates a JSON Web Key (JWK) from a public key.

Show jsdoc
/**
 * Creates a JSON Web Key (JWK) from a public key.
 * @async
 * 
 * @param {Object} publicKey - The public key to convert to JWK format
 * 
 * @returns {Promise<Object>} An object containing the JWK and its thumbprint
 * @property {Object} key - The JSON Web Key representation
 * @property {string} print - Base64URL encoded thumbprint of the key
 */
export async function createJsonWebKey(publicKey) { /*...*/ }

createAccount

Creates a new account on the ACME server.

Show jsdoc
/**
 * Creates a new account on the ACME server.
 * @async
 * 
 * @param {string} nonce - The replay nonce from the server
 * @param {string} newAccountUrl - The URL for creating a new account
 * @param {Object} privateKey - The private key for signing the request
 * @param {Object} jsonWebKey - The JSON Web Key representing the account's public key
 * @param {Object} acmeDirectory - The ACME directory containing URLs for ACME operations
 * 
 * @returns {Promise<Object>} An object containing the account creation result
 * @property {Object|null} get - The created account details
 * @property {string|null} location - The location URL of the created account
 * @property {string|null} nonce - A new replay nonce for subsequent requests
 * 
 * @property {null|Object} error - Error details if account creation fails
 */
export async function createAccount(nonce, newAccountUrl, privateKey, jsonWebKey) { /*...*/ }

createOrder

Creates a new order for certificate issuance on the ACME server.

Show jsdoc
/**
 * Creates a new order for certificate issuance on the ACME server.
 * @async
 * 
 * @param {string} kid - Key Identifier for the account
 * @param {string} nonce - The replay nonce from the server
 * @param {Object} privateKey - The private key for signing the request
 * @param {string[]} identifiers - Domain names to be included in the certificate
 * @param {Object} acmeDirectory - The ACME directory containing URLs for ACME operations
 * 
 * @returns {Promise<Object>} An object containing the order creation result
 * @property {Object|null} get - The created order details
 * @property {string|null} location - The location URL of the created order
 * @property {string|null} nonce - A new replay nonce for subsequent requests
 * 
 * @property {null|Object} error - Error details if order creation fails
 */
export async function createOrder(kid, nonce, privateKey, newOrderUrl, identifiers) { /*...*/ }

finalizeOrder

Finalizes a certificate order by submitting a Certificate Signing Request (CSR).

Show jsdoc
/**
 * Finalizes a certificate order by submitting a Certificate Signing Request (CSR).
 * @async
 * 
 * @param {string} commonName - The primary domain name for the certificate
 * @param {string} kid - Key Identifier for the account
 * @param {string} nonce - The replay nonce from the server
 * @param {Object} privateKey - The private key for signing the request
 * @param {Object} publicKeySign - Public key used for signing the CSR
 * @param {Object} privateKeySign - Private key used for signing the CSR
 * @param {string} finalizeUrl - The URL for finalizing the order
 * @param {Object} acmeDirectory - The ACME directory containing URLs for ACME operations
 * @param {string[]} dnsNames - Additional DNS names to be included in the certificate
 * 
 * @returns {Promise<Object>} An object containing the order finalization result
 * @property {Object|null} get - The finalized order details
 * @property {string|null} location - The location URL of the finalized order
 * @property {string|null} nonce - A new replay nonce for subsequent requests
 * 
 * @property {null|Object} error - Error details if finalization fails
 */
export async function finalizeOrder(commonName, kid, nonce, privateKey, publicKeySign, privateKeySign, finalizeUrl, dnsNames) { /*...*/ }

postAsGet

Performs a POST-as-GET request to retrieve order or authorization status.

Show jsdoc
/**
 * Performs a POST-as-GET request to retrieve order or authorization status.
 * @async
 * 
 * @param {string} kid - Key Identifier for the account
 * @param {string} nonce - The replay nonce from the server
 * @param {Object} privateKey - The private key for signing the request
 * @param {string} url - The URL to retrieve status from
 * @param {Object} acmeDirectory - The ACME directory containing URLs for ACME operations
 * 
 * @returns {Promise<Object>} An object containing the retrieved information
 * @property {Object|null} get - The retrieved resource details
 * @property {string|null} location - The location URL of the resource
 * @property {string|null} nonce - A new replay nonce for subsequent requests
 * 
 * @property {null|Object} error - Error details if retrieval fails
*/
export async function postAsGet(kid, nonce, privateKey, url) { /*...*/ }

postAsGetChal

Performs a POST-as-GET request for challenges

Show jsdoc
/**
 * Performs a POST-as-GET request for challenges
 * @async
 * 
 * @param {string} kid - Key Identifier for the account
 * @param {string} nonce - The replay nonce from the server
 * @param {Object} privateKey - The private key for signing the request
 * @param {string} url - The URL to retrieve challenge details from
 * @param {Object} acmeDirectory - The ACME directory containing URLs for ACME operations
 * 
 * @returns {Promise<Object>} An object containing the challenge details
 * @property {Object|null} get - The retrieved challenge details
 * @property {string|null} location - The location URL of the challenge
 * @property {string|null} nonce - A new replay nonce for subsequent requests
 * 
 * @property {null|Object} error - Error details if retrieval fails
 */
export async function postAsGetChal(kid, nonce, privateKey, url) { /*...*/ }

signPayloadJson

Signs a JSON payload for ACME server requests.

Show jsdoc
/**
 * Signs a JSON payload for ACME server requests.
 * @async
 * 
 * @param {Object} payload - The payload to be signed
 * @param {Object} protectedHeader - The protected header containing metadata
 * @param {Object} privateKey - The private key used for signing
 * 
 * @returns {Promise<string>} A JSON Web Signature (JWS) string
 */
export async function signPayloadJson(payload, protectedHeader, privateKey) { /*...*/ }

signPayload

Signs a payload for ACME server requests.

Show jsdoc
/**
 * Signs a payload for ACME server requests.
 * @async
 * 
 * @param {string|Object} payload - The payload to be signed
 * @param {Object} protectedHeader - The protected header containing metadata
 * @param {Object} privateKey - The private key used for signing
 * 
 * @returns {Promise<string>} A JSON Web Signature (JWS) string
 */
export async function signPayload(payload, protectedHeader, privateKey) { /*...*/ }

formatPublicKey

Formats a PEM-encoded public key to a key object.

Show jsdoc
/**
 * Formats a PEM-encoded public key to a key object.
 * 
 * @param {string} pem - The PEM-encoded public key
 * 
 * @returns {Object} A formatted public key object
 */
export function formatPublicKey(pem) { /*...*/ }

formatPrivateKey

Formats a PEM-encoded private key to a key object.

Show jsdoc
/**
 * Formats a PEM-encoded private key to a key object.
 * 
 * @param {string} pem - The PEM-encoded private key
 * 
 * @returns {Object} A formatted private key object
 */
export function formatPrivateKey(pem) { /*...*/ }

base64urlEncode

Encodes input to a base64url-encoded string.

Show jsdoc
/**
 * Encodes input to a base64url-encoded string.
 *
 * @param {string|Uint8Array} input - The input to encode
 * 
 * @returns {string} A base64url-encoded string
 */
export function base64urlEncode(input) { /*...*/ }

hexToBytes

Converts a hexadecimal string to a Uint8Array of bytes.

Show jsdoc
/**
 * Converts a hexadecimal string to a Uint8Array of bytes.
 * 
 * @param {string} hex - The hexadecimal string to convert. It should contain an even number of characters.
 * 
 * @returns {Uint8Array} A Uint8Array containing the byte values represented by the hexadecimal string.
 * @throws {Error} Throws an error if the input string has an odd length or contains invalid hexadecimal characters.
 */
export function hexToBytes(hex) { /*...*/ }

getNextNonce

Retrieves the next nonce for ACME protocol requests.

Show jsdoc
/**
 * Retrieves the next nonce for ACME protocol requests.
 *
 * If a replay nonce is provided in the headers, it will return that nonce.
 * Otherwise, it will request a new nonce from the ACME directory.
 *
 * @async
 * 
 * @param {Headers} headers - The headers object containing the replay nonce.
 * @param {Object} acmeDirectory - The ACME directory containing URLs for ACME operations
 * 
 * @returns {Promise<string|null>} A promise that resolves to the next nonce as a string,
 *                                  or null if no nonce is available.
 */
export async function getNextNonce(headers, acmeDirectory) { /*...*/ }

fetchRequest

Sends a signed request to the ACME server.

Show jsdoc
/**
 * Sends a signed request to the ACME server.
 * @async
 * 
 * @param {string} method - The HTTP method to use (e.g., 'GET', 'POST')
 * @param {string} url - The URL to send the request to
 * @param {string} signedData - The signed payload to send
 * 
 * @returns {Promise<Response>} The response from the server
 */
export async function fetchRequest(method, url, signedData) { /*...*/ }

fetchSuggestedWindow

Fetches the suggested renewal window information for a certificate from the specified URL.

Show jsdoc
/**
 * Fetches the suggested renewal window information for a certificate from the specified URL.
 * @async
 * 
 * @param {string} renewalInfoUrl - The base URL for fetching renewal information.
 * @param {string} aki- The Authority Key Identifier in hexadecimal format.
 * @param {string} serial - The serial number in hexadecimal format.
 * 
 * @returns {Promise<Object>} A promise that resolves to the parsed JSON of the suggested window
 * @property {Object|null} get - The retrieved suggested window
 * 
 * @property {null|Object} error - Error details if retrieval fails
 * 
 * @throws {Error} Throws an error if the fetch operation fails.
 */
export async function fetchSuggestedWindow(renewalInfoUrl, aki, serial) { /*...*/ }

fetchAndRetryUntilOk

Fetch a resource with multiple retry attempts and progressive backoff.

Show jsdoc
/**
 * Fetch a resource with multiple retry attempts and progressive backoff.
 * @async
 * 
 * @param {string|Request} fetchInput - The URL or Request object to fetch
 * @param {Object} init - optional fetch init object
 * @param {number} [attempts=6] - Maximum number of fetch attempts
 * @param {boolean} silent - true to suppress console output on failure attempt
 * 
 * @returns {Promise<Response|undefined>} The response or undefined if all attempts fail
 * 
 * @description
 * This function attempts to fetch a resource with the following characteristics:
 * - Starts with one fetch attempt
 * - Increments attempts progressively
 * - Implements an increasing delay between failed attempts (650ms * attempt number)
 * - Logs any caught exceptions
 * - Returns immediately on a successful (ok) response
 * - Returns the last response or undefined if all attempts are exhausted
 * 
 * @example
 * const response = await fetchAndRetyUntilOk('https://api.example.com/data');
 * if (response && response.ok) {
 *   const data = await response.json();
 *   // Process successful response
 * }
 */
export async function fetchAndRetryUntilOk(fetchInput, init, attempts = 6, silent = false) { /*...*/ }

fetchAndRetryProtectedUntilOk

Fetch a protected resource with multiple retry attempts and progressive backoff.

Show jsdoc
/**
 * Fetch a protected resource with multiple retry attempts and progressive backoff.
 * @async
 *
 * @param {Object} payload - The payload to be sent with the request
 * @param {Object} protectedHeader - The protected header containing metadata for the request
 * @param {Object} privateKey - The private key for signing the request
 * @param {Object} acmeDirectory - The ACME directory containing URLs for ACME operations
 * @param {number} [attempts=6] - Maximum number of fetch attempts (default: 6)
 * @param {boolean} silent - true to suppress console output on failure attempt
 * 
 * @returns {Promise<Response|undefined>} The response or undefined if all attempts fail
 *
 * @description
 * This function attempts to fetch a protected resource with the following characteristics:
 * - Starts with one fetch attempt
 * - Increments attempts progressively
 * - Implements an increasing delay between failed attempts (650ms * attempt number)
 * - Logs any caught exceptions
 * - Returns immediately on a successful (ok) response
 * - Returns the last response or undefined if all attempts are exhausted
 *
 * @example
 * const response = await fetchAndRetryProtectedUntilOk(
 *   payload, 
 *   protectedHeader, 
 *   privateKey, 
 *   acmeDirectory
 * );
 * if (response && response.ok) {
 *   const data = await response.json();
 *   // Process successful response
 * }
 */
export async function fetchAndRetryProtectedUntilOk(payload, protectedHeader, privateKey, acmeDirectory, attempts = 3, silent = false) { /*...*/ }

Errors/Exceptions

Errors and Exceptions will be returned in an object

// Exceptions
{
  error: {
    type: 'bac:exception:methodName',
    detail: Error: SyntaxError: Unexpected end of input
        at file:///base-acme-client.js:666:11
        at ModuleJob.run (node:internal/modules/esm/module_job:271:25)
        at async onImport.tracePromise.__proto__ (node:internal/modules/esm/loader:547:26)
        at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:116:5),
    status: 777777
  }
}

// Error from the Base ACME Client
{
  error: {
    type: 'bac:failed:methodName',
    detail: 'Could not complete methodName after multiple attempts',
    status: 777777
  }
}

// Error from the ACME Server
{
  error: {
    type: 'urn:ietf:params:acme:error:orderNotReady',
    detail: `Order's status ("valid") is not acceptable for finalization`,
    status: 403
  }
}

Full Working Examples

This module is used by Lets Encrypt ACME Client and Server SSL