onepub-dev / onepub-dev/reVault

Design a trustworthy reciprocal contact key exchange

Open
#226 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

implemented in progress tested
Dominant language
Rust
Stars
4
Forks
0
Avg merge
1h 55m
Merged PRs (30d)
1

Description

Context

The current key server sends a profile key bundle in one direction. That is
enough for Alice to obtain Bob's encryption public key before creating a
lockbox for Bob, but Bob also needs Alice's independently trusted signing
public key before he can authenticate the lockbox as authored by Alice.

Contacts are global. A profile option selects which local identity bundle a
participant discloses; it does not select where the received contact is stored.

Related: #211 and #225.

Goals

  • Exchange exactly one explicitly selected profile bundle in each direction.
  • Include both encryption and signing public keys in each bundle.
  • Keep contacts global while preserving the remote profile/generation metadata.
  • Support asynchronous operation; both users need not be online together.
  • Bind both exchange legs into one versioned transcript.
  • Ensure the server transports keys but never confers human identity trust.
  • Require each party to independently verify the other bundle's fingerprint.
  • Store each complete contact bundle and trust state atomically.
  • Allow one-way receive when the recipient does not want to disclose a profile.

Terminology

Use exchange invitation, not exchange session. This is an asynchronous,
expiring rendezvous record rather than a live connection.

Suggested CLI language:

exchange-offer --from-profile <profile> --recipient <email>
exchange-accept <invitation> --reply-with-profile <profile>
exchange-accept <invitation> --receive-only
exchange-status
exchange-complete
exchange-cancel

--reply-with-profile identifies the local profile whose public keys Bob
returns. Alice is stored as a global contact regardless of this choice.

Proposed flow

Alice                         Key server                         Bob

create invitation
  -- Alice bundle ----------> OFFERED

share invitation link/code ----------------------------------->

                               <-- redeem + Bob bundle --------
                               ACCEPTED
                               --- Alice bundle -------------->

poll with management token
  <-- Bob bundle ------------ RESPONSE DELIVERED

verify Bob independently                             verify Alice independently
trust Bob locally                                    trust Alice locally

acknowledge delivery
  --------------------------> DELETED

The two trust decisions are independent. The server records delivery state,
never trusted state.

Temporal behavior

Recommended initial policy:

Invitation lifetime:       24 hours
Maximum configurable TTL:   7 days
Poll interval:              5-30 seconds with backoff
Post-delivery retention:    until acknowledgement, at most 1 hour
Accepted response:          immutable
Completed invitation:       delete immediately after acknowledgement
Expired invitation:         purge promptly

Server state:

OFFERED -> ACCEPTED -> DELIVERED -> DELETED
    |          |           |
    +----------+-----------+-> EXPIRED
    +------------------------> CANCELLED (before acceptance)

Local contact state, maintained independently by each participant:

received -> pending verification -> trusted | rejected

Expiry or deletion of the server record must not remove a locally verified
contact. Cancellation after acceptance cannot retract keys already delivered.

Invitation identity and capabilities

Email identifies a participant but not a specific invitation. A user can have
multiple profiles, recipients, rotations, and concurrent offers. The protocol
therefore needs an opaque exchange ID even if the normal UX is an emailed link
rather than a manually entered code.

Alice receives:

  • an invitation link/code safe to give Bob;
  • a private management token for poll, retrieve, cancel, acknowledge, and
    delete operations.

Bob uses a private recipient capability for idempotent acceptance retries.
The public invitation identifier must not permit response retrieval,
replacement, cancellation, lifetime extension, or deletion.

The first successful acceptance freezes the recipient bundle. A retry from the
same recipient capability returns the same result; another party cannot replace
it. This must tolerate a response being lost after the server commits Bob's
bundle.

Canonical profile bundle

ProfileBundle {
    protocol_version
    stable_profile_id
    profile_generation
    encryption_public_key
    signing_public_key
    normalized_verified_email
    created_at
    nonce
    proof_of_possession
    bundle_fingerprint
}

