Emergency Key Redistribution (Call)¶
Scope¶
This document describes the emergency key redistribution mechanism used when a participant cannot decrypt media packets.
- Triggered by media decrypt failures (typically audio bursts).
- Requests the current AES session key from the key-rotation host.
Diagrams already exist in this folder; this doc focuses on the code-level flow.
Canonical Code¶
- Detect decrypt failure + request logic:
- Video:
ecall/Modules/Call/Managers/CallEncryptionManager.swift - Audio:
ecall/Core/Security/CustomAudioCrypto.swift - Host + participant signaling handlers:
ecall/Modules/Call/Managers/CallSignalingHandler.swift- Signal types:
ecall/Modules/Call/Managers/SignalingDelegate.swift
Trigger Condition¶
Implemented in CallEncryptionManager.decryptCallDataThreadSafe(...).
The decrypt path attempts:
sessionAESKey(current)backupSessionAESKey(previous)futureSessionAESKey(next)
If all fail, it logs a critical error and calls:
requestKeyFromHostIfNeeded(reason: .mediaDecryptFailed)
Request Flow (Participant → Host)¶
Preconditions¶
The participant will not send a request when:
- It is the host (
GroupCallSessionManager.shared.isKeyRotationHost == true). - A request is already in-flight (
isWaitingForKey == true). - Cooldown not passed (3 seconds).
- Duplicate signature within 1 second (dedupe across audio/video pipelines).
Rate limiting & dedupe¶
- Cooldown:
keyRequestCooldown = 3s - Timeout:
keyResponseTimeout = 10s - Dedupe window:
keyRequestDedupeInterval = 1s
Target selection¶
- Host is resolved from participants list: first participant where
isHostKey == true.
Signal¶
Participant sends:
SignalMessage(type: .request_aes_key, participantId: host.userId, participantDeviceId: host.deviceId, callId: currentCallId, senderId: myUserId, senderDeviceId: myDeviceId)
Sent via:
StompSignalingManager.shared.send(signal)
Response Flow (Host → Participant)¶
Host handling¶
In CallSignalingHandler.handleRequestAESKey(...):
- Verify the receiver is host (
isKeyRotationHost). - Read current key:
CallEncryptionManager.shared.sessionAESKey. - Fetch requester public key.
- Encrypt current key for requester using
GroupCallManager.encryptGroupKeyForParticipant(...). - Send
SignalMessage(type: .send_aes_key, encryptedAESKey: ..., participantId: requesterId, participantDeviceId: requesterDeviceId, callId: currentCallId).
Participant handling¶
In CallSignalingHandler.handleSendAESKey(...):
- Verify message targets current user/device.
- Decrypt
encryptedAESKeyviaGroupCallManager.decryptKeyRotationMessage(...). - Apply immediately:
CallEncryptionManager.setUpAesKey(newKey). - Mark request complete:
markKeyRequestComplete().
Security Notes¶
- The host sends the current session key, encrypted for the requester.
- Public key type is decided by the requester public key (P-256 preferred; RSA fallback).
- Private keys never leave the device.
Related Docs¶
- Key rotation (call folder):
../key-rotation/key-rotation.md - Global security key rotation reference:
../../security/key-rotation.md