Shared .proto definitions and generated gRPC stubs for CryptOS-PKI.
Published as a standalone, versioned Go module. Consumed by cryptos and manager as a Go dependency; consumed by web via generated TypeScript stubs.
Warning
π§ Pre-1.0: any release can change fundamentally. CryptOS is pre-1.0. Until v1.0.0, any release may change configuration, APIs, on-disk and state formats, trust setup, and upgrade paths, sometimes with no migration path. If you run it in production, you accept that risk. Read each release's upgrade notes before you upgrade.
proto/cryptos/v1/ # node .proto sources (the contract)
proto/cryptos/fleet/v1/ # Fleet Manager .proto sources (FleetService, BootstrapService)
go/cryptos/ # generated Go stubs (committed; no toolchain required to consume)
gen/ts/cryptos/ # generated TypeScript stubs for web/ (committed)
buf.yaml # buf module + lint config (STANDARD)
buf.gen.yaml # buf generation: Go (protobuf, gRPC, Connect) + TypeScript (protoc-gen-es)
Taskfile.yml # fmt / lint / generate / test / ci targets
| File | Defines |
|---|---|
node.proto |
NodeService β the per-node management surface: config (ApplyConfig, GetConfig), status and identity, the first-boot ceremony, CA signing and revocation, fetching an issued certificate and its chain by serial (GetIssuedCertificate), key escrow and rotation, reset, in-place image upgrade, Reboot (orderly, CN-confirmed reboot or power-off), SCEP administration (one-time enrolment challenges and the approval queue), the list of TSA certificates, current and past (ListTsaCertificates), and reading and verifying the audit log (ListAuditEvents, VerifyAuditChain). |
identity.proto |
Identity β DER + PEM + leaf SHA-256 for the CA chain. |
ceremony.proto |
CeremonyEvent stream messages + ceremony kind/event enums. |
status.proto |
NodeStatus β role, identity state, TPM state, etcd state, boot count, the revocation preflight result (state, last error, when it was checked), the DNS resolver source and nameservers, the SNTP time-sync state (source, servers, last offset and sync, and the latest error), each enrolment protocol's configured and running state, and whether a stored config change is waiting for a reboot. |
config.proto |
MachineConfig Phase 1 subset (role/network/storage/bootstrap/pki), plus the ACME, EST and SCEP enrolment blocks, the RFC 3161 time-stamp authority block and the Windows autoenrolment (MS-XCEP/WSTEP) block on Pki (acme, est, scep, tsa, windows_enrollment), each with an explicit enabled switch that takes effect at the next boot. |
scep.proto |
The SCEP challenge and approval-queue messages. A challenge is returned once, when it is minted, and no read carries it or its digest. |
tsa.proto |
The TSA certificate messages ListTsaCertificates returns, kept after rotation so old timestamp tokens still verify. |
audit.proto |
AuditEvent β hash-chained audit log entry shape, plus the ListAuditEvents and VerifyAuditChain messages. |
fleet/v1/fleet.proto |
FleetService β the Fleet Manager's surface over the fleet: node (including RenameNode), certificate (including GetCertificate, an issued certificate and its chain by serial), profile, adapter, enrollment and operator-credential RPCs, the manager audit log (ListAudit), MCP agent key management (ListMcpKeys, RevokeMcpKey, CreateMcpKey), step-up approvals (ListApprovals, DecideApproval), the per-node enrolment protocol switch (SetNodeProtocol, with each node's protocol state and reboot_required on NodeSummary), operator CA administration and operator credential requests (see below). |
fleet/v1/bootstrap.proto |
BootstrapService β first run: register the external operator CA and check the first admin certificate (see below). |
fleet/v1/operator_ca.proto |
OperatorCA, its CRL and OCSP state, and the enums both services share. |
fleet/v1/errors.proto |
ErrorCode (the manager's 1600-1699 block) and ErrorReason, the sub-reasons the web branches on. |
Every node has a stable id on NodeSummary: a UUIDv7 in canonical lowercase form that the manager assigns when the node joins the fleet and never changes or reuses. Its name is a display label.
- Every request that addresses a node takes
node_id(child_node_idonCreateEnrollmentRequest). The old name fields still resolve a node's current name for one release and are deprecated. If both are set and name different nodes, the manager returnsInvalidArgument. GetNodeRequest.namealso resolves a name the node held before a rename, when no current node has it, so old links still find the node.Certificate.issuer_node_id,EnrollmentRequest.admitted_node_id,AuditEvent.node_idand the finalAdoptNodeResponse.node_idcarry the ID of the node they point at.RenameNodetakesnode_idandnew_nameand returns the updatedNodeSummary. It fails withNotFoundwhen no node has the ID,AlreadyExistswhen another node has the name, andInvalidArgumentwhen the name isn't an RFC 1123 label. Renaming to the current name changes nothing. It's admin-only and audited asnode-renamed.
AdoptNode streams progress while the manager installs a maintenance node. After the node reboots it presents a new management certificate that nothing links to the maintenance fingerprint confirmed with PreviewAdoption, so the adoption pauses until the operator confirms it:
- Every
AdoptNodeResponsecarriesadoption_id. Theawaiting-fingerprint-confirmationphase also carriespresented_cert_sha256, the lowercase hex SHA-256 of the certificate the node now presents, and the stream waits there. - The operator compares it with the
Mgmt SHA-256line on the node's console and callsConfirmAdoptionFingerprintwithadoption_idandcert_sha256(hex, colons and case ignored). The response is empty; progress continues on theAdoptNodestream. - A mismatch returns
InvalidArgument, ends the adoption with theerrorphase and records nothing.NotFoundmeans no adoption with that ID is waiting. The RPC is admin-only and audited.
A client fetches an issued certificate and its chain by serial:
| RPC | Takes | Returns |
|---|---|---|
NodeService.GetIssuedCertificate |
serial_hex |
certificate_der, chain_der (repeated, issuer up to root), status, revoked_at |
FleetService.GetCertificate |
node_name, serial_hex |
certificate_pem, chain_pem (issuer up to root), status, revoked_at |
statusisvalid,revokedorexpired.revoked_atis RFC3339, empty unless the certificate is revoked.- An unknown serial is
NotFound.GetCertificateis readable at viewer level and above.
The node signs and hash-chains every RPC it serves into an audit log on its encrypted state partition. Two NodeService reads expose it:
| RPC | Takes | Returns |
|---|---|---|
ListAuditEvents |
page_size, page_token, from_time, to_time, event_type, actor |
entries (repeated AuditLogEntry, oldest first), next_page_token |
VerifyAuditChain |
nothing | entry_count, intact, first_broken_sequence, reason |
AuditLogEntryholdsevent(the storedAuditEvent:seq,ts,actor_subject,rpc_method,outcome,details,request_digest_sha256,prev_entry_sha256),entry_sha256(the hash the next entry chains to), andtargetandsummary, which the node derives for display and which aren't part of the chain.from_timeandto_timeare RFC3339 (from_timeinclusive,to_timeexclusive).event_typematchesrpc_methodin full or by method name alone, andactormatches text withinactor_subject. Empty filters match everything.VerifyAuditChainchecks every signature, a gap-freeseqfrom 1, and eachprev_entry_sha256. A broken chain comes back asintactfalse withfirst_broken_sequenceandreason, not as an RPC error.first_broken_sequenceis 0 when the chain is intact.- Both are operator-level reads under the node's existing authorization, like
ListIssued, and are refused in maintenance mode.
The Fleet Manager serves an MCP endpoint for AI agents. An agent authenticates with a long-lived bearer key bound to the serial of the operator certificate that minted it. FleetService carries the key management the web UI needs:
| RPC | Does |
|---|---|
ListMcpKeys |
Lists the caller's keys. An admin sets all to list every operator's keys. |
RevokeMcpKey |
Revokes a key by id. Operators revoke their own keys; admins revoke any. |
CreateMcpKey |
Mints a key with a label and an optional level_ceiling, for MCP clients without the OAuth login. The response's plaintext_key is returned once and never again. |
McpKeyholdsid,label,client_name,operator_cn,operator_serial,level_ceiling,created_at,last_used_atandrevoked_at. It never carries the key or its hash.- A key is identity only. Its effective level is the lower of the bound certificate's live level and
level_ceiling(viewer,operatororadmin), and it stops working when that certificate is revoked or expires. - These RPCs accept an operator certificate only; an MCP key can never mint, list or revoke keys.
AuditEvent (fleet) records the actor on every entry: actor_kind (cert or mcp_key), actor_cn, actor_serial, key_id, via (web, mcp or api), tool, request_digest and outcome (ok, denied, pending or error). approval_id and approver_serial name the step-up approval behind an entry and are empty when none applied. All actor fields are empty on entries recorded before the manager captured an actor.
Some MCP tool calls need a human decision before they run. The manager records each one as an Approval, and the web UI lists and decides them:
| RPC | Does |
|---|---|
ListApprovals |
Lists approvals newest first. status filters to pending, approved, denied, expired or used; empty lists all. |
DecideApproval |
Approves (approve true) or denies a pending approval by id and returns the updated Approval. |
Approvalholdsid,tool,summary,request_digest(lowercase hex SHA-256 of the canonical request),requested_by_cn,requested_by_serial,key_id,required_level(viewer,operatororadmin),created_at,expires_at,status,decided_by_cn,decided_by_serialanddecided_at. Timestamps are RFC3339 strings, empty when unset.- Both RPCs accept an operator certificate only; an MCP key can never list or decide approvals. The decider's level must be at least
required_level.
The operator CA is external (an offline OpenSSL CA or an enterprise CA, never a CryptOS node). The Fleet Manager learns only its certificate and never signs an operator credential.
BootstrapService is served over HTTPS only, outside the client-certificate check, and closes for good the first time an admin certificate authenticates:
| RPC | Does |
|---|---|
GetBootstrapState |
Anonymous. state is NOT_APPLICABLE, OPEN, OPEN_IN_PROGRESS, CLOSED or UNAVAILABLE (with reason_code), plus token_expires_at. |
StartBootstrapSession |
Consumes the one-time token from the manager's log and returns session_secret, sent afterwards in the Fleetos-Bootstrap-Session header. |
RegisterOperatorCA |
Uploads the CA certificate with a CRL source (url, crl_der or none) and an OCSP mode (off, aia, url). The first call returns a preview; a second call with confirm_sha256 stores it. |
SubmitFirstAdminCertificate |
Checks and records the first admin certificate the CA signed, with an optional CSR for the key match. |
FleetService manages operator CAs after first run (ListOperatorCAs, RegisterOperatorCA, RetireOperatorCA, SetOperatorCACRLSource, UploadOperatorCRL, SetOperatorCAOCSP) and operator credentials through requests signed at the CA (CreateOperatorCredentialRequest, ListOperatorCredentialRequests, CancelOperatorCredentialRequest, RecordOperatorCredential). RevokeOperatorCredential puts a credential on the manager's denylist by issuer_sha256 and serial, and OperatorCredential carries kind, issuer_sha256, email, full_name, denylisted, crl_revoked, first_seen_at and last_seen_at. There is no IssueOperatorCredential.
A 16xx failure carries its ErrorCode number on the x-cryptos-error-code error metadata and its ErrorReason name without the prefix (for example IS_NODE_CA) on x-cryptos-error-reason.
Phase 2 will add role-aware service splits, protocol-adapter management, and full machine-config schema. Phase 3 adds HA, fleet, extensions, and recovery RPCs.
go get github.com/CryptOS-PKI/api@latestimport cryptosv1 "github.com/CryptOS-PKI/api/go/cryptos/v1"import fleetv1 "github.com/CryptOS-PKI/api/go/cryptos/fleet/v1"Generated TypeScript stubs for web/ live under gen/ts/cryptos/ (Connect-ES v2, protoc-gen-es).
Requires Go 1.26.8+, buf, npx, and go-task.
The codegen plugins are pinned, so every machine generates the same bytes. task generate installs protoc-gen-go, protoc-gen-go-grpc and protoc-gen-connect-go into .bin/ at the versions set in Taskfile.yml, built with the pinned Go toolchain, and buf.gen.yaml runs them from there. Any copies on your PATH are ignored. Run task generate rather than a bare buf generate. To bump a plugin, change its version in Taskfile.yml and commit the regenerated tree in the same PR.
task ci # fmt + lint + generate-and-verify + test
task generate # regenerate Go and TypeScript stubs after a .proto change
task tools # install the pinned codegen plugins into .bin/ (generate runs this)
task license # re-inject Apache 2.0 headers via golicThe generated stubs under go/ and gen/ts/ are committed; task ci fails if they drift from the protos. The Generated Output check (.github/workflows/ci-generate.yaml) runs the same task generate:verify on every pull request with the same pinned plugins, so a PR whose committed stubs don't match its protos fails. Run task ci before you push to catch it first.
Pre-alpha. The surface is unstable while Phase 1 lands. Tag-based semver kicks in once the v1 contract solidifies.
Apache License 2.0. Copyright The CryptOS Authors.