Files
metacrypt/ARCHITECTURE.md
Kyle Isom fbd6d1af04 Add policy CRUD, cert management, and web UI updates
- Add PUT /v1/policy/rule endpoint for updating policy rules; expose
  full policy CRUD through the web UI with a dedicated policy page
- Add certificate revoke, delete, and get-cert to CA engine and wire
  REST + gRPC routes; fix missing interceptor registrations
- Update ARCHITECTURE.md to reflect v2 gRPC as the active implementation,
  document ACME endpoints, correct CA permission levels, and add policy/cert
  management route tables
- Add POLICY.md documenting the priority-based ACL engine design
- Add web/templates/policy.html for policy management UI

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-15 19:41:11 -07:00

36 KiB

Metacrypt Architecture & Specification

Metacrypt is a cryptographic service for the Metacircular platform. It provides cryptographic resources via a modular "engines" architecture, backed by an encrypted storage barrier inspired by HashiCorp Vault. Authentication is delegated to the Metacircular Identity and Access Service (MCIAS).

Table of Contents

  1. System Overview
  2. Key Hierarchy & Cryptographic Design
  3. Seal/Unseal Lifecycle
  4. Encrypted Storage Barrier
  5. Authentication & Authorization
  6. Engine Architecture
  7. API Surface
  8. Web Interface
  9. Database Schema
  10. Configuration
  11. Deployment
  12. Security Model
  13. Future Work

System Overview

┌─────────────────────────────────────────┐
│         Clients (CLI / Web UI)          │
└──────────────────┬──────────────────────┘
                   │ HTTPS (TLS 1.2+)
┌──────────────────▼──────────────────────┐
│  HTTP API & Web Routes (server/)        │
│   ├── Seal/Unseal endpoints             │
│   ├── Auth endpoints (MCIAS delegation) │
│   ├── Engine request routing            │
│   └── Policy management                 │
├─────────────────────────────────────────┤
│  Middleware                             │
│   ├── Structured logging                │
│   ├── Token authentication              │
│   ├── Policy authorization              │
│   └── Unseal-state gating               │
├─────────────────────────────────────────┤
│  Service Layer                          │
│   ├── Seal Manager     (seal/)          │
│   ├── Authenticator    (auth/)          │
│   ├── Policy Engine    (policy/)        │
│   └── Engine Registry  (engine/)        │
├─────────────────────────────────────────┤
│  Encrypted Storage Barrier (barrier/)   │
│   └── AES-256-GCM per-entry encryption  │
├─────────────────────────────────────────┤
│  Database Layer (db/)                   │
│   └── SQLite (WAL mode, foreign keys)   │
└─────────────────────────────────────────┘

Package Layout

cmd/metacrypt/          CLI entry point (server, init, status, snapshot)
internal/
  config/               TOML configuration loading & validation
  crypto/               Low-level cryptographic primitives
  db/                   SQLite setup & schema migrations
  seal/                 Seal/unseal state machine
  barrier/              Encrypted key-value storage abstraction
  auth/                 MCIAS token authentication & caching
  policy/               Priority-based ACL engine
  engine/               Pluggable engine registry & interface
    ca/                 CA (PKI) engine — X.509 certificate issuance
  acme/                 ACME protocol handler (RFC 8555); EAB, accounts, orders
  server/               REST API HTTP server, routes, middleware
  grpcserver/           gRPC server, interceptors, per-service handlers
  webserver/            Web UI HTTP server, routes, HTMX handlers
proto/metacrypt/
  v1/                   Original gRPC proto definitions (generic Execute RPC)
  v2/                   Typed gRPC proto definitions (per-operation RPCs, Timestamp fields)
gen/metacrypt/v1/       Generated Go gRPC/protobuf code (v1)
gen/metacrypt/v2/       Generated Go gRPC/protobuf code (v2)
web/
  templates/            Go HTML templates (layout, init, unseal, login, dashboard, PKI)
  static/               CSS, HTMX
deploy/                 Docker Compose, example configs

Key Hierarchy & Cryptographic Design

Primitives

