A community based topic aggregation platform built on atproto

docs: Add comprehensive phone verification documentation

Add implementation and security documentation:
- DID_SETUP.md: Keypair generation and deployment
- PHONE_VERIFICATION_IMPLEMENTATION.md: Complete implementation guide
- PHONE_VERIFICATION_SUMMARY.md: Quick reference
- VERIFICATION_SECURITY.md: Security model and attack prevention

Documents the hybrid architecture: privacy-first storage with
cryptographically signed, portable verification badges.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

+800
+96
docs/DID_SETUP.md
···
··· 1 + # DID Setup for Coves Verification Service 2 + 3 + ## Overview 4 + Coves uses a `did:web` DID to sign phone verification tokens. This allows third-party clients to cryptographically verify that verifications were issued by the official Coves service. 5 + 6 + ## Generating the Verification Keypair 7 + 8 + ```bash 9 + # Generate P-256 EC keypair for signing verifications 10 + openssl ecparam -name prime256v1 -genkey -noout -out verification-key.pem 11 + 12 + # Extract public key in JWK format 13 + # You'll need to convert this to JWK format for the DID document 14 + openssl ec -in verification-key.pem -pubout -out verification-key-pub.pem 15 + ``` 16 + 17 + ## Converting to JWK Format 18 + 19 + Use a tool or library to convert the public key to JWK format: 20 + - Go: `github.com/lestrrat-go/jwx/v2/jwk` 21 + - Node.js: `jose` library 22 + - Online: https://8gwifi.org/jwkconvertfunctions.jsp (for dev only) 23 + 24 + ## Updating the DID Document 25 + 26 + 1. Generate the keypair 27 + 2. Extract the public key JWK (x, y coordinates) 28 + 3. Update `.well-known/did.json` with the actual coordinates 29 + 4. Store the private key securely (environment variable or secrets manager) 30 + 31 + ## Serving the DID Document 32 + 33 + The DID document must be served at: 34 + ``` 35 + https://coves.social/.well-known/did.json 36 + ``` 37 + 38 + With headers: 39 + ``` 40 + Content-Type: application/json 41 + Access-Control-Allow-Origin: * 42 + ``` 43 + 44 + ## Environment Configuration 45 + 46 + **DO NOT commit secrets to git!** 47 + 48 + Add to your `.env` file: 49 + ```bash 50 + # DID for your instance (change coves.social to your domain) 51 + VERIFICATION_SERVICE_DID=did:web:coves.social 52 + 53 + # Private key (base64-encoded or plain PEM) 54 + VERIFICATION_PRIVATE_KEY="$(cat verification-key.pem | base64 -w 0)" 55 + ``` 56 + 57 + In production, use a secrets manager: 58 + - AWS Secrets Manager 59 + - HashiCorp Vault 60 + - Google Secret Manager 61 + 62 + **For forked deployments:** 63 + If you're deploying your own instance at `myapp.com`, update: 64 + 1. `VERIFICATION_SERVICE_DID=did:web:myapp.com` 65 + 2. Serve DID document at `https://myapp.com/.well-known/did.json` 66 + 3. Update `verificationMethod[0].id` to use your domain 67 + 68 + ## Verifying Signatures (Third-Party Clients) 69 + 70 + Third-party clients can verify phone verifications by: 71 + 72 + 1. Fetch DID document: `https://coves.social/.well-known/did.json` 73 + 2. Extract public key from `verificationMethod[0].publicKeyJwk` 74 + 3. Verify signature over verification data: 75 + ``` 76 + payload = type + verifiedBy + verifiedAt + expiresAt + subjectDID 77 + verify(payload, signature, publicKey) 78 + ``` 79 + 80 + ## Key Rotation 81 + 82 + When rotating keys: 83 + 1. Generate new keypair 84 + 2. Add new key to `verificationMethod` array with new ID (#verification-key-2) 85 + 3. Keep old key for 30 days to allow verification of existing tokens 86 + 4. Update signing code to use new key 87 + 5. After 30 days, remove old key from DID document 88 + 89 + ## Testing 90 + 91 + ```bash 92 + # Verify DID document is valid 93 + curl https://coves.social/.well-known/did.json | jq . 94 + 95 + # Should return valid JSON with your public key 96 + ```
+297
docs/PHONE_VERIFICATION_IMPLEMENTATION.md
···
··· 1 + # Phone Verification Implementation Guide 2 + 3 + ## Overview 4 + This document outlines the complete implementation of phone verification for Coves using the hybrid approach: privacy-first storage with cryptographically signed, portable verification badges. 5 + 6 + ## Architecture 7 + 8 + ### Data Flow 9 + ``` 10 + 1. User requests verification (mobile app) 11 + 12 + 2. Coves AppView validates + sends OTP via Telnyx 13 + 14 + 3. User enters OTP code 15 + 16 + 4. AppView validates code + creates signed verification 17 + 18 + 5. AppView writes verification to user's PDS profile 19 + 20 + 6. AppView stores phone_hash in local database 21 + 22 + 7. Third-party clients see verification badge (portable) 23 + ``` 24 + 25 + ### Privacy Model 26 + - **Never stored**: Plain phone numbers (anywhere) 27 + - **Hashed in AppView DB**: HMAC-SHA256(phone, pepper) for duplicate detection 28 + - **Stored on PDS**: Signed verification badge (no phone data) 29 + - **Portable**: Badge works across AppViews, can't be forged 30 + 31 + ## Components Implemented 32 + 33 + ### 1. Lexicon Updates 34 + - ✅ **actor/profile.json**: Added `verifications` array 35 + - ✅ **verification/requestPhone.json**: XRPC endpoint for OTP request 36 + - ✅ **verification/verifyPhone.json**: XRPC endpoint for OTP validation 37 + - ✅ **verification/getStatus.json**: XRPC endpoint for verification status 38 + 39 + ### 2. Database Schema (005_create_phone_verification_tables.sql) 40 + ```sql 41 + phone_verifications -- Completed verifications 42 + phone_verification_requests -- Pending OTP requests (10min TTL) 43 + phone_verification_rate_limits -- Rate limit tracking 44 + phone_verification_audit_log -- Security audit trail 45 + ``` 46 + 47 + ### 3. did:web DID for Coves 48 + - **Location**: `.well-known/did.json` 49 + - **DID**: `did:web:coves.social` 50 + - **Purpose**: Signs verification badges 51 + - **Public Key**: P-256 EC key (JWK format) 52 + 53 + ### 4. Core Service Layer 54 + - **interfaces.go**: Service contracts 55 + - **service.go**: Verification logic 56 + - **errors.go**: Domain errors 57 + 58 + ### 5. SMS Provider (Telnyx) 59 + - **telnyx/client.go**: API integration 60 + - **Why Telnyx**: 50% cheaper, owned infrastructure, international support 61 + 62 + ## Environment Configuration 63 + 64 + ### Required Variables (.env) 65 + ```bash 66 + # DID Configuration 67 + VERIFICATION_SERVICE_DID=did:web:coves.social 68 + VERIFICATION_PRIVATE_KEY=<base64-encoded-P256-private-key> 69 + 70 + # Telnyx Configuration 71 + TELNYX_API_KEY=<your-api-key> 72 + TELNYX_MESSAGING_PROFILE_ID=<your-profile-id> 73 + TELNYX_FROM_NUMBER=<e164-phone-number> 74 + 75 + # Security 76 + PHONE_HASH_PEPPER=<base64-32-bytes> # NEVER change after initial setup! 77 + ``` 78 + 79 + ### Setup Steps 80 + 81 + #### 1. Generate DID Keypair 82 + ```bash 83 + # Generate P-256 EC private key 84 + openssl ecparam -name prime256v1 -genkey -noout -out verification-key.pem 85 + 86 + # Extract public key 87 + openssl ec -in verification-key.pem -pubout -out verification-key-pub.pem 88 + 89 + # Convert to JWK format (use library or online tool for dev) 90 + # Update .well-known/did.json with x, y coordinates 91 + 92 + # Store private key in environment 93 + export VERIFICATION_PRIVATE_KEY="$(cat verification-key.pem | base64 -w 0)" 94 + ``` 95 + 96 + #### 2. Generate Phone Hash Pepper 97 + ```bash 98 + export PHONE_HASH_PEPPER="$(openssl rand -base64 32)" 99 + ``` 100 + 101 + ⚠️ **CRITICAL**: Never change `PHONE_HASH_PEPPER` after production launch! 102 + 103 + #### 3. Configure Telnyx 104 + 1. Sign up at https://telnyx.com 105 + 2. Create a Messaging Profile 106 + 3. Purchase a phone number 107 + 4. Get API key from dashboard 108 + 5. Add to `.env` 109 + 110 + #### 4. Serve DID Document 111 + Ensure `https://coves.social/.well-known/did.json` is publicly accessible with: 112 + ``` 113 + Content-Type: application/json 114 + Access-Control-Allow-Origin: * 115 + ``` 116 + 117 + ## Implementation Checklist 118 + 119 + ### Backend (Go) 120 + - [ ] Run migration: `005_create_phone_verification_tables.sql` 121 + - [ ] Implement `PhoneHashProvider` (HMAC-SHA256 with pepper) 122 + - [ ] Implement `SignatureService` (ECDSA P-256 signing) 123 + - [ ] Implement `PDSWriter` (write to user's PDS via OAuth) 124 + - [ ] Implement `VerificationRepository` (PostgreSQL) 125 + - [ ] Create XRPC handlers for verification endpoints 126 + - [ ] Add verification routes to main.go 127 + - [ ] Add background cleanup job (expired requests) 128 + 129 + ### Frontend (Mobile) 130 + - [ ] Add phone input screen with E.164 validation 131 + - [ ] Add OTP entry screen (6-digit code) 132 + - [ ] Call `/xrpc/social.coves.verification.requestPhone` 133 + - [ ] Call `/xrpc/social.coves.verification.verifyPhone` 134 + - [ ] Display verification badge on profiles 135 + - [ ] Handle re-verification flow (annual) 136 + 137 + ### Testing 138 + - [ ] Unit tests for service layer 139 + - [ ] Integration tests for XRPC endpoints 140 + - [ ] Test rate limiting (per phone, per DID) 141 + - [ ] Test signature verification (third-party client) 142 + - [ ] Test PDS write-back 143 + - [ ] Test phone hash collision detection 144 + 145 + ### Security Audit 146 + - [ ] Verify OTP uses crypto/rand 147 + - [ ] Verify constant-time code comparison (bcrypt) 148 + - [ ] Verify rate limits are enforced 149 + - [ ] Verify phone numbers never logged in plaintext 150 + - [ ] Verify audit logs don't leak sensitive data 151 + - [ ] Verify signatures can't be forged 152 + - [ ] Test SMS provider failover/retry logic 153 + 154 + ## Security Considerations 155 + 156 + ### Rate Limits 157 + - **Per Phone**: 3 requests/hour (prevents SMS spam to victim) 158 + - **Per DID**: 5 requests/day (prevents user abuse) 159 + 160 + ### OTP Security 161 + - **Length**: 6 digits (1M combinations) 162 + - **Expiry**: 10 minutes 163 + - **Max Attempts**: 3 (then must request new code) 164 + - **Storage**: Bcrypt hashed (not reversible) 165 + 166 + ### Phone Hash Security 167 + - **Algorithm**: HMAC-SHA256(phone, pepper) 168 + - **Pepper**: 32-byte secret, environment variable 169 + - **Purpose**: Prevent rainbow table attacks 170 + - **Uniqueness**: One phone = one verified account 171 + 172 + ### Signature Security 173 + - **Algorithm**: ECDSA P-256 (ES256) 174 + - **Payload**: type + verifiedBy + verifiedAt + expiresAt + subjectDID 175 + - **Verification**: Third-party clients fetch public key from DID document 176 + 177 + ## Annual Re-verification Flow 178 + 179 + 1. **30 days before expiry**: Set `needsRenewal: true` in status API 180 + 2. **Show banner in app**: "Your verification expires soon, renew now" 181 + 3. **User re-verifies**: Same flow, new phone allowed 182 + 4. **Old verification**: Expires, badge removed from profile 183 + 184 + ## Phone Loss/Change Flow 185 + 186 + 1. User reports "lost phone" in app 187 + 2. AppView removes verification from PDS profile 188 + 3. AppView deletes phone_hash from database 189 + 4. User verifies new phone number 190 + 5. Badge restored with new phone_hash 191 + 192 + ## Third-Party Client Integration 193 + 194 + ### Verifying Badges 195 + ```javascript 196 + // 1. Fetch user profile from PDS 197 + const profileOwnerDID = 'did:plc:abc123' // The DID whose profile you're viewing 198 + const profile = await fetchProfileFromPDS(profileOwnerDID) 199 + 200 + // 2. Check for phone verification 201 + const phoneVerification = profile.verifications?.find(v => v.type === 'phone') 202 + if (!phoneVerification) { 203 + // No phone verification 204 + return false 205 + } 206 + 207 + // 3. Fetch Coves DID document 208 + const didDoc = await fetch('https://coves.social/.well-known/did.json').then(r => r.json()) 209 + const publicKey = didDoc.verificationMethod[0].publicKeyJwk 210 + 211 + // 4. Verify signature (CRITICAL: includes profileOwnerDID to prevent copying) 212 + const payload = phoneVerification.type + 213 + phoneVerification.verifiedBy + 214 + phoneVerification.verifiedAt + 215 + phoneVerification.expiresAt + 216 + profileOwnerDID // ← This binds verification to this specific user 217 + 218 + const isValid = await verifySignature(payload, phoneVerification.signature, publicKey) 219 + 220 + // 5. Check expiry 221 + const expired = new Date(phoneVerification.expiresAt) < new Date() 222 + 223 + return isValid && !expired 224 + 225 + // If Alice tries to copy this verification to her profile, the signature verification 226 + // will fail because her DID is different from the original subject DID in the payload 227 + ``` 228 + 229 + ## Monitoring & Observability 230 + 231 + ### Key Metrics 232 + - SMS delivery rate (should be >99%) 233 + - Verification completion rate (request → verify) 234 + - Rate limit hit rate (should be low) 235 + - Average time to verify 236 + - Cost per verification 237 + 238 + ### Alerts 239 + - SMS delivery failures spike 240 + - Unusual rate limit hits (possible attack) 241 + - Signature validation failures (bug or attack) 242 + - PDS write failures 243 + 244 + ### Audit Log Queries 245 + ```sql 246 + -- Failed verification attempts by DID 247 + SELECT did, COUNT(*) as failures 248 + FROM phone_verification_audit_log 249 + WHERE event_type = 'verification_failed' 250 + AND created_at > NOW() - INTERVAL '24 hours' 251 + GROUP BY did 252 + HAVING COUNT(*) > 5; 253 + 254 + -- Rate limit hits (possible attack) 255 + SELECT phone_hash, COUNT(*) as hits 256 + FROM phone_verification_audit_log 257 + WHERE event_type = 'rate_limit_hit' 258 + AND created_at > NOW() - INTERVAL '1 hour' 259 + GROUP BY phone_hash 260 + ORDER BY hits DESC 261 + LIMIT 10; 262 + ``` 263 + 264 + ## Cost Estimation 265 + 266 + ### Telnyx Pricing (US) 267 + - **SMS**: $0.004/message 268 + - **Monthly estimate**: 10,000 verifications = $40/month 269 + - **Annual re-verification**: Ongoing cost, plan accordingly 270 + 271 + ### Comparison 272 + - Twilio: $0.0079/message = $79/month (98% more expensive) 273 + - AWS SNS: $0.0064/message = $64/month (60% more expensive) 274 + 275 + ## Future Enhancements 276 + 277 + ### v1.1 - Email Verification 278 + - Same architecture, different `type: "email"` 279 + - Reuse signature service 280 + - Add email provider (e.g., AWS SES) 281 + 282 + ### v1.2 - Domain Verification 283 + - Prove ownership of domain 284 + - `type: "domain"` 285 + - DNS TXT record validation 286 + 287 + ### v1.3 - Government ID 288 + - KYC provider integration 289 + - `type: "government_id"` 290 + - Higher trust level 291 + 292 + ## Support 293 + 294 + For questions or issues: 295 + - GitHub Issues: https://github.com/coves-social/coves/issues 296 + - Discord: [your-discord-link] 297 + - Email: support@coves.social
+178
docs/PHONE_VERIFICATION_SUMMARY.md
···
··· 1 + # Phone Verification Implementation Summary 2 + 3 + ## Quick Start 4 + 5 + We've implemented a **privacy-first, cryptographically-signed phone verification system** for Coves that: 6 + 7 + ✅ Keeps phone numbers **completely private** (never stored in plaintext) 8 + ✅ Creates **portable verification badges** (works across third-party apps) 9 + ✅ Uses **did:web DID** for cryptographic signing 10 + ✅ Integrates with **Telnyx SMS** (50% cheaper than Twilio) 11 + ✅ Supports **annual re-verification** and phone number changes 12 + ✅ Includes **rate limiting** and **audit logging** for security 13 + 14 + ## Files Created 15 + 16 + ### Lexicons 17 + - `internal/atproto/lexicon/social/coves/actor/profile.json` (updated) 18 + - `internal/atproto/lexicon/social/coves/verification/requestPhone.json` 19 + - `internal/atproto/lexicon/social/coves/verification/verifyPhone.json` 20 + - `internal/atproto/lexicon/social/coves/verification/getStatus.json` 21 + 22 + ### Database 23 + - `internal/db/migrations/005_create_phone_verification_tables.sql` 24 + 25 + ### Service Layer 26 + - `internal/core/verification/interfaces.go` 27 + - `internal/core/verification/service.go` 28 + - `internal/core/verification/errors.go` 29 + 30 + ### SMS Provider 31 + - `internal/sms/telnyx/client.go` 32 + 33 + ### Configuration 34 + - `.env.example` (with all required environment variables) 35 + - `.well-known/did.json` (DID document for signature verification) 36 + 37 + ### Documentation 38 + - `docs/DID_SETUP.md` (How to set up the DID and keypair) 39 + - `docs/PHONE_VERIFICATION_IMPLEMENTATION.md` (Complete implementation guide) 40 + 41 + ## SMS Provider Decision: Telnyx 42 + 43 + **Winner**: Telnyx 44 + **Pricing**: $0.004/SMS (US) - 50% cheaper than Twilio 45 + **Why**: Owned infrastructure, 10x throughput, free support, international coverage 46 + 47 + **Cost estimate**: 10,000 verifications/month = **$40/month** 48 + 49 + ## Architecture Summary 50 + 51 + ### How It Works 52 + 53 + 1. **User requests verification** → AppView sends OTP via Telnyx 54 + 2. **User enters code** → AppView validates (max 3 attempts) 55 + 3. **AppView creates signed verification** using `did:web:coves.social` private key 56 + 4. **AppView writes to user's PDS** via `com.atproto.repo.putRecord` (OAuth) 57 + 5. **Badge appears on profile** → Third-party apps can verify signature 58 + 6. **AppView stores phone_hash** → Prevents duplicate verifications 59 + 60 + ### Privacy Guarantees 61 + 62 + | Data | Stored Where | Format | 63 + |------|--------------|--------| 64 + | Phone number | **NOWHERE** | Never stored | 65 + | Phone hash | AppView DB | HMAC-SHA256(phone, pepper) | 66 + | OTP code | AppView DB (temp) | Bcrypt hash, 10min TTL | 67 + | Verification badge | User's PDS | Signed JSON (no phone data) | 68 + 69 + ### Security Features 70 + 71 + - ✅ **Rate limits**: 3/hour per phone, 5/day per user 72 + - ✅ **OTP expiry**: 10 minutes 73 + - ✅ **Max attempts**: 3 per OTP request 74 + - ✅ **Cryptographic signatures**: ECDSA P-256 (can't be forged) 75 + - ✅ **Audit logging**: All events tracked for fraud detection 76 + - ✅ **Constant-time comparison**: Prevents timing attacks 77 + 78 + ## Next Steps to Deploy 79 + 80 + ### 1. Generate Secrets 81 + ```bash 82 + # DID signing keypair 83 + openssl ecparam -name prime256v1 -genkey -noout -out verification-key.pem 84 + export VERIFICATION_PRIVATE_KEY="$(cat verification-key.pem | base64 -w 0)" 85 + 86 + # Phone hash pepper 87 + export PHONE_HASH_PEPPER="$(openssl rand -base64 32)" 88 + ``` 89 + 90 + ### 2. Configure Telnyx 91 + - Sign up: https://telnyx.com 92 + - Get API key + messaging profile ID 93 + - Purchase phone number 94 + 95 + ### 3. Update DID Document 96 + - Extract public key from `verification-key.pem` (convert to JWK) 97 + - Update `.well-known/did.json` with actual coordinates 98 + - Deploy to `https://coves.social/.well-known/did.json` 99 + 100 + ### 4. Implement Missing Components 101 + - [ ] `PhoneHashProvider` (HMAC-SHA256 implementation) 102 + - [ ] `SignatureService` (ECDSA P-256 signing) 103 + - [ ] `PDSWriter` (write verifications to PDS via OAuth) 104 + - [ ] `VerificationRepository` (PostgreSQL implementation) 105 + - [ ] XRPC handlers (routes + error mapping) 106 + - [ ] Background cleanup job (expired OTP requests) 107 + 108 + ### 5. Frontend Integration 109 + - [ ] Phone input screen (E.164 validation) 110 + - [ ] OTP entry screen 111 + - [ ] Verification badge display 112 + - [ ] Re-verification flow 113 + 114 + ## Questions Answered 115 + 116 + ### Q: Where is the verification badge stored? 117 + **A**: In the user's PDS profile (`social.coves.actor.profile` record), in the `verifications` array. Third-party apps can read it directly from the PDS. 118 + 119 + ### Q: Can users fake the verification? 120 + **A**: No. The badge includes a cryptographic signature that third-party apps can verify using Coves' public key from the DID document. 121 + 122 + ### Q: What if someone forks Coves? 123 + **A**: They set `VERIFICATION_SERVICE_DID=did:web:their-domain.com` and generate their own keypair. The system is fully self-hostable. 124 + 125 + ### Q: How do third-party apps verify the badge? 126 + **A**: 127 + 1. Fetch the DID document from `https://coves.social/.well-known/did.json` 128 + 2. Extract the public key from `verificationMethod[0].publicKeyJwk` 129 + 3. **CRITICAL**: Verify the signature includes the **profile owner's DID** in the payload 130 + 4. Reconstruct payload: `type + verifiedBy + verifiedAt + expiresAt + profileOwnerDID` 131 + 5. Verify signature matches payload 132 + 133 + **Security Note**: The signature MUST include the subject DID. This prevents users from copying someone else's verification to their profile (the signature won't match because the DID is different). 134 + 135 + ### Q: What happens on phone loss? 136 + **A**: User reports lost phone → AppView removes verification from PDS → User verifies new number → Badge restored. 137 + 138 + ### Q: Why annual re-verification? 139 + **A**: Ensures active users, detects account takeovers, gives a yearly touchpoint for security checks. 140 + 141 + ## File Locations Reference 142 + 143 + ``` 144 + Coves/ 145 + ├── .env.example # Environment config template 146 + ├── .well-known/ 147 + │ └── did.json # DID document (serves at /.well-known/did.json) 148 + ├── docs/ 149 + │ ├── DID_SETUP.md # DID keypair setup guide 150 + │ ├── PHONE_VERIFICATION_IMPLEMENTATION.md # Complete implementation guide 151 + │ └── PHONE_VERIFICATION_SUMMARY.md # This file 152 + ├── internal/ 153 + │ ├── atproto/lexicon/social/coves/ 154 + │ │ ├── actor/profile.json # Updated with verifications array 155 + │ │ └── verification/ 156 + │ │ ├── requestPhone.json # XRPC: Request OTP 157 + │ │ ├── verifyPhone.json # XRPC: Verify OTP 158 + │ │ └── getStatus.json # XRPC: Get verification status 159 + │ ├── core/verification/ 160 + │ │ ├── interfaces.go # Service contracts 161 + │ │ ├── service.go # Verification logic 162 + │ │ └── errors.go # Domain errors 163 + │ ├── db/migrations/ 164 + │ │ └── 005_create_phone_verification_tables.sql # Database schema 165 + │ └── sms/telnyx/ 166 + │ └── client.go # Telnyx API integration 167 + ``` 168 + 169 + ## Resources 170 + 171 + - **Telnyx Docs**: https://developers.telnyx.com/docs/api/v2/messaging 172 + - **DID Spec**: https://www.w3.org/TR/did-core/ 173 + - **atProto Specs**: https://atproto.com/specs/ 174 + - **E.164 Format**: https://en.wikipedia.org/wiki/E.164 175 + 176 + ## Contact 177 + 178 + Questions? Open an issue or reach out on Discord!
+229
docs/VERIFICATION_SECURITY.md
···
··· 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 + 28 + The signature is created over this payload: 29 + ``` 30 + type + 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 + ``` 45 + Bob's DID: did:plc:bob456 46 + Alice's signature: "abc123..." 47 + 48 + Third-party app verifies: 49 + payload = "phone" + "did:web:coves.social" + "2025-01-15T10:00:00Z" + "2026-01-15T10:00:00Z" + "did:plc:bob456" 50 + verify(payload, "abc123...", publicKey) → FALSE ❌ 51 + 52 + The 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 60 + verificationData := &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 72 + const 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 79 + const 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 122 + phoneHash = 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 131 + 1. ✅ User authenticates with OAuth 132 + 2. ✅ AppView verifies phone ownership (OTP) 133 + 3. ✅ AppView creates signature (includes subject DID) 134 + 4. ✅ AppView writes to user's PDS (OAuth scope) 135 + 5. ✅ AppView stores phone_hash (duplicate prevention) 136 + 137 + ### Validation (Third-party apps) 138 + 1. ✅ Fetch user's profile from PDS 139 + 2. ✅ Extract verification from `verifications` array 140 + 3. ✅ Fetch Coves DID document (public key) 141 + 4. ✅ Reconstruct payload (including profile owner DID) 142 + 5. ✅ Verify signature matches 143 + 6. ✅ Check expiry timestamp 144 + 145 + ### Revocation 146 + 1. ✅ User loses phone → requests revocation 147 + 2. ✅ AppView removes verification from PDS 148 + 3. ✅ AppView deletes phone_hash from DB 149 + 4. ✅ 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 + 158 + 1. **Domain ownership**: Only owner of `coves.social` can serve files at `https://coves.social/.well-known/did.json` 159 + 2. **HTTPS**: TLS prevents MITM attacks 160 + 3. **Key rotation**: If private key compromised, we rotate (keep old key for 30 days for existing verifications) 161 + 162 + ### Alternative: DNS TXT Record 163 + We 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 + ``` 167 + This 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 + 193 + When 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 209 + Require 2+ verification types: 210 + - Phone + Email 211 + - Phone + Domain 212 + - Government ID + Phone 213 + 214 + ### 2. Verification Revocation List 215 + Publish 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) 220 + Store signing key in HSM for production: 221 + - AWS CloudHSM 222 + - Google Cloud KMS 223 + - Azure Key Vault 224 + 225 + Prevents key extraction even with server compromise. 226 + 227 + ## Questions? 228 + 229 + Open an issue or ask on Discord!