Proposal: Caution-held Shares, and WebAuthn Transport #7

Open
opened 2026-07-03 12:40:32 +00:00 by ryansquared · 7 comments
ryansquared commented 2026-07-03 12:40:32 +00:00 (Migrated from codeberg.org)

Current status — 2026-09-24

The V1 external-PGP/passkey/mixed-holder design is implemented. This remains the design/trust-model umbrella; it is no longer an unimplemented transport proposal.

Published PR heads checked on 2026-09-24: Platform 1f7e296, Locksmith 9f702bb, Bootproof 821b5c6. Platform's dependency/model/mock/default-runtime pins are aligned at Locksmith d2876e9; subsequent Locksmith changes concern examples, configuration, documentation and terminology. These are published implementation states, not merged/deployed-state claims.

Implemented through #10, #11, #12, Platform #383 and #384: separate Keymaker and key-service lifecycles; external-PGP root recovery; proof-bound holder associations; attested destination-bound single-use UV approval; recryption inside the key-service enclave; native/browser flows and guided dashboard creation. Explicit ImportedV0 recovery is now separate supported functionality. Signed freshness and cross-session replay regressions also exist.

Remaining design/security decisions: effective credential/holder revocation for existing snapshots; common-root compromise scope and constrained private-key capabilities; root/token recovery and migration; service availability/access controls; independent review/fuzzing; exact-revision Nitro/hardware/restart acceptance. A passkey approves one share; multiple passkeys are not extra quorum votes. Distinct holders backed by the same root are not independent custody-compromise domains.

Accepted scope remains: operator-configured service URLs/trust files; Keymaker-owned one-shot serialization with no additional Platform queue; enclave-lifetime key-service root; current proofed V1 generation. Explicit holder-assisted ImportedV0 recovery has since been added separately; it does not restore V0 generation, silently downgrade failed V1 verification, or migrate a secret by generating a new quorum. The accepted CA-certified UID/context profile and fixed V1 encodings remain; a general structure-hash registry is deferred.

The implemented protocol and later clarification comments supersede the historical proposal below, including its speculative transport/schema details. See the current release contract. No ticket is closed by this status update; implementation PRs remain open/unmerged.

Historical proposal

Caution Secret Unlocking Strategy

Out of band: Client runs caution apps verify --save-pcrs so Locksmith can use those PCRs for attestation

Existing Flow: OpenPGP

  • Client: Generate a nonce
  • Client: Get attestation from enclave, verify with nonce, receive enclave's public key
  • Client: Generate an X25519 private+public keypair
  • Client: Uses Keyfork to decrypt the shard, encrypt to aes256gcm(hkdf(x25519_dh(server_pubkey, client_privkey)))
  • Client: Sign { encrypted_payload, public_key: our_public_key }
  • Client: Send signed, encrypted, request. Check status.

Proposed Flow: WebAuthn + Caution Share Storage

Note: 'Caution' in this instance is the enclave that stores the shares,
'enclave' refers to the customer's enclave that is receiving shards

  • Client: Generate att_nonce, get attestation from Caution, receive caution_verifying_key, caution_public_key (x25519) from Caution
  • Client: Receives transport_nonce from Caution
  • Client: Sends nonce to enclave, gets attestation, verify with Caution nonce, receive enclave's public key
  • Client: Generate WebAuthn signed bundle: { enclave_public_key, enclave_attestation, enclave_pcrs }
  • Client: Send WebAuthn signed bundle to Caution
  • Caution: verify WebAuthn bundle, generate X25519 public+private keypair
  • Caution: Use internal secret decryption to decrypt the shard matching client's WebAuthn key, encrypt to above algo
  • Caution: Sign { encrypted_payload, public_key: cautions_public_key } with caution_verifying_key private counterpart
  • Caution: Send signed, encrypted request.
  • Client: Receive signed, encrypted request, verify signature using caution_verifying_key
  • Client: Sign { encrypted_payload, public_key: caution_public_key } with WebAuthn
  • Client: Send signed, encrypted, request. Check status.

Use Cases:

  • Locksmith vault decryption.
  • that's it! :)

