sequenceDiagram
participant C as Client
participant S as Server (UDP listener)
participant FW as Firewall
Note over C: 1. Generate ephemeral Curve25519 keypair
Note over C: 2. ECDH(ephemeral_priv, server_pub) → shared_key
Note over C: 3. Build plaintext: timestamp‖random_nonce‖target_ip‖port
Note over C: 4. Encrypt plaintext → ciphertext (ChaCha20-Poly1305)
Note over C: 5. Assemble packet: version‖ephem_pub‖nonce‖ciphertext
Note over C: 6. Sign packet[0:101] with Ed25519 client_priv
C->>S: UDP packet (165 bytes) [port appears CLOSED]
Note over S: 7. Validate size and version byte
Note over S: 8. ECDH(server_priv, ephem_pub) → shared_key
Note over S: 9. Decrypt ciphertext → plaintext
Note over S: 10. Verify Ed25519 sig against client whitelist
Note over S: 11. Check timestamp within ±60s
Note over S: 12. Check random_nonce not seen before
Note over S: 13. Resolve target IP (:: → source IP)
S->>FW: Add ACCEPT rule for target_ip:port
Note over FW: Rule active for knock_timeout (default 30s)
C->>S: TCP connect (SSH, HTTPS, etc.)
Note over FW: Timer fires → rule removed
Handshake
The openme “handshake” is deliberately one-sided: the client sends one packet and the server acts on it. There is no response, no acknowledgement, no TCP connection, no session. This is what keeps the server stealthy.
Step-by-Step: Client Side
Step-by-Step: Server Processing
The server processes each received packet in a goroutine. Invalid packets are silently discarded at every step — the server never sends any response.
1. Size and version check
if len(packet) != 165 → discard
if packet[0] != 0x01 → discard
Packets of the wrong size or version look like noise and are dropped without logging at the default log level.
3. Decryption
nonce = packet[33:45]
ciphertext = packet[45:103]
plaintext = ChaCha20-Poly1305-Open(shared_key, nonce, ciphertext)
If the AEAD authentication tag fails (tampered data, wrong key, wrong nonce), the packet is discarded silently. An attacker learns nothing from this.
4. Signature verification
msg = packet[0:101]
sig = packet[101:165]
for each client in whitelist:
if Ed25519-Verify(client.pubkey, msg, sig) → matched client found
if no match → discard
All registered clients are tried. The first match wins.
5. Key expiry check
if client.expires != nil && now() > client.expires → discard
6. Replay protection
age = |now() - plaintext.timestamp|
if age > replay_window (60s) → discard
if plaintext.random_nonce in seen_cache → discard
seen_cache.add(plaintext.random_nonce, ttl=replay_window)
See Replay Protection for details.
7. Target IP resolution
target_ip = plaintext.target_ip
if target_ip == :: (all zeros) → target_ip = source IP of UDP packet
8. Firewall rule
firewall.Open(target_ip, client.allowed_ports)
schedule: firewall.Close(target_ip, client.allowed_ports) after knock_timeout
The knock timeout (default 30 seconds) is enough to establish a TCP connection. If another valid knock arrives before the timer fires, the timer is reset.