Secure storage and distribution of cryptographic keys in ATProto applications
at main 412 lines 11 kB view raw view rendered
1# Client Implementation Strategy 2 3## Purpose 4 5This document outlines the design philosophy and implementation strategy for the ATP Keyserver client library. It serves as a roadmap for contributors and explains key architectural decisions. 6 7For protocol details, see [ENCRYPTION_PROTOCOL.md](./ENCRYPTION_PROTOCOL.md). 8For security guidelines, see [SECURITY.md](./SECURITY.md). 9For usage instructions, see [Client README](../packages/client/README.md). 10 11## Design Philosophy 12 13### Client-Side Encryption 14 15**Decision:** Cryptographic operations (encryption/decryption) happen on the client, not the server. 16 17**Rationale:** 18- **True end-to-end encryption**: Server never sees plaintext 19- **Better performance**: <1ms local crypto vs 100-500ms server round-trip 20- **Lower server load**: 50% less CPU, 97% less bandwidth 21- **Aligned with ATProto**: Decentralized architecture, user sovereignty 22- **Cost efficiency**: Scales with user devices, not server capacity 23 24**Trade-offs:** 25- Clients must handle cryptography correctly 26- Requires distributing crypto library (~52KB) 27- Key caching responsibility on client 28 29**Alternative considered:** Server-side encryption (rejected due to trust model and performance) 30 31### Single Package Architecture 32 33**Decision:** One npm package `@atpkeyserver/client` with optional imports via subpath exports. 34 35**Rationale:** 36- Crypto library (@noble/ciphers) is ~52KB and represents 90% of bundle 37- High-level client adds only ~8KB on top of crypto functions 38- Maintenance overhead of separate packages not justified by small size difference 39- Subpath exports allow tree-shaking for minimal bundle impact 40 41**Package structure:** 42``` 43@atpkeyserver/client 44├── /crypto → Just crypto functions (52KB) 45├── /client → Just KeyserverClient (60KB, includes crypto) 46└── / (main) → Both exports (60KB, tree-shaken) 47``` 48 49**Alternative considered:** Separate `@atpkeyserver/client-crypto` and `@atpkeyserver/client` packages (rejected due to minimal size benefit) 50 51### Service Auth Integration 52 53**Decision:** Client library abstracts service auth token management via callback pattern. 54 55**Rationale:** 56- PDS clients vary by platform (@atproto/api, custom implementations) 57- Callback pattern allows any PDS client integration 58- Client library handles token caching automatically 59- Separates concerns: client lib = crypto + keyserver API, user provides = PDS auth 60 61**Implementation:** 62```typescript 63new KeyserverClient({ 64 keyserverDid: 'did:web:keyserver.example.com', 65 getServiceAuthToken: async (aud, lxm) => { 66 // User provides: obtain token from their PDS client 67 return await pdsClient.getServiceAuth({ aud, lxm }) 68 } 69}) 70``` 71 72See [ENCRYPTION_PROTOCOL.md](./ENCRYPTION_PROTOCOL.md#authentication-flow) for service auth details. 73 74## Component Architecture 75 76### Core Components 77 78**1. Crypto Module (`crypto.ts`)** 79- XChaCha20-Poly1305 encryption/decryption 80- Uses @noble/ciphers library 81- Stateless, pure functions 82- No network dependencies 83 84**2. KeyserverClient (`client.ts`)** 85- HTTP client for keyserver API 86- Service auth token management 87- Key caching with TTL 88- Error handling and retries 89 90**3. Cache Module (`cache.ts`)** 91- In-memory LRU cache 92- Separate TTLs for active/historical keys 93- Service auth token caching 94- Automatic expiry 95 96**4. Service Auth Module (`service-auth.ts`)** 97- Token cache management 98- Expiry checking with safety margin 99- Automatic refresh logic 100 101**5. Types Module (`types.ts`)** 102- TypeScript interfaces 103- Configuration types 104- Response types 105 106**6. Errors Module (`errors.ts`)** 107- Custom error classes 108- Structured error handling 109- HTTP status code mapping 110 111### Dependency Graph 112 113``` 114index.ts 115├── crypto.ts 116│ └── @noble/ciphers 117├── client.ts 118│ ├── crypto.ts 119│ ├── cache.ts 120│ ├── service-auth.ts 121│ ├── types.ts 122│ └── errors.ts 123├── types.ts 124└── errors.ts 125``` 126 127## Key Features 128 129### Automatic Caching 130 131**Key Caching:** 132- Active keys: 1 hour TTL (may change with rotation) 133- Historical keys: 24 hour TTL (immutable) 134- LRU eviction when max size reached 135 136**Service Auth Token Caching:** 137- 60 second TTL (or server-specified `exp`) 138- Refresh when <10 seconds remain 139- Memory-only storage 140 141**Benefits:** 142- Reduces keyserver load 143- Improves client performance 144- Handles token refresh automatically 145 146See [SECURITY.md](./SECURITY.md#token-caching) for security considerations. 147 148### Request Deduplication 149 150Multiple simultaneous requests for the same key are collapsed into a single network request: 151 152```typescript 153// Both calls trigger only one network request 154const [key1, key2] = await Promise.all([ 155 client.getGroupKey('did:plc:abc#followers'), 156 client.getGroupKey('did:plc:abc#followers') 157]) 158``` 159 160### Automatic Retry 161 162Network failures retry with exponential backoff: 163- Retry on: 5xx errors, network timeouts 164- Don't retry on: 4xx errors (client errors are permanent) 165- Max retries: 3 attempts 166- Backoff: 100ms, 200ms, 400ms 167 168### Type Safety 169 170Full TypeScript support: 171- Strict typing for all APIs 172- IntelliSense support 173- Compile-time error detection 174- Generated type definitions 175 176## Bundle Size Analysis 177 178Tree-shaking eliminates unused code. Actual bundle sizes depend on what you import: 179 180### Scenario 1: Crypto Functions Only 181 182```typescript 183import { encryptMessage, decryptMessage } from '@atpkeyserver/client/crypto' 184``` 185 186**Bundled:** ~52KB (minified + gzipped ~15KB) 187- @noble/ciphers: 50KB 188- crypto.ts: 2KB 189 190**Use case:** You handle keyserver API calls manually, just need crypto. 191 192### Scenario 2: Full Client 193 194```typescript 195import { KeyserverClient } from '@atpkeyserver/client' 196``` 197 198**Bundled:** ~60KB (minified + gzipped ~18KB) 199- @noble/ciphers: 50KB 200- Client code: 10KB (includes crypto, caching, HTTP, errors) 201 202**Use case:** Full-featured client with automatic caching and token management. 203 204### Scenario 3: Both 205 206```typescript 207import { KeyserverClient, encryptMessage } from '@atpkeyserver/client' 208``` 209 210**Bundled:** ~60KB (same as scenario 2) 211- Client includes crypto, no duplication 212 213**Use case:** Mix high-level and low-level APIs as needed. 214 215### Bundle Optimization 216 217**Automatic:** 218- Tree-shaking removes unused exports 219- Minification reduces code size 220- Gzip compresses for network transfer 221 222**Manual optimization:** 223- Use subpath imports (`/crypto`, `/client`) for smaller bundles 224- Import only what you need 225- Modern bundlers (webpack, vite, rollup) handle this automatically 226 227**Conclusion:** Crypto library dominates bundle size. Single package vs separate packages saves only ~8KB, not worth maintenance complexity. 228 229## Implementation Phases 230 231### Phase 1: Foundation (Complete) 232 233- [x] Crypto functions (encryptMessage, decryptMessage) 234- [x] Type definitions 235- [x] Package structure with subpath exports 236- [x] Build configuration (ESM + CJS) 237 238### Phase 2: Client Library (Complete) 239 240- [x] KeyserverClient class 241- [x] Service auth token management 242- [x] Key caching with TTL 243- [x] HTTP client with retry logic 244- [x] Error handling 245- [x] Request deduplication 246 247### Phase 3: Documentation (Complete) 248 249- [x] Protocol specification (ENCRYPTION_PROTOCOL.md) 250- [x] Security guidelines (SECURITY.md) 251- [x] API reference (API_REFERENCE.md) 252- [x] Client README with usage examples 253- [x] Minimal example implementation (basic-usage.ts) 254 255### Phase 4: Testing 256 257- [ ] Unit tests for crypto functions 258- [ ] Integration tests for client 259- [ ] Mock keyserver for testing 260- [ ] E2E encryption flow test 261- [ ] Cache behavior tests 262- [ ] Service auth token refresh tests 263 264### Phase 5: Polish 265 266- [ ] Performance benchmarks 267- [ ] Bundle size analysis 268- [ ] Documentation review 269- [ ] Example applications 270- [ ] npm package publication 271 272## Success Metrics 273 274### Performance Targets 275 276| Operation | Target | Measurement | 277|-----------|--------|-------------| 278| Encryption | <1ms | Local crypto operation | 279| Decryption (cached key) | <5ms | Cache lookup + crypto | 280| Decryption (cache miss) | <200ms | Network fetch + crypto | 281| Service auth (cached) | <1ms | Cache lookup | 282| Service auth (cache miss) | <100ms | PDS round-trip | 283 284### Developer Experience 285 286- [ ] Complete TypeScript types 287- [ ] Comprehensive JSDoc comments 288- [ ] Working examples for 3+ platforms 289- [ ] <5 minutes from install to first encrypted message 290- [ ] Clear error messages with actionable guidance 291 292### Security 293 294- [ ] Zero plaintext exposure to server 295- [ ] Keys cached in memory only 296- [ ] Service auth tokens cached for max 60 seconds 297- [ ] Proper cleanup on logout 298- [ ] No key or token leakage in logs 299- [ ] Audience binding prevents token misuse 300- [ ] Short-lived tokens limit attack window 301 302### Code Quality 303 304- [ ] >80% test coverage 305- [ ] Zero runtime dependencies beyond @noble/ciphers 306- [ ] Tree-shakeable exports 307- [ ] CommonJS + ESM support 308- [ ] Works in Node.js, browser, React Native 309 310## Testing Strategy 311 312### Unit Tests 313 314**Crypto module:** 315- Encrypt/decrypt round-trip 316- Nonce uniqueness 317- AAD binding 318- Error handling 319 320**Cache module:** 321- TTL expiry 322- LRU eviction 323- Get/set operations 324- Clear operation 325 326**Service auth module:** 327- Token expiry checking 328- Refresh logic 329- Cache key generation 330 331### Integration Tests 332 333**Client:** 334- Key fetch with caching 335- Service auth integration 336- Retry logic 337- Error handling 338 339**E2E:** 340- Full encryption flow (PDS → keyserver → encrypt → store) 341- Full decryption flow (fetch → keyserver → decrypt → display) 342- Key rotation handling 343 344### Mock Server 345 346Build lightweight mock keyserver for testing: 347- Simulates auth verification 348- Returns mock keys 349- Configurable latency 350- Error injection 351 352## Open Questions 353 354### Rate Limiting 355 356**Question:** Should client enforce rate limits? 357 358**Options:** 3591. No client-side limits (rely on server) 3602. Simple debouncing (prevent rapid duplicate requests) 3613. Full rate limit tracking (mirror server limits) 362 363**Current decision:** Option 2 (debouncing via request deduplication) 364 365### Cache Persistence 366 367**Question:** Should keys be persisted to disk? 368 369**Options:** 3701. Memory-only (current implementation) 3712. Encrypted disk cache (requires key derivation) 3723. Platform-specific secure storage (iOS Keychain, Android KeyStore) 373 374**Current decision:** Memory-only for security and simplicity 375 376**Rationale:** 377- Simpler implementation 378- Better security (no disk persistence) 379- Works across all platforms 380- User can implement custom cache if needed 381 382### Error Recovery 383 384**Question:** How should clients handle persistent decryption failures? 385 386**Options:** 3871. Fail silently (hide post) 3882. Show error message 3893. Retry with exponential backoff 3904. Prompt user action (e.g., "Request access") 391 392**Current decision:** Show error message, no retry (decryption failures are permanent) 393 394## Related Documentation 395 396- [Encryption Protocol](./ENCRYPTION_PROTOCOL.md) - Protocol specification 397- [Security Best Practices](./SECURITY.md) - Security guidelines 398- [API Reference](./API_REFERENCE.md) - Complete endpoint reference 399- [Architecture](./ARCHITECTURE.md) - Server architecture 400- [Client README](../packages/client/README.md) - Usage instructions 401 402## Contributing 403 404Contributions welcome! Please: 405 4061. Read existing documentation 4072. Follow code style (Prettier) 4083. Add tests for new features 4094. Update documentation as needed 4105. Submit PR with clear description 411 412For questions, open an issue in the [Codeberg repository](https://codeberg.org/juandjara/atp-keyserver).