summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorkali kaneko (leap communications) <kali@leap.se>2020-01-27 20:44:34 -0600
committerkali kaneko (leap communications) <kali@leap.se>2020-08-20 20:27:26 +0200
commitc236dfcfdd60ea700e5f50ed2568398cd161dd4c (patch)
treedb298b28716a25012dc8806afd402b6454b2b37b
parent7c4a4f5ae0c02f57eb9073fa8f412a38b8f79363 (diff)
[feat] add sip authentication
initial merge of the sip authentication mechanism
-rw-r--r--pkg/vpn/bonafide/auth.go21
-rw-r--r--pkg/vpn/bonafide/auth_anon.go45
-rw-r--r--pkg/vpn/bonafide/auth_sip.go88
-rw-r--r--pkg/vpn/bonafide/bonafide.go41
-rw-r--r--pkg/vpn/bonafide/eip_service.go24
-rw-r--r--pkg/vpn/bonafide/testdata/eip-service3-sip.json174
6 files changed, 372 insertions, 21 deletions
diff --git a/pkg/vpn/bonafide/auth.go b/pkg/vpn/bonafide/auth.go
new file mode 100644
index 0000000..b6b3eec
--- /dev/null
+++ b/pkg/vpn/bonafide/auth.go
@@ -0,0 +1,21 @@
+// Copyright (C) 2018 LEAP
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+package bonafide
+
+type Credentials struct {
+ User string
+ Password string
+}
diff --git a/pkg/vpn/bonafide/auth_anon.go b/pkg/vpn/bonafide/auth_anon.go
new file mode 100644
index 0000000..61916e6
--- /dev/null
+++ b/pkg/vpn/bonafide/auth_anon.go
@@ -0,0 +1,45 @@
+// Copyright (C) 2018-2020 LEAP
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+package bonafide
+
+import (
+ "fmt"
+ "io/ioutil"
+)
+
+type AnonymousAuthentication struct {
+ bonafide *Bonafide
+}
+
+func (a *AnonymousAuthentication) GetPemCertificate() ([]byte, error) {
+ resp, err := a.bonafide.client.Post(certAPI, "", nil)
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Body.Close()
+ if resp.StatusCode == 404 {
+ resp, err = a.bonafide.client.Post(certAPI3, "", nil)
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Body.Close()
+ }
+ if resp.StatusCode != 200 {
+ return nil, fmt.Errorf("Get vpn cert has failed with status: %s", resp.Status)
+ }
+
+ return ioutil.ReadAll(resp.Body)
+}
diff --git a/pkg/vpn/bonafide/auth_sip.go b/pkg/vpn/bonafide/auth_sip.go
new file mode 100644
index 0000000..d8ebedb
--- /dev/null
+++ b/pkg/vpn/bonafide/auth_sip.go
@@ -0,0 +1,88 @@
+// Copyright (C) 2018 LEAP
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+package bonafide
+
+import (
+ "encoding/json"
+ "fmt"
+ "io/ioutil"
+ "log"
+ "net/http"
+ "strings"
+)
+
+type SipAuthentication struct {
+ bonafide *Bonafide
+}
+
+func (a *SipAuthentication) GetPemCertificate() ([]byte, error) {
+ cred := a.bonafide.credentials
+ if cred == nil {
+ return nil, fmt.Errorf("Need bonafide credentials for sip auth")
+ }
+ credJson, err := formatCredentials(cred.User, cred.Password)
+ if err != nil {
+ return nil, fmt.Errorf("Cannot encode credentials: %s", err)
+ }
+ token, err := a.getToken(credJson)
+ if err != nil {
+ return nil, fmt.Errorf("Error while getting token: %s", err)
+ }
+ cert, err := a.getProtectedCert(string(token))
+ if err != nil {
+ return nil, fmt.Errorf("Error while getting cert: %s", err)
+ }
+ return cert, nil
+}
+
+func (a *SipAuthentication) getProtectedCert(token string) ([]byte, error) {
+ req, err := http.NewRequest("POST", certAPI, strings.NewReader(""))
+ req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token))
+ resp, err := a.bonafide.client.Do(req)
+ if err != nil {
+ return nil, fmt.Errorf("Error while getting token: %s", err)
+ }
+ defer resp.Body.Close()
+ if resp.StatusCode != 200 {
+ return nil, fmt.Errorf("Cannot get cert: Error %d", resp.StatusCode)
+ }
+ return ioutil.ReadAll(resp.Body)
+}
+
+func (a *SipAuthentication) getToken(credJson string) ([]byte, error) {
+ /* TODO
+ [ ] get token from disk?
+ [ ] check if expired? set a goroutine to refresh it periodically?
+ */
+ resp, err := http.Post(authAPI, "text/json", strings.NewReader(credJson))
+ if err != nil {
+ log.Fatal("Error on auth request: ", err)
+ }
+ defer resp.Body.Close()
+ if resp.StatusCode != 200 {
+ return nil, fmt.Errorf("Cannot get token: Error %d", resp.StatusCode)
+ }
+ return ioutil.ReadAll(resp.Body)
+}
+
+func formatCredentials(user, pass string) (string, error) {
+ c := Credentials{User: user, Password: pass}
+ credJson, err := json.Marshal(c)
+ if err != nil {
+ return "", err
+ }
+ return string(credJson), nil
+}
diff --git a/pkg/vpn/bonafide/bonafide.go b/pkg/vpn/bonafide/bonafide.go
index fd32f2a..16a900d 100644
--- a/pkg/vpn/bonafide/bonafide.go
+++ b/pkg/vpn/bonafide/bonafide.go
@@ -32,16 +32,21 @@ import (
const (
certAPI = config.APIURL + "1/cert"
certAPI3 = config.APIURL + "3/cert"
+ authAPI = config.APIURL + "3/auth"
secondsPerHour = 60 * 60
retryFetchJSONSeconds = 15
)
+// Bonafide exposes all the methods needed to communicate with the LEAP server.
type Bonafide struct {
client httpClient
eip *eipService
tzOffsetHours int
+ auth Authentication
+ credentials *Credentials
}
+// A Gateway is each one of the remotes we can pass to OpenVPN. It contains a description of all the fields that the eip-service advertises.
type Gateway struct {
Host string
IPAddress string
@@ -55,6 +60,13 @@ type openvpnConfig map[string]interface{}
type httpClient interface {
Post(url, contentType string, body io.Reader) (resp *http.Response, err error)
+ Do(req *http.Request) (*http.Response, error)
+}
+
+// The Authentication interface allows to get a Certificate in Pem format.
+// We implement Anonymous Authentication (Riseup et al), and Sip (Libraries).
+type Authentication interface {
+ GetPemCertificate() ([]byte, error)
}
type geoLocation struct {
@@ -66,6 +78,7 @@ type geoLocation struct {
SortedGateways []string `json:"gateways"`
}
+// New Bonafide: Initializes a Bonafide object. By default, no Credentials are passed.
func New() *Bonafide {
certs := x509.NewCertPool()
certs.AppendCertsFromPEM(config.CaCert)
@@ -79,31 +92,23 @@ func New() *Bonafide {
_, tzOffsetSeconds := time.Now().Zone()
tzOffsetHours := tzOffsetSeconds / secondsPerHour
- return &Bonafide{
+ b := &Bonafide{
client: client,
eip: nil,
tzOffsetHours: tzOffsetHours,
}
+ auth := AnonymousAuthentication{b}
+ b.auth = &auth
+ return b
}
-func (b *Bonafide) GetCertPem() ([]byte, error) {
- resp, err := b.client.Post(certAPI, "", nil)
- if err != nil {
- return nil, err
- }
- defer resp.Body.Close()
- if resp.StatusCode == 404 {
- resp, err = b.client.Post(certAPI3, "", nil)
- if err != nil {
- return nil, err
- }
- defer resp.Body.Close()
- }
- if resp.StatusCode != 200 {
- return nil, fmt.Errorf("get vpn cert has failed with status: %s", resp.Status)
- }
+func (b *Bonafide) SetCredentials(username, password string) {
+ b.credentials = &Credentials{username, password}
+}
- return ioutil.ReadAll(resp.Body)
+func (b *Bonafide) GetPemCertificate() ([]byte, error) {
+ cert, err := b.auth.GetPemCertificate()
+ return cert, err
}
func (b *Bonafide) GetGateways(transport string) ([]Gateway, error) {
diff --git a/pkg/vpn/bonafide/eip_service.go b/pkg/vpn/bonafide/eip_service.go
index c097e8a..148b052 100644
--- a/pkg/vpn/bonafide/eip_service.go
+++ b/pkg/vpn/bonafide/eip_service.go
@@ -23,6 +23,7 @@ type eipService struct {
Gateways []gatewayV3
Locations map[string]location
OpenvpnConfiguration openvpnConfig `json:"openvpn_configuration"`
+ auth string
defaultGateway string
}
@@ -65,6 +66,22 @@ type transportV3 struct {
Options map[string]string
}
+func (b *Bonafide) setupAuthentication(i interface{}) {
+ switch i.(type) {
+ case eipService:
+ switch auth := b.eip.auth; auth {
+ case "anon":
+ // Do nothing, we're set on initialization.
+ case "sip":
+ b.auth = &SipAuthentication{b}
+ default:
+ log.Printf("BUG: unknown authentication method %s", auth)
+ }
+ case eipServiceV1:
+ // Do nothing, no auth on v1.
+ }
+}
+
func (b *Bonafide) fetchEipJSON() error {
resp, err := b.client.Post(eip3API, "", nil)
for err != nil {
@@ -80,24 +97,25 @@ func (b *Bonafide) fetchEipJSON() error {
case 404:
buf := make([]byte, 128)
resp.Body.Read(buf)
- log.Printf("Error fetching eip v3 json: %s", buf)
+ log.Printf("Error fetching eip v3 json")
resp, err = b.client.Post(eip1API, "", nil)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
- return fmt.Errorf("get eip json has failed with status: %s", resp.Status)
+ return fmt.Errorf("Get eip json has failed with status: %s", resp.Status)
}
b.eip, err = decodeEIP1(resp.Body)
default:
- return fmt.Errorf("get eip json has failed with status: %s", resp.Status)
+ return fmt.Errorf("Get eip json has failed with status: %s", resp.Status)
}
if err != nil {
return err
}
+ b.setupAuthentication(b.eip)
b.sortGateways()
return nil
}
diff --git a/pkg/vpn/bonafide/testdata/eip-service3-sip.json b/pkg/vpn/bonafide/testdata/eip-service3-sip.json
new file mode 100644
index 0000000..83f80e5
--- /dev/null
+++ b/pkg/vpn/bonafide/testdata/eip-service3-sip.json
@@ -0,0 +1,174 @@
+{
+ "gateways": [
+ {
+ "capabilities": {
+ "adblock": false,
+ "filter_dns": false,
+ "limited": false,
+ "transport": [
+ {
+ "type": "openvpn",
+ "ports": [
+ "443"
+ ],
+ "protocols": [
+ "tcp"
+ ]
+ },
+ {
+ "type": "obfs4",
+ "ports": [
+ "2345"
+ ],
+ "protocols": [
+ "tcp"
+ ],
+ "options": {
+ "cert": "obfs-cert",
+ "iat-mode": "0"
+ }
+ }
+ ],
+ "user_ips": false
+ },
+ "host": "1.example.com",
+ "ip_address": "1.1.1.1",
+ "location": "a"
+ },
+ {
+ "capabilities": {
+ "adblock": false,
+ "filter_dns": false,
+ "limited": false,
+ "transport": [
+ {
+ "type": "openvpn",
+ "ports": [
+ "443"
+ ],
+ "protocols": [
+ "tcp"
+ ]
+ }
+ ],
+ "user_ips": false
+ },
+ "host": "2.example.com",
+ "ip_address": "2.2.2.2",
+ "location": "b"
+ },
+ {
+ "capabilities": {
+ "adblock": false,
+ "filter_dns": false,
+ "limited": false,
+ "transport": [
+ {
+ "type": "openvpn",
+ "ports": [
+ "443"
+ ],
+ "protocols": [
+ "tcp"
+ ]
+ },
+ {
+ "type": "obfs4",
+ "ports": [
+ "2345"
+ ],
+ "protocols": [
+ "tcp"
+ ],
+ "options": {
+ "cert": "obfs-cert",
+ "iat-mode": "0"
+ }
+ }
+ ],
+ "user_ips": false
+ },
+ "host": "3.example.com",
+ "ip_address": "3.3.3.3",
+ "location": "b"
+ },
+ {
+ "capabilities": {
+ "adblock": false,
+ "filter_dns": false,
+ "limited": false,
+ "transport": [
+ {
+ "type": "openvpn",
+ "ports": [
+ "443"
+ ],
+ "protocols": [
+ "tcp"
+ ]
+ }
+ ],
+ "user_ips": false
+ },
+ "host": "4.example.com",
+ "ip_address": "4.4.4.4",
+ "location": "c"
+ },
+ {
+ "capabilities": {
+ "adblock": false,
+ "filter_dns": false,
+ "limited": false,
+ "transport": [
+ {
+ "type": "obfs4",
+ "ports": [
+ "2345"
+ ],
+ "protocols": [
+ "tcp"
+ ],
+ "options": {
+ "cert": "obfs-cert",
+ "iat-mode": "0"
+ }
+ }
+ ],
+ "user_ips": false
+ },
+ "host": "5.example.com",
+ "ip_address": "5.5.5.5",
+ "location": "c"
+ }
+ ],
+ "locations": {
+ "a": {
+ "country_code": "AA",
+ "hemisphere": "N",
+ "name": "a",
+ "timezone": "-5"
+ },
+ "b": {
+ "country_code": "BB",
+ "hemisphere": "S",
+ "name": "b",
+ "timezone": "+1"
+ },
+ "c": {
+ "country_code": "CC",
+ "hemisphere": "N",
+ "name": "c",
+ "timezone": "+8"
+ }
+ },
+ "openvpn_configuration": {
+ "auth": "SHA1",
+ "cipher": "AES-128-CBC",
+ "keepalive": "10 30",
+ "tls-cipher": "DHE-RSA-AES128-SHA",
+ "tun-ipv6": true
+ },
+ "auth": "sip",
+ "serial": 1,
+ "version": 3
+}