Purpose Algorithm Parameters
Key derivation Argon2id 3 iterations, 128 MiB, 4 threads (configurable)
Symmetric encryption AES-256-GCM 256-bit keys, 12-byte random nonce
Key size 256 bits All symmetric keys
Salt size 256 bits Argon2id salt
CSPRNG crypto/rand Keys, salts, nonces
Constant-time comparison crypto/subtle Password & token comparison
Zeroization Explicit overwrite MEK, KWK, passwords in memory

Key Hierarchy

User Password (not stored)
      │
      ▼
┌─────────────────────────────────────┐
│ Argon2id(password, salt, params)    │  salt + params stored in seal_config
└──────────────────┬──────────────────┘
                   ▼
          Key Wrap Key (KWK)              256-bit, ephemeral (derived on unseal)
                   │
                   ▼
          ┌────────────────┐
          │ AES-256-GCM    │              encrypted_mek stored in seal_config
          │ Decrypt         │
          └───────┬────────┘
                  ▼
       Master Encryption Key (MEK)        256-bit, held in memory only when unsealed
                  │
                  ▼
       ┌──────────────────────┐
       │ Barrier Entries       │           Each entry encrypted individually
       │  ├── Policy rules     │           with MEK via AES-256-GCM
       │  ├── Engine configs   │
       │  └── Engine DEKs      │           Per-engine data encryption keys
       └──────────────────────┘

Ciphertext Format

All encrypted values use a versioned binary format:

[version: 1 byte][nonce: 12 bytes][ciphertext + GCM tag]

The version byte (currently 0x01) enables future algorithm migration, including post-quantum hybrid schemes.


Seal/Unseal Lifecycle

Metacrypt operates as a state machine with four states:

                  ┌────────────────┐
                  │ Uninitialized  │  No seal_config row in DB
                  └───────┬────────┘
                          │ Initialize(password)
                          │  • generate salt
                          │  • derive KWK = Argon2id(password, salt)
                          │  • generate random MEK
                          │  • store Encrypt(KWK, MEK) + salt + params
                          ▼
 Seal()         ┌──────────────────┐
 ◄──────────────│    Unsealed      │  MEK in memory; barrier open
 │              └──────────────────┘
 │                        ▲
 │                        │ Unseal(password)
 │                        │  • load salt + params + encrypted_mek
 │                        │  • derive KWK = Argon2id(password, salt)
 │                        │  • MEK = Decrypt(KWK, encrypted_mek)
 │                        │  • barrier.Unseal(MEK)
 │              ┌──────────────────┐
 └─────────────►│     Sealed       │  MEK zeroized; barrier locked
                └──────────────────┘

Rate Limiting

Unseal attempts are rate-limited to mitigate online brute-force:

  • 5 attempts within a 1-minute sliding window
  • 60-second lockout after exceeding the limit
  • Counter resets after 1 minute of inactivity

Sealing

Calling Seal() immediately:

  1. Zeroizes the MEK from memory
  2. Seals the storage barrier (all reads/writes return ErrSealed)
  3. Seals all mounted engines
  4. Flushes the authentication token cache

Encrypted Storage Barrier

The barrier provides an encrypted key-value store over the barrier_entries table. Every value is independently encrypted with the MEK using AES-256-GCM.

Interface

type Barrier interface {
    Unseal(mek []byte) error
    Seal() error
    IsSealed() bool
    Get(ctx context.Context, path string) ([]byte, error)
    Put(ctx context.Context, path string, value []byte) error
    Delete(ctx context.Context, path string) error
    List(ctx context.Context, prefix string) ([]string, error)
}

Path Namespace Conventions

Prefix Owner Contents
policy/rules/{id} Policy engine JSON-encoded ACL rules
engine/{type}/{mount}/ Engine Config, keys, engine data
engine/ca/{mount}/root/ CA engine Root CA cert + key
engine/ca/{mount}/issuers/ CA engine Issuer certs, keys, config
engine/ca/{mount}/certs/ CA engine Issued cert records (no private keys)

