Verify RSA-SHA256 signatures on all PIA API responses
All checks were successful
Check Flake / check-flake (push) Successful in 3m18s

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.
This commit is contained in:
2026-02-26 22:06:35 -08:00
parent 1dd1b420d5
commit 9fdadd321c
3 changed files with 52 additions and 17 deletions

View File

@@ -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"
}
'';