Gal Ratner

WhiteStar Messenger: Technical Whitepaper

Version 1.0 | April 2026 Published by Inverted Software


Abstract

WhiteStar Messenger is a production-grade, end-to-end encrypted instant messaging platform built for enterprise environments. It implements the Signal Protocol for message encryption, WebRTC for peer-to-peer voice and video calling, and a scalable real-time delivery infrastructure powered by ASP.NET Core, SignalR, and Redis. The system achieves complete server-side blindness: encrypted message payloads are opaque to the server, which functions purely as a routing and queuing layer. This whitepaper details every layer of the architecture, from cryptographic primitives to deployment topology.


1. System Overview

WhiteStar Messenger consists of four major components:

  1. SignalMessenger.Protocol – A standalone .NET library implementing the Signal Protocol (X3DH key exchange + Double Ratchet algorithm) with no server dependencies.
  2. SignalMessenger.Server – An ASP.NET Core backend providing REST APIs, SignalR real-time hub, Redis-backed presence and message queuing, and SQL Server persistent storage.
  3. SignalMessenger.Web.Client – A browser-based client using vanilla JavaScript with direct SignalR connections for real-time messaging.
  4. SignalMessenger.App – A .NET MAUI application targeting Windows and Android, using HybridWebView to host a shared JavaScript UI with native platform capabilities.

All communication between clients and server uses TLS. Message content is always encrypted end-to-end before leaving the sender’s device.


2. Signal Protocol Implementation

2.1 Cryptographic Primitives

The protocol layer uses the following algorithms:

Algorithm Purpose Key/Output Size Standard
X25519 Elliptic-curve Diffie-Hellman 32-byte keys, 32-byte shared secret RFC 7748
Ed25519 Digital signatures 32-byte keys, 64-byte signatures RFC 8032
HKDF-SHA256 Key derivation Variable output RFC 5869
HMAC-SHA256 Message authentication 32 bytes (truncated to 8 for wire format) RFC 2104
AES-256-CBC Symmetric encryption (Double Ratchet) 32-byte key, 16-byte IV NIST
AES-256-GCM Authenticated encryption (transport envelope) 32-byte key, 12-byte nonce, 16-byte tag NIST

Ed25519 keys are converted to X25519 form for Diffie-Hellman operations using the standard Montgomery mapping: u = (1 + y) / (1 - y) mod p where p = 2^255 - 19.

2.2 Key Hierarchy

Each user maintains the following key material, all generated and stored exclusively on their device:

2.3 X3DH Key Exchange (Extended Triple Diffie-Hellman)

Session establishment follows the X3DH protocol. When Alice wants to message Bob for the first time:

  1. Alice fetches Bob’s Pre-Key Bundle from the server, which contains Bob’s identity key, current signed pre-key (with signature), and optionally one one-time pre-key.
  2. Alice verifies the signed pre-key signature using Bob’s Ed25519 identity key. If verification fails, the session is rejected.
  3. Alice generates a fresh ephemeral X25519 keypair (EK_A).
  4. Alice computes four Diffie-Hellman operations:
    • DH1 = X25519(IK_A, SPK_B) – Alice’s identity x Bob’s signed pre-key
    • DH2 = X25519(EK_A, IK_B) – Alice’s ephemeral x Bob’s identity
    • DH3 = X25519(EK_A, SPK_B) – Alice’s ephemeral x Bob’s signed pre-key
    • DH4 = X25519(EK_A, OPK_B) – Alice’s ephemeral x Bob’s one-time pre-key (if available)
  5. The shared secret is derived using HKDF-SHA256:
    • Input key material: 0xFF^32 || DH1 || DH2 || DH3 [|| DH4]
    • Salt: 32 zero bytes
    • Info string: "WhisperText"
    • Output: 32-byte session key (Root Key for Double Ratchet)
  6. Associated data for subsequent MAC operations: AD = IK_A_public || IK_B_public (64 bytes)

The one-time pre-key (DH4) is consumed atomically on the server – once fetched, it is deleted and cannot be reused. This provides forward secrecy even if the signed pre-key is later compromised.

2.4 Double Ratchet Algorithm

After X3DH establishes a shared secret, all subsequent messages use the Double Ratchet for forward secrecy and break-in recovery.

Ratchet State consists of:

Symmetric Ratchet (per message):

Each message derives unique keys from the current chain key using HMAC-SHA256:

This ensures that compromising one message key does not reveal any other message key.

DH Ratchet (per round-trip):

