From c236dfcfdd60ea700e5f50ed2568398cd161dd4c Mon Sep 17 00:00:00 2001 From: "kali kaneko (leap communications)" Date: Mon, 27 Jan 2020 20:44:34 -0600 Subject: [feat] add sip authentication initial merge of the sip authentication mechanism --- pkg/vpn/bonafide/auth.go | 21 +++ pkg/vpn/bonafide/auth_anon.go | 45 ++++++ pkg/vpn/bonafide/auth_sip.go | 88 ++++++++++++ pkg/vpn/bonafide/bonafide.go | 41 +++--- pkg/vpn/bonafide/eip_service.go | 24 +++- pkg/vpn/bonafide/testdata/eip-service3-sip.json | 174 ++++++++++++++++++++++++ 6 files changed, 372 insertions(+), 21 deletions(-) create mode 100644 pkg/vpn/bonafide/auth.go create mode 100644 pkg/vpn/bonafide/auth_anon.go create mode 100644 pkg/vpn/bonafide/auth_sip.go create mode 100644 pkg/vpn/bonafide/testdata/eip-service3-sip.json (limited to 'pkg') 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 . + +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 . + +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 . + +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 +} -- cgit v1.2.3