Files
kca/README.md
T

237 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# KCA — web PKI management
KCA is a browser-based reimplementation of the software-key workflows of
[XCA](https://github.com/chris2511/xca): private keys, PKCS#10 requests,
X.509 certificates, certificate revocation lists and reusable templates,
organised in private **vaults** (each vault is the web equivalent of one XCA
database).
- **`server/`** — Python + [kaya](https://github.com/woggioni/kaya) backend:
OIDC login, Redis sessions, personal API tokens, Postgres (Tortoise ORM +
aerich migrations), AES-256-GCM private-key encryption, and a complete REST
API. The pure PKI domain logic lives in `packages/kca-core`; the deployable
app is `packages/kca-app`.
- **`web/`** — Rust + [Sycamore](https://sycamore.dev) compiled to WebAssembly
by [Trunk](https://trunkrs.dev), styled with Bootstrap 5.3 (vendored under
`web/vendor/`) and a dark PKI-console theme.
## Run the whole stack
```sh
docker compose up --build
```
Postgres, Redis, a mock OIDC provider (test users `alice`, `bob`, `carol`,
`dave`; any username works), the database migrator and the app all start
together. The app listens on `http://127.0.0.1:8080`.
The browser and the app both use the OIDC issuer
`http://mockoauth:8180/kca`, so add a host entry once:
```sh
echo "127.0.0.1 mockoauth" | sudo tee -a /etc/hosts
```
Then open <http://localhost:8080>, click **Sign in with default**, and type
`alice` (no password). The SPA and API are served from the same origin; the
session cookie authenticates both.
For a real deployment set at least:
```sh
# 32 random bytes, base64 or hex:
KCA_MASTER_KEY="$(openssl rand -base64 32)"
# Point these at your IdP and public callback URL:
OIDC_0_ISSUER=https://idp.example.com
OIDC_0_CLIENT_ID=kca
OIDC_0_CLIENT_SECRET=...
OIDC_0_REDIRECT_URI=https://kca.example.com/auth/default/callback
```
`server/.env.example` documents every setting.
## What it does
### Vaults
Every user owns a private set of vaults. A vault contains keys, certificates,
requests, CRLs and templates; non-owners get `404`. Vaults can be renamed,
deleted and configured from **Settings** (default hash, serial length,
explicit/mandatory DN entries, default key type/size, default validity).
### Keys
- Generate RSA (1024–8192), DSA (1024–3072), EC (P-192/P-224/P-256/P-384/P-521,
secp256k1, brainpool) and Ed25519 keys.
- Import PEM/DER public or private keys, PKCS#8, OpenSSH, PKCS#12 and PVK.
- Export PEM public/private, traditional encrypted PEM, PKCS#8 (plain or
encrypted), DER public/private, SSH2 public/private, JWK public/private and
PVK.
- Private keys are stored encrypted with AES-256-GCM under `KCA_MASTER_KEY`
(AAD bound to the key row id). They are never included in normal API
responses; only an explicit export decrypts them.
### Requests and certificates
- Create PKCS#10 requests with subject, key, extensions and attributes
(challenge password, unstructured name); verify imported requests.
- Issue self-signed certificates or certificates signed by a CA in the same
vault, from scratch or from a request. Imported CSRs are marked **signed**
once a certificate is issued from them.
- The certificate wizard covers subject entries, validity, basic constraints,
key usage, extended key usage, SAN/IAN, subject/authority key identifiers,
CRL distribution points, authority information access, name constraints,
certificate policies, policy constraints, inhibit-any-policy, OCSP no-check,
TLS feature (OCSP stapling), Netscape legacy extensions and raw
OID/DER extensions. The SPA exposes typed controls for the common and
advanced extensions plus an advanced JSON editor for raw/less common
extensions, and a server-side **Validate extensions** action. OpenSSL config
text is available on export.
- Certificate details show subject/issuer, serial, validity, signatures,
MD5/SHA-1/SHA-256 fingerprints, parsed extensions, OpenSSL validation
errors, purposes and the issuer chain.
- Renew with a new or kept serial, optionally revoke/replace the old
certificate. Revoke with a reason; manage the CA's revocation list manually,
including revocation dates and invalidity dates. CA properties (CRL days and
default signing template, including built-ins) are editable from the CA
detail page.
- Absolute validity dates and XCA's "no well-defined expiration" mode are
supported alongside duration-based validity.
- Transform certificates to a public key, a CSR or a template.
- Export PEM, PEM chain, PEM+key, PEM+encrypted PKCS#8, PKCS#7, PKCS#7 chain,
DER, PKCS#12, PKCS#12 chain, OpenVPN, OpenSSL config, certificate index,
vCalendar, JWK/JWK chain and the CA's OpenVPN tls-auth key.
### CRLs
Generate a CRL from a CA's revocation entries with configurable hash, dates,
reason inclusion, AKI and issuer alternative name. Imported CRLs are linked
to their CA and their serials are merged into its revocation list. Export PEM,
DER or vCalendar.
### Templates
Built-in **CA**, **TLS server** and **TLS client** templates mirror XCA's
presets. User templates store subject defaults, extension defaults and a
relative validity range. XCA's binary template format is supported for import
and export, and templates can be applied to a certificate/request spec
server-side.
### Import and migration
- Paste PEM or upload files: PEM bundles, DER certificates/requests/CRLs/keys,
PKCS#7, PKCS#12, SSH keys and XCA templates.
- Preview imports with per-item selection before committing; duplicates are
skipped by default.
- Dump a whole vault as a ZIP (`POST /api/vaults/{id}/export`): manifest,
certificate/request/CRL PEM, public keys and optionally encrypted private
keys.
- Import XCA SQLite `.xdb` databases (schema 7/8): software keys, certificates,
requests, CRLs, revocations, authorities and templates. PKCS#11 token items
are skipped, and private keys encrypted with individual passwords are only
imported when the supplied database password decrypts them.
### Search and API tokens
Global search covers internal names, comments, subjects, issuers, serials,
fingerprints and extension text. Personal API tokens (`Authorization: Bearer
kca_…`) let scripts use the REST API; tokens act as their owner, are stored
only as SHA-256 digests, and cannot mint/revoke tokens.
## REST API
OpenAPI JSON is at `/api/openapi.json`; Swagger UI at `/api/docs`.
| Area | Endpoints |
|---|---|
| Identity | `GET /api/health`, `GET /api/me`, `GET /api/auth/providers`, `GET/POST/DELETE /api/tokens` |
| Vaults | `GET/POST /api/vaults`, `GET/PATCH/DELETE /api/vaults/{id}`, `GET/PATCH /api/vaults/{id}/settings`, `GET /api/vaults/{id}/summary`, `GET /api/vaults/{id}/options`, `GET /api/vaults/{id}/search`, `POST /api/vaults/{id}/export` |
| Keys | `GET/POST /api/vaults/{id}/keys`, `GET/PATCH/DELETE .../keys/{key_id}`, `POST .../keys/{key_id}/export` |
| Requests | `GET/POST /api/vaults/{id}/requests`, `GET/PATCH/DELETE .../{id}`, `POST .../{id}/export`, `POST .../{id}/transform/open-key`, `POST .../{id}/transform/template` |
| Certificates | `GET/POST /api/vaults/{id}/certificates`, `POST /api/vaults/{id}/certificates/preview`, `GET/PATCH/DELETE .../{id}`, `POST .../{id}/export`, `POST .../{id}/renew`, `POST .../{id}/revoke`, `GET/PATCH .../{id}/ca`, `GET/POST .../{id}/revocations`, `PATCH/DELETE .../revocations/{rev_id}`, `POST .../{id}/crl`, `POST .../{id}/tls-auth`, transforms |
| CRLs | `GET /api/vaults/{id}/crls`, `GET/DELETE .../{crl_id}`, `POST .../{crl_id}/export` |
| Templates | `GET/POST /api/vaults/{id}/templates`, `GET/PATCH/DELETE .../{template_id}`, `POST .../{template_id}/apply`, `POST .../{template_id}/export` |
| Import | `POST /api/vaults/{id}/import` (`dry_run`, `selected`, `xca_db`) |
Errors are always `{"error": "..."}`. Mutating resources carry a `version`
for optimistic concurrency (`409` on stale writes). Export endpoints return
JSON with `content_base64` so the SPA can download or copy the bytes.
## Development
Backend (from `server/`, using [uv](https://docs.astral.sh/uv/)):
```sh
uv sync --package kca-app
uv run python -m unittest discover -s tests -t .
uv run python -m unittest discover -s packages/kca-core/tests
uv run mypy -p kca.core -p kca
```
Tests run in-process with in-memory sqlite, the in-memory session store and a
fake OIDC user, exactly like the spendwise reference app.
Browser end-to-end tests (from the repository root, with the Compose stack up):
```sh
cd e2e
npm install
npx playwright install chromium
npm test
```
They use Playwright Chromium. Set `KCA_BASE_URL` to target another port, e.g.
`KCA_BASE_URL=http://localhost:8083 npm test`. See `e2e/README.md`.
Frontend (from `web/`, with the backend running on `:8080`):
```sh
trunk serve # SPA on http://localhost:8000, /api and /auth proxied
cargo test
```
Migrations live in `server/migrations/`. After changing `models.py`:
```sh
DATABASE_URL=postgres://kca:password@localhost:5432/kca uv run aerich migrate --name describe_change
DATABASE_URL=postgres://kca:password@localhost:5432/kca uv run aerich upgrade
```
## Layout
```
server/
packages/kca-core/ # pure PKI domain (keys, names, extensions, certs, CSRs,
# CRLs, templates, imports/exports, .xdb, AES-GCM)
packages/kca-app/ # kaya app, config, Tortoise models, routes, static
migrations/ # aerich schema history
tests/ # app integration suite
dev/mockoauth/ # dev OIDC provider image + test users
web/
src/pages/ # dashboard, vault, keys, requests, certificates,
# wizard, CRLs, templates, import, search, settings, tokens
src/components/ # nav, toast, shared UI
src/{api,auth,model,dom}.rs
pwa/ # manifest + app-shell assets
e2e/ # Playwright browser suite (Node)
```
## Limits
- **Smart cards / PKCS#11 / HSMs are not supported.** A browser app cannot use
a local card reader; server-side PKCS#11 would require a separate HSM
deployment and is deliberately out of scope.
- The desktop command-line interface is replaced by the REST API and personal
access tokens.
- PKCS#12 export uses PBES2/AES via `cryptography`; legacy RC2/3DES PKCS#12
output is not available. Legacy inputs are imported when the library
supports them.
- XCA's OpenSSL-config "Advanced" text editor is replaced by typed extension
fields plus an advanced JSON editor carrying raw OID/DER and other less
common extensions. OpenSSL config remains an export format.
- The PWA service worker is intentionally not registered: certificate and key
data must never be served stale from a cache. The manifest and icons are
still available for installation.
- Column layouts, window geometry, language packs and other desktop-only XCA
preferences are not part of the web UI.