When a new DH public key is received from the peer, a DH ratchet step occurs:

  1. Compute DH shared secret with the new peer key
  2. Derive new Root Key and Receiver Chain Key from HKDF(root_key, dh_output)
  3. Generate a new sender DH keypair
  4. Compute another DH shared secret with the new sender key
  5. Derive new Root Key and Sender Chain Key

This provides break-in recovery: even if an attacker compromises the current state, future messages are protected by new DH operations.

Message Authentication:

Each encrypted message carries an 8-byte MAC computed as HMAC-SHA256 over: associated_data || ratchet_public_key || counter || previous_counter || ciphertext, truncated to 8 bytes. MAC verification uses constant-time comparison to prevent timing attacks.

Out-of-Order Message Handling:

When messages arrive out of order (e.g., due to network reordering), the receiver stores skipped message keys in a bounded cache (maximum 1,000 entries). When the expected counter does not match, the receiver advances the chain and caches intermediate keys for later use.

2.5 Wire Format

SignalMessage (in-session, Double Ratchet encrypted):

Version (1 byte, value: 3)
Ratchet Public Key (32 bytes, X25519)
Counter (uint32, little-endian)
Previous Counter (uint32, little-endian)
Ciphertext (variable, AES-256-CBC output)
MAC (8 bytes, truncated HMAC-SHA256)
Timestamp (int64, Unix milliseconds)

PreKeySignalMessage (session establishment):

