ol.clave.certificate

Certificate acquisition and management.

This namespace provides high-level functions for obtaining TLS certificates from ACME servers, abstracting away the multi-step protocol workflow.

The main entry point is obtain which orchestrates the complete ACME workflow: creating orders, solving challenges, finalizing with a CSR, and downloading the issued certificate.

Solvers are maps containing functions that handle challenge provisioning: - :present (required) - provisions resources for ACME validation - :cleanup (required) - removes provisioned resources - :wait (optional) - waits for slow provisioning (e.g., DNS propagation) - :payload (optional) - generates custom challenge response payload

See ol.clave.solver.http for an HTTP-01 solver with Ring middleware.

See also ol.clave.commands for low-level plumbing operations.

validate-solvers

(validate-solvers solvers)

Validate that all solvers have required :present and :cleanup functions.

Solvers are maps that must contain: - :present - function to provision challenge resources - :cleanup - function to clean up provisioned resources

Optional keys are allowed (permissive mode): - :wait - function for slow provisioning operations - :payload - function to generate custom challenge payload - Any other keys (for user metadata)

Returns nil on success, throws on validation failure.


wrap-solver-for-distributed

(wrap-solver-for-distributed storage issuer-key storage-key-fn solver)

Wraps a solver to store/cleanup challenge tokens in shared storage.

This enables distributed challenge solving where multiple instances behind a load balancer can serve ACME validation responses.

On :present: stores challenge data to storage before calling underlying solver. On :cleanup: calls underlying solver cleanup, then deletes from storage.

The stored data is a JSON map containing the full challenge plus key-authorization, enabling any instance to reconstruct and serve the response.

key description

storage

Storage implementation for persisting challenge tokens

issuer-key

Issuer identifier for storage key namespacing

storage-key-fn

Function (fn [issuer-key identifier]) → storage-key

solver

The underlying solver map to wrap


wrap-solvers-for-distributed

(wrap-solvers-for-distributed storage issuer-key storage-key-fn solvers)

Wraps all solvers in a map for distributed challenge solving.

key description

storage

Storage implementation

issuer-key

Issuer identifier

storage-key-fn

Function to generate storage keys

solvers

Map of challenge-type → solver


lookup-challenge-token

(lookup-challenge-token storage issuer-key storage-key-fn identifier)

Lookup a stored challenge token from shared storage.

Returns the challenge data map if found, nil otherwise.

key description

storage

Storage implementation

issuer-key

Issuer identifier

storage-key-fn

Function to generate storage keys

identifier

Domain or IP being validated


obtain

(obtain the-lease session identifiers cert-keypair solvers opts)

Obtain a certificate from an ACME server using configured solvers.

This function automates the complete ACME workflow defined in RFC 8555 Section 7.1: creating an order, solving authorization challenges, finalizing with a CSR, and downloading the issued certificate.

Parameters:

name description

the-lease

Lease for cancellation and timeout control

session

Authenticated ACME session with account key and KID

identifiers

Vector of identifier maps from order/create-identifier

cert-keypair

KeyPair for the certificate (distinct from account key)

solvers

Map of challenge type keyword to solver map

opts

Optional configuration map

Options map:

key description

:not-before

Requested validity start (java.time.Instant)

:not-after

Requested validity end (java.time.Instant)

:profile

ACME profile name when CA supports profiles

:preferred-challenges

Vector of challenge types in preference order

:poll-interval-ms

Polling interval for authorization/order

:poll-timeout-ms

Polling timeout

Returns [updated-session result] where result is a map:

key description

:order

Final order map with status "valid"

:certificates

Vector of certificate maps

:attempts

Number of order creation attempts made


identifiers-from-sans

(identifiers-from-sans sans)

Convert a sequence of SAN strings to identifier maps.

Automatically detects IP addresses (IPv4 and IPv6) vs DNS names.

Example:

(identifiers-from-sans ["example.com" "192.168.1.1" "2001:db8::1"])
;; => [{:type "dns" :value "example.com"}
;;     {:type "ip" :value "192.168.1.1"}
;;     {:type "ip" :value "2001:db8::1"}]

