@peculiar/acme-client

Automatic Certificate Management Environment (ACME) client

Usage no npm install needed!

<script type="module">
  import peculiarAcmeClient from 'https://cdn.skypack.dev/@peculiar/acme-client';
</script>

README

@peculiar/acme-client

License: AGPL v3 npm version

About

@peculiar/acme-client is anAutomatic Certificate Management Environment (ACME) implementing RFC 8555 client.

Installation

npm install @peculiar/acme-client

Usage

Browser

Every release of @peculiar/acme-client will have new build of ./build/acme.js for use in the browser. To get access to module classes use acme global variable.

WARN: We recommend hosting and controlling your own copy for security reasons

<script src="https://unpkg.com/@peculiar/acme-client"></script>

NodeJS

import * as acme  from "@peculiar/acme-client";

WARN: Client requires WebCrypto API and Fetch API modules. Use third-party modules to set crypto provider and fetch client in NodeJS (eg @peculiar/webcrypto, node-fetch).

import { Crypto } from "@peculiar/webcrypto";
import fetch from "node-fetch";

const client = new acme.ApiClient(keys, "https://path/to/acme/directory", {
    crypto,
    fetch,
  });

Examples

Create an ACME client and get a directory object

const client = await ApiClient.create(keys, "http://localhost:4000/acme/directory", {
  // fetch, // required for NodeJS
  // crypto, // required for NodeJS
});

const directory = await client.getDirectory();

Create a new account

// Generate account keys
const alg = { name: "ECDSA", namedCurve: "P-256" };
const keys = await crypto.subtle.generateKey(alg, false, ["sign", "verify"]);

const account = await client.newAccount({
  contact: ["mailto:some@email.net"],
  termsOfServiceAgreed: true,
});

Enroll certificate

WARN: That example uses @peculiar/x509 package for CSR generation

// Create a new order
let order = await client.newOrder({
  identifiers: [
    { type: "dns", value: "some.domain.com" },
  ],
});

for (const link of order.content.authorizations) {
  let authz = await client.getAuthorization(link);

  if (authz.content.status === "pending") {
    const httpChallenge = authz.content.challenges.find(o => o.type === "http-01");
    assert(httpChallenge, `Cannot find http-01 challenge for '${authz.content.identifier.type}:${authz.content.identifier.value}' authorization`);

    console.log(httpChallenge);
    // Get Token and put it to wellknown link of the Server

    // Validate challenge
    const resp = await client.getChallenge(httpChallenge.url, "POST");

    const up = /<([^<>]+)>/.exec(resp.headers.link.find(o => o.includes(`up"`)))[1];
    assert(up, "Cannot get up link from header");

    authz = await client.retryAuthorization(up);
    assert.strictEqual(authz.content.status, "valid");
  }
}

// Generate CSR
const reqKeys = await crypto.subtle.generateKey(alg, false, ["sign", "verify"]) as CryptoKeyPair;
const req = await x509.Pkcs10CertificateRequestGenerator.create({
  keys: reqKeys,
  name: "DC=some.domain.com",
  signingAlgorithm: alg,
}, crypto);

// Request certificate
await client.finalize(order.content.finalize, {
  csr: req.toString("base64url"),
});

// Waiting for enrollment
order = await client.retryOrder(order);
assert.strictEqual(order.content.status, "valid");

// Get issued certificate
const certs = await client.getCertificate(order.content.certificate);
console.log(certs.content);