Purpose of this file: This is the single source of truth for any AI agent (Copilot, Cursor, Cline, etc.) working on the Cosmian KMS codebase. It explains project structure, build commands, CI workflows, coding conventions, and troubleshooting steps so the agent can act autonomously and correctly.
Cosmian KMS is a high-performance, open-source FIPS 140-3 compliant Key Management System written in Rust. It implements KMIP 2.1 over HTTP/TLS and supports AES, RSA, EC, ML-KEM, ML-DSA, SLH-DSA, Covercrypt, and more.
# ── Build ────────────────────────────────────────────────────────────────
cargo build # FIPS mode (default)
cargo build --features non-fips # non-FIPS: extra algorithms, PQC, Covercrypt
# ── Test (cargo aliases defined in .cargo/config.toml) ───────────────────
cargo test-fips # test --lib --workspace
cargo test-non-fips # test --lib --workspace --features non-fips
cargo test -p cosmian_kms_server # single crate
cargo test -p cosmian_kms_cli
# ── Lint ─────────────────────────────────────────────────────────────────
cargo clippy-all # clippy --workspace --all-targets --all-features -- -D warnings
cargo format # fmt --all -- --check
# ── Run locally ──────────────────────────────────────────────────────────
cargo run --bin cosmian_kms -- --database-type sqlite --sqlite-path /tmp/kms-data
# ── Smoke-test (expect 422, not 404) ────────────────────────────────────
curl -s -X POST -H "Content-Type: application/json" -d '{}' http://localhost:9998/kmip/2_1| Alias | Expands to |
|---|---|
format |
fmt --all -- --check |
build-all |
build --workspace --all-targets --all-features --bins |
test-fips |
test --lib --workspace |
test-non-fips |
test --lib --workspace --features non-fips |
clippy-all |
clippy --workspace --all-targets --all-features -- -D warnings |
Start backends with docker compose up -d, then set:
| Variable | Value |
|---|---|
KMS_POSTGRES_URL |
postgresql://kms:kms@127.0.0.1:5432/kms |
KMS_MYSQL_URL |
mysql://kms:kms@localhost:3306/kms |
KMS_SQLITE_PATH |
data/shared |
MySQL tests are currently disabled in CI. Redis-findex tests are skipped in FIPS mode.
crate/
access/ cosmian_kms_access — access-control utilities
cli/ cosmian_kms_cli — CLI client binary
clients/
ckms/ ckms — CLI command tree (subcommands live here)
pkcs11/ — PKCS#11 client
client_utils/ cosmian_kms_client_utils — shared client helpers
crypto/ cosmian_kms_crypto — crypto primitives; build.rs builds OpenSSL 3.6.0
hsm/ HSM PKCS#11 loaders (softhsm2, utimaco, proteccio, crypt2pay, smartcardhsm)
interfaces/ cosmian_kms_interfaces — Database/HSM traits
kmip/ cosmian_kmip — KMIP 2.1 protocol types
kmip-derive/ kmip-derive — proc-macros for KMIP serialisation
kms_client/ cosmian_kms_client — HTTP client library
server/ cosmian_kms_server — server binary + lib (main codebase)
server_database/ cosmian_kms_server_database — DB backends (SQLite, PostgreSQL, Redis-findex)
test_kms_server/ test_kms_server — in-process test server helper
wasm/ cosmian_kms_client_wasm — WASM client for the web UI
.github/ CI workflows (.github/workflows/) and helper scripts (.github/scripts/)
documentation/ MkDocs documentation source
nix/ Nix build expressions and expected vendor hashes
pkg/ deb/rpm service files and configs
resources/ Server config templates
test_data/ Test fixtures (submodule)
ui/ Web UI source (React + Vite + Playwright E2E tests)
ui_non_fips/ Pre-built non-FIPS web UI bundle (committed)
HTTP client
│
▼
crate/server/src/routes/kmip.rs — Actix-web handler, deserialises TTLV
│
▼
crate/server/src/core/operations/dispatch.rs — matches TTLV tag → operation function
│
▼
crate/server/src/core/operations/<op>.rs — one file per KMIP operation (41 total)
│
▼
crate/server/src/core/kms/mod.rs — KMS struct (params, database, crypto_oracles, HSM)
│
├── crate/server_database/ — object & permission stores
└── crate/crypto/ — cryptographic primitives
Enterprise routes:
crate/server/src/routes/aws_xks/— AWS XKScrate/server/src/routes/azure_ekm/— Azure EKMcrate/server/src/routes/google_cse/— Google CSEcrate/server/src/routes/ms_dke/— Microsoft DKE
When you need to change something, start here:
| Intent | File(s) |
|---|---|
| Add/change a KMIP operation | crate/server/src/core/operations/<operation>.rs |
| KMIP operation dispatcher | crate/server/src/core/operations/dispatch.rs |
| KMS struct definition | crate/server/src/core/kms/mod.rs |
| Server config & CLI flags | crate/server/src/config/ |
| Server startup | crate/server/src/start_kms_server.rs |
| OpenSSL provider init | crate/server/src/openssl_providers.rs |
| HTTP routes | crate/server/src/routes/ |
| Middlewares (auth, logging) | crate/server/src/middlewares/ |
| KMIP protocol types | crate/kmip/src/ |
| Crypto primitives | crate/crypto/src/ |
| OpenSSL build script | crate/crypto/build.rs |
| DB backend implementations | crate/server_database/src/ |
| CLI commands | crate/clients/ckms/src/ |
| WASM bindings | crate/wasm/src/ |
| Web UI source | ui/src/ |
| E2E tests (Playwright) | ui/tests/e2e/ |
| E2E test helpers | ui/tests/e2e/helpers.ts |
| Flag | Default | Effect |
|---|---|---|
| (none / fips) | on | FIPS-140-3 mode; only NIST-approved algorithms; loads FIPS provider |
non-fips |
off | Legacy OpenSSL provider, Covercrypt, Redis-findex, PQC CLI module, AES-XTS |
Use --features non-fips to enable all non-approved algorithms.
No external OpenSSL needed. OpenSSL 3.6.0 is downloaded, SHA-256-verified,
and built from source by crate/crypto/build.rs into target/ on first build.
Subsequent builds use the cached artefact.
At runtime, crate/server/src/openssl_providers.rs initialises the correct provider:
- FIPS: loads the FIPS provider once via
OnceLock. - non-FIPS: loads the legacy provider on top of the default provider.
apply_openssl_dir_env_if_needed() sets OPENSSL_MODULES and OPENSSL_CONF in
the process environment before any Provider::try_load() call — critical so
OpenSSL can locate legacy.so / fips.so from the build tree.
All CI runs go through Nix via a single script:
bash .github/scripts/nix.sh [--variant fips|non-fips] [--link static|dynamic] COMMAND [args]| Type | FIPS? | Script | Notes |
|---|---|---|---|
sqlite |
yes | test_sqlite.sh |
Default DB backend |
psql |
yes | test_psql.sh |
Requires PostgreSQL |
mysql |
yes | test_mysql.sh |
Disabled in CI |
percona |
yes | test_percona.sh |
Percona XtraDB |
mariadb |
yes | test_maria.sh |
MariaDB |
wasm |
yes | test_wasm.sh |
WASM package build + tests |
google_cse |
yes | test_google_cse.sh |
Requires OAuth creds |
gcp_cmek |
yes | test_gcp_cmek.sh |
GCP CMEK wrapping |
otel_export |
yes | test_otel_export.sh |
OpenTelemetry metrics |
hsm [backend] |
yes | test_hsm_*.sh |
softhsm2 / utimaco / proteccio / all |
redis |
no | test_redis.sh |
Redis-findex (non-FIPS only) |
pykmip |
no | test_pykmip.sh |
PyKMIP + Synology DSM |
aws_xks |
no | aws_xks_test.sh |
AWS XKS |
azure_ekm |
no | azure_ekm_test.sh |
Azure EKM |
ui |
no | test_ui.sh |
Playwright E2E (see §8) |
deb, rpm, dmg — or omit the type to build all packages for the current platform.
bash .github/scripts/nix.sh docker --variant non-fips --load --test| Workflow | Purpose |
|---|---|
main.yml → main_base.yml |
Push/PR trigger; runs clippy, cargo-deny, cargo-test, test_all, docs |
test_all.yml |
Nix-based test matrix: 15 types × 2 variants + HSM matrix |
packaging.yml |
Multi-platform packaging (Linux/ARM/macOS), GPG-signed |
packaging-docker.yml |
Docker image builds (fips + non-fips) |
test_windows.yml |
Windows-only build + test |
build_windows.yml |
Windows server + UI builder |
Stack: React 19 + Vite 7 + Ant Design 5 + Tailwind CSS 4 + Playwright + pnpm
# Full end-to-end (builds WASM, UI, starts KMS + Vite, runs Playwright):
bash .github/scripts/nix.sh --variant non-fips test ui
# Alternative: manually from ui/ after building WASM + UI:
cd ui && CI=true PLAYWRIGHT_BASE_URL="http://127.0.0.1:5173" pnpm run test:e2e- Build WASM:
wasm-pack build --target web --features non-fips - Copy
crate/wasm/pkg/→ui/src/wasm/pkg/ - Install deps:
pnpm install --frozen-lockfile - Build UI:
VITE_KMS_URL=http://127.0.0.1:9998 pnpm run build(runstsc -b && vite build) - Install Playwright browser:
pnpm exec playwright install chromium - Start KMS server on port 9998 (SQLite, non-fips features)
- Start Vite preview on port 5173
- Run Playwright:
PLAYWRIGHT_WORKERS=10 pnpm run test:e2e - Parse KMS server logs for ERROR/WARN and report
ui/playwright.config.ts— Playwright config (workers, retries, base URL)ui/tests/e2e/helpers.ts— shared test helpers (navigation, form submission, Ant Design select interactions)ui/tests/e2e/*.spec.ts— test specs grouped by featureui/tsconfig.node.json— TypeScript config for Playwright / Vite config filesui/tsconfig.app.json— TypeScript config for the React app (noUnusedLocals: true,strict: true)
- Use
data-testidattributes to locate elements (e.g.[data-testid="submit-btn"]). - Ant Design
<Select>portals render indocument.body; use the helpers inhelpers.tsto interact with them. - Use regex-based assertions (not
{ exact: true }) withtoHaveText()— Playwright'stoHaveTextdoes not support anexactoption. - E2E timeouts are generous (60 s for responses) because CI runs 10 parallel workers against one KMS server.
Always use GH_PAGER=cat to prevent gh from spawning an interactive pager
(which hangs in non-interactive terminal sessions):
# View an issue
GH_PAGER=cat gh issue view <number> --repo Cosmian/kms
# View a pull request (description, status checks, review state)
GH_PAGER=cat gh pr view <number> --repo Cosmian/kms
# View PR diff
GH_PAGER=cat gh pr diff <number> --repo Cosmian/kms
# List recent PR checks / CI status
GH_PAGER=cat gh pr checks <number> --repo Cosmian/kms
# View a specific CI run's logs (find run ID from `gh pr checks` output)
GH_PAGER=cat gh run view <run-id> --repo Cosmian/kms --log-failed
# List recent workflow runs
GH_PAGER=cat gh run list --repo Cosmian/kms --limit 10
# Read CI failure logs for a specific job
GH_PAGER=cat gh run view <run-id> --repo Cosmian/kms --job <job-id> --log- Get the failing checks:
GH_PAGER=cat gh pr checks <pr-number> --repo Cosmian/kms - Find the failed run ID from the output (look for ✗ / fail status).
- Read failed logs:
GH_PAGER=cat gh run view <run-id> --repo Cosmian/kms --log-failed - Identify the root cause in the log output (compiler error, test assertion, timeout, etc.).
- Reproduce locally using the matching
nix.shcommand, e.g.:bash .github/scripts/nix.sh --variant non-fips test sqlite - Fix, commit, push — CI will re-run automatically on the PR.
- Function length: keep functions under 100 lines; extract helpers for longer ones.
- Imports: Rust
usestatements go at the top of each file, never inline. - Error handling: never ignore or skip errors in tests or builds — investigate and fix.
- CHANGELOG: update
CHANGELOG.mdfor every user-visible change. Add a single-line entry in the top section (the next-release block). Follow the formatting and style of existing entries. - Commit scope: make minimal, focused changes. Don't refactor surrounding code or add unrelated improvements alongside a bug fix.
- TypeScript (UI):
tsconfig.app.jsonenforcesstrict: true,noUnusedLocals: true,noUnusedParameters: true. Fix all type errors before committing UI changes.
For each change, compare the current git branch with the upstream branch and add
a one-line summary in CHANGELOG.md in the top section (next release). Use
the formatting style of existing entries. Example:
- **UI E2E**: Fix Playwright select helpers to use regex assertions for exact label matching.RUST_LOG="cosmian_kms_server=trace,cosmian_kms_server_database=trace" \
cargo run --bin cosmian_kms -- --database-type sqlite --sqlite-path /tmp/kms-dataAdd the failing crate to RUST_LOG if the problem originates elsewhere.
docker pull ghcr.io/cosmian/kms:latest
docker run -p 9998:9998 --name kms ghcr.io/cosmian/kms:latest
# Web UI at http://localhost:9998/uiDeb and RPM packages are built via Nix. Vendor hash files live in nix/expected-hashes/.
After updating the package version or Cargo.lock, regenerate the vendor hashes:
# Fake-hash trick: put a wrong hash to get the correct hash from the error output
echo "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" \
> nix/expected-hashes/server.vendor.dynamic.sha256
# Trigger the build — it will fail and print the correct hash; copy it back
.github/scripts/nix.sh --variant non-fips --link dynamic 2>&1 | grep "got:"Repeat for all four combinations (fips/non-fips × dynamic/static).
| Symptom | Cause | Fix |
|---|---|---|
Usage mask errors (Encrypt, Sign denied) |
Key missing required CryptographicUsageMask |
Check the object's attributes |
legacy.so / fips.so not found |
OPENSSL_MODULES not set |
Ensure apply_openssl_dir_env_if_needed() in openssl_providers.rs is called before Provider::try_load() |
| Stale Nix vendor hashes | Cargo.lock or version changed |
Regenerate all four hash files (see §13) |
gh command hangs |
Interactive pager opened | Use GH_PAGER=cat gh ... |
Playwright toHaveText type error with exact |
Unsupported option in Playwright | Use anchored regex instead: toHaveText(/^\s*Label\s*$/) |
| TypeScript unused-variable error in UI tests | noUnusedLocals: true in tsconfig |
Remove the variable or prefix with _ |