Skip to main content

🔗 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:

  1. Store Bob's public keys (IK, SPK, OPKs)
  2. Serve Bob's keys to Alice (when she wants to message Bob)
  3. Handle X3DH handshakes
  4. 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