Never send all local profiles implicitly. Human-readable profile names are
untrusted labels unless separately authenticated.

Transcript binding

Bind at least:

  • protocol version and exchange ID;
  • initiator and recipient roles;
  • expected recipient email, when used;
  • both stable profile IDs and generations;
  • canonical digests of both complete bundles;
  • nonces, creation time, and expiry;
  • state transition and replay identifiers.

Each participant signs its leg. These signatures prove possession and prevent
mix-and-match/replay after trust is established; they do not establish the
person behind a previously unknown key.

Trust model

  • Email verification proves control of an address, not human identity.
  • Server delivery proves only that the server returned a bundle.
  • The receiver computes the fingerprint over email, encryption key, signing
    key, stable profile ID/generation, and protocol context.
  • Each receiver independently verifies the fingerprint by phone, QR, in person,
    or another already trusted channel.
  • A bundle remains pending/untrusted until that local verification succeeds.
  • The same call may verify both directions, but the software records two
    separate decisions.

Relationship to archive delivery

Alice may encrypt an archive for Bob only after she has accepted Bob's
encryption public key for the intended profile. Bob may call the archive
authored by Alice only when:

  • the archive signature is cryptographically valid;
  • its signer matches Alice's independently verified signing key;
  • Alice was authorized by the archive's author policy.

Names asserted by archive contents cannot establish signer identity.

Threats and failure cases

  • Malicious-server substitution or split views.
  • Invitation interception and denial by first acceptance.
  • Crossed exchanges and bundle mix-and-match.
  • Replay of expired or previous-generation bundles.
  • Reflection of Alice's bundle as Bob's response.
  • Email-account compromise.
  • Lost accept/retrieve responses.
  • Partial exchange where only one party becomes trusted.
  • Wrong-profile disclosure.
  • Duplicate acceptance and concurrent polling.
  • Abuse of verification email and invitation creation.
  • Retention of expired bundles, tokens, or relationship metadata.

Plan

  1. Write the reciprocal-exchange threat model and trust boundaries.
  2. Specify the versioned canonical profile bundle and fingerprint.
  3. Specify the transcript, state machine, capabilities, TTL, idempotency, and
    deletion semantics.
  4. Decide whether invitations are delivered as opaque links, short codes, or a
    verified-recipient inbox while retaining an internal opaque exchange ID.
  5. Define the global atomic contact record and pending/trusted/rejected states.
  6. Define explicit profile-disclosure and one-way fallback UX.
  7. Implement typed protocol messages and server storage separately from the
    current one-way payload path.
  8. Implement offer, accept, poll, complete, cancel, and status CLI operations.
  9. Bind received signing keys into archive signer verification.
  10. Add signed rotation using the previously trusted signing key and an
    independently verified recovery path.
  11. Add end-to-end, retry, expiry, failover, and adversarial tests.
  12. Complete independent security review before enabling reciprocal exchange by
    default.

Acceptance criteria

  • Each direction transfers one encryption key and one signing key atomically.
  • Each participant explicitly chooses the local profile disclosed in that
    direction; all profiles are never sent implicitly.
  • Contacts remain global and retain remote profile/generation metadata.
  • Server delivery and email verification never mark a contact trusted.
  • Each direction has an independent local fingerprint-verification decision.
  • Accepted responses are immutable and retries are idempotent.
  • Tokens have least privilege and records expire/delete as documented.
  • The transcript resists replay, reflection, and cross-session substitution.
  • An archive received after exchange is matched against the sender's trusted
    signing key and author policy.
  • Partial, rejected, cancelled, expired, and interrupted exchanges have tested
    recovery behavior.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

The issue names the current one-way payload path and related issues #211 and #225, but no implementation files or tests. Start by reading that path and those related issues, then map the threat model, canonical bundle, state machine, capabilities, and trust states. Done means the protocol and UX are specified, implemented across offer, accept, poll, complete, cancel, and status, and covered by adversarial and recovery tests plus security review.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
backend-api-design, cli, cryptography, security
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.