docs: M-Hard 9-11 — README, examples, CI, benchmarks, migration guide
Some checks failed
Test / test (push) Has been cancelled
Some checks failed
Test / test (push) Has been cancelled
M-Hard 9: Documentation + examples - README.md, SECURITY.md, THREAT-MODEL.md - 5 runnable examples: basic conversation, prekey server, WebSocket tunnel, identity verification, Dokploy deployment M-Hard 10: CI + publishing + benchmarks - GitHub Actions: test workflow with PostgreSQL service container - GitHub Actions: publish workflow for npm releases on git tags - Benchmark suite (bench/run.ts) with markdown output - LICENSE (MIT), CHANGELOG.md, CONTRIBUTING.md M-Hard 11: Migration guide - MIGRATION.md with three-phase rollout strategy - Concrete examples for replacing static AES tunnels - Concrete examples for per-device push notification migration - Sections for Orchestrator and Nova migrations Benchmark highlights: - AES-256-GCM: ~100K ops/sec - Encrypt+decrypt roundtrip: ~17K ops/sec - X3DH handshake: ~165 ops/sec (hardware acceleration limited) - Compute fingerprint: ~76K ops/sec All 11 M-Hard milestones complete. 193 tests passing, 0 failures. Shade is production-ready. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
17
examples/04-identity-verification/README.md
Normal file
17
examples/04-identity-verification/README.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# Example 04: Identity Verification with Safety Numbers
|
||||
|
||||
Shows how two parties can compare safety numbers (60-digit fingerprints) out-of-band to detect a man-in-the-middle attack at session establishment.
|
||||
|
||||
## Run
|
||||
|
||||
```bash
|
||||
bun run main.ts
|
||||
```
|
||||
|
||||
## What it shows
|
||||
|
||||
- Generating identity fingerprints
|
||||
- The "safety number" format (12 groups of 5 digits)
|
||||
- Comparing fingerprints to verify a peer's identity
|
||||
- What it looks like when a MITM is attempted (different fingerprint)
|
||||
- `acceptIdentityChange()` for handling legitimate identity rotation
|
||||
72
examples/04-identity-verification/main.ts
Normal file
72
examples/04-identity-verification/main.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import { ShadeSessionManager } from '../../packages/shade-core/src/index.js';
|
||||
import { SubtleCryptoProvider, MemoryStorage } from '../../packages/shade-crypto-web/src/index.js';
|
||||
|
||||
const crypto = new SubtleCryptoProvider();
|
||||
|
||||
async function main() {
|
||||
console.log('=== Shade Identity Verification ===\n');
|
||||
|
||||
// ─── The Real Bob ────────────────────────────────────
|
||||
const realBob = new ShadeSessionManager(crypto, new MemoryStorage());
|
||||
await realBob.initialize();
|
||||
|
||||
console.log('Real Bob fingerprint:');
|
||||
console.log(' Full: ', await realBob.getIdentityFingerprint());
|
||||
console.log(' Short: ', await realBob.getShortFingerprint());
|
||||
console.log();
|
||||
|
||||
// ─── An Imposter ─────────────────────────────────────
|
||||
const evilBob = new ShadeSessionManager(crypto, new MemoryStorage());
|
||||
await evilBob.initialize();
|
||||
|
||||
console.log('Evil Bob (imposter) fingerprint:');
|
||||
console.log(' Full: ', await evilBob.getIdentityFingerprint());
|
||||
console.log(' Short: ', await evilBob.getShortFingerprint());
|
||||
console.log();
|
||||
|
||||
console.log('━━━ The fingerprints are completely different ━━━');
|
||||
console.log('In a real scenario, Alice would call Real Bob on the phone,');
|
||||
console.log('read her safety number, and Real Bob would read his back.');
|
||||
console.log('If the numbers match, Alice can trust that her session');
|
||||
console.log('is talking to Real Bob and not Evil Bob.\n');
|
||||
|
||||
// ─── Alice connects to (whoever) Bob is ────────────
|
||||
const alice = new ShadeSessionManager(crypto, new MemoryStorage());
|
||||
await alice.initialize();
|
||||
|
||||
// Imagine the prekey server returns Real Bob's bundle
|
||||
const realBobOtpks = await realBob.generateOneTimePreKeys(5);
|
||||
const realBobBundle = await realBob.createPreKeyBundle();
|
||||
realBobBundle.oneTimePreKey = {
|
||||
keyId: realBobOtpks[0].keyId,
|
||||
publicKey: realBobOtpks[0].keyPair.publicKey,
|
||||
};
|
||||
|
||||
await alice.initSessionFromBundle('bob', realBobBundle);
|
||||
|
||||
// After establishing the session, Alice can verify the identity she received
|
||||
const realBobPub = realBob.getPublicIdentity();
|
||||
const evilBobPub = evilBob.getPublicIdentity();
|
||||
|
||||
console.log('Alice verifies who she actually connected to:');
|
||||
console.log(' Is it Real Bob? ', await alice.verifyRemoteIdentity('bob', realBobPub.dhKey));
|
||||
console.log(' Is it Evil Bob? ', await alice.verifyRemoteIdentity('bob', evilBobPub.dhKey));
|
||||
console.log();
|
||||
|
||||
// ─── Identity Change Scenario ─────────────────────────
|
||||
console.log('━━━ Bob legitimately rotates his identity ━━━');
|
||||
const newBundle = await realBob.rotateIdentity();
|
||||
console.log('New Bob fingerprint:', await realBob.getIdentityFingerprint());
|
||||
console.log();
|
||||
console.log('Alice fetches the new bundle and verifies it matches');
|
||||
console.log('expected new identity (after out-of-band confirmation).');
|
||||
|
||||
// Alice would compare fingerprints again, then accept the change:
|
||||
await alice.acceptIdentityChange('bob', newBundle.identityDHKey);
|
||||
console.log('Alice accepted the new identity.');
|
||||
console.log();
|
||||
|
||||
console.log('=== Done ===');
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
Reference in New Issue
Block a user