From 083f4095319b734f33f3e28a9f3234ff9cf6a7d7 Mon Sep 17 00:00:00 2001
From: "kali kaneko (leap communications)" <kali@leap.se>
Date: Mon, 17 May 2021 12:53:24 +0200
Subject: [PATCH] [feat] reuse certificate if found in config folder

---
 docs/circumvention.rst | 25 +++++++++++++++++++++++++
 pkg/vpn/certs.go       | 34 ++++++++++++++++++++++++++++++++++
 pkg/vpn/main.go        |  2 +-
 pkg/vpn/openvpn.go     | 34 ++++++++++++++++++++++------------
 4 files changed, 82 insertions(+), 13 deletions(-)
 create mode 100644 docs/circumvention.rst
 create mode 100644 pkg/vpn/certs.go

diff --git a/docs/circumvention.rst b/docs/circumvention.rst
new file mode 100644
index 00000000..8c220cc8
--- /dev/null
+++ b/docs/circumvention.rst
@@ -0,0 +1,25 @@
+Censorship Circumvention
+================================================================================
+
+This document contains some advice for using BitmaskVPN for censorship
+circumvention.
+
+Bootstrapping the connection
+-----------------------------
+
+There are two different steps where circumvention can be used: boostrapping the
+connection (getting a certificate and the configuration files) and using an
+obfuscated transport protocol. At the moment RiseupVPN offers obfs4 transport
+"bridges" (you can try them with the `--obfs4` command line argument). For the
+initial bootstrap, there are a couple of techniques that will be attempted.
+
+Getting certificates off-band
+-----------------------------
+
+As a last resort, you can place a valid certificate in the config folder (name
+it after the provider domain). You might have downloaded this cert with Tor,
+using a socks proxy etc...
+
+  ~/.config/leap/riseup.net.pem
+
+When the certificate expires you will need to download a new one.
diff --git a/pkg/vpn/certs.go b/pkg/vpn/certs.go
new file mode 100644
index 00000000..300871e0
--- /dev/null
+++ b/pkg/vpn/certs.go
@@ -0,0 +1,34 @@
+package vpn
+
+import (
+	"crypto/x509"
+	"encoding/pem"
+	"io/ioutil"
+	"log"
+	"time"
+)
+
+func isValidCert(path string) bool {
+	data, err := ioutil.ReadFile(path)
+	if err != nil {
+		return false
+	}
+	// skip private key, but there should be one
+	_, rest := pem.Decode(data)
+	certBlock, rest := pem.Decode(rest)
+	if len(rest) != 0 {
+		log.Println("ERROR bad cert data")
+		return false
+	}
+	cert, err := x509.ParseCertificate(certBlock.Bytes)
+	loc, _ := time.LoadLocation("UTC")
+	expires := cert.NotAfter
+	tomorrow := time.Now().In(loc).Add(24 * time.Hour)
+
+	if !expires.After(tomorrow) {
+		return false
+	} else {
+		log.Println("DEBUG We have a valid cert:", path)
+		return true
+	}
+}
diff --git a/pkg/vpn/main.go b/pkg/vpn/main.go
index f40366c9..826e5d45 100644
--- a/pkg/vpn/main.go
+++ b/pkg/vpn/main.go
@@ -68,7 +68,7 @@ func Init() (*Bitmask, error) {
 			}
 	*/
 
-	err = ioutil.WriteFile(b.getCaCertPath(), config.CaCert, 0600)
+	err = ioutil.WriteFile(b.getTempCaCertPath(), config.CaCert, 0600)
 
 	go b.openvpnManagement()
 	return &b, err
diff --git a/pkg/vpn/openvpn.go b/pkg/vpn/openvpn.go
index 4dad0e25..7cfa101f 100644
--- a/pkg/vpn/openvpn.go
+++ b/pkg/vpn/openvpn.go
@@ -22,9 +22,11 @@ import (
 	"log"
 	"os"
 	"path"
+	"path/filepath"
 	"strconv"
 	"strings"
 
+	"0xacab.org/leap/bitmask-vpn/pkg/config"
 	"0xacab.org/leap/shapeshifter"
 )
 
@@ -167,7 +169,7 @@ func (b *Bitmask) startOpenVPN() error {
 		"--verb", "3",
 		"--management-client",
 		"--management", openvpnManagementAddr, openvpnManagementPort,
-		"--ca", b.getCaCertPath(),
+		"--ca", b.getTempCaCertPath(),
 		"--cert", b.certPemPath,
 		"--key", b.certPemPath,
 		"--persist-tun")
@@ -175,17 +177,25 @@ func (b *Bitmask) startOpenVPN() error {
 }
 
 func (b *Bitmask) getCert() (certPath string, err error) {
-	certPath = b.getCertPemPath()
-
-	if _, err := os.Stat(certPath); os.IsNotExist(err) {
-		log.Println("Fetching certificate to", certPath)
-		cert, err := b.bonafide.GetPemCertificate()
-		if err != nil {
-			return "", err
+	persistentCertFile := filepath.Join(config.Path, strings.ToLower(config.Provider)+".pem")
+	if _, err := os.Stat(persistentCertFile); !os.IsNotExist(err) && isValidCert(persistentCertFile) {
+		// reuse cert. for the moment we're not writing one there, this is
+		// only to allow users to get certs off-band and place them there
+		// as a last-resort fallback for circumvention.
+		certPath = persistentCertFile
+		err = nil
+	} else {
+		// download one fresh
+		certPath = b.getTempCertPemPath()
+		if _, err := os.Stat(certPath); os.IsNotExist(err) {
+			log.Println("Fetching certificate to", certPath)
+			cert, err := b.bonafide.GetPemCertificate()
+			if err != nil {
+				return "", err
+			}
+			err = ioutil.WriteFile(certPath, cert, 0600)
 		}
-		err = ioutil.WriteFile(certPath, cert, 0600)
 	}
-
 	return certPath, err
 }
 
@@ -299,10 +309,10 @@ func (b *Bitmask) UseTransport(transport string) error {
 	return nil
 }
 
-func (b *Bitmask) getCertPemPath() string {
+func (b *Bitmask) getTempCertPemPath() string {
 	return path.Join(b.tempdir, "openvpn.pem")
 }
 
-func (b *Bitmask) getCaCertPath() string {
+func (b *Bitmask) getTempCaCertPath() string {
 	return path.Join(b.tempdir, "cacert.pem")
 }
-- 
GitLab