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.

Note
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. All credentials stored on the relay are SHA-256 hashed.

Threat model

AssetProtected byNotes
Account accessJWT access token, wl_ API token (Hashed)Used by dashboard, Android app, and CLI for REST calls.
Agent relay accessPer-agent wla_ token (Hashed)Lets exactly one firmware agent authenticate to /wss.
Wake payload plaintextEWSP session keysNever visible to the relay, database, or reverse proxy.
Firmware updatesHTTPS transport, Ed25519 signatureVerified on-agent 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)>"
}
Note
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 agent 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)
FieldValue
Handshake authHMAC-SHA256(master_key, client_random)
Session ID8 random bytes returned in session_ready
CipherXChaCha20-Poly1305 with 24-byte nonce
Replay defencePer-session monotonic seq counters plus replay bitmap
Session lifetimeUp to 24 hours, with idle timeout and auth-failure limits

Token separation

TokenPrefixUsed byPurpose
Account API tokenwl_Dashboard, Android, CLIREST authentication (Hashed on relay).
Agent Relay tokenwla_ESP32 firmwareRelay WebSocket auth (Hashed on relay).
EWSP E2E Secret(none)Client + agent onlyDerive session keys; client-generated.
Warning
Do not provision a wl_… account token onto the ESP32. The agent should store only its per-agent wla_… relay token and the separate EWSP agent_token (E2E 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 wla_… 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.
  • Review firmware serial logs and relay logs together when diagnosing repeated auth failures or replay errors.

Continue reading