Verify RSA-SHA256 signature on PIA server list response
All checks were successful
Check Flake / check-flake (push) Successful in 3m20s

The server list endpoint returns JSON on line 1 with a base64-encoded
RSA-SHA256 signature on lines 3+. This was previously ignored. Add
verifyServerList() that checks the signature against PIA's public
signing key before trusting the data. On failure the service aborts
and systemd restarts it.

Also bump RestartSec to 5m to avoid hammering PIA servers on repeated
failures, and add openssl to container dependencies.
This commit is contained in:
2026-02-26 22:32:23 -08:00
parent 1dd1b420d5
commit ce9bda8a0b
3 changed files with 36 additions and 9 deletions

View File

@@ -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-----

View File

@@ -1,5 +1,6 @@
let let
caPath = ./ca.rsa.4096.crt; caPath = ./ca.rsa.4096.crt;
pubKeyPath = ./pubkey.pem;
in in
# Bash function library for PIA VPN WireGuard operations. # Bash function library for PIA VPN WireGuard operations.
@@ -21,6 +22,23 @@ in
fi fi
} }
# Verify the server list RSA-SHA256 signature (line 1 = JSON, lines 3+ = base64 signature).
verifyServerList() {
local raw=$1
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: Server list signature verification failed" >&2
rm -f "$sig_file"
return 1
fi
echo "Server list signature verified"
rm -f "$sig_file"
}
fetchPIAToken() { fetchPIAToken() {
local PIA_USER PIA_PASS resp local PIA_USER PIA_PASS resp
echo "Reading PIA credentials..." echo "Reading PIA credentials..."
@@ -39,20 +57,20 @@ in
choosePIAServer() { choosePIAServer() {
local serverLocation=$1 local serverLocation=$1
local servers servers_json totalservers serverindex local raw servers_json totalservers serverindex
servers=$(mktemp)
servers_json=$(mktemp) servers_json=$(mktemp)
echo "Fetching PIA server list..." echo "Fetching PIA server list..."
curl -s $(proxy_args) \ raw=$(curl -s $(proxy_args) \
"https://serverlist.piaservers.net/vpninfo/servers/v6" > "$servers" "https://serverlist.piaservers.net/vpninfo/servers/v6")
head -n 1 "$servers" | tr -d '\n' > "$servers_json" verifyServerList "$raw"
echo "$raw" | head -n 1 | tr -d '\n' > "$servers_json"
totalservers=$(jq -r \ totalservers=$(jq -r \
'.regions | .[] | select(.id=="'"$serverLocation"'") | .servers.wg | length' \ '.regions | .[] | select(.id=="'"$serverLocation"'") | .servers.wg | length' \
"$servers_json") "$servers_json")
if ! [[ "$totalservers" =~ ^[0-9]+$ ]] || [ "$totalservers" -eq 0 ] 2>/dev/null; then if ! [[ "$totalservers" =~ ^[0-9]+$ ]] || [ "$totalservers" -eq 0 ] 2>/dev/null; then
echo "ERROR: Location \"$serverLocation\" not found." >&2 echo "ERROR: Location \"$serverLocation\" not found." >&2
rm -f "$servers_json" "$servers" rm -f "$servers_json"
return 1 return 1
fi fi
echo "Found $totalservers WireGuard servers in region '$serverLocation'" echo "Found $totalservers WireGuard servers in region '$serverLocation'"
@@ -66,7 +84,7 @@ in
"$servers_json") "$servers_json")
WG_SERVER_PORT=$(jq -r '.groups.wg | .[0] | .ports | .[0]' "$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)" echo "Selected server $serverindex/$totalservers: $WG_HOSTNAME ($WG_SERVER_IP:$WG_SERVER_PORT)"
} }

View File

@@ -73,7 +73,7 @@ in
config = { config, pkgs, lib, ... }: config = { config, pkgs, lib, ... }:
let let
scriptPkgs = with pkgs; [ wireguard-tools iproute2 curl jq iptables coreutils ]; scriptPkgs = with pkgs; [ wireguard-tools iproute2 curl jq iptables coreutils openssl ];
in in
{ {
imports = allModules; imports = allModules;
@@ -133,7 +133,7 @@ in
serviceConfig = { serviceConfig = {
Type = "simple"; Type = "simple";
Restart = "always"; Restart = "always";
RestartSec = "10s"; RestartSec = "5m";
RuntimeMaxSec = "30d"; RuntimeMaxSec = "30d";
}; };