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!