5.8 KiB
- this repo is dependent on letsencrypt and its limits
- to simpify the outside API, smartacme is stateful, meaning it works with a mongodb and a collection called 'SmartacmeCert'.
Certificate Request Behavior
As of v7.4.0, SmartAcme no longer automatically requests wildcard certificates for all domain requests. This change was made to fix issues with HTTP-01 only configurations which cannot validate wildcard domains.
- By default,
getCertificateForDomain('example.com')only requests a certificate forexample.com - To request both regular and wildcard certificates, use
getCertificateForDomain('example.com', { includeWildcard: true }) - Wildcard certificates require a DNS-01 challenge handler to be configured
- Direct wildcard requests like
getCertificateForDomain('*.example.com')only request the wildcard certificate
This change ensures HTTP-01 only configurations work properly while still allowing wildcard certificates when needed and supported.
ACME Protocol Implementation
As of v8.1.0, the acme-client npm package has been replaced with a custom OOP implementation under ts/acme/. This uses node:crypto for all cryptographic operations and @peculiar/x509 solely for CSR generation. The implementation follows RFC 8555.
Key files:
ts/acme/acme.classes.client.ts— Top-level facade (AcmeClient), accepts optionalloggercallbackts/acme/acme.classes.crypto.ts— Key gen, JWK, JWS signing, CSR (AcmeCrypto)ts/acme/acme.classes.http-client.ts— JWS-signed HTTP transport with nonce management and loggingts/acme/acme.classes.error.ts— StructuredAcmeErrorwith type URN, subproblems, Retry-After,isRetryable/isRateLimitedts/acme/acme.classes.account.ts— Account registrationts/acme/acme.classes.order.ts— Order lifecycle + pollingts/acme/acme.classes.challenge.ts— Key authorization + challenge completionts/acme/acme.classes.directory.ts— CA directory URL constants (ACME_DIRECTORY_URLS)
Usage in ts/plugins.ts: import * as acme from './acme/index.js' (replaces acme-client)
Concurrency & Rate Limiting (taskbuffer integration)
As of v9.1.0, @push.rocks/lik.InterestMap was replaced with @push.rocks/taskbuffer.TaskManager for coordinating concurrent certificate requests. This provides:
- Per-domain mutex (
cert-domain-mutex): Only one ACME issuance per TLD at a time, withresultSharingMode: 'share-latest'so queued callers get the same result without re-issuing. - Global concurrency cap (
acme-global-concurrency): Limits total parallel ACME operations (default 5, configurable viamaxConcurrentIssuances). - Account-level rate limiting (
acme-account-rate-limit): Sliding-window rate limit (default 250 orders per 3 hours, configurable viamaxOrdersPerWindow/orderWindowMs) to stay under Let's Encrypt limits. - Step-based progress: The cert issuance task uses
notifyStep()for prepare/authorize/finalize/store phases, observable viasmartAcme.certIssuanceEvents.
Key implementation details:
- A single reusable
Tasknamedcert-issuancehandles all domains viatriggerTaskConstrained()with different inputs. - The
shouldExecutecallback on the domain mutex checks the certmanager cache as a safety net. TaskManager.start()is called inSmartAcme.start()andTaskManager.stop()inSmartAcme.stop().- The "no cronjobs specified" log messages during tests come from taskbuffer's internal CronManager polling — harmless noise when no cron tasks are scheduled.
ACME Directory Server (ts_server/)
As of v9.2.0, a built-in ACME Directory Server lives under ts_server/. This is a full RFC 8555-compliant CA server that allows running your own Certificate Authority.
Key files:
ts_server/server.classes.acmeserver.ts— Top-levelAcmeServerfacade (start/stop/config)ts_server/server.classes.ca.ts— Self-signed root CA generation + certificate signing via@peculiar/x509ts_server/server.classes.jws.verifier.ts— JWS signature verification (inverse ofAcmeCrypto.createJws)ts_server/server.classes.router.ts— Minimal HTTP router with:paramsupport using rawnode:httpts_server/server.classes.nonce.ts— Single-use replay nonce managementts_server/server.classes.challenge.verifier.ts— HTTP-01/DNS-01 verification (with bypass mode)ts_server/server.classes.account.store.ts— In-memory account storagets_server/server.classes.order.store.ts— In-memory order/authz/challenge/cert storagets_server/server.handlers.*.ts— Route handlers for each ACME endpoint
Design decisions:
- Uses raw
node:http(no framework dependency —@api.global/typedserverwas explicitly removed in v8.1.0) - Zero new dependencies: uses
node:crypto,@peculiar/x509, and existing project deps - Reuses
AcmeCryptofor JWK thumbprint/base64url, ACME interfaces for response types,AcmeErrorpatterns AcmeCrypto.getAlg()was made public (was private) for use by the JWS verifier- Storage interfaces (
IServerAccountStore,IServerOrderStore) are pluggable, with in-memory defaults challengeVerification: falseoption auto-approves challenges for testingtsbuild tsfoldersautomatically compilests_server/todist_ts_server/
Dependency Notes
acme-clientwas replaced with custom implementation ints/acme/+@peculiar/x509for CSR generation@push.rocks/smartfile,@api.global/typedserver,@push.rocks/smartrequest,@push.rocks/smartpromisewere removed as unused dependencies in v8.1.0- The
@apiclient.xyz/cloudflareconveniencenamespace is deprecated but still functional. TheDns01Handleraccepts anIConvenientDnsProviderinterface which remains stable. - Test imports use
@git.zone/tstest/tapbundle(not@push.rocks/tapbundle) - Build uses
tsbuild tsfolders(v4.3.0+) — auto-discovers and compilests/andts_server/directories