Custody, for engineers
Custody is where records and signing keys live, what Stratl can and cannot do with them, and how to check. The plain-words version.
Backends
Section titled “Backends”| Backend | Records | Signing key | Use it for |
|---|---|---|---|
local |
Folders on the API server’s disk | A key file on the same disk | Development and demos; the portal labels it Local dev |
s3-kms |
One S3 bucket, one prefix per workspace | One AWS KMS key per workspace, created inside KMS, never exportable | Production, Stratl-managed |
| Customer | The customer’s bucket: S3, Google Cloud Storage or Azure Blob | The customer’s key: AWS KMS, Google Cloud KMS or Azure Key Vault | Workspaces that have taken custody |
STRATL_CUSTODY_BACKEND chooses between the first two for the deployment; taking custody is per workspace on top of either.
What s3-kms guarantees
Section titled “What s3-kms guarantees”- The private key never exists outside KMS: created as a non-exportable ECC P-256 key; Stratl holds the public half and can only ask KMS to sign.
- Every signature is checked before it is accepted, against the public key, before anything is written.
- The API cannot delete: its IAM user may put and get objects and sign with keys tagged
stratl:managed; not delete objects, not delete, disable or re-policy keys. Bucket versioning keeps every version. - Signing is in AWS’s log too: each
kms:Signappears in CloudTrail, and the request id is on the key-usage record. - Optional Object Lock on the bucket makes versions undeletable for a period, by anyone in GOVERNANCE mode without a special permission, by no one in COMPLIANCE mode.
Regions
Section titled “Regions”At sign-up the owner picks a region; the workspace’s key is created in that AWS region and its records, checkpoints and content are stored in a bucket there. Only configured regions are offered:
STRATL_CUSTODY_REGIONS=us=us-east-1:records-us,eu=eu-west-1:records-eu,in=ap-south-1:records-inEach workspace remembers its own region and bucket, so editing this later adds or retires choices for new workspaces and never moves an existing one. The promise covers records, content, checkpoints and keys; the PostgreSQL index and the API run in one location for all workspaces.
Every use of the key is a record
Section titled “Every use of the key is a record”The service asks the key service to sign at most twice per record: the record, and later the checkpoint that covers it, plus a manifest signature per exported pack. Each request is noted at the moment it happens with what was signed, the hash, the key id, the backend and the provider’s request id. At each checkpoint the notes since the last one are sealed into a signed DEC-KEY-… record under the subject signing_key and the agent stratl-custody, one action event per use. The ledger’s own signature is a use too and appears in the next ledger; an idle workspace does not write ledgers about ledgers. The Custody page shows the count and links to the latest.
Bring your own custody
Section titled “Bring your own custody”Any cloud that provides a non-exporting key service and versioned, no-delete object storage can hold a workspace’s custody.
| Cloud | Storage | Key | Stratl’s identity | Set-up |
|---|---|---|---|---|
| AWS | S3 bucket with versioning and Object Lock | KMS ECC_NIST_P256 sign/verify key |
A role in your account that Stratl assumes, with your workspace id as external id | deploy/cloudformation/byo-custody.yaml |
| Google Cloud | Cloud Storage bucket with versioning and a retention policy | Cloud KMS EC_SIGN_P256_SHA256 key |
Stratl’s service account granted objectCreator, objectViewer, signerVerifier, publicKeyViewer; or a service-account key by secret reference |
deploy/gcp/byo-custody.sh |
| Azure | Blob container with versioning and an immutability policy | Key Vault EC P-256 key | Stratl’s Entra application granted a create-and-read blob role and Crypto User on the key; or your own app registration by secret reference | deploy/azure/byo-custody.sh |
The Stratl identity to grant is shown in the Take custody dialog and by GET /v1/custody/providers. Secret references (env:NAME, aws-sm:ARN) are resolved at use and never stored.
The procedure
Section titled “The procedure”Only the workspace owner can do this. From the Custody page, Take custody.
-
Run the set-up in your account. Each script or template creates the bucket, the key and a least-power identity, and prints the values to paste. Read it before running it; it is short.
-
Paste the values into the dialog: for AWS the region, bucket, key ARN and role ARN; for Google Cloud the project, bucket and key version resource name; for Azure the tenant id, storage account, container, vault URL and key name.
-
Prove it. Stratl, with the identity you granted, signs a probe and verifies it against the key’s public half, writes a small object under
_stratl/probes/and reads it back, then tries to delete it. The delete must be refused. All four steps are shown; a configuration that fails any of them is refused and nothing changes.POST /v1/custody/testruns the probe alone. -
Take custody. A transfer statement naming the old custody, the new custody, the chain head and the last checkpoint is signed by both keys and written into both storages at
transfers/<id>.jsonwith both public keys. The first record signed with your key isDEC-CT-…, carrying the statement’s fingerprint. From here every new record is signed with your key, stored in your bucket and labelledcustomer. -
History is copied. Every earlier record and its signature, every checkpoint, every public key that ever signed and every original is written into your storage by the scheduler, a batch per tick. The Custody page shows progress. History is never re-signed: the old public key stays in every bundle, so old records still verify.
The CloudFormation template creates a role trusted by Stratl’s AWS account, conditioned on sts:ExternalId equal to your workspace id, with a policy allowing s3:PutObject, s3:PutObjectRetention, s3:GetObject, s3:ListBucket on the bucket and kms:Sign, kms:GetPublicKey, kms:DescribeKey on the key. No delete, no key management.
roles/storage.objectCreator (cannot overwrite or delete) and roles/storage.objectViewer on the bucket; roles/cloudkms.signerVerifier and roles/cloudkms.publicKeyViewer on the key. The bucket has uniform access, versioning, public access prevention and a retention period.
A custom role Stratl Evidence Writer with blob read, write and add actions and no delete, on the container; Key Vault Crypto User on the key. The storage account has versioning and public access off; the container an immutability policy; the vault RBAC and purge protection.
Timestamp authorities
Section titled “Timestamp authorities”STRATL_TSA_URL is a comma-separated list of RFC 3161 authorities tried in order; the default pairs a public authority with a commercial CA’s public server. The token records which answered. A checkpoint no authority stamps is marked failed and retried on later ticks against the whole list.
Keys change, records stay verifiable
Section titled “Keys change, records stay verifiable”Each record and checkpoint carries the key_id that signed it, and every public key that has ever signed for a workspace is kept in the registry and next to the records. A bundle carries the key of the record it contains. One current limit: a bundle holds a single key, so if a record was signed by one key and its checkpoint by a later key, the checkpoint check does not run offline; seal pending records with a checkpoint before changing keys.
Proving managed custody against a real account
Section titled “Proving managed custody against a real account”uv run --directory api python scripts/check_custody.pyIn every configured region it signs in both KMS modes, writes and reads S3, and confirms the API is not allowed to delete. It uses a throwaway workspace name and never touches a real workspace’s chain. The API also refuses to start if a configured bucket is unreachable, and says why.