Properties

  • Encryption at rest: All values encrypted with MEK before database write
  • Fresh nonce per write: Every Put generates a new random nonce
  • Atomic upsert: Uses INSERT ... ON CONFLICT UPDATE for Put
  • Glob listing: List(prefix) returns relative paths matching the prefix
  • Thread-safe: All operations guarded by sync.RWMutex
  • Fail-closed: Returns ErrSealed for any operation when the barrier is sealed

Authentication & Authorization

Authentication (MCIAS Delegation)

Metacrypt does not manage user accounts. All authentication is delegated to MCIAS:

  1. Client sends POST /v1/auth/login with {username, password, totp_code}
  2. Metacrypt forwards credentials to the MCIAS client library
  3. On success, MCIAS returns a bearer token and expiration
  4. Token is returned to the client (also set as metacrypt_token cookie for web UI)
  5. Subsequent requests include Authorization: Bearer <token> or the cookie

Token validation calls MCIAS ValidateToken(), with results cached for 30 seconds (keyed by SHA-256 hash of the token) to reduce MCIAS load.

Admin detection: Users with the admin role in MCIAS are granted admin privileges in Metacrypt.

Authorization (Policy Engine)

The policy engine evaluates access control rules stored in the barrier.

Rule structure:

type Rule struct {
    ID        string    // unique identifier
    Priority  int       // lower number = higher priority
    Effect    Effect    // "allow" or "deny"
    Usernames []string  // match specific users (optional)
    Roles     []string  // match roles (optional)
    Resources []string  // glob patterns, e.g. "engine/transit/*" (optional)
    Actions   []string  // e.g. "read", "write", "admin" (optional)
}

Evaluation algorithm:

  1. If the requester has the admin role, allow immediately (bypass)
  2. Collect all rules where username, role, resource, and action match
  3. Sort matching rules by priority (ascending; lower number = higher priority)
  4. Return the effect of the highest-priority matching rule
  5. Default deny if no rules match

Matching is case-insensitive for usernames and roles. Resources use glob patterns. Empty fields in a rule match everything.


Engine Architecture

Engines are pluggable cryptographic service providers. The CA (PKI) engine is implemented; remaining engine types are planned.

Engine Types

Type Status Purpose
ca Implemented X.509 Certificate Authority (PKI)
sshca Planned SSH Certificate Authority
transit Planned Encrypt/decrypt data in transit (envelope encryption)
user Planned User-to-user end-to-end encryption

Engine Interface

type CallerInfo struct {
    Username string
    Roles    []string
    IsAdmin  bool
}

type Request struct {
    Operation  string
    Path       string
    Data       map[string]interface{}
    CallerInfo *CallerInfo
}

type Engine interface {
    Type() EngineType
    Initialize(ctx context.Context, b barrier.Barrier, mountPath string, config map[string]interface{}) error
    Unseal(ctx context.Context, b barrier.Barrier, mountPath string) error
    Seal() error
    HandleRequest(ctx context.Context, req *Request) (*Response, error)
}

CallerInfo carries authentication context into engine operations, allowing engines to enforce their own auth requirements (e.g. admin-only operations).

Initialize accepts a config map for engine-specific configuration passed at mount time.

Mount Registry

Engines are instantiated through a factory pattern and tracked in a central registry:

registry.RegisterFactory(engine.EngineTypeCA, ca.NewCAEngine)
registry.Mount(ctx, "pki", engine.EngineTypeCA, map[string]interface{}{
    "organization": "Metacircular",
    "key_algorithm": "ecdsa",
    "key_size":      384,
})
// Creates engine at barrier path: engine/ca/pki/

Each mount gets its own namespace in the barrier for isolated data storage (config, keys, operational data). Mounting an engine calls Initialize(), which performs first-time setup (e.g. generating a root CA). On subsequent unseals, Unseal() loads existing state from the barrier.

The registry provides GetEngine(name) and GetMount(name) methods for direct engine access, used by the public PKI routes to serve certificates without authentication.

Request Routing

POST /v1/engine/request {mount: "pki", operation: "issue", path: "infra", data: {...}}
  → Registry.HandleRequest("pki", req)
    → engine.HandleRequest(ctx, req)
      → Response{Data: {...}}