Threats:

  • Attacker controls Caution domain endpoints and deploys their enclave in the same spot as ours
    • Solution: Embed PCRs of the Caution enclave, or signing keys to verify signed PCR files, relating to the Caution team
  • Attacker controls database: only use it as value storage, encrypt everything to Caution controlled quorum
    • Potential issue 1: Can attackers get the shards? No. We only store encrypted values, can be decrypted by the Caution enclave, locked by a Caution OpenPGP quorum
    • Potential issue 2: Can attackers invalidate the relationship between shares and WebAuthn keys? No. It's hardcoded in the shard.
    • Solutions involve append-only databases
      • Authoritative hash
      • Timestamp based
      • Retain last-known valid point (signed by enclave?)
      • Chain of signatures that modify the org, including existing state of the org
        • Adding a new key requires >= 1 signature from an existing key
        • Local policy engine for the org determines whether or not new changes get added to the quorum
        • a la Icepick. :(.
        • Soon™. 4 years from NOW().
  • Attacker replaces the bundle in the source code of the enclave with a different bundle
    • Bundle contains a separate secret and results in a new, distinct decryption key
    • May also result in the same key but different values being decrypted, if instead secrets are replaced
    • Instead, place signatures in the bundle, and allow for users to verify the signatures using their local WebAuthn key
    • Don't use #[serde(deny_unknown_fields)] or even better: add a dummy slot just to hold the signatures
## Current status — 2026-09-24 **The V1 external-PGP/passkey/mixed-holder design is implemented. This remains the design/trust-model umbrella; it is no longer an unimplemented transport proposal.** Published PR heads checked on 2026-09-24: [Platform `1f7e296`](https://codeberg.org/caution/platform/commit/1f7e2963932ac616ba6c4f0e9553112cb548ce72), [Locksmith `9f702bb`](https://codeberg.org/caution/locksmith/commit/9f702bbf465bc4968e0fa57adcbbc366a1c6977c), [Bootproof `821b5c6`](https://codeberg.org/caution/bootproof/commit/821b5c63e80f082f6d67ba3695c11416933489ec). Platform's dependency/model/mock/default-runtime pins are aligned at Locksmith `d2876e9`; subsequent Locksmith changes concern examples, configuration, documentation and terminology. These are published implementation states, not merged/deployed-state claims. Implemented through [#10](https://codeberg.org/caution/locksmith/issues/10), [#11](https://codeberg.org/caution/locksmith/issues/11), [#12](https://codeberg.org/caution/locksmith/issues/12), [Platform #383](https://codeberg.org/caution/platform/issues/383) and [#384](https://codeberg.org/caution/platform/issues/384): separate Keymaker and key-service lifecycles; external-PGP root recovery; proof-bound holder associations; attested destination-bound single-use UV approval; recryption inside the key-service enclave; native/browser flows and guided dashboard creation. Explicit ImportedV0 recovery is now separate supported functionality. Signed freshness and cross-session replay regressions also exist. Remaining design/security decisions: effective credential/holder revocation for existing snapshots; common-root compromise scope and constrained private-key capabilities; root/token recovery and migration; service availability/access controls; independent review/fuzzing; exact-revision Nitro/hardware/restart acceptance. A passkey approves one share; multiple passkeys are not extra quorum votes. Distinct holders backed by the same root are not independent custody-compromise domains. Accepted scope remains: operator-configured service URLs/trust files; Keymaker-owned one-shot serialization with no additional Platform queue; enclave-lifetime key-service root; current proofed V1 generation. Explicit holder-assisted `ImportedV0` recovery has since been added separately; it does not restore V0 generation, silently downgrade failed V1 verification, or migrate a secret by generating a new quorum. The accepted CA-certified UID/context profile and fixed V1 encodings remain; a general structure-hash registry is deferred. The implemented protocol and later clarification comments supersede the historical proposal below, including its speculative transport/schema details. See the [current release contract](https://codeberg.org/caution/locksmith/src/commit/9f702bbf465bc4968e0fa57adcbbc366a1c6977c/docs/share-release.md). No ticket is closed by this status update; implementation PRs remain open/unmerged. ## Historical proposal # Caution Secret Unlocking Strategy Out of band: Client runs `caution apps verify --save-pcrs` so Locksmith can use those PCRs for attestation ## Existing Flow: OpenPGP - Client: Generate a nonce - Client: Get attestation from enclave, verify with nonce, receive enclave's public key - Client: Generate an X25519 private+public keypair - Client: Uses Keyfork to decrypt the shard, encrypt to aes256gcm(hkdf(x25519_dh(server_pubkey, client_privkey))) - Client: Sign { encrypted_payload, public_key: our_public_key } - Client: Send signed, encrypted, request. Check status. # Proposed Flow: WebAuthn + Caution Share Storage Note: 'Caution' in this instance is the enclave that stores the shares, 'enclave' refers to the customer's enclave that is receiving shards - Client: Generate att_nonce, get attestation from Caution, receive caution_verifying_key, caution_public_key (x25519) from Caution - Client: Receives transport_nonce from Caution - Client: Sends nonce to enclave, gets attestation, verify with Caution nonce, receive enclave's public key - Client: Generate WebAuthn signed bundle: { enclave_public_key, enclave_attestation, enclave_pcrs } - Client: Send WebAuthn signed bundle to Caution - Caution: verify WebAuthn bundle, generate X25519 public+private keypair - Caution: Use internal secret decryption to decrypt the shard matching client's WebAuthn key, encrypt to above algo - Caution: Sign { encrypted_payload, public_key: cautions_public_key } with caution_verifying_key private counterpart - Caution: Send signed, encrypted request. - Client: Receive signed, encrypted request, verify signature using caution_verifying_key - Client: Sign { encrypted_payload, public_key: caution_public_key } with WebAuthn - Client: Send signed, encrypted, request. Check status. --- Use Cases: - Locksmith vault decryption. - that's it! :) Threats: - Attacker controls Caution domain endpoints and deploys their enclave in the same spot as ours - Solution: Embed PCRs of the Caution enclave, or signing keys to verify signed PCR files, relating to the Caution team - Attacker controls database: only use it as value storage, encrypt everything to Caution controlled quorum - Potential issue 1: Can attackers get the shards? No. We only store encrypted values, can be decrypted by the Caution enclave, locked by a Caution OpenPGP quorum - Potential issue 2: Can attackers invalidate the relationship between shares and WebAuthn keys? No. It's hardcoded in the shard. - Solutions involve append-only databases - Authoritative hash - Timestamp based - Retain last-known valid point (signed by enclave?) - Chain of signatures that modify the org, including existing state of the org - Adding a new key requires >= 1 signature from an existing key - Local policy engine for the org determines whether or not new changes get added to the quorum - a la Icepick. :(. - Soon™. 4 years from NOW(). - Attacker replaces the bundle in the source code of the enclave with a different bundle - Bundle contains a separate secret and results in a new, distinct decryption key - May also result in the same key but different values being decrypted, if instead secrets are replaced - Instead, place signatures in the bundle, and allow for users to verify the signatures using their local WebAuthn key - Don't use `#[serde(deny_unknown_fields)]` or even better: add a dummy slot just to hold the signatures
ryansquared commented 2026-07-14 18:19:32 +00:00 (Migrated from codeberg.org)

How the bundle is generated:

  • User calls Caution API with a set of credentials (OpenPGP, users from whom to select WebAuthn)
  • Caution API calls The Service That Derives Public Certificates with an org id and bundle id
  • The Service That Derives Public Certificates generates certs and CA's them with derivation path so they can be derived later
  • The public certificates are then passed to Keymaker along with the user-provided OpenPGP certificates
  • Keymaker generates a bundle with a necroproof tying the WebAuthn credentials to the provided OpenPGP certificates

When reconstituting:

  • Shard Recryption Service verifies the necroproof, ensuring the WebAuthn signature provided as an authentication to derive the OpenPGP certificate is valid
  • Shard Recryption Service derives the private counterpart and transport recrypts the shard
How the bundle is generated: - User calls Caution API with a set of credentials (OpenPGP, users from whom to select WebAuthn) - Caution API calls The Service That Derives Public Certificates with an org id and bundle id - The Service That Derives Public Certificates generates certs and CA's them with derivation path so they can be derived later - The public certificates are then passed to Keymaker along with the user-provided OpenPGP certificates - Keymaker generates a bundle with a necroproof tying the WebAuthn credentials to the provided OpenPGP certificates When reconstituting: - Shard Recryption Service verifies the necroproof, ensuring the WebAuthn signature provided as an authentication to derive the OpenPGP certificate is valid - Shard Recryption Service derives the private counterpart and transport recrypts the shard
ryansquared commented 2026-07-14 18:20:42 +00:00 (Migrated from codeberg.org)

Necroproof timestamps can be used to ensure versions of Keymaker that are found to later be vulnerable can't be used to verify existing bundles. If we release a new version of Keymaker in 2027, for example, we only trust bundles made with that version or newer after 2027. When reconstituting, if someone provides a Keymaker bundle but it's timestamped to 2026, we can accept it.

Necroproof timestamps can be used to ensure versions of Keymaker that are found to later be vulnerable can't be used to verify existing bundles. If we release a new version of Keymaker in 2027, for example, we only trust bundles made with that version or newer after 2027. When reconstituting, if someone provides a Keymaker bundle but it's timestamped to 2026, we can accept it.
ryansquared commented 2026-07-14 18:36:42 +00:00 (Migrated from codeberg.org)

When deriving public certificates, build a DerivationPath off of m/pgp'/kmkr'/, then chain the organization ID. For every u32, remove the highest bit. Chain the bundle ID, removing the highest bit. You should get m/pgp'/kmkr'/[u32'/u32'/u32'/u32': org ID]/[u32'/u32'/u32'/u32': bundle ID]/<index>'

When deriving public certificates, build a DerivationPath off of `m/pgp'/kmkr'/`, then chain the organization ID. For every u32, remove the highest bit. Chain the bundle ID, removing the highest bit. You should get `m/pgp'/kmkr'/[u32'/u32'/u32'/u32': org ID]/[u32'/u32'/u32'/u32': bundle ID]/<index>'`
caution-bot commented 2026-07-18 21:46:02 +00:00 (Migrated from codeberg.org)

Cross-repo decomposition for platform #384:

The platform org-user bundle work now has concrete Locksmith/Keymaker implementation tickets:

I left #7 as the authoritative design thread for the WebAuthn signed transport and opened a separate implementation issue so the transport can have testable acceptance criteria without overwriting the proposal text.

Cross-repo decomposition for platform #384: The platform org-user bundle work now has concrete Locksmith/Keymaker implementation tickets: - https://codeberg.org/caution/locksmith/issues/10 — Keymaker: generate v1 quorum bundles from structured OpenPGP/WebAuthn keys - https://codeberg.org/caution/locksmith/issues/11 — Locksmith: load v1 quorum bundles with v0 fallback and upgrade - https://codeberg.org/caution/locksmith/issues/12 — Implement WebAuthn-signed shard recryption transport I left #7 as the authoritative design thread for the WebAuthn signed transport and opened a separate implementation issue so the transport can have testable acceptance criteria without overwriting the proposal text.
caution-bot commented 2026-07-19 21:20:50 +00:00 (Migrated from codeberg.org)

Consistency note after PR #13 landed: the public certificate service request no longer accepts a caller-supplied bundle_id. The service route generates a fresh bundle UUID server-side, uses it for derivation/notions/proofed response data, and returns it in PublicCertificateBundle::V1.bundle_id.

So the high-level design from this thread still stands, but the concrete split is now:

  • platform requests public certificates with organization ID + count;
  • public-cert service generates and returns the bundle ID plus certs in a proofed bundle;
  • platform uses that returned bundle ID and cert material when assembling the Keymaker v1 request tracked in #10;
  • Locksmith loads the resulting v1 bundle via #11 and the WebAuthn transport work is tracked in #12.
Consistency note after PR #13 landed: the public certificate service request no longer accepts a caller-supplied `bundle_id`. The service route generates a fresh bundle UUID server-side, uses it for derivation/notions/proofed response data, and returns it in `PublicCertificateBundle::V1.bundle_id`. So the high-level design from this thread still stands, but the concrete split is now: - platform requests public certificates with organization ID + count; - public-cert service generates and returns the bundle ID plus certs in a proofed bundle; - platform uses that returned bundle ID and cert material when assembling the Keymaker v1 request tracked in #10; - Locksmith loads the resulting v1 bundle via #11 and the WebAuthn transport work is tracked in #12.
vkobel commented 2026-09-14 17:27:35 +00:00 (Migrated from codeberg.org)

Few clarifications and requirements:

  • Support mixed quorums: each holder uses external OpenPGP or Caution-backed custody authorized by WebAuthn.
  • Keep dashboard PGP registration for external holders; explicitly select custody mode per participant. Multiple passkeys for one holder authorize one share.
  • Separate Keymaker generation from the long-running Shard Recryption Service; bootstrap the latter using the Caution team’s external OpenPGP quorum.
  • Derive Caution-backed public certificates first, then pass them alongside external certificates to Keymaker. Preserve organization, bundle, and holder identity throughout.
  • Authenticate the complete credential → certificate → share association. A certificate-service proof alone does not establish holder authorization.
  • Bind approval to the exact bundle/holder, destination attestation and key, authorized PCR policy, service nonce, and expiry; consume requests atomically.
  • Verify destination attestation inside the recryptor. Keep plaintext shares there and return only ciphertext plus the holder’s OpenPGP signature, preserving the existing Locksmith wire format.
  • Keep quorum bundles files in reproducible application inputs (up to users to include it in their app) and fail deployment preflight when required artifacts are missing. Start integration now; require real Nitro validation before production release.
Few clarifications and requirements: - Support mixed quorums: each holder uses external OpenPGP or Caution-backed custody authorized by WebAuthn. - Keep dashboard PGP registration for external holders; explicitly select custody mode per participant. Multiple passkeys for one holder authorize one share. - Separate Keymaker generation from the long-running Shard Recryption Service; bootstrap the latter using the Caution team’s external OpenPGP quorum. - Derive Caution-backed public certificates first, then pass them alongside external certificates to Keymaker. Preserve organization, bundle, and holder identity throughout. - Authenticate the complete credential → certificate → share association. A certificate-service proof alone does not establish holder authorization. - Bind approval to the exact bundle/holder, destination attestation and key, authorized PCR policy, service nonce, and expiry; consume requests atomically. - Verify destination attestation inside the recryptor. Keep plaintext shares there and return only ciphertext plus the holder’s OpenPGP signature, preserving the existing Locksmith wire format. - Keep quorum bundles files in reproducible application inputs (up to users to include it in their app) and fail deployment preflight when required artifacts are missing. Start integration now; require real Nitro validation before production release.
vkobel commented 2026-09-17 15:02:28 +00:00 (Migrated from codeberg.org)

The V1 creation-to-recovery flow has now been exercised end to end.

Implemented:

  • External-PGP, WebAuthn and mixed holders; multiple passkeys authorize one share.
  • External-PGP bootstrap of the custody root, separate from Keymaker.
  • Verified credential/certificate/share associations and destination-bound, single-use WebAuthn authorization.
  • Recryption inside custody, returning the existing holder-signed Locksmith transport.
  • Native/browser approval and deployment preflight for required bundle/policy/encrypted-secret artifacts.

This supersedes earlier comments saying WebAuthn recovery is unimplemented. It does not complete every linked issue:

  • #10/#11: legacy V0 compatibility and upgrade remain absent.
  • #12: targeted signed-attestation freshness and cross-session replay regression coverage remains.
  • Platform #383/#384: service lifecycle/access controls and remaining product/configuration requirements need implementation or explicit scope revision.
The V1 creation-to-recovery flow has now been exercised end to end. Implemented: - External-PGP, WebAuthn and mixed holders; multiple passkeys authorize one share. - External-PGP bootstrap of the custody root, separate from Keymaker. - Verified credential/certificate/share associations and destination-bound, single-use WebAuthn authorization. - Recryption inside custody, returning the existing holder-signed Locksmith transport. - Native/browser approval and deployment preflight for required bundle/policy/encrypted-secret artifacts. This supersedes earlier comments saying WebAuthn recovery is unimplemented. It does not complete every linked issue: - #10/#11: legacy V0 compatibility and upgrade remain absent. - #12: targeted signed-attestation freshness and cross-session replay regression coverage remains. - Platform #383/#384: service lifecycle/access controls and remaining product/configuration requirements need implementation or explicit scope revision.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
public/locksmith#7
No description provided.