QuickZTNA User Guide
Home WireGuard WireGuard Core Security

WireGuard Core Security

What We’re Testing

WireGuard’s security model is built on three guarantees:

  1. Keys never in transit — Only public keys are exchanged. Private keys are generated locally and stay local.
  2. Modern AEAD encryption — ChaCha20-Poly1305 for all traffic, with no negotiation or downgrade possible.
  3. 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

MachineRoleWhat you’ll run
Win-A Primary test machineztna CLI + PowerShell
🐧 Linux-C Packet capture + peer verificationtcpdump, ztna CLI
Win-B Second peerztna 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:

  1. 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 &
  1. On Win-A , force a re-registration:
ztna logout
ztna login --auth-key=YOUR_AUTH_KEY
  1. 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 with sudo
  • 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:

  1. On Win-A , get Linux-C’s tailnet IP:
ztna peers
# Note the IP column for Linux-C
  1. 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 &
  1. On Win-A , send traffic:
ztna ping 100.64.x.x --count 20
  1. 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 ping shows “no route” → run ztna up first, 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:

  1. 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 &
  1. On Win-A , generate sustained traffic for 4+ minutes (forces multiple key rotations):
ztna ping 100.64.x.x --count 240
  1. 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 peers for 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:

  1. 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.

  1. Verify it in the dashboard:

    • Go to login.quickztna.com → Machines → click Win-A
    • Copy the public key shown in machine details
  2. 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_key is null → machine is not registered. Run ztna up first

Summary

Sub-testWhat it provesPass condition
ST1Private key stays localNo private key bytes in network capture
ST2ChaCha20-Poly1305 encryptionPayload is opaque ciphertext
ST3Perfect Forward SecrecyHandshake resets every ~3 minutes
ST4Curve25519 key format44-char base64, matches dashboard