CA (PKI) Engine

The CA engine (internal/engine/ca/) provides X.509 certificate issuance for Metacircular infrastructure. It implements a two-tier PKI: a single root CA issues scoped intermediate CAs ("issuers"), which in turn issue leaf certificates.

Certificate generation uses the certgen package from git.wntrmute.dev/kyle/goutils/certlib/certgen.

Lifecycle

  • Initialize: Generates a self-signed root CA, stores root cert+key and config in the barrier.
  • Unseal: Loads config, root cert+key, and all issuers from the barrier into memory.
  • Seal: Zeroizes all in-memory private key material (root key, all issuer keys), nils out pointers.

Operations

Operation Auth Required Description
get-root None Return root CA cert PEM
get-chain None Return full chain PEM (issuer + root)
get-issuer None Return issuer cert PEM
create-issuer Admin Generate intermediate CA signed by root
delete-issuer Admin Remove issuer and zeroize its key
list-issuers Any auth List issuer names
issue Admin Issue leaf cert from named issuer
get-cert User/Admin Get cert record by serial
list-certs User/Admin List issued cert summaries
renew Admin Re-issue cert with same attributes

Certificate Profiles

Three default profiles control leaf certificate key usage and validity:

Profile Key Usage Ext Key Usage Default TTL
server Digital Signature, Key Encipherment Server Auth 90 days
client Digital Signature Client Auth 90 days
peer Digital Signature, Key Encipherment Server Auth, Client Auth 90 days

Users can override TTL, key usages, ext key usages, and key algorithm at issuance time.

Issuance Flow

  1. Look up issuer by name
  2. Start from named profile defaults, apply user overrides
  3. Generate leaf key pair, build CSR, sign with issuer via profile.SignRequest
  4. Store CertRecord in barrier (cert PEM + metadata; no private key)
  5. Return cert PEM, private key PEM, chain PEM, serial, metadata

Barrier Storage Layout

engine/ca/{mount}/config.json                    CA config (org, key algo, root expiry)
engine/ca/{mount}/root/cert.pem                  Root CA certificate
engine/ca/{mount}/root/key.pem                   Root CA private key
engine/ca/{mount}/issuers/{name}/cert.pem        Issuer certificate
engine/ca/{mount}/issuers/{name}/key.pem         Issuer private key
engine/ca/{mount}/issuers/{name}/config.json     Issuer config
engine/ca/{mount}/certs/{serial_hex}.json         Issued cert record (no private key)

CA Configuration

Passed as config at mount time:

Field Default Description
organization "Metacircular" Root/issuer certificate O field
country "" Root/issuer certificate C field
key_algorithm "ecdsa" Key type: ecdsa, rsa, ed25519
key_size 384 Key size (e.g. 256/384 for ECDSA, 2048/4096 for RSA)
root_expiry "87600h" Root CA validity (10 years)

API Surface

Metacrypt exposes two API surfaces: a JSON REST API and a gRPC API. Both are kept in sync — every operation available via REST has a corresponding gRPC RPC.

Seal/Unseal (Unauthenticated)

Method Path Description Precondition
GET /v1/status Service state + version info None
POST /v1/init First-time seal initialization uninitialized
POST /v1/unseal Unseal with password sealed

Seal Control (Admin Only)

Method Path Description
POST /v1/seal Seal service & engines

Authentication

Method Path Description Auth Required
POST /v1/auth/login MCIAS login → token No
POST /v1/auth/logout Invalidate token Yes
GET /v1/auth/tokeninfo Current user info Yes

Engines (Authenticated)

Method Path Description Auth
GET /v1/engine/mounts List mounted engines User
POST /v1/engine/mount Create new engine mount Admin
POST /v1/engine/unmount Remove engine mount Admin
POST /v1/engine/request Route request to engine User

Policy (Admin Only)

Method Path Description
GET /v1/policy/rules List all policy rules
POST /v1/policy/rules Create a new policy rule
GET /v1/policy/rule?id= Get a policy rule by ID
PUT /v1/policy/rule?id= Update a policy rule by ID
DELETE /v1/policy/rule?id= Delete a policy rule by ID

