Security Architecture
Key Rotation
Overview
SemaFore uses two key rotation mechanisms to enforce periodicity and replenish ephemeral key material. Signed Pre-Key expiry is enforced by the server, and mobile clients rotate expired keys automatically. One-Time Pre-Key replenishment is asynchronous and preserves delivery when a device has been offline.
Signed Pre-Key (SPK) Rotation
Trigger
The SPK is rotated when it exceeds SPK_MAX_AGE_HOURS (default: 168 hours, or 7 days) in age. The server enforces this maximum age for new session initiation.
Procedure
When rotation is triggered:
- Device generates new key pair: The device creates a new Curve25519 key pair
- Device signs the public key: Using its Identity Key private key, the device signs the new SPK public key with Ed25519
- Device uploads via HTTP: The device calls
PUT /api/mobile/key-bundles/{deviceID}/signed-pre-keywith the new public key and signature - Server stores and retains old key: The server stores the new SPK as the current active key and retains the previous SPK in the database for a grace period
Grace Period
For SPK_GRACE_PERIOD_HOURS (default: 168 hours, or 7 days) after rotation, the previous SPK remains available in the server’s key bundle responses. This grace period serves a critical purpose:
In-flight sessions: If a sender initiated an X3DH session with the recipient’s old SPK just before rotation occurred, the sender’s first message will reach the recipient using that old SPK in the header. The recipient’s Double Ratchet will process the message correctly because the SPK used in the X3DH computation is still available. Without the grace period, recipients would reject valid messages that used the previous SPK.
After the grace period expires, the previous SPK is deleted and no longer returned in key bundle lookups.
Expiry Enforcement
If a device’s SPK exceeds SPK_MAX_AGE_HOURS, the server treats it as expired for new session initiation:
- Response: HTTP 428 Precondition Required
- Error code:
spk_expired - Device notification: The server sends a
key_bundle.spk_expiredWebSocket event to the affected device
When the device receives key_bundle.spk_expired, the mobile client generates and uploads a replacement SPK automatically. From the user’s perspective, this is entirely transparent: there is no manual key-management action, no recovery code, and no portal step. Sessions that are already established continue through the Double Ratchet; initiators attempting to start a new session with an expired SPK receive the 428 response and retry after rotation.
One-Time Pre-Key (OPK) Replenishment
Trigger
The server monitors the count of available OPK public keys for each device. When the count falls below KEY_BUNDLE_REPLENISHMENT_THRESHOLD (default: 5), replenishment is triggered.
The threshold is checked after every key-bundle lookup that consumes an OPK. If a lookup reduces the remaining count below the threshold, the server immediately notifies the device.
Notification and Response
The server sends a key_bundle.replenishment_needed WebSocket event to the device with the device ID and the current remaining OPK count. The device responds by:
- Generating a new batch: The device creates a new batch of Curve25519 key pairs (typically 25-50 keys)
- Uploading public keys: The device calls the key-bundle publish endpoint to upload the new batch
- Deleting private keys: The device deletes the private keys for all uploaded OPKs
Replenishment is best-effort and asynchronous. If the device is offline, the notification is not queued—the device will receive it upon reconnection.
Graceful Degradation
If a device is offline and other users attempt to initiate sessions with it, the server will consume the device’s remaining OPKs. If the OPK supply is exhausted while the device remains offline:
- X3DH continues with 3 DH operations: Senders will establish sessions using only
DH1,DH2, andDH3, omitting theDH4operation with the missing OPK - No message loss: Messages are delivered normally; X3DH simply uses the three-key variant
- Upon reconnection: The device receives a
key_bundle.replenishment_needednotification and uploads a new batch
This graceful degradation means offline devices do not accumulate a backlog of undelivered messages—messages are encrypted to the three-key variant and delivered immediately.
SPK_B could allow an attacker to perform ECDH with the recipient’s ephemeral key. While the three-key variant remains cryptographically secure per the Signal Protocol specification, it is stronger to keep OPKs in stock. Devices should check connectivity regularly and replenish their OPK supply as soon as they come online.Summary
| Key Type | Rotation Trigger | Enforcement | Consequence of Delay |
|---|---|---|---|
| SPK | Age exceeds 7 days | Server refuses new sessions; device rotates automatically after key_bundle.spk_expired | Existing sessions continue; new initiators retry after rotation |
| OPK | Count falls below 5 | Server notifies device to replenish | Senders use three-key X3DH variant; no message loss |
Both rotation mechanisms are transparent to users. The server enforces SPK rotation to ensure periodic re-keying; OPK replenishment is automatic and preserves delivery even if the device is offline.