82 lines
3.2 KiB
Markdown
82 lines
3.2 KiB
Markdown
# Implementation Details
|
|
|
|
This document covers concrete technology choices and dependencies. For the architectural design, see [ARCHITECTURE.md](ARCHITECTURE.md).
|
|
|
|
---
|
|
|
|
## Client Connection Flow
|
|
|
|
### New Client Approval
|
|
|
|
When a client whose public key is not yet in the database connects, all connected user agents are asked to approve the connection. The first agent to respond determines the outcome; remaining requests are cancelled via a watch channel.
|
|
|
|
```mermaid
|
|
flowchart TD
|
|
A([Client connects]) --> B[Receive AuthChallengeRequest]
|
|
B --> C{pubkey in DB?}
|
|
|
|
C -- yes --> D[Read nonce\nIncrement nonce in DB]
|
|
D --> G
|
|
|
|
C -- no --> E[Ask all UserAgents:\nClientConnectionRequest]
|
|
E --> F{First response}
|
|
F -- denied --> Z([Reject connection])
|
|
F -- approved --> F2[Cancel remaining\nUserAgent requests]
|
|
F2 --> F3[INSERT client\nnonce = 1]
|
|
F3 --> G[Send AuthChallenge\nwith nonce]
|
|
|
|
G --> H[Receive AuthChallengeSolution]
|
|
H --> I{Signature valid?}
|
|
I -- no --> Z
|
|
I -- yes --> J([Session started])
|
|
```
|
|
|
|
### Known Issue: Concurrent Registration Race (TOCTOU)
|
|
|
|
Two connections presenting the same previously-unknown public key can race through the approval flow simultaneously:
|
|
|
|
1. Both check the DB → neither is registered.
|
|
2. Both request approval from user agents → both receive approval.
|
|
3. Both `INSERT` the client record → the second insert silently overwrites the first, resetting the nonce.
|
|
|
|
This means the first connection's nonce is invalidated by the second, causing its challenge verification to fail. A fix requires either serialising new-client registration (e.g. an in-memory lock keyed on pubkey) or replacing the separate check + insert with an `INSERT OR IGNORE` / upsert guarded by a unique constraint on `public_key`.
|
|
|
|
### Nonce Semantics
|
|
|
|
The `program_client.nonce` column stores the **next usable nonce** — i.e. it is always one ahead of the nonce last issued in a challenge.
|
|
|
|
- **New client:** inserted with `nonce = 1`; the first challenge is issued with `nonce = 0`.
|
|
- **Existing client:** the current DB value is read and used as the challenge nonce, then immediately incremented within the same exclusive transaction, preventing replay.
|
|
|
|
---
|
|
|
|
## Cryptography
|
|
|
|
### Authentication
|
|
- **Signature scheme:** ed25519
|
|
|
|
### Encryption at Rest
|
|
- **Scheme:** Symmetric AEAD — currently **XChaCha20-Poly1305**
|
|
- **Version tracking:** Each `aead_encrypted` database entry carries a `scheme` field denoting the version, enabling transparent migration on unseal
|
|
|
|
### Server Identity
|
|
- **Transport:** TLS with a self-signed certificate
|
|
- **Key type:** Generated on first run; long-term (no rotation mechanism yet)
|
|
|
|
---
|
|
|
|
## Communication
|
|
|
|
- **Protocol:** gRPC with Protocol Buffers
|
|
- **Server identity distribution:** `ServerInfo` protobuf struct containing the TLS public key fingerprint
|
|
- **Future consideration:** grpc-web lacks bidirectional stream support, so a browser-based wallet may require protojson over WebSocket
|
|
|
|
---
|
|
|
|
## Memory Protection
|
|
|
|
The unsealed root key must be held in a hardened memory cell resistant to dumps, page swaps, and hibernation.
|
|
|
|
- **Current:** Using the `memsafe` crate as an interim solution
|
|
- **Planned:** Custom implementation based on `mlock` (Unix) and `VirtualProtect` (Windows)
|