The mount endpoint accepts {name, type, config} where config is an engine-type-specific configuration object. The request endpoint accepts {mount, operation, path, data} and populates CallerInfo from the authenticated user's token.

Public PKI (Unauthenticated, Unsealed Required)

Method Path Description
GET /v1/pki/{mount}/ca Root CA certificate (PEM)
GET /v1/pki/{mount}/ca/chain Full chain: issuer + root (PEM)
GET /v1/pki/{mount}/issuer/{name} Issuer certificate (PEM)

These routes serve certificates with Content-Type: application/x-pem-file, allowing systems to bootstrap TLS trust without authentication. The mount must be of type ca; returns 404 otherwise.

CA Certificate Management (Authenticated)

Method Path Description Auth
GET /v1/ca/{mount}/cert/{serial} Get certificate record by serial User
POST /v1/ca/{mount}/cert/{serial}/revoke Revoke a certificate Admin
DELETE /v1/ca/{mount}/cert/{serial} Delete a certificate record Admin

Policy (Authenticated)

Method Path Description Auth
GET /v1/policy/rules List all rules User
POST /v1/policy/rules Create a rule User
GET /v1/policy/rule?id= Get rule by ID User
DELETE /v1/policy/rule?id= Delete rule by ID User

ACME (RFC 8555)

ACME protocol endpoints are mounted per CA engine instance and require no authentication (per the ACME spec). External Account Binding (EAB) is supported.

Method Path Description Auth
GET /acme/{mount}/directory ACME directory object None
HEAD/GET /acme/{mount}/new-nonce Obtain a fresh replay nonce None
POST /acme/{mount}/new-account Register or retrieve an account None
POST /acme/{mount}/new-order Create a new certificate order None
POST /acme/{mount}/authz/{id} Fetch/respond to an authorization None
POST /acme/{mount}/challenge/{type}/{id} Respond to a challenge None
POST /acme/{mount}/finalize/{id} Finalize an order (submit CSR) None
POST /acme/{mount}/cert/{id} Download issued certificate None
POST /acme/{mount}/revoke-cert Revoke a certificate via ACME None

ACME management endpoints require MCIAS authentication:

Method Path Description Auth
POST /v1/acme/{mount}/eab Create EAB credentials for a user User
PUT /v1/acme/{mount}/config Set default issuer for ACME mount Admin
GET /v1/acme/{mount}/accounts List ACME accounts Admin
GET /v1/acme/{mount}/orders List ACME orders Admin

Error Responses

All API errors return JSON:

{"error": "description of what went wrong"}

HTTP status codes:

  • 401 — missing or invalid token
  • 403 — insufficient privileges
  • 412 — service not initialized
  • 503 — service is sealed

gRPC API

Metacrypt also exposes a gRPC API defined in proto/metacrypt/. Two API versions exist:

v1 (legacy proto definitions)

The v1 API uses a generic Execute RPC for all engine operations. The v1 proto definitions are retained for reference; the active server implementation uses v2.

rpc Execute(ExecuteRequest) returns (ExecuteResponse);

message ExecuteRequest {
  string mount     = 1;
  string operation = 2;
  string path      = 3;
  google.protobuf.Struct data = 4;  // JSON-like map
}

message ExecuteResponse {
  google.protobuf.Struct data = 1;  // JSON-like map
}

Timestamps are represented as RFC3339 strings within the Struct payload. The EngineService also provides Mount, Unmount, and ListMounts RPCs for engine lifecycle management.

v2 (implemented)

