Threat Model - Signal Protocol Implementation
Overview
Comprehensive threat modeling of the Signal Protocol Rust/WASM implementation, analyzing adversary capabilities, attack surfaces, and security properties.
Analysis Date: January 2025 Implementation: Rust with WASM bindings Risk Assessment: 🟡 MEDIUM (Functional with specification gaps)
Executive Summary
The Signal Protocol implementation provides strong cryptographic protection against most adversaries but has specification deviations that introduce attack vectors not present in reference implementations.
Key Threat Findings:
- ✅ Protected: Passive network adversaries (strong encryption)
- 🟡 Partial: Active network adversaries (missing AAD enables attacks)
- 🟡 Partial: Malicious peers (missing signed prekey verification)
- ✅ Protected: Compromised endpoints (forward secrecy + PCS)
- 🟡 Partial: Side-channel adversaries (timing leaks possible)
Critical Vulnerabilities:
- Message reordering attacks (AAD not used)
- Man-in-the-middle on X3DH (no signed prekey verification)
- Interoperability failures (non-standard HKDF)
Adversary Model
1. Passive Network Adversary
Capabilities:
- Monitor all network traffic
- Record encrypted messages
- Analyze metadata (size, timing, frequency)
- Perform traffic analysis
- Cannot modify or inject messages
Protection Status:
| Attack | Protection | Evidence |
|---|---|---|
| Decrypt messages | ✅ Protected | AES-256-GCM encryption |
| Derive session keys | ✅ Protected | X25519 ECDH (128-bit security) |
| Break forward secrecy | ✅ Protected | DH ratchet per message direction |
| Identify participants | ⚠️ Metadata visible | Application-layer responsibility |
| Traffic pattern analysis | ⚠️ Possible | Message sizes observable |
| Statistical analysis | ⚠️ Possible | Timing patterns observable |
Risk Level: 🟢 LOW - Core confidentiality protected
Attack Complexity: ~2^128 operations (computationally infeasible)
2. Active Network Adversary
Capabilities:
- All passive capabilities
- Modify messages in transit
- Inject malicious messages
- Drop or delay messages
- Replay old messages
- Perform man-in-the-middle attacks
Protection Status:
| Attack | Protection | Evidence |
|---|---|---|
| Modify ciphertext | ✅ Protected | GCM authentication tag |
| Forge messages | ✅ Protected | Ed25519 signatures (signed prekey) |
| Reorder messages | ❌ Vulnerable | AAD not used |
| Replay messages | 🟡 Partial | Skipped key tracking (limited) |
| Drop messages | ⚠️ Detectable | Application can detect missing messages |
| MITM on X3DH | ❌ Vulnerable | Signed prekey not verified |
| Downgrade attacks | ✅ Protected | Fixed ciphersuite |
Risk Level: 🔴 HIGH - Two critical vulnerabilities
Critical Attack Vectors:
- Message Reordering (enabled by missing AAD)
- X3DH MITM (enabled by no signed prekey verification)
3. Malicious Peer
Capabilities:
- Legitimate participant in protocol
- Can initiate X3DH key agreement
- Can send encrypted messages
- Knows session secrets
- May have compromised keys
- Attempts to exploit implementation bugs
Protection Status:
| Attack | Protection | Evidence |
|---|---|---|
| Decrypt other sessions | ✅ Protected | Per-session keys (isolation) |
| Forge messages from others | ✅ Protected | Ed25519 signatures |
| Impersonate identity | ✅ Protected | Identity key binding |
| Cause denial of service | ⚠️ Limited | Skipped key limit (1000) |
| Exploit implementation bugs | ⚠️ One panic | simple_ecdh panic issue |
| Interoperability attack | ⚠️ Possible | Non-standard HKDF parameters |
Risk Level: 🟡 MEDIUM - Limited attack surface
4. Compromised Endpoint
Capabilities:
- Full access to device memory
- Read current session state
- Read private keys
- Read plaintext messages
- Exfiltrate all current data
- Cannot access other devices
- Cannot access past messages (if deleted)
Protection Status:
| Attack | Protection | Evidence |
|---|---|---|
| Past messages | ✅ Protected | Forward secrecy (DH ratchet) |
| Current messages | ❌ No protection | Expected behavior |
| Future messages | ✅ Protected | Post-compromise security (PCS) |
| Identity key theft | ❌ No protection | Expected behavior |
| Session key theft | ❌ No protection | Expected behavior |
| Recovery after compromise | ✅ Automatic | Next DH ratchet step heals |
Risk Level: 🟡 MEDIUM - Inherent to end-to-end encryption
Time Window:
- Past messages: ✅ Safe (deleted message keys not recoverable)
- Current epoch: ❌ Compromised
- After next DH ratchet: ✅ Recovered (PCS property)
Recovery Time: Single message round-trip (immediate PCS)
5. Side-Channel Adversary
Capabilities:
- Measure operation timing
- Analyze power consumption (physical access)
- Monitor cache behavior
- Analyze memory access patterns
- Exploit CPU microarchitectural features
Protection Status:
| Attack | Protection | Evidence |
|---|---|---|
| Timing attacks on crypto | ✅ Protected | dalek constant-time libs |
| Timing attacks on comparisons | ⚠️ Vulnerable | Non-constant-time == |
| Power analysis | 🟡 Partial | Platform dependent |
| Cache timing | ⚠️ Vulnerable | HashMap lookups |
| Spectre/Meltdown | ✅ Protected | Browser mitigations |
| Branch prediction | 🟡 Partial | dalek protects crypto |
Risk Level: 🟡 MEDIUM - Practical attacks difficult but possible
Attack Complexity: High (requires physical/local access + sophisticated tools)
Attack Surface Analysis
External Input Points
| Entry Point | Risk | Validation | Issues |
|---|---|---|---|
x3dh_initiate() | 🔴 High | ⚠️ Partial | No signed prekey verify |
x3dh_receive() | 🔴 High | ⚠️ Partial | No signed prekey verify |
ratchet_encrypt() | 🟡 Medium | ✅ Good | AAD not used |
ratchet_decrypt() | 🟡 Medium | ✅ Good | AAD not used |
deserialize_*() | 🟡 Medium | ✅ Good | Serde validation |
Total Attack Surface: 5 major input points
Validation Quality:
- ✅ Length validation present
- ✅ Type validation via serde
- ❌ Cryptographic validation incomplete (signatures not checked)
- ⚠️ AAD missing (metadata not authenticated)
Attack Scenarios & Mitigations
Scenario 1: Message Reordering Attack
Severity: 🔴 CRITICAL
Enabled By: AAD not used in AES-GCM encryption
Attack Flow:
1. Alice sends messages: M1(seq=1), M2(seq=2), M3(seq=3)
2. Attacker intercepts all three messages
3. Attacker delivers in wrong order: M2, M1, M3
4. Bob decrypts successfully (no binding to sequence)
5. Application processes messages out-of-order
6. Potential security bypass depending on application logic
Current Protection: ❌ None
Impact:
- HIGH: Commands executed out-of-order
- State machine confusion possible
- Access control bypass in applications
- Data corruption if order matters
Example Attack:
Message 1: "Transfer $100 to account A"
Message 2: "Transfer $100 to account B"
Message 3: "Cancel previous transfer"
Reordered:
Message 2, Message 3, Message 1
→ Result: Transfer to B cancelled, transfer to A executes
Mitigation:
// Add AAD with message metadata
let mut aad = Vec::new();
aad.extend_from_slice(&message_number.to_le_bytes());
aad.extend_from_slice(&prev_chain_length.to_le_bytes());
let payload = Payload {
msg: plaintext,
aad: &aad,
};
cipher.encrypt(nonce, payload)?;
Effort: 4-6 hours Priority: P0 (critical)
Scenario 2: Man-in-the-Middle on X3DH
Severity: 🔴 CRITICAL
Enabled By: Signed prekey signature never verified
Attack Flow:
1. Alice requests Bob's prekey bundle
2. Server (or MITM) substitutes Bob's signed prekey with attacker's
3. Alice completes X3DH with attacker's key (doesn't verify signature)
4. Attacker completes X3DH with Bob using Bob's real key
5. Attacker acts as transparent proxy, decrypting all messages
Current Protection: ❌ None
Impact:
- CRITICAL: Complete loss of confidentiality
- No authentication of Bob's identity
- Attacker can read/modify all messages
- Defeats purpose of end-to-end encryption
Signal Specification Requirement:
"The client MUST verify the signature on the signed prekey before using it"
Mitigation:
pub fn x3dh_initiate(
alice_identity: &IdentityKeyPair,
alice_ephemeral: &EphemeralKeyPair,
bob_identity_public: &[u8],
bob_signed_prekey_public: &[u8],
bob_signed_prekey_signature: &[u8], // Add parameter
bob_onetime_prekey_public: Option<&[u8]>,
) -> Result<X3DHResult, SignalError> {
// ✅ CRITICAL: Verify signature before using key
if !ed25519_verify(
bob_identity_public,
bob_signed_prekey_public,
bob_signed_prekey_signature
)? {
return Err(SignalError::InvalidSignature);
}
// Rest of X3DH...
}
Effort: 1-2 hours Priority: P0 (critical)
Scenario 3: Replay Attack
Severity: 🟡 MEDIUM
Enabled By: Limited replay protection
Attack Flow:
1. Attacker captures encrypted message M
2. Days/weeks later, attacker replays M
3. Receiver attempts decryption
4. Success depends on key state:
- If in-order: Message already processed, key deleted → fails ✅
- If out-of-order: Key might still be in skipped_keys → succeeds ❌
Current Protection: 🟡 Partial
- In-order messages: Protected (key deleted after use)
- Out-of-order messages: Window of ~1000 messages
Impact:
- MEDIUM: Old messages could be replayed within window
- Application may process duplicate messages
- Depends on application-level duplicate detection
Mitigation:
// Application-level sequence tracking
struct SessionState {
highest_received_seq: u32,
received_messages: HashSet<u32>,
}
fn validate_message_number(seq: u32, state: &SessionState) -> bool {
if received_messages.contains(&seq) {
return false; // Replay detected
}
true
}
Effort: Application responsibility Priority: P2 (application layer)
Scenario 4: Interoperability Attack
Severity: 🟡 MEDIUM
Enabled By: Non-standard HKDF parameters
Attack Flow:
1. User Alice uses this implementation
2. User Bob uses official Signal app (libsignal)
3. Alice and Bob attempt X3DH key agreement
4. Different HKDF derivations produce different shared secrets
5. Session establishment fails
6. Or worse: Appears to succeed but messages undecryptable
Current Protection: ❌ None
Impact:
- MEDIUM: Cannot communicate with standard Signal apps
- Isolated ecosystem (vendor lock-in)
- Potential confusion/misconfiguration attacks
- Users may think they have E2EE when they don't
Mitigation:
// Use EXACT Signal specification parameters
fn derive_x3dh_secret(dh1: &[u8], dh2: &[u8], dh3: &[u8], dh4: Option<&[u8]>) -> Vec<u8> {
let mut km = Vec::new();
km.extend_from_slice(&[0xFF; 32]); // F (padding)
km.extend_from_slice(dh1);
km.extend_from_slice(dh2);
km.extend_from_slice(dh3);
if let Some(dh4) = dh4 {
km.extend_from_slice(dh4);
}
// Standard Signal HKDF usage
let hk = Hkdf::<Sha256>::new(None, &km); // No salt
let mut output = vec![0u8; 32];
hk.expand(b"WhisperText", &mut output).expect("HKDF failed");
output
}
Effort: 2-3 hours Priority: P1 (high)
Scenario 5: Denial of Service via Skipped Keys
Severity: 🟡 MEDIUM
Enabled By: Bounded but potentially abusable skipped key storage
Attack Flow:
1. Attacker sends message with seq=1
2. Attacker sends message with seq=1001
3. Victim stores 1000 skipped keys (hits MAX_SKIP)
4. Attacker repeats, filling memory
5. Legitimate out-of-order messages may be rejected
Current Protection: ✅ Present
- MAX_SKIP = 1000 (bounded)
- Old keys eventually evicted
Impact:
- LOW: Limited memory exhaustion (bounded)
- Potential rejection of legitimate messages
- Annoyance but not catastrophic
Mitigation:
- ✅ Already implemented: MAX_SKIP limit
- Could add: Per-peer rate limiting
- Could add: Adaptive skip window
Priority: P3 (low - already mitigated)
Scenario 6: Timing Side-Channel Attack
Severity: 🟡 MEDIUM
Enabled By: Non-constant-time comparison operations
Attack Flow:
1. Attacker with local access measures decryption timing
2. Timing varies based on:
- Message found in skipped_keys vs. not
- Error types (different codepaths)
- Success vs. failure branches
3. Over many samples, timing leaks information
4. Attacker infers message patterns or state
Current Protection: 🟡 Partial
- Core crypto operations constant-time (dalek)
- Application logic not constant-time
Impact:
- LOW-MEDIUM: Requires local access
- Practical exploitation difficult
- Information leakage limited
Mitigation:
use subtle::ConstantTimeEq;
// Replace regular comparisons
if key1 == key2 { // ⚠️ Not constant-time
// ...
}
// With constant-time comparisons
if bool::from(key1.ct_eq(&key2)) { // ✅ Constant-time
// ...
}
Effort: 2-3 hours Priority: P2 (medium)
Security Properties Matrix
| Property | Passive Net | Active Net | Malicious Peer | Compromised Device | Side-Channel |
|---|---|---|---|---|---|
| Confidentiality | ✅ Protected | ✅ Protected | ✅ Protected | ❌ No protection | ✅ Protected |
| Integrity | ✅ Protected | ⚠️ Partial | ✅ Protected | ❌ No protection | ✅ Protected |
| Authentication | ✅ Protected | ❌ Vulnerable | ✅ Protected | ❌ No protection | ✅ Protected |
| Forward Secrecy | ✅ Protected | ✅ Protected | ✅ Protected | ✅ Protected | ✅ Protected |
| PCS | ✅ Protected | ✅ Protected | ✅ Protected | ✅ Protected | ✅ Protected |
| Message Ordering | N/A | ❌ Vulnerable | ❌ Vulnerable | N/A | N/A |
| Replay Protection | N/A | 🟡 Partial | 🟡 Partial | N/A | N/A |
| Deniability | ✅ Protected | ✅ Protected | ✅ Protected | ✅ Protected | ✅ Protected |
Risk Assessment Summary
By Adversary Type
| Adversary | Risk Level | Primary Threats | Status |
|---|---|---|---|
| Passive Network | 🟢 LOW | Traffic analysis | Acceptable |
| Active Network | 🔴 HIGH | MITM, reordering | Requires fixes |
| Malicious Peer | 🟡 MEDIUM | DoS, exploits | Acceptable |
| Compromised Device | 🟡 MEDIUM | Current keys | Inherent limitation |
| Side-Channel | 🟡 MEDIUM | Timing leaks | Monitoring needed |
By Attack Category
| Attack Type | Likelihood | Impact | Risk | Priority |
|---|---|---|---|---|
| Message Reordering | High | High | 🔴 CRITICAL | P0 |
| X3DH MITM | Medium | Critical | 🔴 CRITICAL | P0 |
| Replay Attack | Low | Medium | 🟡 MEDIUM | P2 |
| Interoperability Failure | High | Medium | 🟡 MEDIUM | P1 |
| DoS (Skipped Keys) | Low | Low | 🟢 LOW | P3 |
| Timing Attacks | Low | Medium | 🟡 MEDIUM | P2 |
Defense in Depth
Layer 1: Cryptographic (✅ STRONG)
- X25519 ECDH key exchange
- Ed25519 digital signatures
- AES-256-GCM authenticated encryption
- HKDF-SHA256 key derivation
- OS CSRNG for random numbers
Layer 2: Protocol (🟡 MODERATE)
- X3DH for asynchronous key agreement
- Double Ratchet for forward secrecy + PCS
- Out-of-order message handling
- ⚠️ Missing: AAD in encryption
- ⚠️ Missing: Signed prekey verification
Layer 3: Implementation (✅ STRONG)
- ✅ Rust memory safety
- ✅ Zero unsafe blocks
- ✅ WASM sandboxing
- ✅ Type safety across boundaries
- ⚠️ One panic issue
Layer 4: Operational (⚠️ PARTIAL)
- ⚠️ No rate limiting
- ⚠️ No monitoring/alerting
- ⚠️ No audit logging
- ⚠️ Limited error messages (good for security)
Recommendations
Critical (P0) - Address Immediately
-
Implement AAD in AES-GCM encryption
- Prevents message reordering attacks
- Effort: 4-6 hours
-
Add signed prekey verification in X3DH
- Prevents MITM attacks
- Effort: 1-2 hours
High (P1) - Within 2 Weeks
-
Fix HKDF parameters for interoperability
- Use Signal specification exactly
- WARNING: Breaks existing sessions
- Effort: 6-8 hours
-
Add constant-time comparisons
- Use
subtlecrate for sensitive data - Prevents timing side-channels
- Effort: 2-3 hours
- Use
Medium (P2) - Within 1 Month
-
Fix simple_ecdh panic issue
- Validate lengths before copy
- Effort: 15 minutes
-
Add application-level replay protection
- Track sequence numbers
- Effort: Application responsibility
Low (P3) - Nice to Have
-
Add monitoring and alerting
- Track attack attempts
- Effort: 1-2 days
-
Add audit logging
- Security events only (no secrets)
- Effort: 1-2 days
Comparison with MLS Threat Model
| Aspect | Signal Protocol | MLS Protocol |
|---|---|---|
| Group size | 1:1 optimal | Multi-party optimal |
| Active adversary risk | 🔴 HIGH (2 critical issues) | 🔴 HIGH (different issues) |
| Replay protection | 🟡 Partial | ❌ None |
| Message ordering | ❌ Not enforced | ❌ Not enforced |
| Forward secrecy | ✅ Per-message | ✅ Per-epoch |
| PCS | ✅ Immediate | ✅ Next commit |
| Implementation quality | 🟡 Specification gaps | 🟡 Missing safeguards |
Common Issues:
- Both lack proper replay protection
- Both have specification compliance gaps
- Both need operational security improvements
Unique to Signal:
- Missing signed prekey verification
- Missing AAD in encryption
Unique to MLS:
- No input validation
- Extensive debug logging
Conclusion
Overall Threat Risk: 🟡 MEDIUM-HIGH
Key Findings:
- ✅ Strong cryptographic foundation
- ✅ Excellent memory safety (Rust)
- ❌ Two critical protocol-level vulnerabilities
- ⚠️ Specification deviations affect security
Attack Surface:
- External: 5 input points with partial validation
- Internal: 1 panic vulnerability
- Cryptographic: Solid (no weaknesses)
- Protocol: Gaps in specification compliance
Biggest Risks:
- Message reordering attacks (AAD not used)
- X3DH man-in-the-middle (no signature verification)
- Interoperability failures (non-standard HKDF)
Recommendation: Address P0 issues before production deployment. Current implementation is functional but vulnerable to active adversaries exploiting specification deviations.
Time to Secure: ~8-12 hours of development to fix critical issues
Document Version: 1.0 Last Updated: January 2025 Next Review: After P0/P1 fixes implemented