From 9fdadd321c6beaf436ef6c62759f151387aed165 Mon Sep 17 00:00:00 2001 From: Zuckerberg Date: Thu, 26 Feb 2026 22:06:35 -0800 Subject: [PATCH] Verify RSA-SHA256 signatures on all PIA API responses Every PIA API response includes a trailing RSA-SHA256 signature (line 1 = JSON, lines 3+ = base64-encoded signature) which was previously ignored entirely. Add verifyPIAResponse() that checks each response against PIA's public signing key before trusting the data. On verification failure the service aborts and systemd restarts it. Also bump RestartSec to 5m to avoid hammering PIA servers on repeated failures. --- common/network/pia-vpn/pubkey.pem | 9 ++++ common/network/pia-vpn/scripts.nix | 56 +++++++++++++++++------- common/network/pia-vpn/vpn-container.nix | 4 +- 3 files changed, 52 insertions(+), 17 deletions(-) create mode 100644 common/network/pia-vpn/pubkey.pem diff --git a/common/network/pia-vpn/pubkey.pem b/common/network/pia-vpn/pubkey.pem new file mode 100644 index 0000000..9848559 --- /dev/null +++ b/common/network/pia-vpn/pubkey.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzLYHwX5Ug/oUObZ5eH5P +rEwmfj4E/YEfSKLgFSsyRGGsVmmjiXBmSbX2s3xbj/ofuvYtkMkP/VPFHy9E/8ox +Y+cRjPzydxz46LPY7jpEw1NHZjOyTeUero5e1nkLhiQqO/cMVYmUnuVcuFfZyZvc +8Apx5fBrIp2oWpF/G9tpUZfUUJaaHiXDtuYP8o8VhYtyjuUu3h7rkQFoMxvuoOFH +6nkc0VQmBsHvCfq4T9v8gyiBtQRy543leapTBMT34mxVIQ4ReGLPVit/6sNLoGLb +gSnGe9Bk/a5V/5vlqeemWF0hgoRtUxMtU1hFbe7e8tSq1j+mu0SHMyKHiHd+OsmU +IQIDAQAB +-----END PUBLIC KEY----- diff --git a/common/network/pia-vpn/scripts.nix b/common/network/pia-vpn/scripts.nix index e5fabb1..c8ec5df 100644 --- a/common/network/pia-vpn/scripts.nix +++ b/common/network/pia-vpn/scripts.nix @@ -1,5 +1,6 @@ let caPath = ./ca.rsa.4096.crt; + pubKeyPath = ./pubkey.pem; in # Bash function library for PIA VPN WireGuard operations. @@ -21,14 +22,33 @@ in fi } + # Verify a raw PIA API response (line 1 = JSON, lines 3+ = base64 RSA-SHA256 signature). + verifyPIAResponse() { + local raw=$1 label=$2 + local sig_file + sig_file=$(mktemp) + echo "$raw" | tail -n +3 | base64 -d > "$sig_file" + if ! echo -n "$(echo "$raw" | head -n 1 | tr -d '\n')" | \ + openssl dgst -sha256 -verify "${pubKeyPath}" \ + -signature "$sig_file"; then + echo "ERROR: $label signature verification failed" >&2 + rm -f "$sig_file" + return 1 + fi + echo "$label signature verified" + rm -f "$sig_file" + } + fetchPIAToken() { - local PIA_USER PIA_PASS resp + local PIA_USER PIA_PASS raw resp echo "Reading PIA credentials..." PIA_USER=$(sed '1q;d' /run/agenix/pia-login.conf) PIA_PASS=$(sed '2q;d' /run/agenix/pia-login.conf) echo "Requesting PIA authentication token..." - resp=$(curl -s $(proxy_args) -u "$PIA_USER:$PIA_PASS" \ + raw=$(curl -s $(proxy_args) -u "$PIA_USER:$PIA_PASS" \ "https://www.privateinternetaccess.com/gtoken/generateToken") + verifyPIAResponse "$raw" "generateToken" + resp=$(echo "$raw" | head -n 1) PIA_TOKEN=$(echo "$resp" | jq -r '.token') if [[ -z "$PIA_TOKEN" || "$PIA_TOKEN" == "null" ]]; then echo "ERROR: Failed to fetch PIA token: $resp" >&2 @@ -39,20 +59,20 @@ in choosePIAServer() { local serverLocation=$1 - local servers servers_json totalservers serverindex - servers=$(mktemp) + local raw servers_json totalservers serverindex servers_json=$(mktemp) echo "Fetching PIA server list..." - curl -s $(proxy_args) \ - "https://serverlist.piaservers.net/vpninfo/servers/v6" > "$servers" - head -n 1 "$servers" | tr -d '\n' > "$servers_json" + raw=$(curl -s $(proxy_args) \ + "https://serverlist.piaservers.net/vpninfo/servers/v6") + verifyPIAResponse "$raw" "serverList" + echo "$raw" | head -n 1 | tr -d '\n' > "$servers_json" totalservers=$(jq -r \ '.regions | .[] | select(.id=="'"$serverLocation"'") | .servers.wg | length' \ "$servers_json") if ! [[ "$totalservers" =~ ^[0-9]+$ ]] || [ "$totalservers" -eq 0 ] 2>/dev/null; then echo "ERROR: Location \"$serverLocation\" not found." >&2 - rm -f "$servers_json" "$servers" + rm -f "$servers_json" return 1 fi echo "Found $totalservers WireGuard servers in region '$serverLocation'" @@ -66,7 +86,7 @@ in "$servers_json") WG_SERVER_PORT=$(jq -r '.groups.wg | .[0] | .ports | .[0]' "$servers_json") - rm -f "$servers_json" "$servers" + rm -f "$servers_json" echo "Selected server $serverindex/$totalservers: $WG_HOSTNAME ($WG_SERVER_IP:$WG_SERVER_PORT)" } @@ -77,14 +97,16 @@ in } authorizeKeyWithPIAServer() { - local addKeyResponse + local raw addKeyResponse echo "Sending addKey request to $WG_HOSTNAME ($WG_SERVER_IP:$WG_SERVER_PORT)..." - addKeyResponse=$(curl -s -G $(proxy_args) \ + raw=$(curl -s -G $(proxy_args) \ --connect-to "$WG_HOSTNAME::$WG_SERVER_IP:" \ --cacert "${caPath}" \ --data-urlencode "pt=$PIA_TOKEN" \ --data-urlencode "pubkey=$PUBLIC_KEY" \ "https://$WG_HOSTNAME:$WG_SERVER_PORT/addKey") + verifyPIAResponse "$raw" "addKey" + addKeyResponse=$(echo "$raw" | head -n 1) local status status=$(echo "$addKeyResponse" | jq -r '.status') if [[ "$status" != "OK" ]]; then @@ -158,13 +180,15 @@ in } reservePortForward() { - local payload_and_signature + local raw payload_and_signature echo "Requesting port forward signature from $WG_HOSTNAME..." - payload_and_signature=$(curl -s -m 5 \ + raw=$(curl -s -m 5 \ --connect-to "$WG_HOSTNAME::$WG_SERVER_IP:" \ --cacert "${caPath}" \ -G --data-urlencode "token=$PIA_TOKEN" \ "https://$WG_HOSTNAME:19999/getSignature") + verifyPIAResponse "$raw" "getSignature" + payload_and_signature=$(echo "$raw" | head -n 1) local status status=$(echo "$payload_and_signature" | jq -r '.status') if [[ "$status" != "OK" ]]; then @@ -195,14 +219,16 @@ in } refreshPIAPort() { - local bindPortResponse + local raw bindPortResponse echo "Refreshing port forward binding with $WG_HOSTNAME..." - bindPortResponse=$(curl -Gs -m 5 \ + raw=$(curl -Gs -m 5 \ --connect-to "$WG_HOSTNAME::$WG_SERVER_IP:" \ --cacert "${caPath}" \ --data-urlencode "payload=$PORT_PAYLOAD" \ --data-urlencode "signature=$PORT_SIGNATURE" \ "https://$WG_HOSTNAME:19999/bindPort") + verifyPIAResponse "$raw" "bindPort" + bindPortResponse=$(echo "$raw" | head -n 1) echo "bindPort response: $bindPortResponse" } ''; diff --git a/common/network/pia-vpn/vpn-container.nix b/common/network/pia-vpn/vpn-container.nix index ed6b081..4631681 100644 --- a/common/network/pia-vpn/vpn-container.nix +++ b/common/network/pia-vpn/vpn-container.nix @@ -73,7 +73,7 @@ in config = { config, pkgs, lib, ... }: let - scriptPkgs = with pkgs; [ wireguard-tools iproute2 curl jq iptables coreutils ]; + scriptPkgs = with pkgs; [ wireguard-tools iproute2 curl jq iptables coreutils openssl ]; in { imports = allModules; @@ -133,7 +133,7 @@ in serviceConfig = { Type = "simple"; Restart = "always"; - RestartSec = "10s"; + RestartSec = "5m"; RuntimeMaxSec = "30d"; };