The v2 API (proto/metacrypt/v2/) replaces the generic Execute RPC with strongly-typed, per-operation RPCs and uses google.protobuf.Timestamp for all time fields. The gRPC server is fully implemented against v2. Key changes from v1:

  • CAService: 14 typed RPCs — ImportRoot, GetRoot, CreateIssuer, DeleteIssuer, ListIssuers, GetIssuer, GetChain, IssueCert, GetCert, ListCerts, RenewCert, SignCSR, RevokeCert, DeleteCert.
  • EngineService: Retains Mount, Unmount, ListMounts; drops the generic Execute RPC. MountRequest.config is map<string, string> instead of google.protobuf.Struct.
  • Timestamps: All issued_at / expires_at fields use google.protobuf.Timestamp instead of RFC3339 strings.
  • Message types: CertRecord (full certificate data) and CertSummary (lightweight, for list responses) replace the generic struct maps.
  • ACMEService: CreateEAB, SetConfig, ListAccounts, ListOrders.
  • AuthService: String timestamps replaced by google.protobuf.Timestamp.

The v2 proto definitions pass buf lint with no warnings. Generated Go code lives in gen/metacrypt/v2/.

gRPC Interceptors (v2)

The gRPC server (internal/grpcserver/) uses three interceptor maps to gate access:

Interceptor map Effect
sealRequiredMethods Returns UNAVAILABLE if the barrier is sealed
authRequiredMethods Validates MCIAS bearer token; populates caller info
adminRequiredMethods Requires IsAdmin == true on the caller

All CA write operations, engine lifecycle RPCs, policy mutations, and ACME management RPCs are gated on unseal state, authentication, and (where appropriate) admin privilege.


Web Interface

Metacrypt includes an HTMX-powered web UI for basic operations:

Route Purpose
/ Redirects based on service state
/init Password setup form (first-time only)
/unseal Password entry to unseal
/login MCIAS login form (username, password, TOTP)
/dashboard Engine mounts, service state, admin controls
/dashboard/mount-ca Mount a new CA engine (POST, admin)
/pki PKI overview: list issuers, download CA/issuer PEMs
/pki/import-root Import an existing root CA (POST)
/pki/create-issuer Create a new intermediate issuer (POST)
/pki/issue Issue a leaf certificate (POST)
/pki/download/{token} Download issued cert bundle as .tar.gz
/pki/issuer/{name} Issuer detail: certificates issued by that issuer
/pki/cert/{serial} Certificate detail page
/pki/cert/{serial}/download Download certificate files
/pki/cert/{serial}/revoke Revoke a certificate (POST)
/pki/cert/{serial}/delete Delete a certificate record (POST)
/pki/{issuer} Issuer detail (alternate path)

The dashboard shows mounted engines, the service state, and (for admins) a seal button. Templates use Go's html/template with a shared layout. HTMX provides form submission without full page reloads.

The PKI pages communicate with the backend via the internal gRPC client (internal/webserver/client.go), which uses the v2 typed gRPC stubs (CAService, EngineService, SystemService, etc.). The issuer detail page supports filtering certificates by common name (case-insensitive substring match) and sorting by common name (default) or expiry date.


Database Schema

SQLite with WAL mode, foreign keys enabled, 5-second busy timeout.

Tables

seal_config — Single row storing the encrypted master key material.

Column Type Description
id INTEGER Always 1 (enforced primary key)
encrypted_mek BLOB MEK encrypted with KWK
kdf_salt BLOB 32-byte Argon2id salt
argon2_time INTEGER Argon2id time cost
argon2_memory INTEGER Argon2id memory cost (KiB)
argon2_threads INTEGER Argon2id parallelism
initialized_at DATETIME Timestamp of initialization

barrier_entries — Encrypted key-value store.

Column Type Description
path TEXT Primary key; hierarchical path
value BLOB AES-256-GCM encrypted value
created_at DATETIME Row creation time
updated_at DATETIME Last modification time

schema_migrations — Tracks applied schema versions.

Column Type Description
version INTEGER Migration version number
applied_at DATETIME When applied

Migrations are idempotent and run sequentially at startup.


Configuration

TOML configuration with environment variable overrides (METACRYPT_*).

[server]
listen_addr = ":8443"           # required
grpc_addr   = ":8444"           # optional; gRPC server disabled if unset
tls_cert    = "/path/cert.pem"  # required
tls_key     = "/path/key.pem"   # required

[web]
listen_addr  = "127.0.0.1:8080" # optional; web UI server address
vault_grpc   = "127.0.0.1:9443" # gRPC address of the vault server
vault_ca_cert = "/path/ca.pem"  # optional; CA cert to verify vault TLS

