Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,17 @@ jobs:
steps:
- uses: actions/checkout@v4

- name: Install independent EST interoperability client
uses: actions/setup-go@v5
with:
go-version: '1.26.x'
cache: false

- name: Build pinned GlobalSign EST client
run: |
go install github.com/globalsign/est/cmd/estclient@v1.0.7
echo "ATOM_EST_CLIENT=$(go env GOPATH)/bin/estclient" >> "$GITHUB_ENV"

- uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy
Expand Down
40 changes: 39 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ serde_json = "1"
jsonschema = { version = "0.18", default-features = false }
uuid = { version = "1", features = ["serde", "v4"] }
chrono = { version = "0.4", features = ["serde"] }
cms = "0.2.3"
const-oid = { version = "0.9", features = ["db"] }
der = { version = "0.7", features = ["alloc", "derive", "oid"] }
jsonwebtoken = "9"
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -624,7 +624,7 @@ Generic application mapping:
| `ATOM_GRPC_TLS_CERT_PATH` | *(unset)* | PEM server certificate chain; set with `ATOM_GRPC_TLS_KEY_PATH` to enable gRPC TLS |
| `ATOM_GRPC_TLS_KEY_PATH` | *(unset)* | PEM server private key; setting only one TLS cert/key path fails startup |
| `ATOM_GRPC_TLS_CLIENT_CA_PATH` | *(unset)* | PEM client CA bundle; requires server cert/key and enables mandatory mTLS |
| `ATOM_PKI_ENROLLMENT_ENABLED` | `false` | Enables the dedicated native enrollment TLS listener |
| `ATOM_PKI_ENROLLMENT_ENABLED` | `false` | Enables the dedicated native and RFC 7030 EST enrollment TLS listener |
| `ATOM_PKI_ENROLLMENT_LISTEN_ADDR` | `0.0.0.0:8443` | Enrollment bind address; this is a public machine-facing surface |
| `ATOM_PKI_ENROLLMENT_TLS_CERT_PATH` / `ATOM_PKI_ENROLLMENT_TLS_KEY_PATH` | *(unset)* | Required server cert/key pair when enrollment is enabled; TLS terminates in Atom |
| `ATOM_PKI_ENROLLMENT_ENTITY_RATE_LIMIT` / `ATOM_PKI_ENROLLMENT_ENTITY_RATE_WINDOW_SECS` | `10` / `60` | Durable per-entity enrollment limit and fixed-window seconds |
Expand Down
25 changes: 23 additions & 2 deletions apidocs/pki-enrollment.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Native PKI enrollment API
# PKI enrollment APIs

The native enrollment API is served only on the dedicated TLS listener
The native and RFC 7030 EST enrollment APIs are served only on the dedicated TLS listener
configured by `ATOM_PKI_ENROLLMENT_LISTEN_ADDR`. It is not mounted on Atom's
main HTTP port.

Expand Down Expand Up @@ -60,3 +60,24 @@ include `Retry-After`.
Expired, revoked, unknown, or inactive certificate subjects cannot use
re-enrollment. Recover by calling first enrollment with an active
non-certificate Atom credential.

## RFC 7030 EST adapter

The same listener exposes the standard EST paths below. EST is a wire adapter
over the native enrollment service; the authenticated subject still determines
the tenant, client profile, and active issuer. Atom does not implement an EST
additional path segment, and no selector field is accepted.

| Operation | Authentication | Request | Response |
| --- | --- | --- | --- |
| `GET /.well-known/est/cacerts` | none | empty | base64 certs-only PKCS#7 matching `/certs/trust-bundle.pem` |
| `POST /.well-known/est/simpleenroll` | HTTP Basic Atom password credential, or Bearer token | base64 DER PKCS#10; `application/pkcs10` | base64 certs-only PKCS#7 |
| `POST /.well-known/est/simplereenroll` | certificate being replaced in the TLS handshake | base64 DER PKCS#10; `application/pkcs10` | base64 certs-only PKCS#7 |
| `POST /.well-known/est/serverkeygen` | HTTP Basic Atom password credential, or Bearer token | base64 DER PKCS#10; `application/pkcs10` | `multipart/mixed` containing a one-time PKCS#8 key and certs-only PKCS#7 |
| `GET /.well-known/est/csrattrs` | HTTP Basic Atom password credential, or Bearer token | empty | base64 `application/csrattrs` derived from the applicable client profile |

For HTTP Basic authentication, use the entity UUID as the username and its
Atom password credential as the password. Atom derives scope from the
credential; URL segments, query parameters, and headers cannot choose another
tenant, issuer, entity, or profile. The generated private key returned by
`serverkeygen` is zeroized after the response and is never persisted or logged.
24 changes: 24 additions & 0 deletions docs/content/docs/authentication/certificates.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,30 @@ exact `renewal_threshold_seconds`, and `idempotent_replay`. Keep the CSR private
key locally; it never enters Atom. Exact retries return the same certificate.
The old certificate remains active for the normal renewal overlap window.

The listener also exposes RFC 7030 EST for standards-based firmware and tools:

```text
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
```

EST requests and responses use the RFC media types and base64 transfer
encoding. `simpleenroll`, `serverkeygen`, and `csrattrs` accept HTTP Basic with
the entity UUID and its Atom password credential (or an Atom Bearer token).
`simplereenroll` accepts only the certificate being replaced in the TLS
handshake. `serverkeygen` returns a multipart PKCS#8 key and certs-only PKCS#7;
the key is delivered once and never persisted. `cacerts` is the PKCS#7
representation of the same database trust bundle as
`/certs/trust-bundle.pem`.

EST adds no subject selectors: an additional path segment is not mounted, and
query parameters or headers cannot select an entity, tenant, issuer, or
profile. Those values are resolved by the same subject-driven enrollment
service used by the native adapter.

Enable and bind the listener with:

```text
Expand Down
17 changes: 17 additions & 0 deletions product-docs/12-certificates.md
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,15 @@ The listener is opt-in with `ATOM_PKI_ENROLLMENT_ENABLED=true` and requires
unknown, or otherwise inactive certificate subjects must recover through first
enrollment with a still-active non-certificate credential.

PR-014b attaches RFC 7030 EST to that same listener and service boundary.
`/.well-known/est/simpleenroll`, `simplereenroll`, `serverkeygen`, `csrattrs`,
and `cacerts` translate only EST authentication and wire encodings. HTTP Basic
or Bearer authentication bootstraps first enrollment; the exact verified TLS
leaf authorizes re-enrollment. The authenticated subject remains the sole
source of entity, tenant, profile, and issuer scope. `cacerts` encodes the
PR-003 database trust bundle as certs-only PKCS#7, and `serverkeygen` delivers a
non-persisted PKCS#8 key once in a multipart response.

---

## Interfaces
Expand All @@ -244,6 +253,14 @@ The dedicated TLS enrollment listener exposes authenticated native operations:
- `POST /pki/enroll`
- `POST /pki/reenroll`

It also exposes RFC 7030 operations:

- `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`

Runtime services use Atom gRPC:

- `CertificateService.ResolveCertificateV2`
Expand Down
Loading
Loading