Privoxy on Android (with EC2 VPN)

This week’s challenge was getting a certificate-based VPN up on an Android phone, and getting all web traffic through a filtering proxy (Privoxy) to both scrub some of the ads from content, and obscure the IP address and location of the phone.

In my quest to forever banish ads and other unwanted content from all of my digital experience, I’ve discovered a number of things along the way: AdBlock (not to be confused with Adblock Plus), Ghostery (which stops tracking scripts/cookies), Privoxy (to altogether disable scripts by default on pages, as well as randomly set user-agent strings), Tab Cookies (to require opting-in to cookies per-site), and Proxy SwitchySharp (to allow easy Chromium-local toggling of proxy choice).

Now, the remaining frontier has been my phone. Considering it’s the medium for most lunchtime distraction and time-wasting exercises, I wanted to bring the Amazon EC2-based Privoxy service to it, by way of a VPN.

Stumbling blocks along the way were:

  • Getting that annoying “Network May Be Monitored by an Unknown Third Party” warning to go away after installing a self-signed CA and credentials
  • Chrome just doesn’t support proxies, and the Android system only supports per-WiFi network proxying.

Getting rid of the certificate warning requires a rooted phone and diving into the shell. Firefox was the ultimate solution to forcing all web content through a proxy regardless of WiFi/mobile data/etc. On to the details:

Strongswan EC2 side config:

config setup

conn %default

conn roadwarrior
    # leftsubnet=,::/0 # Use to blackhole all traffic
    leftsubnet= # Use to only provide EC2 server access

Then, generate certificates as specified on OpenWRT’s page on the matter. There’s a nice script there:

ipsec pki --gen --outform pem > caKey.pem
ipsec pki --self --in caKey.pem --dn "C=US, O=xxx, CN=xxxx" --ca --outform pem > caCert.pem
ipsec pki --gen --outform pem > serverKey.pem
ipsec pki --pub --in serverKey.pem | ipsec pki --issue --cacert caCert.pem --cakey caKey.pem --dn "C=US, O=xxx," --san="" --flag serverAuth --flag ikeIntermediate --outform pem > serverCert.pem
ipsec pki --gen --outform pem > clientKey.pem
ipsec pki --pub --in clientKey.pem | ipsec pki --issue --cacert caCert.pem --cakey caKey.pem --dn "C=US, O=xxx, CN=client" --outform pem > clientCert.pem
openssl pkcs12 -export -inkey clientKey.pem -in clientCert.pem -name "client" -certfile caCert.pem -caname "xxxx" -out clientCert.p12

# where to put them...
mv caCert.pem /etc/ipsec.d/cacerts/
mv serverCert.pem /etc/ipsec.d/certs/
mv serverKey.pem /etc/ipsec.d/private/
mv clientCert.pem /etc/ipsec.d/certs/
mv clientKey.pem /etc/ipsec.d/private/
mv caKey.pem /etc/ipsec.d/private/
  • Make sure to add the right bits to /etc/ipsec.secrets and then upload the clientCert.p12 to the phone.
  • Also open the right ports through iptables/ufw, as well as through EC2 security groups.
  • Install Privoxy. If it’s an Amazon EC2 instance with Ubuntu, it should suffice to do sudo apt-get install privoxy
  • Configure privoxy with standard settings. For use with an out-of-subnet client I had to utilize the permit-access clause in /etc/privoxy/config

Phone side: On CyanogenMod 13 (“CM13”, Android Marshmallow 6.0.1)

  • Install StrongSwan app
  • Add the connection. Choose IKEv2 Certificate + EAP (Username/Password)
  • Import the newly created certificate. About now the connection monitored warning appears. The VPN should be connectable.
  • This page describes how to fix that warning (with exceptions), however I ended up having to fetch that generated certificate in text format, upload it to the phone, and:
mount -o remount,rw /system
cp /sdcard/caCert.pem /system/etc/security/cacerts/ffffffff.0

After the reboot, that warning is gone. Now, the VPN is connectable and the phone can see the AWS server by its internal IP.

  • Install Firefox. (I used Firefox Beta in this case)
  • Navigate to about:config and change the following:
    network.proxy.http = [ec2 internal IP]
    network.proxy.http_port = 8118 (Privoxy’s default port)
    (same settings for network.proxy.https*
    network.proxy.type = 1

Now, the browser will only connect to a site if the VPN is up, and in that case websites only see traffic coming from somewhere in a land far away.

Some final notes

I had initially planned on using leftsubnet= in the server strongswan config, to force all of the phone’s internet traffic through the cloud. Without IP masquerading, any traffic not explicitly directed at the proxy is blocked.

To allow all traffic to be bounced off AWS:

iptables --table nat --append POSTROUTING --source -j MASQUERADE

To only make exceptions for google services (since google apps work over https), do this instead:

for i in `nslookup -q=TXT |grep spf1 |awk -F" '{print $2}' |tr ' ' 'n' |awk -F: '$1=="ip4"{print $2}'`; do
        iptables --table nat --append POSTROUTING --source --destination "$i" -j MASQUERADE
for i in `nslookup -q=TXT |grep spf1 |awk -F" '{print $2}' |tr ' ' 'n' |awk -F: '$1=="ip4"{print $2}'`; do
        iptables --table nat --append POSTROUTING --source --destination "$i" -j MASQUERADE

There were still some issues though, such as initial location services saying I’m in Virginia and giving me really long drives in Maps for places around the corner until GPS kicks in (maybe this is desirable behavior?)

Another issue is that streaming music and other media would eventually bump me out of free tier Amazon. A third issue was blocking the theft protection app Prey from working properly.

I tried getting around this by using split-exclude functionality in strongswan, where all internet traffic is sent to the cloud, except for the networks defined by split-tunneling exclusion lists. For some reason, the StrongSwan VPN client for android wouldn’t recognize these policies, and very little information about the specific error was available, so I decided to stick with browser proxying only. With so many other things to do, I think I’ll leave messing with the phone’s iptables rules for the next time I’m snowed in.