ChromeOS + OpenVPN (+ TLSAuth)

This is a guide to get OpenVPN (with TLS Auth) working for a ChromeOS client. Note this guide assumes you to have control of the OpenVPN server and associated configuration. This guide doesn’t explain the specifics of port forwarding on your router, or use of Dynamic DNS – if you’re doing all the below I’ll assume you know about doing those things – if not there are plenty of tutorials around.

Versions used:

  • ChromeOS 57.0.2987.115 beta – on Samsung Chromebook Plus
  • Ubuntu 14.04 LTS (Bit old I know, but systemd 😦 )
  • OpenVPN 2.3.2 (openvpn 2.3.2-7ubuntu3.1)

Install OpenVPN server and easy-rsa

sudo apt-get install openvpn easy-rsa
sudo mkdir /etc/openvpn/easy-rsa/
sudo cp -r /usr/share/easy-rsa/* /etc/openvpn/easy-rsa/

Create certificates

cd /etc/openvpn/easy-rsa

Edit vars file to update the values

  • Set KEY_SIZE to 2048
  • Also set KEY_COUNTRY, KEY_PROVINCE, KEY_CITY, KEY_ORG, KEY_EMAIL parameters. Don’t leave any of these parameters blank.

Run ./vars to load parameters
Run ./clean-all to clear keys and previous files

Now lets create our CA cert and key:

Run ./build-ca. The majority of the defaults will be loaded of the var specified values, but you must enter the Common Name (CN) – enter a name that identifies your CA. MyVPN-CA for example. This will create two files 1) ca.crt your CA cert (public) and 2) ca.key you CA private key (secret!)

Now to create the server cert and key:

Run ./build-key-server server. Like the previous command most values can be defaulted. When prompted for CN, enter server. Then select yes for both Sign Certificate and Commit. This will create two files 1) server.crt your servers cert (public) and 2) server.key your servers private key (secret!)

Time for the client(s) cert and key(s):

Run ./build-key-client client1. When prompted for CN, enter a name unique for each client – e.g. client1. Then select yes for both Sign Certificate and Commit. This will create two files 1) client1.crt your clients cert (public) and 2) client1.key your clients private key (secret!)

Now we need to put the client cert and key into a format understood by ChromeOS, namely pkcs12. Run openssl pkcs12 -export -in client1.crt -inkey client1.key -certfile ca.crt -name MyClient -out client1.p12. Enter an export passphrase. This will create a file called client1.p12.

You can repeat the above each client, and just increment the client number: client2, client3 etc…

Now to generate the Diffie Hellman parameters. Run ./build-dh – this may take a few to many minutes. This will create a file called dh2048.pem – this is not secret.

Finally, we should create an OpenVPN static key. Run openvpn --genkey --secret ./keys/ta.key. This will create a file called ta.key – this is another secret. Now we need this is a strange and specific format for ChromeOS where it’s all in one line with inline line break escape characters ‘\n’. So lets do that with a bit of Perl – grep -v '#' ./keys/ta.key | perl -p -e 's/\n/\\n/' > ./keys/ta-oneliner.key.

Now we need to copy the files required by the server into the appropriate directory for your OpenVPN server, like this: cp ./keys/ca.rt ./keys/server.crt ./keys/server.key ./keys/ta.key ./keys/dh2048.pem /etc/openvpn/

While we are here, there are a number of files that you need to get to your client (e.g ChromeOS). There many ways to do this – for example copy somewhere using scp then copying into Google Drive. The files your client needs are client1.p12, ca.crt and ta-oneliner.key.

Configure server

sudo nano /etc/openvpn/server.conf

Here is the content of mine with comments for each line – known to work with ChromeOS clients (see version above)

Enable IPv4 forwarding:

Edit /etc/sysctl.confand uncomment net.ipv4.ip_forward=1 to enable IP forwarding. Then make it come into effect by running sudo sysctl -p /etc/sysctl.conf

Restart Openvpn server:

sudo service openvpn restart. And verify it’s actually running – sudo service openvpn status. If it’s not look in \var\log\syslog for any errors/hints.

Client Configuration (ChromeOS)

Open Chrome – and goto chrome://settings/certificates

Select ‘Authorities’, then ‘Import’, and load in the ca.crt file. When prompted tick the ‘Trust this certificate for identifying websites.’ You should see your certificate in the list under the ‘Private’ parent.

In the same certificates window select the ‘Your Certificates’ tab – then ‘Import and Bind to device…’ and load in the client.p12 and enter the passphrase you specified when creating it. You should now see your client certificate listed.

Now we need to create a ONC file for ChromeOS:

  1. Generate two random GUIDs via https://www.uuidgenerator.net/ or similar. Refresh the page to get your second one. Take note of both, I will refer to them as GUID#1 and GUID#2
  2. Copy the following into a text editor on your ChromeBook

3. Replace the following values in the above files:

  • <GUID#1> – paste value from earlier
  • <GUID#2> – paste value from earlier
  • <VPN_NAME>: Enter a name for your connection. This what you’ll see in the ChromeOS VPN UI.
  • <CA-CERT>: this is the contents of the CA.crt, without the header lines, on one long line, so it will be one long string of base64 encoded ascii, typically begining with “MII” and continuing on for some lines, remove the newlines in the cert. The footer line “—–END CERTIFICATE—–” is also not included.
  • <HOSTNAME>: This is simply the hostname of your VPN server. Do not include port – as this is specified by the ‘port’ parameter – change that if you’re not using 443.
  • <USERNAME>: Is your username on the vpn server.
  • <TLS_AUTH_KEY>: This one is the TLS auth key. Open ta-oneliner.key and paste the contents.

Save your ONC file. Not it contains secret information to treat accordingly. Any filename will do, but maintain the .onc extension

Now we need to install the ONC file:

  • In Chrome goto chrome://net-internals#chromeos
  • Click ‘Choose File’ under ‘Import ONC file’
  • Set your ONC file. Note you may get no postive or negative response from the import attempt. Just go to the VPN UI in the ChromeOS launcher – if the import succeeded you’ll see your VPN connection listed.
Test!

Drop comments/queries below and I’ll assist if I can.

Source and extra reading

Selective VPN routing [Solution: DSVR]

Before sharing about what I believe is my best solution yet, i’ll take a walk down memory lane…

Client VPN

Got my first VPN account (PPTP/L2TP) and happily used it from my various Windows/Mac/iOS clients. Very quickly I came across a few limitations, namely:

1) Only one device at a time could use the VPN.
2) More restricted clients did not have a client side capability to configure the VPN (like the Apple TV/PS3)
3) When a device was on the VPN, all traffic went down it – not ideal from a performance perspective, and when you’re location is determine by IP (e.g. Google Maps).
4) Minor at the time, but could not have multiple VPNs running.

Router VPN [+Source Based Routing]

Configuring  VPN connection at a single aggregation point (i.e. the router) was the next step, as this did solve some of the limitations (1 & 2 above). After a bunch of research (read: trial/error) I concluded that there was no practical way to be selective at the router based on destination as many services used over the VPN where using CDNs with massive network ranges which we’re not practically predictable.

I decided that source based routing was a good solution, in my case I selected my Apple TV to be the only source to push down the VPN this has served me well,  and I published a tutorial on it a while back.

Defining the next step

Even though my problem/use-case had largely been solved, I wanted to go further. I set myself the following goals for my next solution:

1) Selective destination routing, route only sites/domains I want.
2) Multiple concurrent VPN client support, each with their own destination mappings.
3) Multiple clients able to share the outgoing VPN(s) connection(s)
4) A ‘plug-n-play’ appliance, with no configuration changes to exiting infrastructure (clients/router/modem)
5) Simple web administration interface.

With those goals in mind – it was time to determine a solution.

New Solution: DSVR [Domain-Specific VPN Router]

dsvr-logical-v0.2

1. Selective destination routing

I’ve already touched on some of the challenges (e.g CDNs) – there were a few more – but ultimately I determined that a pre-populated list of IP routes for a service will not work.

Then an idea came to me – what if I made something that I’d best describe as a ‘DNS Router’, at a high-level it would need to do the following:

1) Intercept all outgoing DNS queries from my clients.
2) Analysis the query name against which domains I’m interested in pushing down the VPN.
3) Pass the unmodified DNS request upstream to a valid DNS server, and get a valid response back.
4) Before passing the DNS response to the original client, add routes for the RR data within the DNS record

What does this do? For each matched DNS request we are creating static routes down the VPN!

Theory is great – but I needed to now make this thing.

It was clear to me that the best starting point was to leverage existing work that’s been done creating a DNS proxy. I found a great one – dnschef – by Peter Kacherginsky. Using this as a strong foundation I was able to modify and expand this to perform the 4 points above, and also handle some edge cases around CNAME responses etc.

In my testing – this has proven to work and be stable.

2. ‘Plug-n-play’ appliance, no configuration changes, clients able to share

Deciding on a hardware platform for the appliance was easy – Raspberry Pi – it’s low-cost, low-power, runs linux and small. It’s not without it’s limitations though (e.g. NIC limited by USB bus)

The aggregation point I selected to place the Raspberry Pi was between the existing CE/Modem and home router. This meant the RPi had to be configured as a typical NAT router. Other than the OS level configuration required to create the router, the RPi only has a single NIC, so I added a second NIC interface using a USB add-on.

To achieve the second portion of this goal ‘no configuration changes to exiting infrastructure’, I used iptables to transparent redirect all outbound DNS queries to my new custom DNS proxy/router.

dsvr-physical-v0.2

3. Concurrent VPN client support

This was wasn’t too tough (for PPTP), basically just needed to configure the following for each:

1) PPP peer file
2) IP-UP script
3) INIT.D script

OpenVPN support is in the pipeline.

4. Web administration

Seeing that the core capability was done in Python, it was a logical choice to create the web interface in the same. I selected the Flask web-framework, and I used there small dev web server in lieu of the big boys like Apache.

The web interface allows for the following functions:

1) PPTP VPNs – Add/Modify/Delete
2) Specify which wildcard domains should route down which VPN
3) Stop/Start/Restart the core DSVR service
4) Status display for PPTP tunnel status, IP assignments, Route counts, uptime, mem/cpu usage
5) Reboot the RPi

webadmin (1)

Get/Build it

This blog post is just a static introduction. The code, instructions, limitations, issues and TODO’s are being maintained on the DSVR github page.

Do note that this should be considered an ALPHA release – it has not been tested by many people – use at your own risk.

gitHub-download-button

StrongVPN PPTP on DD-WRT – Source based routing (improved)

Update: Suggest you consider a new solution, that I’ve posted here

Hi there –

Myself and others have had a problem using the solution that I posted here. It works for a couple of hours, but then stops and requires a reboot to get it running again. I finally took some time to try and figure it out, and found that I needed to modify the IP-UP script to reestablish a couple of things. Here is an update to the original post:

I’m a StrongVPN customer and just today I had a use case that required that I was able to setup the VPN tunnel from my router and only route a specific host down the VPN.

Here is how I did it using these:

Do the following on your router:

Services->VPN

  • PPTP Client Options: Enable
  • Server IP or DNS name: <this is the IP of the VPN server, hostnames DON’T work>
  • Remote Subnet: <This is the IP that the YOU receive from the VPN server, to get this first connect on Mac/Win and check what IP you get)
  • Remote Subnet Mask: <This is the Subnet Mask of the remote VPN server, to get this first connect on Mac/Win and check what subnet you get – likely that 255.255.255.0 would be fine>
  • MPPE Encryption: mppe required,stateless
  • MTU: 1450
  • MRU: 1450
  • NAT: Enable
  • Username: <This is your StrongVPN user ID>
  • Password: <This is your StrongVPN password>

Setup -> Basic Setup

Under: Network Address Server Settings (DHCP)

  • Static DNS 1: 216.131.94.5
  • Static DNS 2: 216.131.95.20

Administration -> Commands

  • EDIT the INT and SOURCETOROUTE and interface (e.g ppp0) to suit and paste the following to the end of the STARTUP script, then click ‘save startup’. The IP specified in SOURCETOROUTE is the source IP that will be routed via the VPN.
    # Customize PPTPD client
    sleep 50
    mkdir /tmp/etc/config
    echo "#!/bin/sh" > /tmp/pptpd_client/ip-up;
    echo "SOURCETOROUTE=192.168.1.119" >> /tmp/pptpd_client/ip-up;
    echo "REMOTEIP=\$(ifconfig ppp0 | sed -n 's/.*inet *addr:\([0-9\.]*\).*/\1/p')" >> /tmp/pptpd_client/ip-up;
    #echo "ip rule add from \$SOURCETOROUTE table 200" >> /tmp/pptpd_client/ip-up;
    echo "/usr/sbin/ip route add default via \$REMOTEIP dev ppp0 table 200" >> /tmp/pptpd_client/ip-up;
    echo "/usr/sbin/ip route flush cache" >> /tmp/pptpd_client/ip-up;
    echo "touch /tmp/execute-debug" >> /tmp/pptpd_client/ip-up;
    echo "/usr/sbin/iptables --table nat --append POSTROUTING --out-interface ppp0 --jump MASQUERADE" >> /tmp/pptpd_client/ip-up;
    echo "/usr/sbin/iptables --insert FORWARD --protocol tcp --tcp-flags SYN,RST SYN --jump TCPMSS --clamp-mss-to-pmtu" >> /tmp/pptpd_client/ip-up;
    #echo "echo "/tmp/pptpd_client/ip-up - $(date)" >> /tmp/dbvpndebug" >> /tmp/pptpd_client/ip-up;
    chmod 777 /tmp/pptpd_client/ip-up;
    
  • Then in the same area, add the following to the FIREWALL script, and once again modify the SOURCETOROUTE and interface as necessary:
    INT=ppp0
    SOURCETOROUTE=192.168.1.119
    echo “sleep 40″ > /tmp/firewall_script.sh
    echo “/usr/sbin/iptables –table nat –append POSTROUTING –out-interface $INT –jump MASQUERADE” >> /tmp/firewall_script.sh ;
    echo “/usr/sbin/iptables –insert FORWARD –protocol tcp –tcp-flags SYN,RST SYN –jump TCPMSS –clamp-mss-to-pmtu” >> /tmp/firewall_script.sh ;
    echo “ip rule add from $SOURCETOROUTE table 200″ >> /tmp/firewall_script.sh ;
    echo “REMOTEIP=\$(ifconfig ppp0 | sed -n ‘s/.*inet *addr:\([0-9\.]*\).*/\1/p’)” >> /tmp/firewall_script.sh ;
    echo “/usr/sbin/ip route add default via \$REMOTEIP dev ppp0 table 200″ >> /tmp/firewall_script.sh ;
    #echo “echo “/tmp/firewall_script.sh – $(date)” >> /tmp/dbvpndebug” >> /tmp/firewall_script.sh ;
    sh /tmp/firewall_script.sh &
    

    Enjoy.

