Atom
Reference

Certificate Lifecycle

Detailed requirements and lifecycle rules for Atom-managed certificates.

Atom Certificates

Status: Active v2 (managed multi-tenant PKI)

Date: 2026-08-14

Atom owns the certificate authority registry, per-tenant issuance, revocation state, publication artifacts, subject enrollment (native + RFC 7030 EST), and runtime resolution. The legacy v1 "file issuer" mode has been removed — there is no ATOM_CERTS_* env, no ca_chain GraphQL query, no /certs/ca-chain route, no /certs/crl / /certs/ocsp global endpoints, no issueCertificate / renewCertificate / revokeCertificate v1 mutations, and no ResolveCertificate v1 gRPC method.

Every CA in an Atom deployment lives in the pki_authorities table and is managed through the same lifecycle mutations.


Architecture Summary

  • One offline root trust anchor, imported into pki_authorities as a PublicOnly row. The root's private key never enters Atom.
  • One online platform intermediate, generated by Atom, signed offline by the root operator, then imported back.
  • Optional platform leaf issuer for global (tenantless) entities.
  • One active tenant intermediate per tenant, provisioned automatically once a platform intermediate is active. Rotation replaces the active row while leaving retired versions available for CRL/OCSP.
  • CA private keys are stored envelope-encrypted in Postgres by default. ATOM_PKI_CA_KEY_BACKEND=pkcs11 swaps in a PKCS#11 HSM.
  • Every issued leaf carries an issuer_id pointing at its authority. Per-tenant CRL and OCSP responders are keyed by that ID.
  • Public artifact URLs (ocsp, crl, ca_issuers) are embedded in every issued leaf's AIA and CRL-distribution-point extensions at issuance, so relying parties don't need to know the tenant / issuer / URL scheme.
  • Runtime resolution v2 accepts a leaf DER, fingerprint, or (issuer fingerprint, serial) tuple and returns the credential-owning entity plus its tenant.
  • Subject-driven first enrollment and re-enrollment are exposed as native POST /pki/enroll / POST /pki/reenroll and as RFC 7030 EST (/.well-known/est/*) on a dedicated TLS listener.

Authority Kinds

Defined in src/certs/authority/mod.rs.

KindPurposeSigning key backend
rootTrust anchor. Config-only bootstrap.PublicOnly — root key stays offline.
platform_intermediateSigns tenant intermediates. Config-only bootstrap (bring your own pre-signed cert + key).Encrypted DB or PKCS#11.
platform_leaf_issuerSigns leaves for global / tenantless entities.Encrypted DB or PKCS#11.
tenant_intermediateSigns leaves for one tenant. Auto-provisioned per tenant.Encrypted DB or PKCS#11.

AuthorityKind::can_issue_leaf_credentials() returns true only for platform_leaf_issuer and tenant_intermediate. Publication URLs (ocsp_url, ca_issuers_url, crl_distribution_point_url) are populated at activation only for those two — they're what the PkiIssuer requires.


Bootstrap

Root and platform intermediate are config-only — no GraphQL mutation, no UI. Both are one-time, security-critical trust-anchor decisions that belong in the deployment manifest.

  1. Generate the root offline — this key never enters Atom:
    openssl ecparam -name prime256v1 -genkey -noout -out ./certs/pki-root.key
    openssl req -x509 -new -key ./certs/pki-root.key -days 3650 \
      -out ./certs/pki-root.pem \
      -subj "/CN=Atom PKI Root" \
      -addext "keyUsage=critical,keyCertSign,cRLSign" \
      -addext "basicConstraints=critical,CA:TRUE,pathlen:2"
  2. Generate the platform intermediate offline and sign it with the root:
    openssl ecparam -name prime256v1 -genkey -noout -out ./certs/pki-platform-intermediate.key
    openssl req -new -key ./certs/pki-platform-intermediate.key \
      -out ./certs/pki-platform-intermediate.csr \
      -subj "/CN=Atom Platform Intermediate v1"
    printf "basicConstraints=critical,CA:TRUE,pathlen:1\nkeyUsage=critical,keyCertSign,cRLSign,digitalSignature\nsubjectKeyIdentifier=hash\nauthorityKeyIdentifier=keyid,issuer\n" > ./certs/pki-platform-intermediate.ext
    openssl x509 -req -CA ./certs/pki-root.pem -CAkey ./certs/pki-root.key -CAcreateserial \
      -in ./certs/pki-platform-intermediate.csr -days 1825 \
      -out ./certs/pki-platform-intermediate.pem \
      -extfile ./certs/pki-platform-intermediate.ext
  3. Point Atom at both and (re)start:
    ATOM_PKI_ROOT_CERT_PATH=/certs/pki-root.pem
    ATOM_PKI_PLATFORM_INTERMEDIATE_CERT_PATH=/certs/pki-platform-intermediate.pem
    ATOM_PKI_PLATFORM_INTERMEDIATE_KEY_PATH=/certs/pki-platform-intermediate.key
    All three are idempotent: same fingerprint = no-op. If the platform intermediate PEM changes, Atom retires the previous one and activates the new. Atom wraps the platform intermediate private key with the CA KEK before persisting.
  4. Provision a tenant intermediate — after any tenant is created:
    • provisionTenantAuthorityAutomatically(tenantId) — Atom generates + signs with the active platform intermediate + activates. Populates OCSP / CA-issuers / CRL URLs from ATOM_PUBLIC_BASE_URL. Also reachable from /pki/actions in the UI.

Shortcut for local dev / demos: make pki-material generates all four PEMs in ./certs/, wires the three env vars into .env, and restarts the atom container. make up already depends on it, so a fresh clone just works. For an end-to-end visual walkthrough, see the UI test playbook.


CA Key Backends

Selected by ATOM_PKI_CA_KEY_BACKEND. See src/certs/authority/key_provider.rs.

encrypted_database (default)

CA private keys are AES-GCM-encrypted with a data-encryption key that's itself wrapped by the deployment's KEK (ATOM_PKI_CA_KEY_ENCRYPTION_KEY, base64 of 32 bytes) and stored on the pki_authorities row. Startup rejects keys wrapped by an unknown KEK ID.

pkcs11

Atom holds only a token-object reference. Signing goes through the PKCS#11 module (see PKCS#11 Operations). Configure via ATOM_PKI_PKCS11_MODULE_PATH, _TOKEN_LABEL, _USER_PIN plus optional _OPERATION_TIMEOUT_MS, _MAX_RETRIES, _MAX_IN_FLIGHT, _CIRCUIT_FAILURE_THRESHOLD, _CIRCUIT_RESET_SECS.

The two backends coexist per authority: a deployment can hold some CAs encrypted-in-DB and others in PKCS#11. Backend selection is stored on the authority row and cannot be changed after provisioning.


Model

Every issued leaf is a credentials row with:

  • kind = 'certificate'
  • identifier = normalized lowercase-hex serial number
  • issuer_id = the managed authority that signed it (never NULL in v2)
  • expires_at = the certificate's notAfter
  • metadata = certificate PEM, subject, SANs, fingerprint, subject-key-id, issuer fingerprint, subject profile, identity URI, etc.
  • secret_hash = null

Serial-number uniqueness is (issuer_id, identifier) — independent issuers may reuse serials. Fingerprints (fingerprint_sha256) remain globally unique.

Generated leaf private keys are shown once in the issuance response as privateKeyPem and never stored. CSR-issued certificates never expose a private key to Atom.

Revocation state is recorded in certificate_revocations (the immutable ledger, migrations 016 / 022 / 023). Publication continuity survives authority purge so already-issued certificates retain durable revocation evidence.


Storage

TablePurpose
pki_authoritiesAll managed CAs (root / platform_intermediate / platform_leaf_issuer / tenant_intermediate).
credentialsIssued leaves as kind='certificate' rows keyed by (issuer_id, identifier).
certificate_revocationsImmutable revocation ledger. Survives authority purge with issuer_id cascaded to NULL; publication continuity keyed by issuer_fingerprint_sha256.
certificate_crl_stateCached DER CRLs, per issuer fingerprint, with dirty and next_update bookkeeping.
pki_certificate_profilesReusable issuance policies (algorithm, TTL bounds, EKU, SAN rules, renewal windows).
pki_enrollment_rate_windowsRolling per-entity / per-tenant enrollment counters.

Useful queries:

-- All active managed authorities
SELECT id, kind, tenant_id, subject, status, not_after
FROM pki_authorities
WHERE status = 'active'
ORDER BY kind, subject;
 
-- Every leaf for a tenant
SELECT c.id AS credential_id, c.identifier AS serial, c.status,
       c.expires_at, a.subject AS issuer_subject
FROM credentials c
JOIN pki_authorities a ON a.id = c.issuer_id
WHERE c.kind = 'certificate' AND a.tenant_id = $1
ORDER BY c.created_at DESC;
 
-- Fresh CRLs (regenerated on demand)
SELECT issuer_fingerprint_sha256, crl_number, next_update, dirty
FROM certificate_crl_state;

Lifecycle

Atom supports, all through v2 mutations / handlers only:

  • Root and platform intermediate bootstrap (config-only — no mutation)
  • Tenant intermediate provisioning (beginTenantAuthorityProvisioning, provisionTenantAuthorityAutomatically)
  • Certificate issuance from CSR (issueCertificateFromCsrV2)
  • Certificate issuance with server-generated key (issueGeneratedCertificateV2)
  • Renewal by exact credential (renewCertificateFromCsrV2, renewGeneratedCertificateV2)
  • Single revocation (revokeCertificateV2) — accepts credential ID, fingerprint, or (issuer, serial)
  • Entity-wide revocation (revokeEntityCertificates)
  • Bounded fleet revocation with cursor pagination (bulkRevokeCertificates) — scope selector is exactly one of tenant / issuer / principal group
  • Authority retirement (beginAuthorityRetirementcompleteAuthorityRetirement)

CSR-issued leaves are forced to non-CA digitalSignature + clientAuth. TTLs above the profile / issuer bounds are rejected. Serial-number collisions retry inside a savepoint. All issuance / renewal / revoke paths commit inside a single transaction alongside the audit event and outbox row (see AGENTS.md § three channels).


Native Subject Enrollment

Dedicated TLS listener, opt-in with ATOM_PKI_ENROLLMENT_ENABLED=true. Binds ATOM_PKI_ENROLLMENT_LISTEN_ADDR (default 0.0.0.0:8443) with TLS material at ATOM_PKI_ENROLLMENT_TLS_CERT_PATH + _TLS_KEY_PATH.

  • POST /pki/enroll — first enrollment. Authenticates a non-certificate credential (Bearer token or existing session) and derives the subject from that credential.
  • POST /pki/reenroll — re-enrollment. Ignores bearer credentials; accepts only the leaf certificate verified by the in-process TLS handshake and maps that DER to the exact credential through the v2 runtime resolver.

Both handlers accept CSR + optional TTL + idempotency key. Tenant / entity / issuer / profile / scope come from the authenticated subject — the caller cannot select them.

Rate limits: ATOM_PKI_ENROLLMENT_ENTITY_RATE_LIMIT, _TENANT_RATE_LIMIT, _ENTITY_RATE_WINDOW_SECS, _TENANT_RATE_WINDOW_SECS, plus the separate IP-based ATOM_HTTP_RATE_LIMIT_ENROLLMENT policy when ATOM_RATE_LIMIT_ENABLED is true. IPv6 source buckets use /64 by default and can be tuned separately with ATOM_PKI_ENROLLMENT_IPV6_PREFIX_LEN (connections) and ATOM_HTTP_RATE_LIMIT_IPV6_PREFIX_LEN (requests). TLS/HTTP connection bounds: _MAX_CONNECTIONS, _MAX_CONNECTIONS_PER_IP, _HANDSHAKE_TIMEOUT_SECS, _HTTP_HEADER_TIMEOUT_SECS, _REQUEST_TIMEOUT_SECS, _CONNECTION_TIMEOUT_SECS, _SHUTDOWN_DRAIN_TIMEOUT_SECS; HTTP keep-alive is disabled by default and may be enabled with ATOM_PKI_ENROLLMENT_HTTP_KEEP_ALIVE=true.

RFC 7030 EST is served on the same listener at /.well-known/est/{cacerts,csrattrs,simpleenroll,simplereenroll,serverkeygen} — see EST and ACME.


Interfaces

GraphQL (all under POST /graphql)

Queries:

  • pkiAuthority(id) / pkiAuthorities(tenantId)
  • certificate(credentialId) / certificates(entityId, tenantId, issuerId, status, expiresFrom, expiresBefore, limit, offset)

Mutations — authorities:

  • beginTenantAuthorityProvisioning, provisionTenantAuthorityAutomatically
  • beginAuthorityRetirement, completeAuthorityRetirement, transitionRetirement

(Root import, platform intermediate import, and offline CSR signing are removed from GraphQL — bootstrap those via ATOM_PKI_ROOT_CERT_PATH / ATOM_PKI_PLATFORM_INTERMEDIATE_{CERT,KEY}_PATH.)

Mutations — certificates:

  • issueCertificateFromCsrV2, issueGeneratedCertificateV2
  • renewCertificateFromCsrV2, renewGeneratedCertificateV2
  • revokeCertificateV2, revokeEntityCertificates, bulkRevokeCertificates

HTTP — public, unauthenticated

  • GET /certs/trust-bundle.pem — the deployment's trust anchors as PEM.
  • GET /certs/issuers/:issuer_id/crl — DER CRL per issuer.
  • POST /certs/issuers/:issuer_id/ocsp — RFC 6960 OCSP per issuer.

The URLs above are what Atom embeds in every issued leaf's AIA / CRL-distribution-point extensions, so relying parties don't have to know the URL scheme.

HTTP — authenticated (enrollment listener)

  • POST /pki/enroll, POST /pki/reenroll
  • GET /.well-known/est/cacerts, GET /.well-known/est/csrattrs
  • POST /.well-known/est/simpleenroll, POST /.well-known/est/simplereenroll, POST /.well-known/est/serverkeygen

gRPC

  • CertificateService.ResolveCertificateV2 — the v2 resolver. Accepts leaf DER, fingerprint, or (issuer fingerprint, serial). Returns entity, tenant, credential ID, issuer, expiry.
  • CertificateService.RevokeEntityCertificates

Authorization

Certificate operations use the standard Atom credential authorization surface. Each operation applies the scope rules described by the GraphQL schema and the access model.

  • Issue / renew / revoke on entity certs: credential.manage on the entity, or exact credential.rotate / .revoke on the credential.
  • Authority provisioning / retirement: pki.provision on the target tenant scope + pki.provision_automated on platform for the auto flow.
  • List authorities: platform-scoped read, or tenant-scoped read for that tenant's authorities only.
  • Runtime resolve: authz.check on the resolved tenant or platform.
  • Trust bundle, per-issuer CRL, per-issuer OCSP: public.

Tenant admins can manage certificates only for tenant-owned entities in their tenant, unless explicit platform policy delegates authority.


Configuration Reference

Env varDefaultPurpose
ATOM_PUBLIC_BASE_URLhttp://localhost:8080Base URL Atom embeds in issued certs' AIA/CRL URLs.
ATOM_PKI_ROOT_CERT_PATH(unset)Path to a PEM root cert imported at startup. Idempotent by fingerprint. Required for any downstream provisioning.
ATOM_PKI_PLATFORM_INTERMEDIATE_CERT_PATH(unset)Path to a pre-signed platform intermediate PEM (chain to the configured root is verified). Paired with the key path below.
ATOM_PKI_PLATFORM_INTERMEDIATE_KEY_PATH(unset)Path to the platform intermediate private key (PKCS#8 or SEC1 PEM). Atom wraps it with the CA KEK before persisting.
ATOM_PKI_CA_KEY_BACKENDencrypted_databaseencrypted_database | pkcs11.
ATOM_PKI_CA_KEY_ENCRYPTION_KEY(unset)Base64(32) KEK wrapping encrypted-DB CA private keys. Must not reuse ATOM_KEY_ENCRYPTION_KEY.
ATOM_PKI_CA_KEY_ENCRYPTION_KEY_IDlocal-ca:v1 (local:pki:v1 in Compose)Rotation identifier on stored key material.
ATOM_PKI_PKCS11_MODULE_PATH / _TOKEN_LABEL / _USER_PINRequired when the PKCS#11 backend is selected.
ATOM_PKI_PKCS11_OPERATION_TIMEOUT_MS2000Per-operation timeout.
ATOM_PKI_PKCS11_MAX_RETRIES1Bounded retry on transient errors.
ATOM_PKI_PKCS11_MAX_IN_FLIGHT8Concurrent PKCS#11 op cap.
ATOM_PKI_PKCS11_CIRCUIT_FAILURE_THRESHOLD3Circuit breaker trip count.
ATOM_PKI_PKCS11_CIRCUIT_RESET_SECS30Circuit breaker reset window.
ATOM_PKI_GENERATED_KEY_ISSUANCE_ENABLEDfalseFeature flag for the issueGeneratedCertificateV2 path.
ATOM_PKI_ENROLLMENT_ENABLEDfalseBring up the dedicated /pki/* + EST TLS listener.
ATOM_PKI_ENROLLMENT_LISTEN_ADDR0.0.0.0:8443Enrollment listener bind.
ATOM_PKI_ENROLLMENT_TLS_CERT_PATH / _TLS_KEY_PATHServer TLS material for the enrollment listener. Required when enabled.
ATOM_PKI_ENROLLMENT_ENTITY_RATE_LIMIT / _WINDOW_SECS10 / 60Per-entity enrollment rate cap.
ATOM_PKI_ENROLLMENT_TENANT_RATE_LIMIT / _WINDOW_SECS1000 / 60Per-tenant enrollment rate cap.
ATOM_PKI_ENROLLMENT_MAX_CSR_BYTES65536CSR body limit.
ATOM_PKI_ENROLLMENT_MAX_CONNECTIONS / _MAX_CONNECTIONS_PER_IP256 / 8Global and per-source concurrent enrollment TLS connection caps.
ATOM_PKI_ENROLLMENT_IPV6_PREFIX_LEN / _HTTP_KEEP_ALIVE64 / falseIPv6 aggregation prefix for connection caps; opt in to HTTP/1.1 connection reuse.
ATOM_HTTP_RATE_LIMIT_IPV6_PREFIX_LEN64IPv6 aggregation prefix for HTTP IP-rate buckets.
ATOM_PKI_ENROLLMENT_HTTP_HEADER_TIMEOUT_SECS / _REQUEST_TIMEOUT_SECS10 / 30Maximum header-read time and total request/handler time.
ATOM_PKI_LIFECYCLE_ENABLEDfalseTurn on the expiry / warning sweeper.
ATOM_PKI_LIFECYCLE_INTERVAL_SECS60Sweeper cadence.
ATOM_PKI_LIFECYCLE_BATCH_SIZE250Per-tick bound.
ATOM_PKI_EXPIRY_WARNING_SECS86400Emit certificate.expiring this far ahead of leaf expiry.
ATOM_PKI_AUTHORITY_WARNING_SECS2592000Emit certificate.authority_expiring this far ahead of authority expiry.

Removed in v2

  • All ATOM_CERTS_* env vars (_ENABLED, _CA_MODE, _ROOT_CA_*, _INTERMEDIATE_CA_*, _LEAF_*).
  • CertsCaMode enum, CertificateIssuer struct, load_file_issuer_if_enabled, in-process global CA state.
  • GraphQL: caChain, issueCertificate, issueCertificateFromCsr, renewCertificate, revokeCertificate — replaced by the *V2 set above.
  • HTTP: GET /certs/ca-chain, GET /certs/crl, POST /certs/ocsp — replaced by /certs/trust-bundle.pem and the per-issuer /certs/issuers/:id/{crl,ocsp} routes.
  • Test binary m17_certificates (v1 file-issuer coverage).
  • Health status field certificate_issuer (there is no single global issuer to report on).

Existing credentials rows with kind='certificate' and issuer_id IS NULL (legacy leaves from v1 deployments) remain in the database but are no longer resolvable through any v2 code path. They will expire on their own timeline and can be swept later; no runtime consumer accepts them.