Version (1 byte, value: 3)
Registration ID (uint32)
Pre-Key ID (optional uint32)
Signed Pre-Key ID (uint32)
Base Key (32 bytes, Alice's ephemeral X25519 public)
Identity Key (32 bytes, Alice's Ed25519 public)
Nested SignalMessage (the first encrypted message)

MessageEnvelope (transport wrapper):

Id: GUID string
SenderAddress: string (username)
RecipientAddress: string (username)
Type: integer (1=PreKey, 2=Signal, 3=Receipt, 4=Typing, 5=Plaintext)
Payload: byte[] (AES-256-GCM encrypted serialized inner message)
Timestamp: int64 (Unix milliseconds)

The server sees only the MessageEnvelope. The Payload field is opaque – the server cannot decrypt it.

2.6 Key Management

Pre-Key Replenishment:

Clients monitor their one-time pre-key count via the REST API. When the count drops below 20, the client generates a fresh batch (typically 100 keys) and uploads them. The server tracks remaining count and signals replenishment need.

Signed Pre-Key Rotation:

Clients periodically generate and upload new signed pre-keys. The server marks the previous key as non-current. A background maintenance service deletes non-current signed pre-keys older than 90 days.

Identity Persistence:

Identity keys are stored in a local SQLite database (MAUI) or localStorage (web). The private key never leaves the device. On first contact with a peer, their identity key is recorded (Trust On First Use). Subsequent sessions verify the peer’s identity matches the stored key; mismatches generate a security alert.


3. Server Architecture

3.1 Technology Stack

3.2 Authentication

Registration creates a new user with username, email, display name, and password. The client generates an Ed25519 identity keypair and uploads the public keys. The server stores password hashes using ASP.NET Identity’s PBKDF2 implementation. Password requirements: minimum 10 characters, must include uppercase, digit, and non-alphanumeric character.

Login validates credentials and returns a JWT containing claims for user ID (NameIdentifier), username (Name), display name, and a unique token ID (Jti). Tokens are signed with HMAC-SHA256. Account lockout activates after 5 failed attempts, locking the account for 10 minutes.

Password Recovery uses a two-stage flow: an 8-character alphanumeric code (excluding ambiguous characters 0/O, 1/I/L) is emailed to the user with a 15-minute TTL. After code verification, a 32-byte random reset token (10-minute TTL, single-use) is issued for the actual password change.

3.3 Real-Time Messaging Hub

The SignalR hub handles all real-time operations with the following configuration:

Connection Lifecycle:

On connect, the server registers the user’s SignalR connection ID in a Redis set (presence:{userId}), sets a short-lived online indicator (online:{userId} with 61-second TTL), flushes any pending offline messages, and broadcasts online status to all accepted contacts.

On disconnect, the connection ID is removed. If no connections remain, all presence keys are deleted and offline status is broadcast.

Message Routing:

When a message is sent, the server resolves the recipient username to a user ID, checks the bidirectional block list, and attempts live delivery via SignalR. If the recipient is offline or delivery fails, the message is enqueued in Redis with a 24-hour TTL. The sender receives a delivery status callback (“delivered” or “queued”) with timestamp.

Call Signaling:

The hub provides secure methods for WebRTC call signaling: SendCallOffer, SendCallAnswer, SendIceCandidate, SendCallHangup, SendCallBusy, and SendCallRinging. Every call signaling method:

3.4 Presence System

Presence is tracked entirely in Redis with three keys per user:

Key Pattern Value TTL Purpose
presence:{userId} Set of connection IDs 5 minutes Message routing
online:{userId} “1” 61 seconds Quick online/offline detection
callcap:{userId} “audio,video” 61 seconds Call capability advertisement

Clients send a combined heartbeat every 50 seconds that refreshes all three keys in a single Redis batch operation. If a client stops sending heartbeats (crash, network loss), the online indicator expires after 61 seconds and a background monitor broadcasts offline status to contacts.

A dedicated PresenceMonitorService runs every 10 seconds to detect expired online indicators and broadcast status changes.

3.5 Offline Message Queue

Messages for offline recipients are stored in Redis:

When the recipient connects, all pending messages are flushed in chronological order. The client acknowledges each message, which triggers immediate deletion from Redis. Unacknowledged messages persist until the 24-hour TTL expires.

3.6 Rate Limiting

All endpoints are protected by fixed-window rate limiters (per IP address, no request queuing):

Endpoint Category Limit Window
Authentication (login, register, password reset) 10 requests 1 minute
Key management (pre-key upload) 30 requests 1 minute
File upload 10 requests 1 minute
General API (search, connections, profile) 60 requests 1 minute

Exceeded limits return HTTP 429 (Too Many Requests).

3.7 Security Headers

All responses include:


4. WebRTC Voice and Video Calling

4.1 Architecture

Calls are peer-to-peer using WebRTC. The server acts only as a signaling relay – call media (audio/video) flows directly between participants via DTLS-SRTP encrypted channels. The server never processes, records, or stores call media.

4.2 ICE Configuration

STUN servers are used for NAT traversal. The client fetches ICE server configuration from the backend (which can inject TURN credentials), with a fallback to public Google STUN servers. Configuration is cached for 5 minutes.

4.3 Media Parameters

Audio:

Video:

Adaptive Bitrate:

A quality monitor runs every 5 seconds, checking packet loss statistics from RTCStatsReport. When packet loss exceeds 8%, the video bitrate is reduced. When loss drops below 2%, bitrate is restored. This prevents video degradation on constrained networks.

4.4 Call Flow

  1. Caller selects audio or video call from the chat interface
  2. Client detects media devices and requests permissions
  3. getUserMedia acquires local media streams
  4. SDP offer is created and sent via SignalR to the recipient
  5. Recipient’s device rings (synthesized two-tone via Web Audio API, 440+480 Hz)
  6. If accepted: SDP answer is returned, ICE candidates trickle between peers
  7. RTCPeerConnection establishes DTLS-SRTP encrypted media channel
  8. Call connects within 30 seconds or times out
  9. Either party can end the call at any time

4.5 Call Capabilities

Each client advertises its call capabilities (none, audio, video, or audio+video) as part of the heartbeat. Contacts see available call types in the chat header and can only initiate supported call types.


5. File Transfer

5.1 Upload Flow

Files are uploaded via a dedicated REST endpoint (POST /api/files/upload) using multipart/form-data. The upload is separate from the message system – only a file reference (ID, name, size, content type) is sent as a message through the encrypted channel.

Maximum file size: 20 MB. Files are stored in a temporary server directory with a 6-hour expiry. A background cleanup service runs every 30 minutes to delete expired files and their database records.

5.2 Security Validation

Every uploaded file passes through four layers of validation:

  1. Extension Blacklist: Blocks 40+ dangerous extensions including executables (.exe, .dll, .com, .scr), scripts (.bat, .ps1, .vbs, .sh, .js), macro-enabled Office documents (.docm, .xlsm, .pptm), and risky archives (.iso, .img, .cab).
  2. Double Extension Detection: Rejects files like photo.jpg.exe by checking every extension segment.
  3. MIME Type Blacklist: Blocks application/x-msdownload, application/x-executable, application/x-dosexec, and application/x-msi.
  4. Magic Byte Inspection: Reads the first 8 bytes to detect renamed executables – catches Windows PE headers (MZ / 0x4D5A), Linux ELF headers (0x7F454C46), and shell script shebangs (#!).

Filenames are sanitized by stripping all characters except letters, digits, dots, underscores, and hyphens.

5.3 Access Control

Only the sender and designated recipient can download a file. The server verifies the requesting user’s identity against the file’s sender and recipient fields before serving the file.


6. Cross-Platform Architecture

6.1 MAUI HybridWebView Bridge

The MAUI application uses HybridWebView to host a JavaScript-based UI identical to the web client. Communication between JavaScript and .NET uses a bidirectional bridge:

JavaScript to .NET: bridge.invoke('namespace.action', { payload }) serializes a JSON message and sends it via HybridWebView.SendRawMessage. The .NET side deserializes, routes to the appropriate handler, and returns a JSON response via EvaluateJavaScriptAsync.

NET to JavaScript: Push events (incoming messages, call offers, presence changes) are forwarded from SignalR event handlers through EvaluateJavaScriptAsync, which invokes a window.__bridge_message_handler function that dispatches events to registered JS listeners.

6.2 Shared Code Strategy

The JavaScript UI layer is shared between web and MAUI with one critical exception:

6.3 Windows System Tray

On Windows, the application supports background operation. When the user closes the window, the app minimizes to the system tray rather than exiting. A native Win32 context menu (via TrackPopupMenuEx) provides “Show” and “Exit” options. The SignalR connection remains active, and Windows toast notifications alert the user to new messages while the window is hidden. This behavior is controlled by a user-facing setting (“Keep Running in Background”) that defaults to enabled.

6.4 Android Background Service

On Android, a foreground service keeps the SignalR connection alive when the app is backgrounded. Android 13+ runtime permissions for notifications are requested after login.


7. Data Model

7.1 Persistent Storage (SQL Server)

Entity Key Fields Purpose
ApplicationUser Id (GUID), UserName, IdentityKeyHex, DhIdentityKeyHex User identity and profile
SignedPreKeyRecord UserId, KeyId, PublicKeyHex, SignatureHex, IsCurrent Medium-term DH key with signature
OneTimePreKeyRecord UserId, KeyId, PublicKeyHex Single-use DH keys
ConnectionRequest SenderId, ReceiverId, Status (Pending/Accepted/Rejected) Contact relationships
BlockedUser BlockerId, BlockedId Bidirectional block list
FileTransfer Id (GUID), SenderId, RecipientId, StoragePath, ExpiresAt Temporary file metadata
PasswordResetCode UserId, Code, Purpose, ExpiresAt, IsUsed Password recovery flow

7.2 Ephemeral Storage (Redis)

Key Pattern Value Type TTL Purpose
presence:{userId} Set of connection IDs 5 min SignalR routing
online:{userId} String “1” 61 sec Online status
callcap:{userId} String (capabilities) 61 sec Call capability advertisement
pending:{userId}:{msgId} JSON (MessageEnvelope) 24 hrs Offline message queue
pending:{userId}:index Sorted set (timestamps) 24 hrs Message ordering

8. Threat Model and Mitigations

8.1 Server Compromise

Threat: An attacker gains access to the server.

Mitigation: The server never possesses message plaintext. All message payloads are AES-256-GCM encrypted envelopes containing Double Ratchet ciphertext. Even with full server access, an attacker can only observe message metadata (sender, recipient, timestamp, size) but cannot read content. Private keys exist only on client devices.

8.2 Man-in-the-Middle

Threat: An attacker intercepts the X3DH key exchange.

Mitigation: Signed pre-keys carry Ed25519 signatures verified against the peer’s known identity key. Safety numbers (derived from both identity keys) can be verified out-of-band. Identity key changes trigger alerts (Trust On First Use model).

8.3 Key Compromise

Threat: An attacker obtains a user’s current session state.

Mitigation: Forward secrecy ensures past messages remain secure (each message uses a unique derived key that is deleted after use). Break-in recovery ensures future messages become secure after the next DH ratchet step (typically the next message round-trip).

8.4 Replay Attacks

Threat: An attacker resends previously captured messages.

Mitigation: Double Ratchet counters prevent replay – each message has a unique (ratchet key, counter) pair. One-time pre-keys are consumed atomically and cannot be reused. The skipped-key cache is bounded to 1,000 entries to limit resource exhaustion.

8.5 Brute-Force Authentication

Threat: An attacker attempts to guess user passwords.

Mitigation: Passwords require minimum 10 characters with complexity (uppercase, digit, non-alphanumeric). Account lockout activates after 5 failed attempts (10-minute duration). Rate limiting restricts authentication endpoints to 10 requests per minute per IP. The web client enforces reCAPTCHA v3 on login and registration.

8.6 Malicious File Upload

Threat: An attacker uploads executable files to compromise recipients.

Mitigation: Four-layer validation: extension blacklist (40+ types), MIME type blacklist, double-extension detection, and magic byte inspection (PE, ELF, shebang headers). Files expire after 6 hours with automatic cleanup.

8.7 Call Eavesdropping

Threat: An attacker intercepts voice/video call media.

Mitigation: WebRTC enforces DTLS-SRTP encryption on all media channels. Call media flows peer-to-peer and never passes through the server. ICE connectivity checks validate endpoint authenticity.


9. Deployment Architecture

                        +------------------+
                        |   Load Balancer  |
                        |   (TLS Termination)|
                        +--------+---------+
                                 |
                    +------------+------------+
                    |                         |
          +---------+--------+    +-----------+---------+
          |  Web Client      |    |  API Server         |
          |  (im.whitestar   |    |  (imserver.whitestar|
          |   labs.com)      |    |   labs.com)         |
          |                  |    |                     |
          |  Static HTML/JS  |    |  ASP.NET Core       |
          |  Reverse proxy   |    |  SignalR Hub        |
          |  for /api/*      |    |  REST Controllers   |
          +------------------+    +----+-----+-----+----+
                                       |     |     |
                              +--------+  +--+--+  +--------+
                              |           |     |           |
                         +----+----+ +----+----+ +----+-----+
                         |SQL Server| |  Redis  | | File     |
                         |         | | Backplane| | Storage  |
                         | Users   | | Presence | | (Temp)   |
                         | Keys    | | Queues   | |          |
                         | Contacts| | PubSub   | |          |
                         +---------+ +---------+ +----------+

Direct SignalR Connection: The web client connects its SignalR WebSocket directly to the API server, bypassing the web client’s reverse proxy. This avoids WebSocket proxy issues with IIS and ensures reliable real-time delivery.

Horizontal Scaling: The Redis backplane enables multiple API server instances. All presence, message queuing, and pub/sub operations go through Redis. API controllers are stateless. SignalR messages are broadcast across all nodes via Redis channels.


10. Configuration Reference

Parameter Value Description
X25519/Ed25519 key size 32 bytes Asymmetric key length
AES-256-GCM nonce 12 bytes Transport encryption nonce
AES-256-GCM tag 16 bytes Authentication tag
AES-256-CBC IV 16 bytes Double Ratchet encryption IV
HMAC truncation 8 bytes Wire-format MAC length
Max skipped message keys 1,000 Out-of-order tolerance
Offline message TTL 24 hours Redis queue expiry
Presence set TTL 5 minutes Connection routing
Online indicator TTL 61 seconds Quick offline detection
Heartbeat interval 50 seconds Client-side
File upload max size 20 MB Per-file limit
File storage TTL 6 hours Auto-cleanup
Password min length 10 characters With complexity requirements
Account lockout 10 minutes after 5 failures Brute-force protection
JWT expiry 10 years Long-lived session
SignalR message limit 64 KB Per-message maximum
SignalR keep-alive 15 seconds Connection health
Video max bitrate 500 kbps Adaptive ceiling
Audio max bitrate 32 kbps Opus speech quality
Video resolution 640x480 SD quality
Video frame rate 24 fps Smooth motion
Audio sample rate 16 kHz mono Speech-optimized
Call ring timeout 30 seconds Before auto-decline
Signed pre-key max age 90 days Server-side cleanup
OTK replenishment threshold 20 keys Client-driven

11. Technology Stack

Layer Technology
Protocol Custom Signal Protocol implementation (.NET)
Backend ASP.NET Core (.NET 10)
Database SQL Server + Entity Framework Core
Real-time SignalR + Redis backplane
Cache/Queue Redis (StackExchange.Redis)
Authentication ASP.NET Identity + JWT (HS256)
Cryptography .NET BCL (AES, HMAC, HKDF) + Curve25519/Ed25519
Web Client Vanilla JavaScript, HTML5, CSS3
Desktop/Mobile .NET MAUI with HybridWebView
Voice/Video WebRTC (RTCPeerConnection, VP8/Opus)
System Tray H.NotifyIcon.WinUI + Win32 API
Bot Protection Google reCAPTCHA v3 (web only)

12. Conclusion

WhiteStar Messenger demonstrates that enterprise-grade encrypted messaging can be achieved with modern .NET technologies while maintaining complete server-side blindness. The Signal Protocol provides mathematically proven forward secrecy and break-in recovery. The Redis-backed infrastructure ensures horizontal scalability and reliable offline message delivery. WebRTC enables peer-to-peer calling without server-side media processing. The cross-platform MAUI HybridWebView architecture maximizes code reuse while preserving native platform capabilities.

The system is designed for organizations that require private, auditable, and scalable communication infrastructure without compromising on usability or security guarantees.


WhiteStar Messenger is developed and maintained by Inverted Software. For more information, visit https://whitestarlabs.com