StrongVPN PPTP on DD-WRT – Source based routing

Update: Improved version posted here

I’m a StrongVPN customer and just today I had a use case that required that I was able to setup the VPN tunnel from my router and only route a specific host down the VPN.

Here is how I did it using these:

Do the following on your router:

Services->VPN

  • PPTP Client Options: Enable
  • Server IP or DNS name: <this is the IP of the VPN server, hostnames DON’T work>
  • Remote Subnet: <This is the IP that the YOU receive from the VPN server, to get this first connect on Mac/Win and check what IP you get)
  • Remote Subnet Mask: 255.255.255.0
  • MPPE Encryption: mppe required,stateless
  • MTU: 1450
  • MRU: 1450
  • NAT: Enable
  • Username: <This is your StrongVPN user ID>
  • Password: <This is your StrongVPN password>

Setup -> Basic Setup

Under: Network Address Server Settings (DHCP)

  • Static DNS 1: 216.131.94.5
  • Static DNS 2: 216.131.95.20

Security -> Firewall

  • SPI Firewall: Disable

Administration -> Commands

  • EDIT the INT and SOURCETOROUTE variables to suite and paste the following, then click ‘save firewall’. The IP specified in SOURCETOROUTE is the source IP that will be routed via the VPN.
    INT=ppp0
    SOURCETOROUTE=192.168.1.119
    echo "sleep 40" &gt; /tmp/firewall_script.sh
    echo "/usr/sbin/iptables --table nat --append POSTROUTING --out-interface $INT --jump MASQUERADE" &gt;&gt;  /tmp/firewall_script.sh ;
    echo "/usr/sbin/iptables --insert FORWARD --protocol tcp --tcp-flags SYN,RST SYN --jump TCPMSS --clamp-mss-to-pmtu" &gt;&gt; /tmp/firewall_script.sh ;
    echo "ip rule add from $SOURCETOROUTE table 200" &gt;&gt; /tmp/firewall_script.sh ;
    echo "REMOTEIP=\$(ifconfig $INT | sed -n 's/.*inet *addr:\([0-9\.]*\).*/\1/p')"  &gt;&gt; /tmp/firewall_script.sh ;
    echo "ip route add default via \$REMOTEIP dev $INT table 200"  &gt;&gt; /tmp/firewall_script.sh ;
    echo "ip route flush cache" &gt;&gt;  /tmp/firewall_script.sh ;
    sh /tmp/firewall_script.sh &amp;
    
  • Reboot your router, after which give it about 5mins and give it a try from the machine specified in SOURCETOROUTE