A community based topic aggregation platform built on atproto
1# Verification Security Model 2 3## Attack Prevention: Signature Copying 4 5### The Threat 6**Question**: What if Bob copies Alice's verification to his profile? 7 8```json 9// Alice's profile (did:plc:alice123) 10{ 11 "verifications": [ 12 { 13 "type": "phone", 14 "verifiedBy": "did:web:coves.social", 15 "verifiedAt": "2025-01-15T10:00:00Z", 16 "expiresAt": "2026-01-15T10:00:00Z", 17 "signature": "abc123..." 18 } 19 ] 20} 21 22// Bob copies this to his profile (did:plc:bob456) 23// Will this work? NO! 24``` 25 26### The Defense 27 28The signature is created over this payload: 29``` 30type + verifiedBy + verifiedAt + expiresAt + subjectDID 31``` 32 33**Critical**: `subjectDID` is the DID of the person being verified. 34 35### Example 36 37**Alice's signature payload:** 38``` 39"phone" + "did:web:coves.social" + "2025-01-15T10:00:00Z" + "2026-01-15T10:00:00Z" + "did:plc:alice123" 40→ Signature: "abc123..." 41``` 42 43**Bob tries to use Alice's verification:** 44``` 45Bob's DID: did:plc:bob456 46Alice's signature: "abc123..." 47 48Third-party app verifies: 49payload = "phone" + "did:web:coves.social" + "2025-01-15T10:00:00Z" + "2026-01-15T10:00:00Z" + "did:plc:bob456" 50verify(payload, "abc123...", publicKey) → FALSE ❌ 51 52The signature doesn't match because Bob's DID is different from Alice's DID! 53``` 54 55### Implementation 56 57**Service (when creating signature):** 58```go 59// service.go:216-222 60verificationData := &VerificationData{ 61 Type: "phone", 62 VerifiedBy: s.signer.GetVerifierDID(), 63 VerifiedAt: now, 64 ExpiresAt: expiresAt, 65 SubjectDID: did, // ← This binds it to the user 66} 67``` 68 69**Third-party app (when verifying):** 70```javascript 71// Reconstruct the EXACT same payload 72const payload = verification.type + 73 verification.verifiedBy + 74 verification.verifiedAt + 75 verification.expiresAt + 76 profileOwnerDID // ← MUST use the profile owner's DID 77 78// Verify signature 79const isValid = verifyECDSA(payload, verification.signature, publicKey) 80``` 81 82## Other Attack Vectors 83 84### 1. Replay Attack (Using old verification) 85**Defense**: Expiry timestamp (`expiresAt`) is part of signed payload 86- Clients check `expiresAt < now()` → reject 87- Annual re-verification ensures freshness 88 89### 2. Forged Signature 90**Defense**: ECDSA P-256 signature with private key known only to Coves 91- Attacker would need Coves' private key 92- Private key stored in secure environment (secrets manager) 93- Can't be brute-forced (256-bit security) 94 95### 3. Man-in-the-Middle (DID document swap) 96**Defense**: HTTPS + optional DID pinning 97- DID document served over HTTPS 98- Clients can pin Coves' DID public key in app 99- DNS/TLS security prevents MITM 100 101### 4. Verification Badge Removal 102**Defense**: User controls their own PDS 103- Only the user (or authorized apps with OAuth) can modify their profile 104- AppView writes via OAuth (with user consent) 105- Other apps can't remove verifications without permission 106 107### 5. Phone Number Reuse (Carrier recycling) 108**Defense**: Expiry + re-verification 109- Verifications expire after 1 year 110- If carrier recycles number to new person, old verification expires 111- New person must verify to get new badge 112 113### 6. SMS Interception (SIM swap) 114**Defense**: Rate limiting + audit logging 115- Max 3 OTP requests per phone per hour 116- Audit logs track suspicious patterns 117- Future: Add 2FA backup methods (email, authenticator) 118 119### 7. Phone Hash Rainbow Table 120**Defense**: HMAC with secret pepper 121```go 122phoneHash = HMAC-SHA256(phoneNumber, secretPepper) 123``` 124- Even if DB is leaked, attacker can't reverse hashes without pepper 125- Pepper stored separately (environment variable) 126- Prevents bulk phone number enumeration 127 128## Verification Lifecycle Security 129 130### Creation 1311. ✅ User authenticates with OAuth 1322. ✅ AppView verifies phone ownership (OTP) 1333. ✅ AppView creates signature (includes subject DID) 1344. ✅ AppView writes to user's PDS (OAuth scope) 1355. ✅ AppView stores phone_hash (duplicate prevention) 136 137### Validation (Third-party apps) 1381. ✅ Fetch user's profile from PDS 1392. ✅ Extract verification from `verifications` array 1403. ✅ Fetch Coves DID document (public key) 1414. ✅ Reconstruct payload (including profile owner DID) 1425. ✅ Verify signature matches 1436. ✅ Check expiry timestamp 144 145### Revocation 1461. ✅ User loses phone → requests revocation 1472. ✅ AppView removes verification from PDS 1483. ✅ AppView deletes phone_hash from DB 1494. ✅ Third-party apps see no verification (fetching latest from PDS) 150 151## did:web Security 152 153### Why `.well-known/did.json` is Safe 154 155**Q**: Can someone just change the DID document? 156**A**: No, because: 157 1581. **Domain ownership**: Only owner of `coves.social` can serve files at `https://coves.social/.well-known/did.json` 1592. **HTTPS**: TLS prevents MITM attacks 1603. **Key rotation**: If private key compromised, we rotate (keep old key for 30 days for existing verifications) 161 162### Alternative: DNS TXT Record 163We could also add a DNS TXT record for extra verification: 164``` 165_did.coves.social TXT "did=did:web:coves.social key=<public-key-fingerprint>" 166``` 167This provides defense-in-depth (DNS + HTTPS both need to be compromised). 168 169## Comparison to Other Verification Systems 170 171### Twitter Blue (Centralized) 172- ❌ Phone stored on Twitter servers 173- ❌ Not portable (can't take verification to other apps) 174- ❌ Twitter controls badge (can remove) 175- ✅ Simple to verify (just trust Twitter) 176 177### Bluesky Verification (Domain-based) 178- ✅ Decentralized (DNS TXT record) 179- ✅ Portable (works across apps) 180- ❌ Only works for domain owners 181- ❌ No phone verification 182 183### Coves Verification (Hybrid) 184- ✅ Privacy-first (phone never stored) 185- ✅ Portable (PDS-stored, cryptographically signed) 186- ✅ Works for non-domain owners 187- ✅ Third-party verifiable (public key in DID document) 188- ✅ User-controlled (badge on their PDS) 189- ⚠️ Requires crypto verification (more complex for clients) 190 191## Security Checklist 192 193When implementing, ensure: 194 195- [ ] Private key stored securely (secrets manager, not in code) 196- [ ] Phone hash pepper never changes (backup safely) 197- [ ] OTP generated with `crypto/rand` (not `math/rand`) 198- [ ] OTP comparison is constant-time (bcrypt) 199- [ ] Subject DID included in signature payload 200- [ ] Third-party apps verify signature correctly 201- [ ] Rate limits enforced at network edge 202- [ ] Audit logs don't leak phone numbers 203- [ ] HTTPS enforced for DID document 204- [ ] PDS writes use OAuth (user consent) 205 206## Future Enhancements 207 208### 1. Multi-factor Verification 209Require 2+ verification types: 210- Phone + Email 211- Phone + Domain 212- Government ID + Phone 213 214### 2. Verification Revocation List 215Publish revoked verifications at `https://coves.social/.well-known/revocations.json` 216- Third-party apps can check if verification was revoked 217- User privacy preserved (DID hash, not full DID) 218 219### 3. Hardware Security Module (HSM) 220Store signing key in HSM for production: 221- AWS CloudHSM 222- Google Cloud KMS 223- Azure Key Vault 224 225Prevents key extraction even with server compromise. 226 227## Questions? 228 229Open an issue or ask on Discord!