obtain-for-sans

(obtain-for-sans the-lease session sans cert-key solvers)
(obtain-for-sans the-lease session sans cert-key solvers opts)

Simplified certificate acquisition for the common case.

Automatically creates identifiers from SAN strings.

Parameters:

name description

the-lease

Lease for cancellation/timeout

session

Authenticated ACME session

sans

Vector of SAN strings (domains, IPs)

cert-key

KeyPair for certificate

solvers

Map of challenge type keyword to solver map

opts

Optional configuration map (see obtain)

Returns [updated-session result] as with obtain-certificate.

Example:

(obtain-certificate-for-sans
  (lease/background)
  session
  ["example.com" "www.example.com"]
  cert-key
  {:http-01 http-solver})

;; With options
(obtain-certificate-for-sans
  (lease/background)
  session
  ["example.com"]
  cert-key
  {:http-01 http-solver :tls-alpn-01 tls-solver}
  {:preferred-challenges [:http-01 :tls-alpn-01]})

csr

(csr keypair sans & [opts])

Generate a PKCS#10 CSR from a KeyPair

SANs (Subject Alternative Names) are automatically processed: - Unicode domains are converted to Punycode using IDNA encoding. - Wildcard usage is validated per RFC 6125 (DNS names only). - IP address format is validated for both IPv4 and IPv6. - Values are deduplicated and normalized.

Arguments: key-pair - java.security.KeyPair (RSA, EC, or EdDSA). Required. sans - Vector of strings (domain names or IP addresses). Required. Examples: ["example.com" "*.example.com" "192.0.2.1" "2001:db8::1"]

opts      - Map of options. Optional, defaults to {}.
            :use-cn? - Boolean. If true, set Subject CN to the first DNS SAN.
                       Default false. IPs are skipped when searching for CN value.
                       When false, Subject is empty and all identifiers are in SANs only
                       (one of three valid options per RFC 8555 Section 7.4).

Returns: {:csr-pem String - PEM-encoded CSR :csr-der bytes - Raw DER bytes :csr-b64url String - Base64URL-encoded DER (no padding) for ACME :algorithm Keyword - :rsa-2048, :rsa-3072, :rsa-4096, :ec-p256, :ec-p384, or :ed25519 :details Map - Algorithm OIDs, signature info}

Supports RSA (2048, 3072, 4096), ECDSA (P-256, P-384), and Ed25519. Automatically handles IDNA conversion for internationalized domains. Validates and normalizes Subject Alternative Names. No other extensions or key types are supported. If you need more features then you will need to use external tools to provide your own CSR.

Examples: ;; Modern ACME: no CN in subject (use-cn? = false, the default) (create-csr kp ["example.com" "*.example.com"]) (create-csr kp ["example.com" "www.example.com"] {})

;; Legacy: CN = first DNS SAN (IPs are skipped)
(create-csr kp ["example.com" "www.example.com"] {:use-cn? true})
;; Mixed DNS and IP SANs (auto-detected)
(create-csr kp ["example.com" "192.0.2.1" "2001:db8::1"])
;; Unicode domains (auto-converted to Punycode)
(create-csr kp ["münchen.example" "www.münchen.example"])

private-key→pem

(private-key->pem private-key)

Encode a private key as PKCS#8 PEM-formatted string.

(private-key->pem (.getPrivate keypair))
;; => "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n"

keypair

(keypair)
(keypair key-type)

Generate a keypair of the specified type.

key-type must be one of supported-key-types: - :ed25519 - Ed25519 curve - :p256 - ECDSA P-256 (default, recommended) - :p384 - ECDSA P-384 - :rsa2048 - RSA 2048-bit - :rsa4096 - RSA 4096-bit - :rsa8192 - RSA 8192-bit

If you don’t know which one to choose, just use the default.

Returns a java.security.KeyPair.

(generate :p256)
;; => #object[java.security.KeyPair ...]

(.getPublic (generate :ed25519))
;; => #object[sun.security.ec.ed.EdDSAPublicKeyImpl ...]