[database]
path = "/path/metacrypt.db"     # required

[mcias]
server_url = "https://mcias.metacircular.net:8443"  # required
ca_cert    = "/path/ca.pem"     # optional, for custom CA

[seal]
argon2_time    = 3              # default: 3
argon2_memory  = 131072         # default: 128 MiB (in KiB)
argon2_threads = 4              # default: 4

[log]
level = "info"                  # default: "info"

Required fields are validated at startup; the server refuses to start if any are missing.


Deployment

Docker

Multi-stage build:

  1. Builder stage: Go compilation with symbols stripped (-s -w)
  2. Runtime stage: Alpine 3.21, non-root metacrypt user
VOLUME /data    # config, certs, database
EXPOSE 8443
ENTRYPOINT ["metacrypt", "server", "--config", "/data/metacrypt.toml"]

TLS Configuration

  • Minimum TLS version: 1.2
  • Cipher suites: TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
  • Timeouts: read 30s, write 30s, idle 120s

CLI Commands

Command Purpose
server Start the HTTPS server
init Interactive first-time seal setup
status Query a running server's state
snapshot Create a consistent database backup (VACUUM INTO)

Graceful Shutdown

The server handles SIGINT and SIGTERM signals, sealing all engines and closing connections before exit.


Security Model

Threat Mitigations

Threat Mitigation
Database theft All barrier values encrypted with MEK (AES-256-GCM)
Brute-force unseal Argon2id (128 MiB memory-hard), rate limiting (5/min + lockout)
MEK exposure Held in memory only when unsealed; zeroized on seal
Token theft 30-second cache TTL; MCIAS-backed validation
Privilege escalation Default-deny policy; admin bypass only for MCIAS admin role
Nonce reuse Fresh random nonce per encryption operation
Timing attacks Constant-time comparison for passwords and tokens
Unauthorized access at rest Database file permissions 0600; non-root container user
TLS downgrade Minimum TLS 1.2; only AEAD cipher suites
CA key compromise CA/issuer keys encrypted in barrier; zeroized on seal; two-tier PKI limits blast radius
Leaf key leakage via storage Issued cert private keys never persisted; only returned to requester

Security Invariants

  1. The MEK never leaves process memory and is never logged or serialized in plaintext.
  2. The seal password is never stored; only its Argon2id-derived output is used transiently.
  3. All barrier writes produce fresh ciphertexts (random nonce per encryption).
  4. The service is fail-closed: a sealed barrier rejects all operations.
  5. Admin privileges are determined solely by MCIAS role membership; Metacrypt has no local user database.
  6. Issued certificate private keys are returned to the caller but never stored in the barrier. Only cert metadata is persisted.
  7. CA and issuer private keys are encrypted at rest in the barrier and zeroized from memory on seal (explicit overwrite of ECDSA D, RSA D and primes, Ed25519 key bytes).

Future Work

Remaining Engine Implementations

  • SSH CA Engine — Sign SSH host and user certificates
  • Transit Engine — Encrypt/decrypt payloads on behalf of applications (envelope encryption); key rotation
  • User Engine — Key exchange and encryption between Metacircular users

CA Engine Enhancements

  • CRL management — Certificate revocation lists
  • OCSP responder — Online certificate status checking
  • Certificate templates — Admin-defined custom profiles beyond server/client/peer

Planned Capabilities

  • Post-quantum readiness — Hybrid key exchange (ML-KEM + ECDH); the versioned ciphertext format and engine interface are designed for algorithm agility
  • Key rotation — MEK and per-engine DEK rotation without re-sealing
  • Audit logging — Tamper-evident log of all cryptographic operations
  • Engine persistence — Auto-remounting engines from barrier state on unseal

Public Key Algorithms

The CA engine supports ECDSA (P-256, P-384, P-521), RSA, and Ed25519 for certificate key pairs, configurable per-CA and overridable per-issuer or per-issuance. The system is designed for algorithm agility to support future post-quantum algorithms.