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.
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
| Asset | Protected by | Notes |
|---|---|---|
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.
{
"v": "1.0",
"sid": "a1b2c3d4e5f6a7b8",
"seq": 42,
"p": "<base64(ciphertext || 16-byte tag)>"
}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.
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)| Field | Value |
|---|---|
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
| Token | Prefix | Used by | Purpose |
|---|---|---|---|
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. |
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 rotatewld_…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.