# SSH CA Engine Implementation Plan ## Overview The SSH CA engine signs SSH host and user certificates using Go's `golang.org/x/crypto/ssh` package. It follows the same architecture as the CA engine: a single CA key pair signs certificates directly (no intermediate hierarchy, since SSH certificates are flat). ## Engine Type `sshca` — registered constant already exists in `internal/engine/engine.go`. ## Mount Configuration Passed as `config` at mount time: | Field | Default | Description | |-----------------|------------------|------------------------------------------| | `key_algorithm` | `"ed25519"` | CA key type: `ed25519`, `ecdsa-p256`, `ecdsa-p384` | | `max_ttl` | `"87600h"` | Maximum certificate validity | | `default_ttl` | `"24h"` | Default certificate validity | RSA is intentionally excluded — Ed25519 and ECDSA are preferred for SSH CAs. This avoids the need for a `key_size` parameter and simplifies key generation. ## Barrier Storage Layout ``` engine/sshca/{mount}/config.json Engine configuration engine/sshca/{mount}/ca/key.pem CA private key (PEM, PKCS8) engine/sshca/{mount}/ca/pubkey.pub CA public key (SSH authorized_keys format) engine/sshca/{mount}/profiles/{name}.json Signing profiles engine/sshca/{mount}/certs/{serial}.json Signed cert records engine/sshca/{mount}/krl_version.json KRL version counter ``` ## In-Memory State ```go type SSHCAEngine struct { barrier barrier.Barrier config *SSHCAConfig caKey crypto.PrivateKey // CA signing key caSigner ssh.Signer // ssh.Signer wrapping caKey mountPath string krlVersion uint64 // monotonically increasing mu sync.RWMutex } ``` Key material (`caKey`, `caSigner`) is zeroized on `Seal()`. ## Lifecycle ### Initialize 1. Parse and validate config: ensure `key_algorithm` is one of `ed25519`, `ecdsa-p256`, `ecdsa-p384`. Parse `max_ttl` and `default_ttl` as `time.Duration`. 2. Store config in barrier as `{mountPath}config.json`. 3. Generate CA key pair: - `ed25519`: `ed25519.GenerateKey(rand.Reader)` - `ecdsa-p256`: `ecdsa.GenerateKey(elliptic.P256(), rand.Reader)` - `ecdsa-p384`: `ecdsa.GenerateKey(elliptic.P384(), rand.Reader)` 4. Marshal private key to PEM using `x509.MarshalPKCS8PrivateKey` → `pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: der})`. 5. Store private key PEM in barrier at `{mountPath}ca/key.pem`. 6. Generate SSH public key via `ssh.NewPublicKey(pubKey)`, marshal with `ssh.MarshalAuthorizedKey`. Store at `{mountPath}ca/pubkey.pub`. 7. Load key into memory: `ssh.NewSignerFromKey(caKey)` → `caSigner`. 8. Initialize `krlVersion` to 0, store in barrier. ### Unseal 1. Load config JSON from barrier, unmarshal into `*SSHCAConfig`. 2. Load `{mountPath}ca/key.pem` from barrier, decode PEM, parse with `x509.ParsePKCS8PrivateKey` → `caKey`. 3. Create `caSigner` via `ssh.NewSignerFromKey(caKey)`. 4. Load `krl_version.json` from barrier → `krlVersion`. ### Seal 1. Zeroize `caKey` using the shared `zeroizeKey` helper (see Implementation References below). 2. Nil out `caSigner`, `config`. ## Operations | Operation | Auth Required | Description | |-------------------|---------------|-----------------------------------------------------| | `get-ca-pubkey` | None | Return CA public key in SSH authorized_keys format | | `sign-host` | User+Policy | Sign an SSH host certificate | | `sign-user` | User+Policy | Sign an SSH user certificate | | `create-profile` | Admin | Create a signing profile | | `update-profile` | Admin | Update a signing profile | | `get-profile` | User/Admin | Get signing profile details | | `list-profiles` | User/Admin | List signing profiles | | `delete-profile` | Admin | Delete a signing profile | | `get-cert` | User/Admin | Get cert record by serial | | `list-certs` | User/Admin | List issued cert summaries | | `revoke-cert` | Admin | Revoke a certificate (soft flag) | | `delete-cert` | Admin | Delete a certificate record | ### HandleRequest dispatch Follow the CA engine's pattern (`internal/engine/ca/ca.go:284-317`): ```go func (e *SSHCAEngine) HandleRequest(ctx context.Context, req *engine.Request) (*engine.Response, error) { switch req.Operation { case "get-ca-pubkey": return e.handleGetCAPublicKey(ctx) case "sign-host": return e.handleSignHost(ctx, req) case "sign-user": return e.handleSignUser(ctx, req) case "create-profile": return e.handleCreateProfile(ctx, req) case "update-profile": return e.handleUpdateProfile(ctx, req) case "get-profile": return e.handleGetProfile(ctx, req) case "list-profiles": return e.handleListProfiles(ctx, req) case "delete-profile": return e.handleDeleteProfile(ctx, req) case "get-cert": return e.handleGetCert(ctx, req) case "list-certs": return e.handleListCerts(ctx, req) case "revoke-cert": return e.handleRevokeCert(ctx, req) case "delete-cert": return e.handleDeleteCert(ctx, req) default: return nil, fmt.Errorf("sshca: unknown operation: %s", req.Operation) } } ``` ### sign-host Request data: | Field | Required | Description | |---------------|----------|------------------------------------------------| | `public_key` | Yes | SSH public key to sign (authorized_keys format) | | `hostnames` | Yes | Valid principals (hostnames) | | `ttl` | No | Validity duration (default: `default_ttl`) | | `extensions` | No | Map of extensions to include | Flow: 1. Authenticate caller (`req.CallerInfo.IsUser()`); admins bypass policy checks. 2. Parse the supplied SSH public key with `ssh.ParsePublicKey(ssh.ParseAuthorizedKey(...))`. 3. Parse TTL: if provided parse as `time.Duration`, cap at `config.MaxTTL`. If not provided, use `config.DefaultTTL`. 4. Policy check: for each hostname, check policy on `sshca/{mount}/id/{hostname}`, action `sign`. Use `req.CheckPolicy`. Fail early before generating a serial or building the cert. 5. Generate a 64-bit serial: `var buf [8]byte; rand.Read(buf[:]); serial := binary.BigEndian.Uint64(buf[:])`. 6. Build `ssh.Certificate`: ```go cert := &ssh.Certificate{ Key: parsedPubKey, Serial: serial, CertType: ssh.HostCert, KeyId: fmt.Sprintf("host:%s:%d", hostnames[0], serial), ValidPrincipals: hostnames, ValidAfter: uint64(time.Now().Unix()), ValidBefore: uint64(time.Now().Add(ttl).Unix()), Permissions: ssh.Permissions{Extensions: extensions}, } ``` 7. Sign: `cert.SignCert(rand.Reader, e.caSigner)`. 8. Store `CertRecord` in barrier at `{mountPath}certs/{serial}.json`. 9. Return: `{"certificate": ssh.MarshalAuthorizedKey(cert), "serial": serial}`. ### sign-user Request data: | Field | Required | Description | |------------------|----------|------------------------------------------------| | `public_key` | Yes | SSH public key to sign (authorized_keys format) | | `principals` | Yes | Valid usernames/principals | | `ttl` | No | Validity duration (default: `default_ttl`) | | `profile` | No | Signing profile name (see below) | | `extensions` | No | Map of extensions (e.g. `permit-pty`) | Critical options are **not accepted directly** in the sign request. They can only be applied via a signing profile. This prevents unprivileged users from setting security-sensitive options like `force-command` or `source-address`. Flow: 1. Authenticate caller (`IsUser()`); admins bypass. 2. Parse the supplied SSH public key. 3. If `profile` is specified, load the signing profile from barrier and check policy (`sshca/{mount}/profile/{profile_name}`, action `read`). Merge the profile's critical options and extensions into the certificate. Any extensions in the request are merged with profile extensions; conflicts are resolved in favor of the profile. If the profile specifies `allowed_principals`, verify all requested principals are in the list. 4. If the profile specifies `max_ttl`, enforce it (cap the requested TTL). 5. Policy check: `sshca/{mount}/id/{principal}` for each principal, action `sign`. Default rule: a user can only sign certs for their own username as principal, unless a policy grants access to other principals. Implement by checking `req.CallerInfo.Username == principal` as the default-allow case. Fail early before generating a serial or building the cert. 6. Generate a 64-bit serial using `crypto/rand`. 7. Build `ssh.Certificate` with `CertType: ssh.UserCert`, principals, validity. 8. Set `Permissions.CriticalOptions` from profile (if any) and `Permissions.Extensions` from merged extensions. Default extensions when none specified: `{"permit-pty": ""}`. 9. Sign with `caSigner`. 10. Store `CertRecord` in barrier (includes profile name if used). 11. Return signed certificate in OpenSSH format + serial. ### Signing Profiles A signing profile is a named, admin-defined template that controls what goes into a signed user certificate. Profiles are the only way to set critical options, and access to each profile is policy-gated. #### Profile Configuration ```go type SigningProfile struct { Name string `json:"name"` CriticalOptions map[string]string `json:"critical_options"` // e.g. {"force-command": "/usr/bin/rsync", "source-address": "10.0.0.0/8"} Extensions map[string]string `json:"extensions"` // merged with request extensions MaxTTL string `json:"max_ttl,omitempty"` // overrides engine max_ttl if shorter AllowedPrincipals []string `json:"allowed_principals,omitempty"` // if set, restricts principals } ``` #### Storage ``` engine/sshca/{mount}/profiles/{name}.json ``` #### Policy Gating Access to a profile is controlled via policy on resource `sshca/{mount}/profile/{profile_name}`, action `read`. A user must have policy access to both the profile and the requested principals to sign a certificate using that profile. Example use cases: - **`restricted-sftp`**: `force-command: "internal-sftp"`, `source-address: "10.0.0.0/8"` — grants users SFTP-only access from internal networks. - **`deploy`**: `force-command: "/usr/local/bin/deploy"`, `source-address: "10.0.1.0/24"` — CI/CD deploy key with restricted command. - **`unrestricted`**: empty critical options — for trusted users who need full shell access (admin-only policy). ## CertRecord ```go type CertRecord struct { Serial uint64 `json:"serial"` CertType string `json:"cert_type"` // "host" or "user" Principals []string `json:"principals"` CertData string `json:"cert_data"` // OpenSSH authorized_keys format KeyID string `json:"key_id"` // certificate KeyId field Profile string `json:"profile,omitempty"` // signing profile used (if any) IssuedBy string `json:"issued_by"` IssuedAt time.Time `json:"issued_at"` ExpiresAt time.Time `json:"expires_at"` Revoked bool `json:"revoked,omitempty"` RevokedAt time.Time `json:"revoked_at,omitempty"` RevokedBy string `json:"revoked_by,omitempty"` } ``` Serial is stored as `uint64` (not string) since SSH certificate serials are uint64 natively. Barrier path uses the decimal string representation: `fmt.Sprintf("%d", serial)`. ## Key Revocation List (KRL) SSH servers cannot query Metacrypt in real time to check whether a certificate has been revoked. Instead, the SSH CA engine generates a KRL that SSH servers fetch periodically and reference via `RevokedKeys` in `sshd_config`. ### KRL Generation — Custom Implementation **Important**: `golang.org/x/crypto/ssh` does **not** provide KRL generation helpers. It can parse KRLs but not build them. The engine must implement KRL serialization directly per the OpenSSH KRL format specification (`PROTOCOL.krl` in the OpenSSH source). The KRL format is a binary structure: ``` MAGIC = "OPENSSH_KRL\x00" (12 bytes) VERSION = uint32 (format version, always 1) KRL_VERSION = uint64 (monotonically increasing per rebuild) GENERATED_DATE = uint64 (Unix timestamp) FLAGS = uint64 (0) RESERVED = string (empty) COMMENT = string (empty) SECTIONS... (one or more typed sections) ``` For serial-based revocation (the simplest and most compact representation): ``` Section type: KRL_SECTION_CERTIFICATES (0x01) CA key blob: ssh.MarshalAuthorizedKey(caSigner.PublicKey()) Subsection type: KRL_SECTION_CERT_SERIAL_LIST (0x20) Revoked serials: sorted list of uint64 serials ``` Implement as a `buildKRL` function: ```go func (e *SSHCAEngine) buildKRL(revokedSerials []uint64) []byte { // 1. Sort serials. // 2. Write MAGIC header. // 3. Write KRL_VERSION (e.krlVersion), GENERATED_DATE (now), FLAGS (0). // 4. Write RESERVED (empty string), COMMENT (empty string). // 5. Write section header: type=0x01 (KRL_SECTION_CERTIFICATES). // 6. Write CA public key blob. // 7. Write subsection: type=0x20 (KRL_SECTION_CERT_SERIAL_LIST), // followed by each serial as uint64 big-endian. // 8. Return assembled bytes. } ``` Use `encoding/binary` with `binary.BigEndian` for all integer encoding. SSH strings are length-prefixed: `uint32(len) + bytes`. The KRL version counter is persisted in barrier at `{mountPath}krl_version.json` and incremented on each rebuild. On unseal, the counter is loaded from barrier. The KRL is rebuilt (not stored in barrier — it's a derived artifact) on: - `revoke-cert` — collects all revoked serials, rebuilds. - `delete-cert` — if the cert was revoked, rebuilds from remaining revoked certs. - Engine unseal — rebuilds from all revoked certs. ### Distribution KRL distribution is a pull model. SSH servers fetch the current KRL via an unauthenticated endpoint (analogous to the public CA key endpoint): | Method | Path | Description | |--------|-------------------------------------|--------------------------------| | GET | `/v1/sshca/{mount}/krl` | Current KRL (binary) | The response includes: - `Content-Type: application/octet-stream` - `ETag` header: `fmt.Sprintf("%d", e.krlVersion)`, enabling conditional fetches. - `Cache-Control: max-age=60` to encourage periodic refresh. SSH servers should be configured to fetch the KRL on a cron schedule (e.g. every 1–5 minutes) and write it to a local file referenced by `sshd_config`: ``` RevokedKeys /etc/ssh/metacrypt_krl ``` ## gRPC Service (proto/metacrypt/v2/sshca.proto) ```protobuf service SSHCAService { rpc GetCAPublicKey(GetCAPublicKeyRequest) returns (GetCAPublicKeyResponse); rpc SignHost(SignHostRequest) returns (SignHostResponse); rpc SignUser(SignUserRequest) returns (SignUserResponse); rpc CreateProfile(CreateProfileRequest) returns (CreateProfileResponse); rpc UpdateProfile(UpdateProfileRequest) returns (UpdateProfileResponse); rpc GetProfile(GetProfileRequest) returns (GetProfileResponse); rpc ListProfiles(ListProfilesRequest) returns (ListProfilesResponse); rpc DeleteProfile(DeleteProfileRequest) returns (DeleteProfileResponse); rpc GetCert(SSHGetCertRequest) returns (SSHGetCertResponse); rpc ListCerts(SSHListCertsRequest) returns (SSHListCertsResponse); rpc RevokeCert(SSHRevokeCertRequest) returns (SSHRevokeCertResponse); rpc DeleteCert(SSHDeleteCertRequest) returns (SSHDeleteCertResponse); rpc GetKRL(GetKRLRequest) returns (GetKRLResponse); } ``` ## REST Endpoints Public (unseal required, no auth): | Method | Path | Description | |--------|-------------------------------------|--------------------------------| | GET | `/v1/sshca/{mount}/ca` | CA public key (SSH format) | | GET | `/v1/sshca/{mount}/krl` | Current KRL (binary) | Typed endpoints (auth required): | Method | Path | Description | |--------|--------------------------------------------|----------------------| | POST | `/v1/sshca/{mount}/sign-host` | Sign host cert | | POST | `/v1/sshca/{mount}/sign-user` | Sign user cert | | POST | `/v1/sshca/{mount}/profiles` | Create profile | | GET | `/v1/sshca/{mount}/profiles` | List profiles | | GET | `/v1/sshca/{mount}/profiles/{name}` | Get profile | | PUT | `/v1/sshca/{mount}/profiles/{name}` | Update profile | | DELETE | `/v1/sshca/{mount}/profiles/{name}` | Delete profile | | GET | `/v1/sshca/{mount}/certs` | List cert records | | GET | `/v1/sshca/{mount}/cert/{serial}` | Get cert record | | POST | `/v1/sshca/{mount}/cert/{serial}/revoke` | Revoke cert | | DELETE | `/v1/sshca/{mount}/cert/{serial}` | Delete cert record | ### REST Route Registration Add to `internal/server/routes.go` in `registerRoutes`, following the CA engine's pattern with `chi.URLParam`: ```go // SSH CA public routes (no auth, unseal required). r.Get("/v1/sshca/{mount}/ca", s.requireUnseal(s.handleSSHCAPublicKey)) r.Get("/v1/sshca/{mount}/krl", s.requireUnseal(s.handleSSHCAKRL)) // SSH CA typed routes (auth required). r.Post("/v1/sshca/{mount}/sign-host", s.requireAuth(s.handleSSHCASignHost)) r.Post("/v1/sshca/{mount}/sign-user", s.requireAuth(s.handleSSHCASignUser)) r.Post("/v1/sshca/{mount}/profiles", s.requireAdmin(s.handleSSHCACreateProfile)) r.Get("/v1/sshca/{mount}/profiles", s.requireAuth(s.handleSSHCAListProfiles)) r.Get("/v1/sshca/{mount}/profiles/{name}", s.requireAuth(s.handleSSHCAGetProfile)) r.Put("/v1/sshca/{mount}/profiles/{name}", s.requireAdmin(s.handleSSHCAUpdateProfile)) r.Delete("/v1/sshca/{mount}/profiles/{name}", s.requireAdmin(s.handleSSHCADeleteProfile)) r.Get("/v1/sshca/{mount}/certs", s.requireAuth(s.handleSSHCAListCerts)) r.Get("/v1/sshca/{mount}/cert/{serial}", s.requireAuth(s.handleSSHCAGetCert)) r.Post("/v1/sshca/{mount}/cert/{serial}/revoke", s.requireAdmin(s.handleSSHCARevokeCert)) r.Delete("/v1/sshca/{mount}/cert/{serial}", s.requireAdmin(s.handleSSHCADeleteCert)) ``` Each handler extracts `chi.URLParam(r, "mount")`, builds an `engine.Request` with the appropriate operation name and data, and calls `s.engines.HandleRequest(...)`. Follow the `handleGetCert`/`handleRevokeCert` pattern in the existing code. All operations are also accessible via the generic `POST /v1/engine/request`. ### gRPC Interceptor Maps Add to `sealRequiredMethods`, `authRequiredMethods`, and `adminRequiredMethods` in `internal/grpcserver/server.go`: ```go // sealRequiredMethods: "/metacrypt.v2.SSHCAService/GetCAPublicKey": true, "/metacrypt.v2.SSHCAService/SignHost": true, "/metacrypt.v2.SSHCAService/SignUser": true, "/metacrypt.v2.SSHCAService/CreateProfile": true, "/metacrypt.v2.SSHCAService/UpdateProfile": true, "/metacrypt.v2.SSHCAService/GetProfile": true, "/metacrypt.v2.SSHCAService/ListProfiles": true, "/metacrypt.v2.SSHCAService/DeleteProfile": true, "/metacrypt.v2.SSHCAService/GetCert": true, "/metacrypt.v2.SSHCAService/ListCerts": true, "/metacrypt.v2.SSHCAService/RevokeCert": true, "/metacrypt.v2.SSHCAService/DeleteCert": true, "/metacrypt.v2.SSHCAService/GetKRL": true, // authRequiredMethods (all except GetCAPublicKey and GetKRL): "/metacrypt.v2.SSHCAService/SignHost": true, "/metacrypt.v2.SSHCAService/SignUser": true, "/metacrypt.v2.SSHCAService/CreateProfile": true, "/metacrypt.v2.SSHCAService/UpdateProfile": true, "/metacrypt.v2.SSHCAService/GetProfile": true, "/metacrypt.v2.SSHCAService/ListProfiles": true, "/metacrypt.v2.SSHCAService/DeleteProfile": true, "/metacrypt.v2.SSHCAService/GetCert": true, "/metacrypt.v2.SSHCAService/ListCerts": true, "/metacrypt.v2.SSHCAService/RevokeCert": true, "/metacrypt.v2.SSHCAService/DeleteCert": true, // adminRequiredMethods: "/metacrypt.v2.SSHCAService/CreateProfile": true, "/metacrypt.v2.SSHCAService/UpdateProfile": true, "/metacrypt.v2.SSHCAService/DeleteProfile": true, "/metacrypt.v2.SSHCAService/RevokeCert": true, "/metacrypt.v2.SSHCAService/DeleteCert": true, ``` Also add SSH CA operations to `adminOnlyOperations` in `routes.go` (keys are `engineType:operation` to avoid cross-engine name collisions): ```go // SSH CA engine. "sshca:create-profile": true, "sshca:update-profile": true, "sshca:delete-profile": true, "sshca:revoke-cert": true, "sshca:delete-cert": true, ``` ## Web UI Add to `/dashboard` the ability to mount an SSH CA engine. Add an `/sshca` page (or section on the existing PKI page) displaying: - CA public key (for `TrustedUserCAKeys` / `@cert-authority` lines) - Sign host/user certificate form - Certificate list with detail view ## Implementation Steps 1. **Move `zeroizeKey` to shared location**: Copy the `zeroizeKey` function from `internal/engine/ca/ca.go` (lines 1481–1498) to a new file `internal/engine/helpers.go` in the `engine` package. Export it as `engine.ZeroizeKey`. Update the CA engine to call `engine.ZeroizeKey` instead of its local copy. This avoids a circular import (sshca cannot import ca). 2. **`internal/engine/sshca/`** — Implement `SSHCAEngine`: - `types.go` — `SSHCAConfig`, `CertRecord`, `SigningProfile` structs. - `sshca.go` — `NewSSHCAEngine` factory, lifecycle methods (`Type`, `Initialize`, `Unseal`, `Seal`), `HandleRequest` dispatch. - `sign.go` — `handleSignHost`, `handleSignUser`. - `profiles.go` — Profile CRUD handlers. - `certs.go` — `handleGetCert`, `handleListCerts`, `handleRevokeCert`, `handleDeleteCert`. - `krl.go` — `buildKRL`, `rebuildKRL`, `handleGetKRL`, `collectRevokedSerials`. 3. **Register factory** in `cmd/metacrypt/server.go` (line 76): ```go engineRegistry.RegisterFactory(engine.EngineTypeSSHCA, sshca.NewSSHCAEngine) ``` 4. **Proto definitions** — `proto/metacrypt/v2/sshca.proto`, run `make proto`. 5. **gRPC handlers** — `internal/grpcserver/sshca.go`. Follow `internal/grpcserver/ca.go` pattern: `sshcaServer` struct wrapping `GRPCServer`, helper function for error mapping, typed RPC methods. Register with `pb.RegisterSSHCAServiceServer(s.srv, &sshcaServer{s: s})` in `server.go`. 6. **REST routes** — Add to `internal/server/routes.go` per the route registration section above. 7. **Tests** — `internal/engine/sshca/sshca_test.go`: unit tests with in-memory barrier following the CA test pattern. Test: - Initialize + unseal lifecycle - sign-host: valid signing, TTL enforcement, serial uniqueness - sign-user: own-principal default, profile merging, profile TTL cap - Profile CRUD - Certificate list/get/revoke/delete - KRL rebuild correctness (revoked serials present, unrevoked absent) - Seal zeroizes key material ## Dependencies - `golang.org/x/crypto/ssh` (already in `go.mod` via transitive deps) - `encoding/binary` (stdlib, for KRL serialization) ## Security Considerations - CA private key encrypted at rest in barrier, zeroized on seal. - Signed certificates do not contain private keys. - Serial numbers are always generated server-side using `crypto/rand` (64-bit); user-provided serials are not accepted. - `max_ttl` is enforced server-side; the engine rejects TTL values exceeding it. - User cert signing defaults to allowing only the caller's own username as principal, preventing privilege escalation. - Critical options (`force-command`, `source-address`, etc.) are only settable via admin-defined signing profiles, never directly in the sign request. This prevents unprivileged users from bypassing `sshd_config` restrictions. - Profile access is policy-gated: a user must have policy access to `sshca/{mount}/profile/{name}` to use a profile. - RSA keys are excluded to reduce attack surface and simplify the implementation. ## Implementation References These existing code patterns should be followed exactly: | Pattern | Reference File | Lines | |---------|---------------|-------| | HandleRequest switch dispatch | `internal/engine/ca/ca.go` | 284–317 | | zeroizeKey helper | `internal/engine/ca/ca.go` | 1481–1498 | | CertRecord storage (JSON in barrier) | `internal/engine/ca/ca.go` | cert storage pattern | | REST route registration with chi | `internal/server/routes.go` | 38–50 | | gRPC handler structure | `internal/grpcserver/ca.go` | full file | | gRPC interceptor maps | `internal/grpcserver/server.go` | 107–192 | | Engine factory registration | `cmd/metacrypt/server.go` | 76 | | adminOnlyOperations map | `internal/server/routes.go` | 259–279 |