# est-ca
[RFC 7030 Enrollment over Secure Transport (EST)][rfc7030] + an internal
X.509 CA, in pure Rust.
[rfc7030]: https://datatracker.ietf.org/doc/html/rfc7030
Three things commonly needed together when running an internal PKI that
issues short-lived client certificates to programmatic callers
(devices, services, tenants, CI runners):
- **Internal X.509 CA** — load a signing key+cert, apply a strict profile,
and issue leaves from PKCS#10 CSRs via [`rcgen`].
- **EST server** — `axum` handlers for `/cacerts`, `/simpleenroll`, and
`/simplereenroll` with pluggable authentication.
- **EST client** — generate a CSR and enroll; renew via `/simplereenroll`
over mTLS.
## Feature flags
| `client` (default) | EST client: CSR generation + enroll + renew. |
| `server` | EST server handlers (implies `ca`). |
| `ca` | Internal CA primitives (issuer, profile, serial). |
| `full` | All of the above. |
## Quick start (server)
```rust,no_run
# #[cfg(all(feature = "server", feature = "ca"))]
# async fn _doc() -> anyhow::Result<()> {
use std::sync::Arc;
use est_ca::auth::{AuthBackend, Principal};
use est_ca::ca::{CertProfile, Issuer, serial::InMemorySerialStore};
use est_ca::est::server::EstServer;
struct OpenAuth;
impl AuthBackend for OpenAuth {
fn verify_bootstrap(&self, user: &str, _pass: &str) -> Result<Principal, String> {
Ok(Principal::new(user))
}
}
let issuer = Issuer::generate_self_signed(
"my dev CA",
CertProfile::client_auth_for_days(7),
Arc::new(InMemorySerialStore::new()),
)?;
let app = EstServer::new(issuer, Arc::new(OpenAuth)).router();
// Bind with your preferred TLS-enabled axum setup:
axum::Server::bind(&"0.0.0.0:4440".parse()?)
.serve(app.into_make_service()).await?;
# Ok(()) }
```
## Quick start (client)
```rust,no_run
# #[cfg(feature = "client")]
# async fn _doc() -> anyhow::Result<()> {
use est_ca::est::client;
let http = reqwest::Client::new();
let (csr_der, key_pem) = client::make_csr("my-device")?;
let cert_der = client::simpleenroll(
&http, "https://est.example.com", "my-device", "bootstrap-token", &csr_der,
).await?;
// Persist `cert_der` and `key_pem`; use for mTLS to your upstream server.
# Ok(()) }
```
## Transport
RFC 7030 mandates TLS. This crate provides the *protocol* layer only —
the consumer puts a TLS listener in front of the server router and,
for `/simplereenroll`, verifies the client cert and forwards the
verified Common Name via the `x-est-principal` request header.
## Non-goals
- Public-web CA compliance (CA/B Forum Baseline Requirements, WebTrust).
- CRL / OCSP publication — short-lived certs are the revocation story.
- EST's optional endpoints (`/fullcmc`, `/serverkeygen`, `/csrattrs`).
## License
Licensed under either of **Apache-2.0** or **MIT**, at your option.
[`rcgen`]: https://docs.rs/rcgen