Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

TOFU Fingerprint Verification

Overview

Conclave uses a Trust On First Use (TOFU) model — similar to SSH known_hosts — to let users detect signing key changes after initial contact. Each user’s MLS signing public key is hashed to produce a fingerprint. Clients store the first-seen fingerprint for each user and flag any subsequent changes.

Fingerprint Computation

The fingerprint is the SHA-256 hash of the user’s MLS signing public key, represented as a 64-character lowercase hexadecimal string:

fingerprint = lowercase_hex(SHA-256(signing_public_key_bytes))

The resulting string is exactly 64 characters (256 bits / 4 bits per hex digit).

Fingerprint Display Format

For user-facing display, fingerprints SHOULD be formatted as 8 groups of 8 hex characters separated by spaces:

a1b2c3d4 e5f6a7b8 c9d0e1f2 a3b4c5d6 e7f8a9b0 c1d2e3f4 a5b6c7d8 e9f0a1b2

For comparison and storage, fingerprints SHOULD be normalized by removing all whitespace and converting to lowercase.

Fingerprint Distribution

Upload

Clients compute their fingerprint locally and upload it to the server during key package upload (via the signing_key_fingerprint field in UploadKeyPackageRequest). This occurs:

  • On registration (initial key package upload).
  • On login (key package re-upload).
  • On account reset (new identity, new key packages).

Distribution

The server stores the fingerprint in the user’s record and distributes it alongside member data in:

  • GroupMember.signing_key_fingerprint: Included in ListGroupsResponse, so clients receive fingerprints for all co-members.
  • UserInfoResponse.signing_key_fingerprint: Returned by the user lookup endpoints (GET /api/v1/users/{username}, GET /api/v1/users/by-id/{user_id}, GET /api/v1/me).

Local TOFU Store

Clients MUST maintain a local store of known fingerprints, tracking the first-seen fingerprint for each user. The store contains:

FieldTypeDescription
user_idintegerPrimary key — the user being tracked
fingerprintstringThe stored fingerprint (64-char hex)
verifiedbooleanWhether the fingerprint has been manually verified

Verification States

A user’s fingerprint can be in one of four states:

StateConditionDisplay Indicator
UnknownThe server has no fingerprint for this user (e.g., legacy account, key packages not yet uploaded)[?]
UnverifiedThe client has stored a fingerprint on first contact, but the user has not manually confirmed it[?]
VerifiedThe user has confirmed the fingerprint out-of-band via the verify command(none)
ChangedThe server’s fingerprint differs from the locally stored value[!]

State Transitions

stateDiagram-v2
    [*] --> Unverified : First seen
    Unverified --> Verified : Manual verify
    Unverified --> Changed : Key change detected
    Verified --> Changed : Key change detected

    Unverified : Display: [?]
    Verified : Display: (none)
    Changed : Display: [!]

Key Change Detection

When a user performs an account reset, their signing keys are regenerated and a new fingerprint is uploaded to the server. Other clients detect the fingerprint change when they next receive the user’s updated member data (via ListGroupsResponse or UserInfoResponse).

A changed fingerprint triggers the [!] warning indicator. This alerts members to verify the new fingerprint out-of-band. Key changes are expected after /reset (account reset with new signing keys) but could also indicate a key substitution attack.

An IdentityResetEvent SSE event is sent to co-members when a user performs an external rejoin after reset, providing an additional signal.

User Commands

Clients SHOULD provide the following commands for fingerprint verification:

CommandDescription
/whois [username]Display a user’s fingerprint and verification status. With no argument, displays the current user’s own fingerprint.
/verify <username> <fingerprint>Manually verify a user’s fingerprint. The client checks that the provided fingerprint matches the server’s current fingerprint for that user, then stores it as verified. If the fingerprint does not match, the command is rejected.
/unverify <username>Remove verification status for a user, resetting them to Unverified (TOFU) state.
/trustedList all users in the local TOFU store with their fingerprints and verification status.

Security Model

Protection Provided

  • Post-first-contact key substitution: After the initial fingerprint is stored, any change — whether from a compromised server, man-in-the-middle, or legitimate key reset — is detected and flagged with [!].

Limitations

  • No protection against first-contact attacks: If the server is compromised during the initial key exchange, it could substitute a different key. This is the inherent limitation of TOFU.
  • Server-mediated distribution: The server distributes fingerprints. A compromised server could substitute fingerprints for new contacts before the client has stored them.

Stronger Assurance

Users can use /verify to confirm fingerprints through a trusted out-of-band channel (in person, phone call, verified messaging, etc.). This eliminates the first-contact vulnerability for verified users, upgrading from TOFU trust to verified trust.

The TOFU model is a practical trade-off between usability (no PKI or certificate authority required) and security (detects key changes after initial contact). For communities requiring stronger guarantees, out-of-band /verify provides a path to full fingerprint verification.