🔗 Server Integration
Integrating Signal Protocol with Servers
In 15 minutes: Understand server requirements for Signal Protocol
Prerequisites: X3DH (Bob uploads keys)
🎯 The Simple Story
Signal Protocol needs server to store keys for offline users.
Server responsibilities:
- Store Bob's public keys (IK, SPK, OPKs)
- Serve Bob's keys to Alice (when she wants to message Bob)
- Handle X3DH handshakes
- Store/retrieve one-time pre-keys (consumed usage)
🧠 Mental Model
Hold this picture in your head:
Server Integration:
Bob (offline) → Server (stores keys)
↑ ↓
Alice (online) ← Downloads keys ← Server
Server Stores:
- Bob_IK_public (identity key, public)
- Bob_SPK_public (signed pre-key, public)
- Bob_OPK_public[] (one-time pre-keys, public)
- SIG_B (SPK signature)
Alice Requests:
"Send Bob's public keys for user +15551234567"
Server Responds:
- Bob's 4 public key types
- SIG_B
Alice Verifies:
- Is SIG_B valid for Bob_SPK_public?
- If yes: Bob's keys, proceed
- If no: Eve's keys, reject
Alice Initiates X3DH (with Bob's keys)
Server Also:
- Track which OPK is being used
- When Alice uses OPK_B[0], mark as used
- When Bob needs OPK, server sends OPK_B[1], etc.
📊 Server API
Upload Bob's Keys
POST /api/v1/keys/upload
Content-Type: application/json
{
"user_id": "+15551234567",
"identity_key": "base64_encoded_public_key",
"signed_pre_key": "base64_encoded_public_key",
"signature": "base64_encoded_signature",
"one_time_pre_keys": [
"base64_encoded_opk[0]",
"base64_encoded_opk[1]",
...
]
}
Response:
201 Created
{
"success": true
}
Download Bob's Keys
GET /api/v1/keys/+15551234567
Response:
200 OK
{
"identity_key": "base64_encoded_public_key",
"signed_pre_key": "base64_encoded_public_key",
"signature": "base64_encoded_signature",
"one_time_pre_key": "base64_encoded_opk",
"opk_index": 0 // Which OPK used
}
Reserve OPK
When user messages Bob, OPK reserved:
POST /api/v1/opk/reserve/+15551234567?index=0
Response:
200 OK
{
"success": true
}
🔧 Server Implementation
Key Storage
type KeyStore struct {
database Database
}
func (s *KeyStore) StoreKeys(userID string, keys *Keys) error {
// Store in database
identityKey := keys.IdentityKey
signedPreKey := keys.SignedPreKey
signature := keys.Signature
oneTimePreKeys := keys.OneTimePreKeys
// Encrypt before storage
encrypted := encryptKeys(keys, serverSecret)
return s.database.Save(userID, encrypted)
}
func (s *KeyStore) GetKeys(userID string, opkIndex int) (*Keys, error) {
// Retrieve from database
encrypted, err := s.database.Load(userID)
if err != nil {
return nil, err
}
// Decrypt
keys := decryptKeys(encrypted, serverSecret)
// Remove used OPK
delete(keys.OneTimePreKeys, opkIndex)
return keys, nil
}
💡 Server Considerations
Encryption
Store encrypted keys:
- Never store plaintext keys
- Encrypt with server-side key
- Decrypt only when serving to users
Access Control
Only allow:
- Bob to upload his keys
- Alice to download Bob's public keys
- No one else to modify
Rate Limiting
Prevent abuse:
- Limit upload attempts
- Prevent DoS on key download
✅ Quick Check
What does server store?
Public keys:
IK_public, SPK_public, OPK_public[], SIG_B
Why encrypt database?
Security:
If server compromised, Eve can't read keys directly.
Server decrypts only when serving to authorized users.
📋 Summary
✅ Server stores: Bob's public keys
✅ Alice downloads: Bob's keys from server
✅ Server tracks: OPK usage (consume one per message)
✅ Encrypt storage: Keys encrypted in database
✅ Access control: Only owner uploads, anyone downloads