Security & Encryption

Learn how WakeLink keeps the relay blind by separating relay authentication from EWSP encryption and by verifying every encrypted packet end to end.

💡Tip

WakeLink is designed so the relay can authenticate and route traffic without learning the plaintext wake command, target MAC address, or the long-term EWSP secret that protects the command payload.

Threat model

AssetProtected byNotes
Account access
JWT access token, wl_ API token, optional TOTP
Used by dashboard, Android app, and CLI for REST calls.
Agent relay access
Per-device wld_ token
Lets exactly one firmware agent authenticate to /wss.
Wake payload plaintext
EWSP session keys
Never visible to the relay, database, or reverse proxy.
Firmware updates
HTTPS transport, optional SHA-256, optional Ed25519 signature
Verified on-device before rebooting into the new image.

Blind relay

The relay only needs enough metadata to forward frames between a logged-in client session and the correct firmware connection. The outer EWSP envelope exposes the negotiated protocol version, an 8-byte session identifier, a monotonic sequence number, and the ciphertext blob. The encrypted inner payload containing commands such as wake, info, or ota is never decrypted server-side.

json
{
"v": "1.0",
"sid": "a1b2c3d4e5f6a7b8",
"seq": 42,
"p": "<base64(ciphertext || 16-byte tag)>"
}
💡Tip

Because the relay is blind, there is no public REST endpoint that directly sends a wake packet. Clients must speak EWSP through the relay to reach an on-LAN firmware agent.

EWSP cryptography

EWSP authenticates the opening handshake with HMAC-SHA256, derives a fresh 32-byte encryption key with HKDF-SHA256, and protects each packet with XChaCha20-Poly1305. Sequence counters and replay bitmaps on the device prevent old ciphertext from being accepted again.

python
master_key = sha256(agent_token_plaintext)
ikm        = master_key + client_random + device_random
salt       = session_id
info       = b"ewsp_enc"
enc_key    = hkdf_sha256(salt=salt, ikm=ikm, info=info, length=32)
FieldValue
Handshake auth
HMAC-SHA256(master_key, client_random)
Session ID
8 random bytes returned in session_ready
Cipher
XChaCha20-Poly1305 with 24-byte nonce
Replay defence
Per-session monotonic seq counters plus replay bitmap
Session lifetime
Up to 24 hours, with idle timeout and auth-failure limits

Token separation

TokenPrefixUsed byPurpose
Account API token
wl_
Dashboard, Android, CLI
REST authentication and account-scoped actions.
Device relay token
wld_
ESP32 firmware
Authenticate one agent to the relay WebSocket.
EWSP agent secret
agent_token
Client + agent only
Derive end-to-end session keys; never sent to the relay.
⚠️Warning

Do not provision a wl_… account token onto the ESP32. The firmware should store only the per-device wld_… relay token and the separate EWSP agent_token secret.

Operational practices

  • Terminate public traffic with HTTPS and WSS, even when you self-host behind nginx.
  • Rotate wl_… account tokens after suspected compromise and rotate wld_… tokens per agent when decommissioning or reprovisioning hardware.
  • Store account tokens in platform credential stores such as Android Keystore or secure CLI environment variables.
  • Require signed OTA payloads for unattended rollouts; the firmware verifies Ed25519 signatures and can also compare SHA-256 checksums.
  • Review firmware serial logs and relay logs together when diagnosing repeated auth failures or replay errors.

Continue reading