What We’re Testing
WireGuard’s security model is built on three guarantees:
- Keys never in transit — Only public keys are exchanged. Private keys are generated locally and stay local.
- Modern AEAD encryption — ChaCha20-Poly1305 for all traffic, with no negotiation or downgrade possible.
- Perfect Forward Secrecy — Ephemeral session keys mean past sessions can’t be decrypted even if long-term keys leak.
These aren’t just claims — every one of them can be verified with tools already on your machines.
Your Test Setup
| Machine | Role | What you’ll run |
|---|---|---|
| ⊞ Win-A | Primary test machine | ztna CLI + PowerShell |
| 🐧 Linux-C | Packet capture + peer verification | tcpdump, ztna CLI |
| ⊞ Win-B | Second peer | ztna CLI |
All three machines must be enrolled and online in your QuickZTNA org before starting.
Pre-check: Open a terminal on each machine and confirm:
ztna status
Expected: Authenticated: true and Mode: tun (or userspace) on all three.
ST1 — Verify Private Key Never Leaves the Machine
What it verifies: The WireGuard private key is generated locally and is not transmitted to the QuickZTNA control plane or any peer.
Steps:
- On 🐧 Linux-C , start a packet capture on the interface used for outbound traffic:
# Find your primary interface
ip route get 8.8.8.8 | awk '{print $5; exit}'
# Start capture (replace eth0 with your interface)
tcpdump -i eth0 -w /tmp/ztna-reg.pcap -n host login.quickztna.com &
- On ⊞ Win-A , force a re-registration:
ztna logout
ztna login --auth-key=YOUR_AUTH_KEY
- On 🐧 Linux-C , stop capture and inspect:
kill %1
# Look for any 44-char base64 string (WireGuard key format)
tcpdump -r /tmp/ztna-reg.pcap -A | grep -E '[A-Za-z0-9+/]{43}='
Expected output:
# Zero lines returned, OR only lines matching your PUBLIC key (visible in dashboard).
# Private key is never sent over the wire.
Pass: No 44-char base64 strings in capture, or only the known public key appears.
Fail / Common issues:
tcpdump: Permission denied→ run withsudo- To distinguish public vs private key: compare captured key with dashboard → Machines → Win-A → Public Key
ST2 — Confirm ChaCha20-Poly1305 Encryption
What it verifies: All WireGuard tunnel traffic is encrypted with ChaCha20-Poly1305. Payload is opaque ciphertext.
Steps:
- On ⊞ Win-A , get Linux-C’s tailnet IP:
ztna peers
# Note the IP column for Linux-C
- On 🐧 Linux-C , capture tunnel traffic from Win-A:
# Replace 100.64.x.x with Win-A tailnet IP
tcpdump -i any udp and host 100.64.x.x -w /tmp/wg-traffic.pcap &
- On ⊞ Win-A , send traffic:
ztna ping 100.64.x.x --count 20
- On 🐧 Linux-C , stop capture and verify ciphertext:
kill %1
tcpdump -r /tmp/wg-traffic.pcap -A | head -60
Expected output:
# All payload bytes look like random binary — no readable strings
# No HTTP headers, no hostnames, no IP addresses in payload
# Protocol is UDP
Pass: Payload is opaque. No plaintext strings visible in the captured bytes.
Fail / Common issues:
- Packets are TCP not UDP → traffic may be going to control plane, not through WireGuard tunnel
ztna pingshows “no route” → runztna upfirst, then retry
ST3 — Verify Perfect Forward Secrecy (Session Key Rotation)
What it verifies: WireGuard rotates ephemeral session keys every ~180 seconds. Past sessions cannot be decrypted after rotation.
Steps:
- On 🐧 Linux-C , capture WireGuard handshake initiation packets on the physical NIC. Handshake packets are 148 bytes:
# Replace eth0 with your interface
sudo tcpdump -i eth0 udp and 'udp[0:4] = 0x01000000' -n -c 5 &
- On ⊞ Win-A , generate sustained traffic for 4+ minutes (forces multiple key rotations):
ztna ping 100.64.x.x --count 240
- On 🐧 Linux-C , observe the tcpdump output. Each WireGuard handshake initiation packet indicates a new ephemeral key exchange.
Expected output:
# Multiple handshake initiation packets captured over ~4 minutes:
14:02:33 IP 203.x.x.x.41641 > 178.x.x.x.41641: UDP, length 148
14:05:35 IP 203.x.x.x.41641 > 178.x.x.x.41641: UDP, length 148
# ~180 second gap = new ephemeral session keys
Pass: Multiple handshake packets captured with ~180-second intervals — confirming ephemeral key rotation.
Fail / Common issues:
- Only 1 handshake captured in 4 minutes → traffic may be going via DERP (not direct UDP). Check
ztna peersfor path type - No handshakes at all → capture may be on wrong interface. Try
tcpdump -i any
ST4 — Key Format Verification (Curve25519)
What it verifies: WireGuard public keys are Curve25519 (32-byte), base64-encoded as 44 characters.
Steps:
- On ⊞ Win-A , display the full machine details in JSON:
ztna status --json
Look for the node_key field. Note: ztna status (human-readable) shows a truncated key. Use --json for the full value.
-
Verify it in the dashboard:
- Go to
login.quickztna.com→ Machines → click Win-A - Copy the public key shown in machine details
- Go to
-
Compare: the node key prefix from CLI should match the dashboard public key.
Expected output:
{
"node_key": "bpkM2YWJEhXfP...",
"authenticated": true,
"machine": {
"tailnet_ip": "100.64.0.1",
...
}
}
The full public key is a 44-character base64 string (Curve25519 / X25519).
Pass: 44-character base64 key visible in --json output, matches dashboard public key.
Fail / Common issues:
- Key mismatch → wait 30s for heartbeat sync, then check again
node_keyis null → machine is not registered. Runztna upfirst
Summary
| Sub-test | What it proves | Pass condition |
|---|---|---|
| ST1 | Private key stays local | No private key bytes in network capture |
| ST2 | ChaCha20-Poly1305 encryption | Payload is opaque ciphertext |
| ST3 | Perfect Forward Secrecy | Handshake resets every ~3 minutes |
| ST4 | Curve25519 key format | 44-char base64, matches dashboard |