est-ca 0.2.0

RFC 7030 Enrollment over Secure Transport (EST) — client, server, and an internal X.509 CA in pure Rust.
# 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

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