summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/api.go110
-rw-r--r--src/config.go42
-rw-r--r--src/discovery.go165
-rw-r--r--src/fsm.go197
-rw-r--r--src/http.go172
-rw-r--r--src/log.go66
-rw-r--r--src/oauth.go393
-rw-r--r--src/openvpn.go12
-rw-r--r--src/server.go178
-rw-r--r--src/server_test.go214
-rw-r--r--src/state.go108
-rw-r--r--src/test_data/empty0
-rw-r--r--src/test_data/generate.sh58
-rw-r--r--src/test_data/generate_forged.py37
-rw-r--r--src/test_data/organization_list.json1
-rw-r--r--src/test_data/organization_list.json.minisig4
-rw-r--r--src/test_data/organization_list.json.tc_servlist.minisig4
-rw-r--r--src/test_data/other_list.json1
-rw-r--r--src/test_data/other_list.json.minisig4
-rw-r--r--src/test_data/public.key2
-rw-r--r--src/test_data/random.txt1
-rw-r--r--src/test_data/secret.key2
-rw-r--r--src/test_data/server_list.json3
-rw-r--r--src/test_data/server_list.json.blake2bbin64 -> 0 bytes
-rw-r--r--src/test_data/server_list.json.forged_keyid.minisig4
-rw-r--r--src/test_data/server_list.json.forged_pure.minisig4
-rw-r--r--src/test_data/server_list.json.large_time.minisig4
-rw-r--r--src/test_data/server_list.json.minisig4
-rw-r--r--src/test_data/server_list.json.pure.minisig4
-rw-r--r--src/test_data/server_list.json.tc_earliertime.minisig4
-rw-r--r--src/test_data/server_list.json.tc_emptyfile.minisig4
-rw-r--r--src/test_data/server_list.json.tc_emptytime.minisig4
-rw-r--r--src/test_data/server_list.json.tc_latertime.minisig4
-rw-r--r--src/test_data/server_list.json.tc_nofile.minisig4
-rw-r--r--src/test_data/server_list.json.tc_nohashed.minisig4
-rw-r--r--src/test_data/server_list.json.tc_notime.minisig4
-rw-r--r--src/test_data/server_list.json.tc_orglist.minisig4
-rw-r--r--src/test_data/server_list.json.tc_otherfile.minisig4
-rw-r--r--src/test_data/server_list.json.tc_random.minisig4
-rw-r--r--src/test_data/server_list.json.wrong_key.minisig4
-rw-r--r--src/test_data/wrong_public.key2
-rw-r--r--src/test_data/wrong_secret.key2
-rw-r--r--src/util.go21
-rw-r--r--src/verify.go205
-rw-r--r--src/verify_test.go140
-rw-r--r--src/wireguard.go52
46 files changed, 0 insertions, 2260 deletions
diff --git a/src/api.go b/src/api.go
deleted file mode 100644
index 93c1c42..0000000
--- a/src/api.go
+++ /dev/null
@@ -1,110 +0,0 @@
-package eduvpn
-
-import (
- "encoding/json"
- "errors"
- "fmt"
- "net/http"
- "net/url"
-)
-
-// Authenticated wrappers on top of HTTP
-func (server *Server) apiAuthenticated(method string, endpoint string, opts *HTTPOptionalParams) (http.Header, []byte, error) {
- // Ensure optional is not nil as we will fill it with headers
- if opts == nil {
- opts = &HTTPOptionalParams{}
- }
- url := server.Endpoints.API.V3.API + endpoint
-
- // Ensure we have valid tokens
- oauthErr := server.OAuth.EnsureTokens()
-
- if oauthErr != nil {
- return nil, nil, oauthErr
- }
-
- headerKey := "Authorization"
- headerValue := fmt.Sprintf("Bearer %s", server.OAuth.Token.Access)
- if opts.Headers != nil {
- opts.Headers.Add(headerKey, headerValue)
- } else {
- opts.Headers = http.Header{headerKey: {headerValue}}
- }
- return HTTPMethodWithOpts(method, url, opts)
-}
-
-func (server *Server) apiAuthenticatedRetry(method string, endpoint string, opts *HTTPOptionalParams) (http.Header, []byte, error) {
- header, body, bodyErr := server.apiAuthenticated(method, endpoint, opts)
- if bodyErr != nil {
- var error *HTTPStatusError
-
- // Only retry authenticated if we get a HTTP 401
- if errors.As(bodyErr, &error) && error.Status == 401 {
- GetVPNState().Log(LOG_INFO, fmt.Sprintf("API: Got HTTP error %v, retrying authenticated", error))
- // Tell the method that the token is expired
- server.OAuth.Token.ExpiredTimestamp = GenerateTimeSeconds()
- return server.apiAuthenticated(method, endpoint, opts)
- }
- return header, nil, bodyErr
- }
- return header, body, bodyErr
-}
-
-func (server *Server) APIInfo() error {
- _, body, bodyErr := server.apiAuthenticatedRetry(http.MethodGet, "/info", nil)
- if bodyErr != nil {
- return bodyErr
- }
- structure := ServerProfileInfo{}
- jsonErr := json.Unmarshal(body, &structure)
-
- if jsonErr != nil {
- return jsonErr
- }
-
- server.Profiles = structure
- server.ProfilesRaw = string(body)
- return nil
-}
-
-func (server *Server) APIConnectWireguard(profile_id string, pubkey string) (string, string, error) {
- headers := http.Header{
- "content-type": {"application/x-www-form-urlencoded"},
- "accept": {"application/x-wireguard-profile"},
- }
-
- urlForm := url.Values{
- "profile_id": {profile_id},
- "public_key": {pubkey},
- }
- header, connectBody, connectErr := server.apiAuthenticatedRetry(http.MethodPost, "/connect", &HTTPOptionalParams{Headers: headers, Body: urlForm})
- if connectErr != nil {
- return "", "", connectErr
- }
-
- expires := header.Get("expires")
- return string(connectBody), expires, nil
-}
-
-func (server *Server) APIConnectOpenVPN(profile_id string) (string, string, error) {
- headers := http.Header{
- "content-type": {"application/x-www-form-urlencoded"},
- "accept": {"application/x-openvpn-profile"},
- }
-
- urlForm := url.Values{
- "profile_id": {profile_id},
- }
- header, connectBody, connectErr := server.apiAuthenticatedRetry(http.MethodPost, "/connect", &HTTPOptionalParams{Headers: headers, Body: urlForm})
- if connectErr != nil {
- return "", "", connectErr
- }
-
- expires := header.Get("expires")
- return string(connectBody), expires, nil
-}
-
-// This needs no further return value as it's best effort
-func (server *Server) APIDisconnect() {
- server.apiAuthenticatedRetry(http.MethodPost, "/disconnect", nil)
-}
diff --git a/src/config.go b/src/config.go
deleted file mode 100644
index 6db3cb2..0000000
--- a/src/config.go
+++ /dev/null
@@ -1,42 +0,0 @@
-package eduvpn
-
-import (
- "encoding/json"
- "fmt"
- "io/ioutil"
- "os"
- "path"
-)
-
-func (eduvpn *VPNState) EnsureConfigDir() error {
- mkdirErr := os.MkdirAll(eduvpn.ConfigDirectory, os.ModePerm)
- if mkdirErr != nil {
- return mkdirErr
- }
- return nil
-}
-
-func (eduvpn *VPNState) GetConfigName() string {
- pathString := path.Join(eduvpn.ConfigDirectory, eduvpn.Name)
- return fmt.Sprintf("%s.json", pathString)
-}
-
-func (eduvpn *VPNState) WriteConfig() error {
- configDirErr := eduvpn.EnsureConfigDir()
- if configDirErr != nil {
- return configDirErr
- }
- jsonString, marshalErr := json.Marshal(eduvpn)
- if marshalErr != nil {
- return marshalErr
- }
- return ioutil.WriteFile(eduvpn.GetConfigName(), jsonString, 0o644)
-}
-
-func (eduvpn *VPNState) LoadConfig() error {
- bytes, readErr := ioutil.ReadFile(eduvpn.GetConfigName())
- if readErr != nil {
- return readErr
- }
- return json.Unmarshal(bytes, eduvpn)
-}
diff --git a/src/discovery.go b/src/discovery.go
deleted file mode 100644
index fa3cac6..0000000
--- a/src/discovery.go
+++ /dev/null
@@ -1,165 +0,0 @@
-package eduvpn
-
-import (
- "encoding/json"
- "fmt"
-)
-
-type DiscoFileError struct {
- URL string
- Err error
-}
-
-func (e *DiscoFileError) Error() string {
- return fmt.Sprintf("failed obtaining disco file %s with error %v", e.URL, e.Err)
-}
-
-type DiscoSigFileError struct {
- URL string
- Err error
-}
-
-func (e *DiscoSigFileError) Error() string {
- return fmt.Sprintf("failed obtaining disco signature file %s with error %v", e.URL, e.Err)
-}
-
-type DiscoVerifyError struct {
- File string
- Sigfile string
- Err error
-}
-
-func (e *DiscoVerifyError) Error() string {
- return fmt.Sprintf("failed verifying file %s with signature %s due to error %v", e.File, e.Sigfile, e.Err)
-}
-
-type DiscoJSONError struct {
- Body string
- Err error
-}
-
-func (e *DiscoJSONError) Error() string {
- return fmt.Sprintf("failed parsing JSON for contents %s with error %v", e.Body, e.Err)
-}
-
-type OrganizationList struct {
- JSON json.RawMessage `json:"organization_list"`
- Version uint64 `json:"v"`
- Timestamp int64 `json:"-"`
-}
-
-type ServersList struct {
- JSON json.RawMessage `json:"server_list"`
- Version uint64 `json:"v"`
- Timestamp int64 `json:"-"`
-}
-
-type DiscoLists struct {
- Organizations OrganizationList
- Servers ServersList
-}
-
-// Helper function that gets a disco json
-func getDiscoFile(jsonFile string, previousVersion uint64, structure interface{}) error {
- // Get json data
- discoURL := "https://disco.eduvpn.org/v2/"
- fileURL := discoURL + jsonFile
- _, fileBody, fileErr := HTTPGet(fileURL)
-
- if fileErr != nil {
- return &DiscoFileError{fileURL, fileErr}
- }
-
- // Get signature
- sigFile := jsonFile + ".minisig"
- sigURL := discoURL + sigFile
- _, sigBody, sigFileErr := HTTPGet(sigURL)
-
- if sigFileErr != nil {
- return &DiscoSigFileError{URL: sigURL, Err: sigFileErr}
- }
-
- // Verify signature
- // Set this to true when we want to force prehash
- forcePrehash := false
- verifySuccess, verifyErr := Verify(string(sigBody), fileBody, jsonFile, previousVersion, forcePrehash)
-
- if !verifySuccess || verifyErr != nil {
- return &DiscoVerifyError{File: jsonFile, Sigfile: sigFile, Err: verifyErr}
- }
-
- // Parse JSON to extract version and list
- jsonErr := json.Unmarshal(fileBody, structure)
-
- if jsonErr != nil {
- return &DiscoJSONError{Body: string(fileBody), Err: jsonErr}
- }
-
- return nil
-}
-
-type GetListError struct {
- File string
- Err error
-}
-
-func (e *GetListError) Error() string {
- return fmt.Sprintf("failed getting disco list file %s with error %v", e.File, e.Err)
-}
-
-// FIXME: Implement based on
-// https://github.com/eduvpn/documentation/blob/v3/SERVER_DISCOVERY.md
-// - [IMPLEMENTED] on "first launch" when offering the search for "Institute Access" and "Organizations";
-// - [TODO] when the user tries to add new server AND the user did NOT yet choose an organization before;
-// - [TODO] when the authorization for the server associated with an already chosen organization is triggered, e.g. after expiry or revocation.
-func (eduvpn *VPNState) DetermineOrganizationsUpdate() bool {
- return string(eduvpn.DiscoList.Organizations.JSON) == ""
-}
-
-// https://github.com/eduvpn/documentation/blob/v3/SERVER_DISCOVERY.md
-// - [Implemented] The application MUST always fetch the server_list.json at application start.
-// - The application MAY refresh the server_list.json periodically, e.g. once every hour.
-func (eduvpn *VPNState) DetermineServersUpdate() bool {
- // No servers, we should update
- if string(eduvpn.DiscoList.Servers.JSON) == "" {
- return true
- }
- // 1 hour from the last update
- should_update_time := eduvpn.DiscoList.Servers.Timestamp + 3600
- now := GenerateTimeSeconds()
- if now >= should_update_time {
- return true
- }
- GetVPNState().Log(LOG_INFO, "No update needed for servers, 1h is not passed yet")
- return false
-}
-
-// Get the organization list
-func (eduvpn *VPNState) GetOrganizationsList() (string, error) {
- if !eduvpn.DetermineOrganizationsUpdate() {
- return string(eduvpn.DiscoList.Organizations.JSON), nil
- }
- file := "organization_list.json"
- err := getDiscoFile(file, eduvpn.DiscoList.Organizations.Version, &eduvpn.DiscoList.Organizations)
- if err != nil {
- // Return previous with an error
- return string(eduvpn.DiscoList.Organizations.JSON), &GetListError{File: file, Err: err}
- }
- return string(eduvpn.DiscoList.Organizations.JSON), nil
-}
-
-// Get the server list
-func (eduvpn *VPNState) GetServersList() (string, error) {
- if !eduvpn.DetermineServersUpdate() {
- return string(eduvpn.DiscoList.Servers.JSON), nil
- }
- file := "server_list.json"
- err := getDiscoFile(file, eduvpn.DiscoList.Servers.Version, &eduvpn.DiscoList.Servers)
- if err != nil {
- // Return previous with an error
- return string(eduvpn.DiscoList.Servers.JSON), &GetListError{File: file, Err: err}
- }
- // Update servers timestamp
- eduvpn.DiscoList.Servers.Timestamp = GenerateTimeSeconds()
- return string(eduvpn.DiscoList.Servers.JSON), nil
-}
diff --git a/src/fsm.go b/src/fsm.go
deleted file mode 100644
index 223b42f..0000000
--- a/src/fsm.go
+++ /dev/null
@@ -1,197 +0,0 @@
-package eduvpn
-
-import (
- "fmt"
- "os"
- "sort"
-)
-
-type (
- FSMStateID int8
- FSMStateIDSlice []FSMStateID
-)
-
-func (v FSMStateIDSlice) Len() int {
- return len(v)
-}
-
-func (v FSMStateIDSlice) Less(i, j int) bool {
- return v[i] < v[j]
-}
-
-func (v FSMStateIDSlice) Swap(i, j int) {
- v[i], v[j] = v[j], v[i]
-}
-
-const (
- // Deregistered means the app is not registered with the wrapper
- DEREGISTERED FSMStateID = iota
-
- // No Server means the user has not chosen a server yet
- NO_SERVER
-
- // Chosen Server means the user has chosen a server to connect to
- CHOSEN_SERVER
-
- // OAuth Started means the OAuth process has started
- OAUTH_STARTED
-
- // Authenticated means the OAuth process has finished and the user is now authenticated with the server
- AUTHENTICATED
-
- // Requested config means the user has requested a config for connecting
- REQUEST_CONFIG
-
- // Has config means the user has gotten a config
- HAS_CONFIG
-
- // Ask profile means the go code is asking for a profile selection from the ui
- ASK_PROFILE
-
- // Connected means the user has been connected to the server
- CONNECTED
-)
-
-func (s FSMStateID) String() string {
- switch s {
- case DEREGISTERED:
- return "Deregistered"
- case NO_SERVER:
- return "No_Server"
- case CHOSEN_SERVER:
- return "Chosen_Server"
- case OAUTH_STARTED:
- return "OAuth_Started"
- case HAS_CONFIG:
- return "Has_Config"
- case REQUEST_CONFIG:
- return "Request_Config"
- case ASK_PROFILE:
- return "Ask_Profile"
- case AUTHENTICATED:
- return "Authenticated"
- case CONNECTED:
- return "Connected"
- default:
- panic("unknown conversion of state to string")
- }
-}
-
-type FSMTransition struct {
- To FSMStateID
- Description string
-}
-
-type (
- FSMTransitions []FSMTransition
- FSMStates map[FSMStateID]FSMTransitions
-)
-
-type FSM struct {
- States FSMStates
- Current FSMStateID
-}
-
-func (eduvpn *VPNState) HasTransition(check FSMStateID) bool {
- for _, transition_state := range eduvpn.FSM.States[eduvpn.FSM.Current] {
- if transition_state.To == check {
- return true
- }
- }
-
- return false
-}
-
-func (eduvpn *VPNState) InState(check FSMStateID) bool {
- return check == eduvpn.FSM.Current
-}
-
-func (eduvpn *VPNState) writeGraph() {
- graph := eduvpn.GenerateGraph()
-
- f, err := os.Create("debug.graph")
- if err != nil {
- eduvpn.Log(LOG_INFO, fmt.Sprintf("Failed to write debug fsm graph with error %v", err))
- }
-
- defer f.Close()
-
- f.WriteString(graph)
-}
-
-func (eduvpn *VPNState) GoTransitionWithData(newState FSMStateID, data string) bool {
- ok := eduvpn.HasTransition(newState)
-
- if ok {
- oldState := eduvpn.FSM.Current
- eduvpn.FSM.Current = newState
- if eduvpn.Debug {
- eduvpn.writeGraph()
- }
- eduvpn.StateCallback(oldState.String(), newState.String(), data)
- }
-
- return ok
-}
-
-func (eduvpn *VPNState) GoTransition(newState FSMStateID) bool {
- return eduvpn.GoTransitionWithData(newState, "")
-}
-
-func (eduvpn *VPNState) generateDotGraph() string {
- graph := `digraph eduvpn_fsm {
-nodesep = 2;
-remincross = false;
-`
- graph += "node[color=blue]; " + eduvpn.FSM.Current.String() + ";\n"
- graph += "node [color=black];\n"
- for state, transitions := range eduvpn.FSM.States {
- for _, transition := range transitions {
- graph += state.String() + " -> " + transition.To.String() + " [label=\"" + transition.Description + "\"]\n"
- }
- }
- graph += "}"
- return graph
-}
-
-func (eduvpn *VPNState) generateMermaidGraph() string {
- graph := "graph TD\n"
- sorted_fsm := make(FSMStateIDSlice, 0, len(eduvpn.FSM.States))
- for state_id := range eduvpn.FSM.States {
- sorted_fsm = append(sorted_fsm, state_id)
- }
- sort.Sort(sorted_fsm)
- for _, state := range sorted_fsm {
- transitions := eduvpn.FSM.States[state]
- for _, transition := range transitions {
- if state == eduvpn.FSM.Current {
- graph += "\nstyle " + state.String() + " fill:cyan\n"
- } else {
- graph += "\nstyle " + state.String() + " fill:white\n"
- }
- graph += state.String() + "(" + state.String() + ") " + "-->|" + transition.Description + "| " + transition.To.String() + "\n"
- }
- }
- return graph
-}
-
-func (eduvpn *VPNState) GenerateGraph() string {
- return eduvpn.generateMermaidGraph()
-}
-
-func (eduvpn *VPNState) InitializeFSM() {
- eduvpn.FSM = FSM{
- States: FSMStates{
- DEREGISTERED: {{NO_SERVER, "Client registers"}},
- NO_SERVER: {{CHOSEN_SERVER, "User chooses a server"}},
- CHOSEN_SERVER: {{AUTHENTICATED, "Found tokens in config"}, {OAUTH_STARTED, "No tokens found in config"}},
- OAUTH_STARTED: {{AUTHENTICATED, "User authorizes with browser"}},
- AUTHENTICATED: {{OAUTH_STARTED, "Re-authenticate with OAuth"}, {REQUEST_CONFIG, "Client requests a config"}},
- REQUEST_CONFIG: {{ASK_PROFILE, "Multiple profiles found"}, {HAS_CONFIG, "Success, only one profile"}},
- ASK_PROFILE: {{HAS_CONFIG, "User chooses profile and success"}},
- HAS_CONFIG: {{CONNECTED, "OS reports connected"}},
- CONNECTED: {{AUTHENTICATED, "OS reports disconnected"}},
- },
- Current: DEREGISTERED,
- }
-}
diff --git a/src/http.go b/src/http.go
deleted file mode 100644
index bbc866b..0000000
--- a/src/http.go
+++ /dev/null
@@ -1,172 +0,0 @@
-package eduvpn
-
-import (
- "fmt"
- "io"
- "io/ioutil"
- "net/http"
- "net/url"
- "strings"
-)
-
-type HTTPResourceError struct {
- URL string
- Err error
-}
-
-func (e *HTTPResourceError) Error() string {
- return fmt.Sprintf("failed obtaining HTTP resource %s with error %v", e.URL, e.Err)
-}
-
-type HTTPStatusError struct {
- URL string
- Status int
-}
-
-func (e *HTTPStatusError) Error() string {
- return fmt.Sprintf("failed obtaining HTTP resource %s as it gave an unsuccesful status code %d", e.URL, e.Status)
-}
-
-type HTTPReadError struct {
- URL string
- Err error
-}
-
-func (e *HTTPReadError) Error() string {
- return fmt.Sprintf("failed reading HTTP resource %s with error %v", e.URL, e.Err)
-}
-
-type HTTPParseJsonError struct {
- URL string
- Body string
- Err error
-}
-
-func (e *HTTPParseJsonError) Error() string {
- return fmt.Sprintf("failed parsing json %s for HTTP resource %s with error %v", e.Body, e.URL, e.Err)
-}
-
-type HTTPRequestCreateError struct {
- URL string
- Err error
-}
-
-func (e *HTTPRequestCreateError) Error() string {
- return fmt.Sprintf("failed to create HTTP request with url %s and error %v", e.URL, e.Err)
-}
-
-type URLParameters map[string]string
-
-type HTTPOptionalParams struct {
- Headers http.Header
- URLParameters URLParameters
- Body url.Values
-}
-
-// Construct an URL including on parameters
-func HTTPConstructURL(baseURL string, parameters URLParameters) (string, error) {
- url, err := url.Parse(baseURL)
- if err != nil {
- return "", err
- }
-
- q := url.Query()
-
- for parameter, value := range parameters {
- q.Set(parameter, value)
- }
- url.RawQuery = q.Encode()
- return url.String(), nil
-}
-
-// Convenience functions
-func HTTPGet(url string) (http.Header, []byte, error) {
- return HTTPMethodWithOpts(http.MethodGet, url, nil)
-}
-
-func HTTPPost(url string, body url.Values) (http.Header, []byte, error) {
- return HTTPMethodWithOpts(http.MethodGet, url, &HTTPOptionalParams{Body: body})
-}
-
-func HTTPGetWithOpts(url string, opts *HTTPOptionalParams) (http.Header, []byte, error) {
- return HTTPMethodWithOpts(http.MethodGet, url, opts)
-}
-
-func HTTPPostWithOpts(url string, opts *HTTPOptionalParams) (http.Header, []byte, error) {
- return HTTPMethodWithOpts(http.MethodPost, url, opts)
-}
-
-func httpOptionalURL(url string, opts *HTTPOptionalParams) (string, error) {
- if opts != nil {
- url, urlErr := HTTPConstructURL(url, opts.URLParameters)
-
- if urlErr != nil {
- return url, &HTTPRequestCreateError{URL: url, Err: urlErr}
- }
- return url, nil
- }
- return url, nil
-}
-
-func httpOptionalHeaders(req *http.Request, opts *HTTPOptionalParams) {
- // Add headers
- if opts != nil && req != nil {
- for k, v := range opts.Headers {
- req.Header.Add(k, v[0])
- }
- }
-}
-
-func httpOptionalBodyReader(opts *HTTPOptionalParams) io.Reader {
- if opts != nil && opts.Body != nil {
- return strings.NewReader(opts.Body.Encode())
- }
- return nil
-}
-
-func HTTPMethodWithOpts(method string, url string, opts *HTTPOptionalParams) (http.Header, []byte, error) {
- // Make sure the url contains all the parameters
- // This can return an error,
- // it already has the right error so so we don't wrap it further
- url, urlErr := httpOptionalURL(url, opts)
- if urlErr != nil {
- return nil, nil, urlErr
- }
-
- // Create a client
- client := &http.Client{}
-
- // Create request object with the body reader generated from the optional arguments
- req, reqErr := http.NewRequest(method, url, httpOptionalBodyReader(opts))
- if reqErr != nil {
- return nil, nil, &HTTPRequestCreateError{URL: url, Err: reqErr}
- }
-
- // See https://stackoverflow.com/questions/17714494/golang-http-request-results-in-eof-errors-when-making-multiple-requests-successi
- req.Close = true
-
- // Make sure the headers contain all the parameters
- httpOptionalHeaders(req, opts)
-
- // Do request
- resp, respErr := client.Do(req)
- if respErr != nil {
- return nil, nil, &HTTPResourceError{URL: url, Err: respErr}
- }
-
- // Request successful, make sure body is closed at the end
- defer resp.Body.Close()
-
- // Return a string
- body, readErr := ioutil.ReadAll(resp.Body)
- if readErr != nil {
- return resp.Header, nil, &HTTPReadError{URL: url, Err: readErr}
- }
-
- if resp.StatusCode < 200 || resp.StatusCode > 299 {
- return resp.Header, body, &HTTPStatusError{URL: url, Status: resp.StatusCode}
- }
-
- // Return the body in bytes and signal the status error if there was one
- return resp.Header, body, nil
-}
diff --git a/src/log.go b/src/log.go
deleted file mode 100644
index 7402e31..0000000
--- a/src/log.go
+++ /dev/null
@@ -1,66 +0,0 @@
-package eduvpn
-
-import (
- "fmt"
- "log"
- "os"
- "path"
-)
-
-type FileLogger struct {
- Level LogLevel
- File *os.File
-}
-
-type LogLevel int8
-
-const (
- LOG_NOTSET LogLevel = iota
- LOG_INFO
- LOG_WARNING
- LOG_ERROR
-)
-
-func (e LogLevel) String() string {
- switch e {
- case LOG_NOTSET:
- return "NOTSET"
- case LOG_INFO:
- return "INFO"
- case LOG_WARNING:
- return "WARNING"
- case LOG_ERROR:
- return "ERROR"
- default:
- return "UNKNOWN"
- }
-}
-
-func (eduvpn *VPNState) getLogFilename() string {
- pathString := path.Join(eduvpn.ConfigDirectory, eduvpn.Name)
- return fmt.Sprintf("%s.log", pathString)
-}
-
-func (eduvpn *VPNState) InitLog(level LogLevel) error {
- configDirErr := eduvpn.EnsureConfigDir()
- if configDirErr != nil {
- return configDirErr
- }
- logFile, logOpenErr := os.OpenFile(eduvpn.getLogFilename(), os.O_RDWR|os.O_CREATE|os.O_APPEND, 0o666)
- if logOpenErr != nil {
- return logOpenErr
- }
- log.SetOutput(logFile)
- eduvpn.LogFile = FileLogger{Level: level, File: logFile}
- return nil
-}
-
-func (eduvpn *VPNState) Log(level LogLevel, str string) {
- if level >= eduvpn.LogFile.Level && eduvpn.LogFile.Level != LOG_NOTSET {
- log.Printf("[%s]: %s", level.String(), str)
- }
-}
-
-func (eduvpn *VPNState) CloseLog() {
- eduvpn.LogFile.File.Close()
-}
diff --git a/src/oauth.go b/src/oauth.go
deleted file mode 100644
index 263690e..0000000
--- a/src/oauth.go
+++ /dev/null
@@ -1,393 +0,0 @@
-package eduvpn
-
-import (
- "context"
- "crypto/sha256"
- "encoding/base64"
- "encoding/json"
- "errors"
- "fmt"
- "net/http"
- "net/url"
-)
-
-// Generates a random base64 string to be used for state
-// https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-04#section-4.1.1
-// "state": OPTIONAL. An opaque value used by the client to maintain
-// state between the request and callback. The authorization server
-// includes this value when redirecting the user agent back to the
-// client.
-func genState() (string, error) {
- randomBytes, err := MakeRandomByteSlice(32)
- if err != nil {
- return "", &OAuthGenStateUnableError{Err: err}
- }
-
- // For consistency we also use raw url encoding here
- return base64.RawURLEncoding.EncodeToString(randomBytes), nil
-}
-
-// Generates a sha256 base64 challenge from a verifier
-// https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-04#section-7.8
-func genChallengeS256(verifier string) string {
- hash := sha256.Sum256([]byte(verifier))
-
- // We use raw url encoding as the challenge does not accept padding
- return base64.RawURLEncoding.EncodeToString(hash[:])
-}
-
-// Generates a verifier
-// https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-04#section-4.1.1
-// The code_verifier is a unique high-entropy cryptographically random
-// string generated for each authorization request, using the unreserved
-// characters [A-Z] / [a-z] / [0-9] / "-" / "." / "_" / "~", with a
-// minimum length of 43 characters and a maximum length of 128
-// characters.
-func genVerifier() (string, error) {
- randomBytes, err := MakeRandomByteSlice(32)
- if err != nil {
- return "", &OAuthGenVerifierUnableError{Err: err}
- }
-
- return base64.RawURLEncoding.EncodeToString(randomBytes), nil
-}
-
-type OAuth struct {
- Session OAuthExchangeSession `json:"-"`
- Token OAuthToken `json:"token"`
- TokenURL string `json:"token_url"`
-}
-
-// This structure gets passed to the callback for easy access to the current state
-type OAuthExchangeSession struct {
- // returned from the callback
- CallbackError error
-
- // filled in in initialize
- ClientID string
- State string
- Verifier string
-
- // filled in when constructing the callback
- Context context.Context
- Server http.Server
-}
-
-// Struct that defines the json format for /.well-known/vpn-user-portal"
-type OAuthToken struct {
- Access string `json:"access_token"`
- Refresh string `json:"refresh_token"`
- Type string `json:"token_type"`
- Expires int64 `json:"expires_in"`
- ExpiredTimestamp int64 `json:"expires_in_timestamp"`
-}
-
-// Gets an authenticated HTTP client by obtaining refresh and access tokens
-func (oauth *OAuth) getTokensWithCallback() error {
- oauth.Session.Context = context.Background()
- mux := http.NewServeMux()
- addr := "127.0.0.1:8000"
- oauth.Session.Server = http.Server{
- Addr: addr,
- Handler: mux,
- }
- mux.HandleFunc("/callback", oauth.Callback)
- if err := oauth.Session.Server.ListenAndServe(); err != http.ErrServerClosed {
- return &OAuthFailedCallbackError{Addr: addr, Err: err}
- }
- return oauth.Session.CallbackError
-}
-
-// Get the access and refresh tokens
-// Access tokens: https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-04#section-1.4
-// Refresh tokens: https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-04#section-1.3.2
-func (oauth *OAuth) getTokensWithAuthCode(authCode string) error {
- // Make sure the verifier is set as the parameter
- // so that the server can verify that we are the actual owner of the authorization code
-
- reqURL := oauth.TokenURL
- data := url.Values{
- "client_id": {oauth.Session.ClientID},
- "code": {authCode},
- "code_verifier": {oauth.Session.Verifier},
- "grant_type": {"authorization_code"},
- "redirect_uri": {"http://127.0.0.1:8000/callback"},
- }
- headers := http.Header{
- "content-type": {"application/x-www-form-urlencoded"},
- }
- opts := &HTTPOptionalParams{Headers: headers, Body: data}
- current_time := GenerateTimeSeconds()
- _, body, bodyErr := HTTPPostWithOpts(reqURL, opts)
- if bodyErr != nil {
- return bodyErr
- }
-
- tokenStructure := OAuthToken{}
-
- jsonErr := json.Unmarshal(body, &tokenStructure)
-
- if jsonErr != nil {
- return &HTTPParseJsonError{URL: reqURL, Body: string(body), Err: jsonErr}
- }
-
- tokenStructure.ExpiredTimestamp = current_time + tokenStructure.Expires
- oauth.Token = tokenStructure
- return nil
-}
-
-func (oauth *OAuth) isTokensExpired() bool {
- expired_time := oauth.Token.ExpiredTimestamp
- current_time := GenerateTimeSeconds()
- return current_time >= expired_time
-}
-
-// Get the access and refresh tokens with a previously received refresh token
-// Access tokens: https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-04#section-1.4
-// Refresh tokens: https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-04#section-1.3.2
-func (oauth *OAuth) getTokensWithRefresh() error {
- reqURL := oauth.TokenURL
- data := url.Values{
- "refresh_token": {oauth.Token.Refresh},
- "grant_type": {"refresh_token"},
- }
- headers := http.Header{
- "content-type": {"application/x-www-form-urlencoded"},
- }
- opts := &HTTPOptionalParams{Headers: headers, Body: data}
- current_time := GenerateTimeSeconds()
- _, body, bodyErr := HTTPPostWithOpts(reqURL, opts)
- if bodyErr != nil {
- return bodyErr
- }
-
- tokenStructure := OAuthToken{}
- jsonErr := json.Unmarshal(body, &tokenStructure)
-
- if jsonErr != nil {
- return &HTTPParseJsonError{URL: reqURL, Body: string(body), Err: jsonErr}
- }
-
- tokenStructure.ExpiredTimestamp = current_time + tokenStructure.Expires
- oauth.Token = tokenStructure
- return nil
-}
-
-//
-//// The callback to retrieve the authorization code: https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-04#section-1.3.1
-func (oauth *OAuth) Callback(w http.ResponseWriter, req *http.Request) {
- // Extract the authorization code
- code, success := req.URL.Query()["code"]
- if !success {
- oauth.Session.CallbackError = &OAuthFailedCallbackParameterError{Parameter: "code", URL: req.URL.String()}
- go oauth.Session.Server.Shutdown(oauth.Session.Context)
- return
- }
- // The code is the first entry
- extractedCode := code[0]
-
- // Make sure the state is present and matches to protect against cross-site request forgeries
- // https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-04#section-7.15
- state, success := req.URL.Query()["state"]
- if !success {
- oauth.Session.CallbackError = &OAuthFailedCallbackParameterError{Parameter: "state", URL: req.URL.String()}
- go oauth.Session.Server.Shutdown(oauth.Session.Context)
- return
- }
- // The state is the first entry
- extractedState := state[0]
- if extractedState != oauth.Session.State {
- oauth.Session.CallbackError = &OAuthFailedCallbackStateMatchError{State: extractedState, ExpectedState: oauth.Session.State}
- go oauth.Session.Server.Shutdown(oauth.Session.Context)
- return
- }
-
- // Now that we have obtained the authorization code, we can move to the next step:
- // Obtaining the access and refresh tokens
- err := oauth.getTokensWithAuthCode(extractedCode)
- if err != nil {
- oauth.Session.CallbackError = &OAuthFailedCallbackGetTokensError{Err: err}
- go oauth.Session.Server.Shutdown(oauth.Session.Context)
- return
- }
-
- // Shutdown the server as we're done listening
- go oauth.Session.Server.Shutdown(oauth.Session.Context)
-}
-
-// Initializes the OAuth for eduvpn.
-// It needs a vpn state that was gotten from `Register`
-// It returns the authurl for the browser and an error if present
-func (eduvpn *VPNState) InitializeOAuth() error {
- if !eduvpn.HasTransition(OAUTH_STARTED) {
- return errors.New(fmt.Sprintf("Failed starting oauth, invalid state %s", eduvpn.FSM.Current.String()))
- }
- // Generate the state
- state, stateErr := genState()
- if stateErr != nil {
- return &OAuthFailedInitializeError{Err: stateErr}
- }
-
- // Generate the verifier and challenge
- verifier, verifierErr := genVerifier()
- if verifierErr != nil {
- return &OAuthFailedInitializeError{Err: verifierErr}
- }
- challenge := genChallengeS256(verifier)
-
- parameters := map[string]string{
- "client_id": eduvpn.Name,
- "code_challenge_method": "S256",
- "code_challenge": challenge,
- "response_type": "code",
- "scope": "config",
- "state": state,
- "redirect_uri": "http://127.0.0.1:8000/callback",
- }
-
- server, serverErr := eduvpn.Servers.GetCurrentServer()
- if serverErr != nil {
- return errors.New("OAuth Initialize no server found")
- }
- authURL, urlErr := HTTPConstructURL(server.Endpoints.API.V3.Authorization, parameters)
-
- if urlErr != nil { // shouldn't happen
- panic(urlErr)
- }
-
- // Fill the struct with the necessary fields filled for the next call to getting the HTTP client
- oauthSession := OAuthExchangeSession{ClientID: eduvpn.Name, State: state, Verifier: verifier}
- server.OAuth = OAuth{TokenURL: server.Endpoints.API.V3.Token, Session: oauthSession}
- eduvpn.GoTransitionWithData(OAUTH_STARTED, authURL)
- return nil
-}
-
-// Error definitions
-func (eduvpn *VPNState) FinishOAuth() error {
- if !eduvpn.HasTransition(AUTHENTICATED) {
- return errors.New("invalid state to finish oauth")
- }
- server, serverErr := eduvpn.Servers.GetCurrentServer()
- if serverErr != nil {
- return errors.New("OAuth Initialize No server found")
- }
- tokenErr := server.OAuth.getTokensWithCallback()
- if tokenErr != nil {
- return tokenErr
- }
- eduvpn.GoTransition(AUTHENTICATED)
- return nil
-}
-
-func (state *VPNState) LoginOAuth() error {
- authInitializeErr := state.InitializeOAuth()
-
- if authInitializeErr != nil {
- return authInitializeErr
- }
-
- oauthErr := state.FinishOAuth()
-
- if oauthErr != nil {
- return oauthErr
- }
- return nil
-}
-
-func (oauth *OAuth) Login() error {
- return GetVPNState().LoginOAuth()
-}
-
-func (oauth *OAuth) NeedsRelogin() bool {
- // Access Token or Refresh Tokens empty, definitely needs a relogin
- if oauth.Token.Access == "" || oauth.Token.Refresh == "" {
- GetVPNState().Log(LOG_INFO, "OAuth: Tokens are empty")
- return true
- }
-
- // We have tokens...
-
- // The tokens are not expired yet
- // No relogin is needed
- if !oauth.isTokensExpired() {
- GetVPNState().Log(LOG_INFO, "OAuth: Tokens are not expired, re-login not needed")
- return false
- }
-
- refreshErr := oauth.getTokensWithRefresh()
- // We have obtained new tokens with refresh
- if refreshErr == nil {
- GetVPNState().Log(LOG_INFO, "OAuth: Tokens could be re-acquired using the refresh token, re-login not needed")
- return false
- }
-
- // Otherwise relogin is really needed
- return true
-}
-
-func (oauth *OAuth) EnsureTokens() error {
- if oauth.NeedsRelogin() {
- GetVPNState().Log(LOG_INFO, "OAuth: Tokens are invalid, relogging in")
- return oauth.Login()
- }
- return nil
-}
-
-type OAuthGenStateUnableError struct {
- Err error
-}
-
-func (e *OAuthGenStateUnableError) Error() string {
- return fmt.Sprintf("failed generating state with error %v", e.Err)
-}
-
-type OAuthGenVerifierUnableError struct {
- Err error
-}
-
-func (e *OAuthGenVerifierUnableError) Error() string {
- return fmt.Sprintf("failed generating verifier with error %v", e.Err)
-}
-
-type OAuthFailedCallbackError struct {
- Addr string
- Err error
-}
-
-func (e *OAuthFailedCallbackError) Error() string {
- return fmt.Sprintf("failed callback %s with error %v", e.Addr, e.Err)
-}
-
-type OAuthFailedCallbackParameterError struct {
- Parameter string
- URL string
-}
-
-func (e *OAuthFailedCallbackParameterError) Error() string {
- return fmt.Sprintf("failed retrieving parameter %s in url %s", e.Parameter, e.URL)
-}
-
-type OAuthFailedCallbackStateMatchError struct {
- State string
- ExpectedState string
-}
-
-func (e *OAuthFailedCallbackStateMatchError) Error() string {
- return fmt.Sprintf("failed matching state, got %s, want %s", e.State, e.ExpectedState)
-}
-
-type OAuthFailedCallbackGetTokensError struct {
- Err error
-}
-
-func (e *OAuthFailedCallbackGetTokensError) Error() string {
- return fmt.Sprintf("failed getting tokens with error %v", e.Err)
-}
-
-type OAuthFailedInitializeError struct {
- Err error
-}
-
-func (e *OAuthFailedInitializeError) Error() string {
- return fmt.Sprintf("failed initializing OAuth with error %v", e.Err)
-}
diff --git a/src/openvpn.go b/src/openvpn.go
deleted file mode 100644
index 95e1328..0000000
--- a/src/openvpn.go
+++ /dev/null
@@ -1,12 +0,0 @@
-package eduvpn
-
-func (server *Server) OpenVPNGetConfig() (string, error) {
- profile_id := server.Profiles.Current
- configOpenVPN, _, configErr := server.APIConnectOpenVPN(profile_id)
-
- if configErr != nil {
- return "", configErr
- }
-
- return configOpenVPN, nil
-}
diff --git a/src/server.go b/src/server.go
deleted file mode 100644
index 3dca26b..0000000
--- a/src/server.go
+++ /dev/null
@@ -1,178 +0,0 @@
-package eduvpn
-
-import (
- "encoding/json"
- "errors"
-)
-
-type Server struct {
- BaseURL string `json:"base_url"`
- Endpoints ServerEndpoints `json:"endpoints"`
- OAuth OAuth `json:"oauth"`
- Profiles ServerProfileInfo `json:"profiles"`
- ProfilesRaw string `json:"profiles_raw"`
-}
-
-type Servers struct {
- List map[string]*Server `json:"list"`
- Current string `json:"current"`
-}
-
-func (servers *Servers) GetCurrentServer() (*Server, error) {
- if servers.List == nil {
- return nil, errors.New("No map found to get Current Server")
- }
- server, exists := servers.List[servers.Current]
-
- if !exists || server == nil {
- return nil, errors.New("Current Server not found")
- }
- return server, nil
-}
-
-func (servers *Servers) EnsureServer(url string) *Server {
- if servers.List == nil {
- servers.List = make(map[string]*Server)
- }
-
- server, exists := servers.List[url]
-
- if !exists || server == nil {
- server = &Server{}
- server.Initialize(url)
- servers.List[url] = server
- }
- servers.Current = url
- return server
-}
-
-type ServerProfile struct {
- ID string `json:"profile_id"`
- DisplayName string `json:"display_name"`
- VPNProtoList []string `json:"vpn_proto_list"`
- DefaultGateway bool `json:"default_gateway"`
-}
-
-type ServerProfileInfo struct {
- Current string `json:"current_profile"`
- Info struct {
- ProfileList []ServerProfile `json:"profile_list"`
- } `json:"info"`
-}
-
-type ServerEndpointList struct {
- API string `json:"api_endpoint"`
- Authorization string `json:"authorization_endpoint"`
- Token string `json:"token_endpoint"`
-}
-
-// Struct that defines the json format for /.well-known/vpn-user-portal"
-type ServerEndpoints struct {
- API struct {
- V2 ServerEndpointList `json:"http://eduvpn.org/api#2"`
- V3 ServerEndpointList `json:"http://eduvpn.org/api#3"`
- } `json:"api"`
- V string `json:"v"`
-}
-
-func (server *Server) Initialize(url string) error {
- server.BaseURL = url
- endpointsErr := server.GetEndpoints()
- if endpointsErr != nil {
- return endpointsErr
- }
- return nil
-}
-
-func (server *Server) NeedsRelogin() bool {
- // Check if OAuth needs relogin
- return server.OAuth.NeedsRelogin()
-}
-
-func (server *Server) GetEndpoints() error {
- url := server.BaseURL + "/.well-known/vpn-user-portal"
- _, body, bodyErr := HTTPGet(url)
-
- if bodyErr != nil {
- return bodyErr
- }
-
- endpoints := ServerEndpoints{}
- jsonErr := json.Unmarshal(body, &endpoints)
-
- if jsonErr != nil {
- return jsonErr
- }
-
- server.Endpoints = endpoints
-
- return nil
-}
-
-func (profile *ServerProfile) supportsWireguard() bool {
- for _, proto := range profile.VPNProtoList {
- if proto == "wireguard" {
- return true
- }
- }
- return false
-}
-
-func (server *Server) getCurrentProfile() (*ServerProfile, error) {
- profile_id := server.Profiles.Current
- for _, profile := range server.Profiles.Info.ProfileList {
- if profile.ID == profile_id {
- return &profile, nil
- }
- }
- return nil, errors.New("no profile found for id")
-}
-
-func (server *Server) getConfigWithProfile() (string, error) {
- if !GetVPNState().HasTransition(HAS_CONFIG) {
- return "", errors.New("cannot get a config with a profile, invalid state")
- }
- profile, profileErr := server.getCurrentProfile()
-
- if profileErr != nil {
- return "", profileErr
- }
-
- if profile.supportsWireguard() {
- return server.WireguardGetConfig()
- }
- return server.OpenVPNGetConfig()
-}
-
-func (server *Server) askForProfileID() error {
- if !GetVPNState().HasTransition(ASK_PROFILE) {
- return errors.New("cannot ask for a profile id, invalid state")
- }
- GetVPNState().GoTransitionWithData(ASK_PROFILE, server.ProfilesRaw)
- return nil
-}
-
-func (server *Server) GetConfig() (string, error) {
- if !GetVPNState().InState(REQUEST_CONFIG) {
- return "", errors.New("cannot get a config, invalid state")
- }
- infoErr := server.APIInfo()
-
- if infoErr != nil {
- return "", infoErr
- }
-
- // Set the current profile if there is only one profile
- if len(server.Profiles.Info.ProfileList) == 1 {
- server.Profiles.Current = server.Profiles.Info.ProfileList[0].ID
- return server.getConfigWithProfile()
- }
-
- profileErr := server.askForProfileID()
-
- if profileErr != nil {
- return "", nil
- }
-
- return server.getConfigWithProfile()
-}
diff --git a/src/server_test.go b/src/server_test.go
deleted file mode 100644
index ccf58f6..0000000
--- a/src/server_test.go
+++ /dev/null
@@ -1,214 +0,0 @@
-package eduvpn
-
-import (
- "crypto/tls"
- "errors"
- "fmt"
- "net/http"
- "os"
- "os/exec"
- "strconv"
- "strings"
- "testing"
- "time"
-)
-
-func runCommand(t *testing.T, errBuffer *strings.Builder, name string, args ...string) error {
- cmd := exec.Command(name, args...)
-
- cmd.Stderr = errBuffer
- err := cmd.Start()
- if err != nil {
- return err
- }
-
- return cmd.Wait()
-}
-
-func LoginOAuthSelenium(t *testing.T, url string) {
- // We could use the go selenium library
- // But it does not support the latest selenium v4 just yet
- var errBuffer strings.Builder
- err := runCommand(t, &errBuffer, "python3", "../selenium_eduvpn.py", url)
- if err != nil {
- t.Errorf("Login OAuth with selenium script failed with error %v and stderr %s", err, errBuffer.String())
- }
-}
-
-func StateCallback(t *testing.T, oldState string, newState string, data string) {
- if newState == "OAuth_Started" {
- go LoginOAuthSelenium(t, data)
- }
-}
-
-func Test_server(t *testing.T) {
- state := GetVPNState()
-
- // Do not verify because during testing, the cert is self-signed
- http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
-
- state.Register("org.eduvpn.app.linux", "configstest", func(old string, new string, data string) {
- StateCallback(t, old, new, data)
- }, false)
-
- _, configErr := state.Connect("https://eduvpnserver")
-
- if configErr != nil {
- t.Errorf("Connect error: %v", configErr)
- }
-}
-
-func test_connect_oauth_parameter(t *testing.T, parameters URLParameters, expectedErr interface{}) {
- state := GetVPNState()
- state.Deregister()
-
- // Do not verify because during testing, the cert is self-signed
- http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
-
- state.Register("org.eduvpn.app.linux", "configsnologin", func(oldState string, newState string, data string) {
- if newState == "OAuth_Started" {
- baseURL := "http://127.0.0.1:8000/callback"
- url, err := HTTPConstructURL(baseURL, parameters)
- if err != nil {
- t.Errorf("Error: Constructing url %s with parameters %s", baseURL, fmt.Sprint(parameters))
- }
- go http.Get(url)
-
- }
- }, false)
- _, configErr := state.Connect("https://eduvpnserver")
-
- if !errors.As(configErr, expectedErr) {
- t.Errorf("error %T = %v, wantErr %T", configErr, configErr, expectedErr)
- }
-}
-
-func Test_connect_oauth_parameters(t *testing.T) {
- var (
- failedCallbackParameterError *OAuthFailedCallbackParameterError
- failedCallbackStateMatchError *OAuthFailedCallbackStateMatchError
- )
-
- tests := []struct {
- expectedErr interface{}
- parameters URLParameters
- }{
- {&failedCallbackParameterError, URLParameters{}},
- {&failedCallbackParameterError, URLParameters{"code": "42"}},
- {&failedCallbackStateMatchError, URLParameters{"code": "42", "state": "21"}},
- }
-
- for _, test := range tests {
- test_connect_oauth_parameter(t, test.parameters, test.expectedErr)
- }
-}
-
-func Test_token_expired(t *testing.T) {
- expiredTTL := os.Getenv("OAUTH_EXPIRED_TTL")
- if expiredTTL == "" {
- t.Log("No expired TTL present, skipping this test. Set EXPIRED_TTL env variable to run it")
- return
- }
-
- // Convert the env variable to an int and signal error if it is not possible
- expiredInt, expiredErr := strconv.Atoi(expiredTTL)
- if expiredErr != nil {
- t.Errorf("Cannot convert EXPIRED_TTL env variable to an int with error %v", expiredErr)
- }
-
- // Get a vpn state
- state := GetVPNState()
-
- state.Deregister()
-
- // Do not verify because during testing, the cert is self-signed
- http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
-
- state.Register("org.eduvpn.app.linux", "configsexpired", func(old string, new string, data string) {
- StateCallback(t, old, new, data)
- }, false)
-
- _, configErr := state.Connect("https://eduvpnserver")
-
- if configErr != nil {
- t.Errorf("Connect error before expired: %v", configErr)
- }
-
- server, serverErr := state.Servers.GetCurrentServer()
- if serverErr != nil {
- t.Errorf("No server found")
- }
-
- accessToken := server.OAuth.Token.Access
- refreshToken := server.OAuth.Token.Refresh
-
- // Wait for TTL so that the tokens expire
- time.Sleep(time.Duration(expiredInt) * time.Second)
-
- infoErr := server.APIInfo()
-
- if infoErr != nil {
- t.Errorf("Info error after expired: %v", infoErr)
- }
-
- // Check if tokens have changed
- accessTokenAfter := server.OAuth.Token.Access
- refreshTokenAfter := server.OAuth.Token.Refresh
-
- if accessToken == accessTokenAfter {
- t.Errorf("Access token is the same after refresh")
- }
-
- if refreshToken == refreshTokenAfter {
- t.Errorf("Refresh token is the same after refresh")
- }
-}
-
-func Test_token_invalid(t *testing.T) {
- state := GetVPNState()
-
- // Do not verify because during testing, the cert is self-signed
- http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
-
- state.Deregister()
-
- state.Register("org.eduvpn.app.linux", "configsinvalid", func(old string, new string, data string) {
- StateCallback(t, old, new, data)
- }, false)
-
- _, configErr := state.Connect("https://eduvpnserver")
-
- if configErr != nil {
- t.Errorf("Connect error before invalid: %v", configErr)
- }
-
- // Fake connect and then back to authenticated so that we can re-authenticate
- // Going to authenticated fakes a disconnect
- state.GoTransition(CONNECTED)
- state.GoTransition(AUTHENTICATED)
-
- dummy_value := "37"
-
- server, serverErr := state.Servers.GetCurrentServer()
- if serverErr != nil {
- t.Errorf("No server found")
- }
-
- // Override tokens with invalid values
- server.OAuth.Token.Access = dummy_value
- server.OAuth.Token.Refresh = dummy_value
-
- infoErr := server.APIInfo()
-
- if infoErr != nil {
- t.Errorf("Info error after invalid: %v", infoErr)
- }
-
- if server.OAuth.Token.Access == dummy_value {
- t.Errorf("Access token is equal to dummy value: %s", dummy_value)
- }
-
- if server.OAuth.Token.Refresh == dummy_value {
- t.Errorf("Refresh token is equal to dummy value: %s", dummy_value)
- }
-}
diff --git a/src/state.go b/src/state.go
deleted file mode 100644
index c0e512f..0000000
--- a/src/state.go
+++ /dev/null
@@ -1,108 +0,0 @@
-package eduvpn
-
-import (
- "errors"
-)
-
-type VPNState struct {
- // Info passed by the client
- ConfigDirectory string `json:"-"`
- Name string `json:"-"`
- StateCallback func(string, string, string) `json:"-"`
- StateCallbackData string `json:"-"`
-
- // The chosen server
- Servers Servers `json:"servers"`
-
- // The list of servers and organizations from disco
- DiscoList DiscoLists `json:"-"`
-
- // The file we keep open for logging
- LogFile FileLogger `json:"-"`
-
- // The fsm
- FSM FSM `json:"-"`
-
- // Whether to enable debugging
- Debug bool `json:"-"`
-}
-
-func (state *VPNState) Register(name string, directory string, stateCallback func(string, string, string), debug bool) error {
- if !state.InState(DEREGISTERED) {
- return errors.New("app already registered")
- }
- state.InitializeFSM()
- state.Name = name
- state.ConfigDirectory = directory
- state.StateCallback = stateCallback
- state.Debug = debug
-
- LogLevel := LOG_WARNING
-
- if debug {
- LogLevel = LOG_INFO
- }
-
- // Initialize the logger
- state.InitLog(LogLevel)
-
- // Try to load the previous configuration
- if state.LoadConfig() != nil {
- // This error can be safely ignored, as when the config does not load, the struct will not be filled
- state.Log(LOG_INFO, "Previous configuration not found")
- }
- state.GoTransition(NO_SERVER)
- return nil
-}
-
-func (state *VPNState) Deregister() error {
- // Close the log file
- state.CloseLog()
-
- // Write the config
- state.WriteConfig()
-
- // Re-initialize the servers and FSM
- state.Servers = Servers{}
- state.InitializeFSM()
- return nil
-}
-
-func (state *VPNState) Connect(url string) (string, error) {
- // New server chosen, ensure the server is fresh
- server := state.Servers.EnsureServer(url)
- // Make sure we are in the chosen state if available
- state.GoTransition(CHOSEN_SERVER)
- // Relogin with oauth
- // This moves the state to authenticated
- if server.NeedsRelogin() {
- loginErr := state.LoginOAuth()
-
- if loginErr != nil {
- return "", loginErr
- }
- } else { // OAuth was valid, ensure we are in the authenticated state
- state.GoTransition(AUTHENTICATED)
- }
-
- state.GoTransition(REQUEST_CONFIG)
-
- config, configErr := server.GetConfig()
-
- if configErr != nil {
- return "", configErr
- } else {
- state.GoTransition(HAS_CONFIG)
- }
-
- return config, nil
-}
-
-var VPNStateInstance *VPNState
-
-func GetVPNState() *VPNState {
- if VPNStateInstance == nil {
- VPNStateInstance = &VPNState{}
- }
- return VPNStateInstance
-}
diff --git a/src/test_data/empty b/src/test_data/empty
deleted file mode 100644
index e69de29..0000000
--- a/src/test_data/empty
+++ /dev/null
diff --git a/src/test_data/generate.sh b/src/test_data/generate.sh
deleted file mode 100644
index b1b4545..0000000
--- a/src/test_data/generate.sh
+++ /dev/null
@@ -1,58 +0,0 @@
-#!/bin/bash
-# Generate testcases with fake keys
-
-# Make sure we do not delete *.minisigs etc. in the wrong directory
-if [ ${PWD##*/} != "test_data" ]
-then
- >&2 echo "Wrong directory, should be run in test_data/"
- exit 1
-fi
-
-rm -f *.minisig *.blake2b
-
-# Uncomment to regenerate keys
-#rm -f *.key
-#echo -en "\n\n" | minisign -Gf -p public.key -s secret.key &
-#echo -en "\n\n" | minisign -Gf -p wrong_public.key -s wrong_secret.key &
-#wait
-
-# Try to create pure signature with default Minisign (works with version < 0.10)
-echo | minisign -Sm server_list.json -x server_list.json.pure.minisig -t $'timestamp:10\tfile:server_list.json' -s secret.key
-# Check if it is actually a prehashed signature
-if echo | minisign -VHm server_list.json -x server_list.json.pure.minisig -p public.key
-then
- echo "minisign version is >0.9, trying minisign-0.9"
- # If it is, try to sign with some minisign-0.9 program
- if ! echo | minisign-0.9 -Sm server_list.json -x server_list.json.pure.minisig -t $'timestamp:10\tfile:server_list.json' -s secret.key
- then
- >&2 echo -e "\n\nTo produce a non-prehashed signature we need Minisign 0.9\n\n"
- fi
-fi
-
-# Rest works with Minisign 0.9 and 0.10 (and up, probably)
-
-echo | minisign -SHm server_list.json -t $'timestamp:10\tfile:server_list.json\thashed' -s secret.key &
-echo | minisign -SHm server_list.json -x server_list.json.tc_nohashed.minisig -t $'timestamp:10\tfile:server_list.json' -s secret.key &
-echo | minisign -SHm server_list.json -x server_list.json.tc_latertime.minisig -t $'timestamp:20\tfile:server_list.json\t hashed' -s secret.key &
-echo | minisign -SHm server_list.json -x server_list.json.tc_orglist.minisig -t $'timestamp:10\tfile:organization_list.json\thashed' -s secret.key &
-wait
-echo | minisign -SHm server_list.json -x server_list.json.tc_otherfile.minisig -t $'timestamp:10\tfile:otherfile\thashed' -s secret.key &
-echo | minisign -SHm server_list.json -x server_list.json.tc_nofile.minisig -t $'timestamp:10\thashed' -s secret.key &
-echo | minisign -SHm server_list.json -x server_list.json.tc_notime.minisig -t $'file:server_list.json\thashed' -s secret.key &
-echo | minisign -SHm server_list.json -x server_list.json.tc_emptytime.minisig -t $'timestamp:\tfile:server_list.json\thashed' -s secret.key &
-wait
-echo | minisign -SHm server_list.json -x server_list.json.tc_emptyfile.minisig -t $'timestamp:10\tfile:\thashed' -s secret.key &
-echo | minisign -SHm server_list.json -x server_list.json.tc_earliertime.minisig -t $'timestamp:9\tfile:server_list.json\thashed' -s secret.key &
-echo | minisign -SHm server_list.json -x server_list.json.tc_random.minisig -t 'random stuff' -s secret.key &
-echo | minisign -SHm server_list.json -x server_list.json.large_time.minisig -t $'timestamp:4300000000\tfile:server_list.json' -s secret.key &
-wait
-
-echo | minisign -SHm organization_list.json -t $'timestamp:10\tfile:organization_list.json\thashed' -s secret.key &
-echo | minisign -SHm organization_list.json -x organization_list.json.tc_servlist.minisig -t $'timestamp:10\tfile:server_list.json\thashed' -s secret.key &
-
-echo | minisign -SHm other_list.json -t $'timestamp:10\tfile:other_list.json\thashed' -s secret.key &
-
-echo | minisign -SHm server_list.json -x server_list.json.wrong_key.minisig -t $'timestamp:10\tfile:server_list.json\thashed' -s wrong_secret.key &
-wait
-
-./generate_forged.py
diff --git a/src/test_data/generate_forged.py b/src/test_data/generate_forged.py
deleted file mode 100644
index 843b32d..0000000
--- a/src/test_data/generate_forged.py
+++ /dev/null
@@ -1,37 +0,0 @@
-#!/usr/bin/env python3
-
-import hashlib
-import base64
-
-# Hash server_list.json
-
-with open("server_list.json", "rb") as f:
- b = f.read()
-
-with open("server_list.json.blake2b", "wb") as f:
- f.write(hashlib.blake2b(b).digest())
-
-# Forge pure signature on hash, see https://github.com/jedisct1/minisign/issues/104
-
-with open("server_list.json.minisig", "rb") as f:
- siglines = f.readlines()
-
-siglines[0] = b"untrusted comment: this signature has ED changed to Ed\n"
-sig = base64.b64decode(siglines[1])
-siglines[1] = base64.b64encode(b"Ed" + sig[2:]) + b"\n"
-
-with open("server_list.json.forged_pure.minisig", "wb") as f:
- f.writelines(siglines)
- # Should now work: minisign -Vm server_list.json.blake2b -x server_list.json.forged_pure.minisig -p public-key
-
-# Try to forge key ID
-
-with open("server_list.json.wrong_key.minisig", "rb") as f:
- siglines = f.readlines()
-
-siglines[0] = b"untrusted comment: this signature was created with wrong_secret.key but has key ID changed to that of public.key\n"
-sig_wrong = base64.b64decode(siglines[1])
-siglines[1] = base64.b64encode(sig_wrong[:2] + sig[2:2+8] + sig_wrong[2+8:]) + b"\n"
-
-with open("server_list.json.forged_keyid.minisig", "wb") as f:
- f.writelines(siglines)
diff --git a/src/test_data/organization_list.json b/src/test_data/organization_list.json
deleted file mode 100644
index 8c53044..0000000
--- a/src/test_data/organization_list.json
+++ /dev/null
@@ -1 +0,0 @@
-{"organization_list": [{}]} \ No newline at end of file
diff --git a/src/test_data/organization_list.json.minisig b/src/test_data/organization_list.json.minisig
deleted file mode 100644
index 1fa546e..0000000
--- a/src/test_data/organization_list.json.minisig
+++ /dev/null
@@ -1,4 +0,0 @@
-untrusted comment: signature from minisign secret key
-RURMm6vfaPgH31cHjNvTEh+TCqDVCwUgFVZoRdgWYAaQDxH3L3UIsRi9Qb1O4vLI4V1CYPatKzXZnSodSJM/AZgl9v7l/5bfPQ0=
-trusted comment: timestamp:10 file:organization_list.json hashed
-21zZv1DviMpLCdv1NgzLBl6d+F1ZllSNyjAquYxhTHGcs2F64bDFpqY0I0xjCHIoXly6HKqJKIBXNgud12ijCQ==
diff --git a/src/test_data/organization_list.json.tc_servlist.minisig b/src/test_data/organization_list.json.tc_servlist.minisig
deleted file mode 100644
index a7fe41f..0000000
--- a/src/test_data/organization_list.json.tc_servlist.minisig
+++ /dev/null
@@ -1,4 +0,0 @@
-untrusted comment: signature from minisign secret key
-RURMm6vfaPgH31cHjNvTEh+TCqDVCwUgFVZoRdgWYAaQDxH3L3UIsRi9Qb1O4vLI4V1CYPatKzXZnSodSJM/AZgl9v7l/5bfPQ0=
-trusted comment: timestamp:10 file:server_list.json hashed
-R6hjM/oMS5LAvpYM4F6E7iUpnlPxqiY0QfuOnpum31CW0sUy/Ypy2PiomSwvZXKVR7keEZS/+lZjyra9TkrLDQ==
diff --git a/src/test_data/other_list.json b/src/test_data/other_list.json
deleted file mode 100644
index 25ba1a8..0000000
--- a/src/test_data/other_list.json
+++ /dev/null
@@ -1 +0,0 @@
-{"other_list": [{}]} \ No newline at end of file
diff --git a/src/test_data/other_list.json.minisig b/src/test_data/other_list.json.minisig
deleted file mode 100644
index eaa2248..0000000
--- a/src/test_data/other_list.json.minisig
+++ /dev/null
@@ -1,4 +0,0 @@
-untrusted comment: signature from minisign secret key
-RURMm6vfaPgH366C1RnYeUAgEeX/S5A1Z9qmkV2+GJaVj06FWGd4aMLc+HS7iFMhG69u3TVD4YmzMH12rk7hQrnyCC6ex8ypIQA=
-trusted comment: timestamp:10 file:other_list.json hashed
-26+608n+bjQF9lwNdXbIK6t5bP8dzhjNQ9hACeYJLiB2tr437Aec2GkmJh0jSiRv1QV4RYBcKJeHQBUcV2grCQ==
diff --git a/src/test_data/public.key b/src/test_data/public.key
deleted file mode 100644
index 72676d3..0000000
--- a/src/test_data/public.key
+++ /dev/null
@@ -1,2 +0,0 @@
-untrusted comment: minisign public key DF07F868DFAB9B4C
-RWRMm6vfaPgH39iT++NBiUKZim2nDWnalgkNROovPbZdSwVFgUdKU4ac
diff --git a/src/test_data/random.txt b/src/test_data/random.txt
deleted file mode 100644
index b6fc4c6..0000000
--- a/src/test_data/random.txt
+++ /dev/null
@@ -1 +0,0 @@
-hello \ No newline at end of file
diff --git a/src/test_data/secret.key b/src/test_data/secret.key
deleted file mode 100644
index 6e4af37..0000000
--- a/src/test_data/secret.key
+++ /dev/null
@@ -1,2 +0,0 @@
-untrusted comment: minisign encrypted secret key
-RWRTY0IyobkTOt4ugAHNTPB6zOxHgX8spW6HQWddB5IrdCPDAgsAAAACAAAAAAAAAEAAAAAAvK1S1gsOgozZHuIdLWXq1IwxnWVr+dlySiykTbO6F85HvzPtgxZ7oLcGkT/vPdskAh0SV9H2ylHlt9oarXcWNDKs2r6EcZw/qy5FsD+5uhPfxwWV4qDF+1G456tYDYID63d50CgzdO0=
diff --git a/src/test_data/server_list.json b/src/test_data/server_list.json
deleted file mode 100644
index 67c4c8d..0000000
--- a/src/test_data/server_list.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
-"server_list": [{}]
-} \ No newline at end of file
diff --git a/src/test_data/server_list.json.blake2b b/src/test_data/server_list.json.blake2b
deleted file mode 100644
index 5d2ca5a..0000000
--- a/src/test_data/server_list.json.blake2b
+++ /dev/null
Binary files differ
diff --git a/src/test_data/server_list.json.forged_keyid.minisig b/src/test_data/server_list.json.forged_keyid.minisig
deleted file mode 100644
index efa349d..0000000
--- a/src/test_data/server_list.json.forged_keyid.minisig
+++ /dev/null
@@ -1,4 +0,0 @@
-untrusted comment: this signature was created with wrong_secret.key but has key ID changed to that of public.key
-RURMm6vfaPgH35aarz3NMq4gbv6JvzOnjG003bDe6USu+HT/JzuxHjQcQGE/KBPdyCF6BDDwwFu+NVmi5jotYCJHWOEqSBU70gE=
-trusted comment: timestamp:10 file:server_list.json hashed
-3BWYJamM3t6ImuXQufTeO81UMZNyM7TujMu7SCmR+oapsSEBpmkazGOgzlJYR53HP9K9zrEA+4lV8gFFngooBA==
diff --git a/src/test_data/server_list.json.forged_pure.minisig b/src/test_data/server_list.json.forged_pure.minisig
deleted file mode 100644
index a362504..0000000
--- a/src/test_data/server_list.json.forged_pure.minisig
+++ /dev/null
@@ -1,4 +0,0 @@
-untrusted comment: this signature has ED changed to Ed
-RWRMm6vfaPgH3997FX/cHwhXJpcluwbNiznrfYV83WS/Gsd3BeO/g10Mo7Z9N5rMSXcpGrmT2CagiEEm5zSw/MEnTqs4YWICdQs=
-trusted comment: timestamp:10 file:server_list.json hashed
-oK41aX7rmpbO2ohF3v3+JGgSexQaVlfWvYPzaKEkDlJm8mVZtuK/h26SCRuL6PbTR92DLZU59rw8ckICUH/ADw==
diff --git a/src/test_data/server_list.json.large_time.minisig b/src/test_data/server_list.json.large_time.minisig
deleted file mode 100644
index 79a2a52..0000000
--- a/src/test_data/server_list.json.large_time.minisig
+++ /dev/null
@@ -1,4 +0,0 @@
-untrusted comment: signature from minisign secret key
-RURMm6vfaPgH3997FX/cHwhXJpcluwbNiznrfYV83WS/Gsd3BeO/g10Mo7Z9N5rMSXcpGrmT2CagiEEm5zSw/MEnTqs4YWICdQs=
-trusted comment: timestamp:4300000000 file:server_list.json
-L9C58LIq7bTLf4otqW4Eb+ASL0+FM7nRRjstCBuCPtuUerFIsOqNUpDp2AQJJ4pZJKE7SkgIq2tV8/IaVpzxBQ==
diff --git a/src/test_data/server_list.json.minisig b/src/test_data/server_list.json.minisig
deleted file mode 100644
index 143585b..0000000
--- a/src/test_data/server_list.json.minisig
+++ /dev/null
@@ -1,4 +0,0 @@
-untrusted comment: signature from minisign secret key
-RURMm6vfaPgH3997FX/cHwhXJpcluwbNiznrfYV83WS/Gsd3BeO/g10Mo7Z9N5rMSXcpGrmT2CagiEEm5zSw/MEnTqs4YWICdQs=
-trusted comment: timestamp:10 file:server_list.json hashed
-oK41aX7rmpbO2ohF3v3+JGgSexQaVlfWvYPzaKEkDlJm8mVZtuK/h26SCRuL6PbTR92DLZU59rw8ckICUH/ADw==
diff --git a/src/test_data/server_list.json.pure.minisig b/src/test_data/server_list.json.pure.minisig
deleted file mode 100644
index 57dccfc..0000000
--- a/src/test_data/server_list.json.pure.minisig
+++ /dev/null
@@ -1,4 +0,0 @@
-untrusted comment: signature from minisign secret key
-RWRMm6vfaPgH3zQ/rcq2GMsNz1SYySz+olupm0I+nzNpOkPyUHTBwig3Pep4biOk/bH73bH+0sLNoZPcDk1f2Acn8JINc9MWMw4=
-trusted comment: timestamp:10 file:server_list.json
-FZ0eA96SlADsMrSOUgStQJpmUnBGpPbRvNI/oaYhKrylu6jUcXOgsRu6571mmDxYdlruSuUSlQbdmG81Qbl4AA==
diff --git a/src/test_data/server_list.json.tc_earliertime.minisig b/src/test_data/server_list.json.tc_earliertime.minisig
deleted file mode 100644
index 03da710..0000000
--- a/src/test_data/server_list.json.tc_earliertime.minisig
+++ /dev/null
@@ -1,4 +0,0 @@
-untrusted comment: signature from minisign secret key
-RURMm6vfaPgH3997FX/cHwhXJpcluwbNiznrfYV83WS/Gsd3BeO/g10Mo7Z9N5rMSXcpGrmT2CagiEEm5zSw/MEnTqs4YWICdQs=
-trusted comment: timestamp:9 file:server_list.json hashed
-vw3wjLDNZWoV98/GnFv38REiaeh+wUPEZgmBUvY35CEq00jDdHiJcYRV/7zBoKv+n9TAYxZ8WKUOGWNOPonTBg==
diff --git a/src/test_data/server_list.json.tc_emptyfile.minisig b/src/test_data/server_list.json.tc_emptyfile.minisig
deleted file mode 100644
index a7aa3ed..0000000
--- a/src/test_data/server_list.json.tc_emptyfile.minisig
+++ /dev/null
@@ -1,4 +0,0 @@
-untrusted comment: signature from minisign secret key
-RURMm6vfaPgH3997FX/cHwhXJpcluwbNiznrfYV83WS/Gsd3BeO/g10Mo7Z9N5rMSXcpGrmT2CagiEEm5zSw/MEnTqs4YWICdQs=
-trusted comment: timestamp:10 file: hashed
-g4drZ91TcYXNLnIGbeH5ZIFzrs2wWB9JTXjV3Jwg9ehSC2D8lCTqw3u2Rg+PvLPRvYmXTHyuJoKNWelsSh64CA==
diff --git a/src/test_data/server_list.json.tc_emptytime.minisig b/src/test_data/server_list.json.tc_emptytime.minisig
deleted file mode 100644
index d3ef01e..0000000
--- a/src/test_data/server_list.json.tc_emptytime.minisig
+++ /dev/null
@@ -1,4 +0,0 @@
-untrusted comment: signature from minisign secret key
-RURMm6vfaPgH3997FX/cHwhXJpcluwbNiznrfYV83WS/Gsd3BeO/g10Mo7Z9N5rMSXcpGrmT2CagiEEm5zSw/MEnTqs4YWICdQs=
-trusted comment: timestamp: file:server_list.json hashed
-lw5rnZsPi+TkZ4lOCy7bjsUgTXxG+jaGOGdHuNL95FSD2mmP9ZzEJPrJ2jnH7iYfkF3zDm0QvEUDxhEirlHBDA==
diff --git a/src/test_data/server_list.json.tc_latertime.minisig b/src/test_data/server_list.json.tc_latertime.minisig
deleted file mode 100644
index 8237123..0000000
--- a/src/test_data/server_list.json.tc_latertime.minisig
+++ /dev/null
@@ -1,4 +0,0 @@
-untrusted comment: signature from minisign secret key
-RURMm6vfaPgH3997FX/cHwhXJpcluwbNiznrfYV83WS/Gsd3BeO/g10Mo7Z9N5rMSXcpGrmT2CagiEEm5zSw/MEnTqs4YWICdQs=
-trusted comment: timestamp:20 file:server_list.json hashed
-rHcsHF2mmcZvDLreeuljVauuFULWiY8luCsxyBxxobcJkCedEDW3/RX5KeT+2NjHSFuQxkmrYOBWTY9+ECuUDQ==
diff --git a/src/test_data/server_list.json.tc_nofile.minisig b/src/test_data/server_list.json.tc_nofile.minisig
deleted file mode 100644
index 3c1dcbe..0000000
--- a/src/test_data/server_list.json.tc_nofile.minisig
+++ /dev/null
@@ -1,4 +0,0 @@
-untrusted comment: signature from minisign secret key
-RURMm6vfaPgH3997FX/cHwhXJpcluwbNiznrfYV83WS/Gsd3BeO/g10Mo7Z9N5rMSXcpGrmT2CagiEEm5zSw/MEnTqs4YWICdQs=
-trusted comment: timestamp:10 hashed
-NonaTZH7RDbsHXv85M7sL43YE7CTzs5qDRRoFYjajeqzHa+hdIuMGyemK85rAJ3prLGnMdWHkZhD4hsr3cZoDA==
diff --git a/src/test_data/server_list.json.tc_nohashed.minisig b/src/test_data/server_list.json.tc_nohashed.minisig
deleted file mode 100644
index 1d140c1..0000000
--- a/src/test_data/server_list.json.tc_nohashed.minisig
+++ /dev/null
@@ -1,4 +0,0 @@
-untrusted comment: signature from minisign secret key
-RURMm6vfaPgH3997FX/cHwhXJpcluwbNiznrfYV83WS/Gsd3BeO/g10Mo7Z9N5rMSXcpGrmT2CagiEEm5zSw/MEnTqs4YWICdQs=
-trusted comment: timestamp:10 file:server_list.json
-HaPGKT+Jqxjyw2Nt1GEKaPIZsAmVl/RI6p1mQ+S1LqzYicVgT5GxPs9NR6khdGGIFvo/xhVkXFceAWTRUCVQAg==
diff --git a/src/test_data/server_list.json.tc_notime.minisig b/src/test_data/server_list.json.tc_notime.minisig
deleted file mode 100644
index 39625c3..0000000
--- a/src/test_data/server_list.json.tc_notime.minisig
+++ /dev/null
@@ -1,4 +0,0 @@
-untrusted comment: signature from minisign secret key
-RURMm6vfaPgH3997FX/cHwhXJpcluwbNiznrfYV83WS/Gsd3BeO/g10Mo7Z9N5rMSXcpGrmT2CagiEEm5zSw/MEnTqs4YWICdQs=
-trusted comment: file:server_list.json hashed
-dMhb+0Y0KAO2tzI4g0ukL/VdMiLVopmXa9BS1RQBY8bYwzmebdIM4DAIZrhtO1avkpdy0prZehuhA1No6cOSAw==
diff --git a/src/test_data/server_list.json.tc_orglist.minisig b/src/test_data/server_list.json.tc_orglist.minisig
deleted file mode 100644
index 7c2a3a8..0000000
--- a/src/test_data/server_list.json.tc_orglist.minisig
+++ /dev/null
@@ -1,4 +0,0 @@
-untrusted comment: signature from minisign secret key
-RURMm6vfaPgH3997FX/cHwhXJpcluwbNiznrfYV83WS/Gsd3BeO/g10Mo7Z9N5rMSXcpGrmT2CagiEEm5zSw/MEnTqs4YWICdQs=
-trusted comment: timestamp:10 file:organization_list.json hashed
-NreDM4iGEjMWs5sfaJCGZBZ7D9QLqxBKJ/fVW2lvIDr249DSUNR4ZRca8UL73e3c9eTXgHnY/ojsjDtzxDScDw==
diff --git a/src/test_data/server_list.json.tc_otherfile.minisig b/src/test_data/server_list.json.tc_otherfile.minisig
deleted file mode 100644
index 58a29b2..0000000
--- a/src/test_data/server_list.json.tc_otherfile.minisig
+++ /dev/null
@@ -1,4 +0,0 @@
-untrusted comment: signature from minisign secret key
-RURMm6vfaPgH3997FX/cHwhXJpcluwbNiznrfYV83WS/Gsd3BeO/g10Mo7Z9N5rMSXcpGrmT2CagiEEm5zSw/MEnTqs4YWICdQs=
-trusted comment: timestamp:10 file:otherfile hashed
-PfDEIMlt2aNFyOnqHb45S7xm4fIg0vfUUbqXENPxry9GEZFX14c5BGtgcL/krDg8WFJHcIA5bzYcX58kgBiZCA==
diff --git a/src/test_data/server_list.json.tc_random.minisig b/src/test_data/server_list.json.tc_random.minisig
deleted file mode 100644
index 7240980..0000000
--- a/src/test_data/server_list.json.tc_random.minisig
+++ /dev/null
@@ -1,4 +0,0 @@
-untrusted comment: signature from minisign secret key
-RURMm6vfaPgH3997FX/cHwhXJpcluwbNiznrfYV83WS/Gsd3BeO/g10Mo7Z9N5rMSXcpGrmT2CagiEEm5zSw/MEnTqs4YWICdQs=
-trusted comment: random stuff
-szGsyESH0EizTXH6n0yuQg6sHTKXr+TJW/Er9ZNJYgQV+1hVM+fc5q1EmVsJlA3kW4Rt/d1p9F0ShLIIgW2vAA==
diff --git a/src/test_data/server_list.json.wrong_key.minisig b/src/test_data/server_list.json.wrong_key.minisig
deleted file mode 100644
index 5a83c0e..0000000
--- a/src/test_data/server_list.json.wrong_key.minisig
+++ /dev/null
@@ -1,4 +0,0 @@
-untrusted comment: signature from minisign secret key
-RUTQvDHvQuYCCJaarz3NMq4gbv6JvzOnjG003bDe6USu+HT/JzuxHjQcQGE/KBPdyCF6BDDwwFu+NVmi5jotYCJHWOEqSBU70gE=
-trusted comment: timestamp:10 file:server_list.json hashed
-3BWYJamM3t6ImuXQufTeO81UMZNyM7TujMu7SCmR+oapsSEBpmkazGOgzlJYR53HP9K9zrEA+4lV8gFFngooBA==
diff --git a/src/test_data/wrong_public.key b/src/test_data/wrong_public.key
deleted file mode 100644
index aa794d4..0000000
--- a/src/test_data/wrong_public.key
+++ /dev/null
@@ -1,2 +0,0 @@
-untrusted comment: minisign public key 802E642EF31BCD0
-RWTQvDHvQuYCCPDLi3UCXzj3BbzFM5QxUFfrp174iaqYo8lT0VaAkhOt
diff --git a/src/test_data/wrong_secret.key b/src/test_data/wrong_secret.key
deleted file mode 100644
index 68e9092..0000000
--- a/src/test_data/wrong_secret.key
+++ /dev/null
@@ -1,2 +0,0 @@
-untrusted comment: minisign encrypted secret key
-RWRTY0Iyrc2CTG2W1ZqEq9tb94oQWTnYUy4k8boMf13478FwlDYAAAACAAAAAAAAAEAAAAAA2gFhwOtjETu5WN1LpgtJHV1dk/7466LBJ8dgO/pZoQ3LLAYxlswJHVR/N/Q1HmmKvlxWo2jNTJcARXuHHlMTEgg1MERTldE88CqETrVbvq1JaqJlAY/HMkiqNEUR3L6+5VHbYPKXlVQ=
diff --git a/src/util.go b/src/util.go
deleted file mode 100644
index 87340f9..0000000
--- a/src/util.go
+++ /dev/null
@@ -1,21 +0,0 @@
-package eduvpn
-
-import (
- "crypto/rand"
- "time"
-)
-
-// Creates a random byteslice of `size`
-func MakeRandomByteSlice(size int) ([]byte, error) {
- byteSlice := make([]byte, size)
- _, err := rand.Read(byteSlice)
- if err != nil {
- return nil, err
- }
- return byteSlice, nil
-}
-
-func GenerateTimeSeconds() int64 {
- current := time.Now()
- return current.Unix()
-}
diff --git a/src/verify.go b/src/verify.go
deleted file mode 100644
index 3c87fe1..0000000
--- a/src/verify.go
+++ /dev/null
@@ -1,205 +0,0 @@
-package eduvpn
-
-import (
- "errors"
- "fmt"
- "os"
-
- "github.com/jedisct1/go-minisign"
-)
-
-// getKeys returns keys taken from https://git.sr.ht/~eduvpn/disco.eduvpn.org#public-keys.
-func getKeys() []string {
- return []string{
- "RWRtBSX1alxyGX+Xn3LuZnWUT0w//B6EmTJvgaAxBMYzlQeI+jdrO6KF", // fkooman@tuxed.net, kolla@uninett.no
- "RWQKqtqvd0R7rUDp0rWzbtYPA3towPWcLDCl7eY9pBMMI/ohCmrS0WiM", // RoSp
- }
-}
-
-// Verify verifies the signature (.minisig file format) on signedJson.
-//
-// expectedFileName must be set to the file type to be verified, either "server_list.json" or "organization_list.json".
-// minSign must be set to the minimum UNIX timestamp (without milliseconds) for the file version.
-// This value should not be smaller than the time on the previous document verified.
-// forcePrehash indicates whether or not we want to force the use of prehashed signatures
-// In the future we want to remove this parameter and only allow prehashed signatures
-//
-// The return value will either be (true, nil) for a valid signature or (false, VerifyError) otherwise.
-//
-// Verify is a wrapper around verifyWithKeys where allowedPublicKeys is set to the list from https://git.sr.ht/~eduvpn/disco.eduvpn.org#public-keys.
-func Verify(signatureFileContent string, signedJson []byte, expectedFileName string, minSignTime uint64, forcePrehash bool) (bool, error) {
- keyStrs := getKeys()
- if extraKey != "" {
- keyStrs = append(keyStrs, extraKey)
- _, err := fmt.Fprintf(os.Stderr, "INSECURE TEST MODE ENABLED WITH KEY %q\n", extraKey)
- if err != nil {
- panic(err)
- }
- }
- valid, err := verifyWithKeys(signatureFileContent, signedJson, expectedFileName, minSignTime, keyStrs, forcePrehash)
- if err != nil {
- var verifyCreatePublickeyError *VerifyCreatePublicKeyError
- if errors.As(err, &verifyCreatePublickeyError) {
- panic(err) // This should not happen unless keyStrs has an invalid key
- }
- return valid, err
- }
- return valid, nil
-}
-
-// extraKey is an extra allowed key for testing.
-var extraKey = ""
-
-// InsecureTestingSetExtraKey adds an extra allowed key for verification with Verify.
-// ONLY USE FOR TESTING. Applies to all threads. Probably not thread-safe. Do not call in parallel to Verify.
-//
-// keyString must be a Base64-encoded Minisign key, or empty to reset.
-func InsecureTestingSetExtraKey(keyString string) {
- extraKey = keyString
-}
-
-type VerifyUnknownExpectedFilenameError struct {
- Filename string
- Expected string
-}
-
-func (e *VerifyUnknownExpectedFilenameError) Error() string {
- return fmt.Sprintf("invalid filename %s, expected %s", e.Filename, e.Expected)
-}
-
-type VerifyInvalidSignatureFormatError struct {
- Err error
-}
-
-func (e *VerifyInvalidSignatureFormatError) Error() string {
- return fmt.Sprintf("invalid signature format, error %v", e.Err)
-}
-
-type VerifyInvalidSignatureAlgorithmError struct {
- Algorithm string
- WantedAlgorithm string
-}
-
-func (e *VerifyInvalidSignatureAlgorithmError) Error() string {
- return fmt.Sprintf("invalid signature algorithm %s, wanted %s", e.Algorithm, e.WantedAlgorithm)
-}
-
-type VerifyCreatePublicKeyError struct {
- PublicKey string
- Err error
-}
-
-func (e *VerifyCreatePublicKeyError) Error() string {
- return fmt.Sprintf("failed to create public key %s with error %v", e.PublicKey, e.Err)
-}
-
-type VerifyInvalidSignatureError struct {
- Err error
-}
-
-func (e *VerifyInvalidSignatureError) Error() string {
- return fmt.Sprintf("invalid signature with error %v", e.Err)
-}
-
-type VerifyInvalidTrustedCommentError struct {
- TrustedComment string
- Err error
-}
-
-func (e *VerifyInvalidTrustedCommentError) Error() string {
- return fmt.Sprintf("invalid trusted comment %s with error %v", e.TrustedComment, e.Err)
-}
-
-type VerifyWrongSigFilenameError struct {
- Filename string
- SigFilename string
-}
-
-func (e *VerifyWrongSigFilenameError) Error() string {
- return fmt.Sprintf("wrong filename %s, expected filename %s for signature", e.Filename, e.SigFilename)
-}
-
-type VerifySigTimeEarlierError struct {
- SigTime uint64
- MinSigTime uint64
-}
-
-func (e *VerifySigTimeEarlierError) Error() string {
- return fmt.Sprintf("Sign time %d is earlier than sign time %d", e.SigTime, e.MinSigTime)
-}
-
-type VerifyUnknownKeyError struct {
- Filename string
-}
-
-func (e *VerifyUnknownKeyError) Error() string {
- return fmt.Sprintf("signature for filename %s was created with an unknown key", e.Filename)
-}
-
-// verifyWithKeys verifies the Minisign signature in signatureFileContent (minisig file format) over the server_list/organization_list JSON in signedJson.
-//
-// Verification is performed using a matching key in allowedPublicKeys.
-// The signature is checked to be a Ed25519 Minisign (optionally Ed25519 Blake2b-512 prehashed, see forcePrehash) signature with a valid trusted comment.
-// The file type that is verified is indicated by expectedFileName, which must be one of "server_list.json"/"organization_list.json".
-// The trusted comment is checked to be of the form "timestamp:<timestamp>\tfile:<expectedFileName>", optionally suffixed by something, e.g. "\thashed".
-// The signature is checked to have a timestamp with a value of at least minSignTime, which is a UNIX timestamp without milliseconds.
-//
-// The return value will either be (true, nil) on success or (false, detailedVerifyError) on failure.
-func verifyWithKeys(signatureFileContent string, signedJson []byte, filename string, minSignTime uint64, allowedPublicKeys []string, forcePrehash bool) (bool, error) {
- switch filename {
- case "server_list.json", "organization_list.json":
- break
- default:
- return false, &VerifyUnknownExpectedFilenameError{Filename: filename, Expected: "server_list.json or organization_list.json"}
- }
-
- sig, err := minisign.DecodeSignature(signatureFileContent)
- if err != nil {
- return false, &VerifyInvalidSignatureFormatError{Err: err}
- }
-
- // Check if signature is prehashed, see https://jedisct1.github.io/minisign/#signature-format
- if forcePrehash && sig.SignatureAlgorithm != [2]byte{'E', 'D'} {
- return false, &VerifyInvalidSignatureAlgorithmError{Algorithm: string(sig.SignatureAlgorithm[:]), WantedAlgorithm: "ED (BLAKE2b-prehashed EdDSA)"}
- }
-
- // Find allowed key used for signature
- for _, keyStr := range allowedPublicKeys {
- key, err := minisign.NewPublicKey(keyStr)
- if err != nil {
- // Should only happen if Verify is wrong or extraKey is invalid
- return false, &VerifyCreatePublicKeyError{PublicKey: keyStr, Err: err}
- }
-
- if sig.KeyId != key.KeyId {
- continue // Wrong key
- }
-
- valid, err := key.Verify(signedJson, sig)
- if !valid {
- return false, &VerifyInvalidSignatureError{Err: err}
- }
-
- // Parse trusted comment
- var signTime uint64
- var sigFileName string
- // sigFileName cannot have spaces
- _, err = fmt.Sscanf(sig.TrustedComment, "trusted comment: timestamp:%d\tfile:%s", &signTime, &sigFileName)
- if err != nil {
- return false, &VerifyInvalidTrustedCommentError{TrustedComment: sig.TrustedComment, Err: err}
- }
-
- if sigFileName != filename {
- return false, &VerifyWrongSigFilenameError{Filename: filename, SigFilename: sigFileName}
- }
-
- if signTime < minSignTime {
- return false, &VerifySigTimeEarlierError{SigTime: signTime, MinSigTime: minSignTime}
- }
-
- return true, nil
- }
-
- // No matching allowed key found
- return false, &VerifyUnknownKeyError{Filename: filename}
-}
diff --git a/src/verify_test.go b/src/verify_test.go
deleted file mode 100644
index fc78ec3..0000000
--- a/src/verify_test.go
+++ /dev/null
@@ -1,140 +0,0 @@
-package eduvpn
-
-import (
- "bufio"
- "errors"
- "fmt"
- "io/ioutil"
- "os"
- "testing"
-)
-
-func Test_verifyWithKeys(t *testing.T) {
- var err error
-
- var pk []string
- {
- file, err := os.Open("test_data/public.key")
- if err != nil {
- panic(err)
- }
- defer file.Close()
-
- // Get last line (key string) from file
- scanner := bufio.NewScanner(file)
- for i := 0; i < 2; i++ {
- if !scanner.Scan() {
- panic(scanner.Err())
- }
- }
- pk = []string{scanner.Text()}
- }
-
- var (
- verifyCreatePublicKeyError *VerifyCreatePublicKeyError
- verifyInvalidSignatureAlgorithmError *VerifyInvalidSignatureAlgorithmError
- verifyWrongSigFilenameError *VerifyWrongSigFilenameError
- verifyInvalidTrustedCommentError *VerifyInvalidTrustedCommentError
- verifyInvalidSignatureFormatError *VerifyInvalidSignatureFormatError
- verifyInvalidSignatureError *VerifyInvalidSignatureError
- verifySigTimeEarlierError *VerifySigTimeEarlierError
- verifyUnknownExpectedFilenameError *VerifyUnknownExpectedFilenameError
- verifyUnknownKeyError *VerifyUnknownKeyError
- )
-
- tests := []struct {
- expectedErr interface{}
- testName string
- signatureFile string
- jsonFile string
- expectedFileName string
- minSignTime uint64
- allowedPks []string
- }{
- {&verifyInvalidSignatureAlgorithmError, "pure", "server_list.json.pure.minisig", "server_list.json", "server_list.json", 10, pk},
-
- {nil, "valid server_list", "server_list.json.minisig", "server_list.json", "server_list.json", 10, pk},
- {nil, "TC no hashed", "server_list.json.tc_nohashed.minisig", "server_list.json", "server_list.json", 10, pk},
- {nil, "TC later time", "server_list.json.tc_latertime.minisig", "server_list.json", "server_list.json", 10, pk},
- {&verifyWrongSigFilenameError, "server_list TC file:organization_list", "server_list.json.tc_orglist.minisig", "server_list.json", "server_list.json", 10, pk},
- {&verifyWrongSigFilenameError, "organization_list as server_list", "organization_list.json.minisig", "organization_list.json", "server_list.json", 10, pk},
- {&verifyWrongSigFilenameError, "TC file:otherfile", "server_list.json.tc_otherfile.minisig", "server_list.json", "server_list.json", 10, pk},
- {&verifySigTimeEarlierError, "TC no file", "server_list.json.tc_nofile.minisig", "server_list.json", "server_list.json", 10, pk},
- {&verifySigTimeEarlierError, "TC no time", "server_list.json.tc_notime.minisig", "server_list.json", "server_list.json", 10, pk},
- {&verifySigTimeEarlierError, "TC empty time", "server_list.json.tc_emptytime.minisig", "server_list.json", "server_list.json", 10, pk},
- {&verifyInvalidSignatureFormatError, "TC empty file", "server_list.json.tc_emptyfile.minisig", "server_list.json", "server_list.json", 10, pk},
- {&verifyInvalidTrustedCommentError, "TC random", "server_list.json.tc_random.minisig", "server_list.json", "server_list.json", 10, pk},
- {nil, "large time", "server_list.json.large_time.minisig", "server_list.json", "server_list.json", 43e8, pk},
- {nil, "lower min time", "server_list.json.minisig", "server_list.json", "server_list.json", 5, pk},
- {&verifySigTimeEarlierError, "higher min time", "server_list.json.minisig", "server_list.json", "server_list.json", 11, pk},
-
- {nil, "valid organization_list", "organization_list.json.minisig", "organization_list.json", "organization_list.json", 10, pk},
- {&verifyWrongSigFilenameError, "organization_list TC file:server_list", "organization_list.json.tc_servlist.minisig", "organization_list.json", "organization_list.json", 10, pk},
- {&verifyWrongSigFilenameError, "server_list as organization_list", "server_list.json.minisig", "server_list.json", "organization_list.json", 10, pk},
-
- {&verifyUnknownExpectedFilenameError, "valid other_list", "other_list.json.minisig", "other_list.json", "other_list.json", 10, pk},
- {&verifyWrongSigFilenameError, "other_list as server_list", "other_list.json.minisig", "other_list.json", "server_list.json", 10, pk},
-
- {&verifyInvalidSignatureFormatError, "invalid signature file", "random.txt", "server_list.json", "server_list.json", 10, pk},
- {&verifyInvalidSignatureFormatError, "empty signature file", "empty", "server_list.json", "server_list.json", 10, pk},
-
- {&verifyUnknownKeyError, "wrong key", "server_list.json.wrong_key.minisig", "server_list.json", "server_list.json", 10, pk},
-
- {&verifyInvalidSignatureAlgorithmError, "forged pure signature", "server_list.json.forged_pure.minisig", "server_list.json.blake2b", "server_list.json", 10, pk},
- {&verifyInvalidSignatureError, "forged key ID", "server_list.json.forged_keyid.minisig", "server_list.json", "server_list.json", 10, pk},
-
- {&verifyUnknownKeyError, "no allowed keys", "server_list.json.minisig", "server_list.json", "server_list.json", 10, []string{}},
- {nil, "multiple allowed keys 1", "server_list.json.minisig", "server_list.json", "server_list.json", 10, []string{
- pk[0], "RWSf0PYToIUJmDlsz21YOXvgQzHj9NSdyJUqEY5ZdfS9GepeXt3+JJRZ",
- }},
- {nil, "multiple allowed keys 2", "server_list.json.minisig", "server_list.json", "server_list.json", 10, []string{
- "RWSf0PYToIUJmDlsz21YOXvgQzHj9NSdyJUqEY5ZdfS9GepeXt3+JJRZ", pk[0],
- }},
- {&verifyCreatePublicKeyError, "invalid allowed key", "server_list.json.minisig", "server_list.json", "server_list.json", 10, []string{"AAA"}},
- }
-
- // Cache file contents in map, mapping file names to contents
- files := map[string][]byte{}
- loadFile := func(name string) {
- content, loaded := files[name]
- if !loaded {
- content, err = ioutil.ReadFile("test_data/" + name)
- if err != nil {
- panic(err)
- }
- files[name] = content
- }
- }
- for _, test := range tests {
- loadFile(test.signatureFile)
- loadFile(test.jsonFile)
- }
-
- forcePrehash := true
- for _, tt := range tests {
- t.Run(tt.testName, func(t *testing.T) {
- t.Parallel()
- valid, err := verifyWithKeys(string(files[tt.signatureFile]), files[tt.jsonFile],
- tt.expectedFileName, tt.minSignTime, tt.allowedPks, forcePrehash)
- compareResults(t, valid, err, tt.expectedErr, func() string {
- return fmt.Sprintf("verifyWithKeys(%q, %q, %q, %v, %v, %t)",
- tt.signatureFile, tt.jsonFile, tt.expectedFileName, tt.minSignTime, tt.allowedPks, forcePrehash)
- })
- })
- }
-}
-
-// compareResults compares returned ret, err from a verify function with expected error code expected.
-// callStr is called to get the formatted parameters passed to the function.
-func compareResults(t *testing.T, ret bool, err error, expectedErr interface{}, callStr func() string) {
- // different error returned
- if expectedErr != nil && !errors.As(err, expectedErr) {
- t.Errorf("%v\nerror %T = %v, wantErr %T", callStr(), err, err, expectedErr)
- return
- }
- // different boolean returned
- expectedBool := expectedErr == nil
- if ret != expectedBool {
- t.Errorf("%v\n= %v, want %v", callStr(), ret, expectedBool)
- }
-}
diff --git a/src/wireguard.go b/src/wireguard.go
deleted file mode 100644
index 2f1c41c..0000000
--- a/src/wireguard.go
+++ /dev/null
@@ -1,52 +0,0 @@
-package eduvpn
-
-import (
- "fmt"
- "regexp"
-
- "golang.zx2c4.com/wireguard/wgctrl/wgtypes"
-)
-
-func wireguardGenerateKey() (wgtypes.Key, error) {
- key, error := wgtypes.GeneratePrivateKey()
- return key, error
-}
-
-// FIXME: Instead of doing a regex replace, decide if we should use a parser
-func wireguardConfigAddKey(config string, key wgtypes.Key) string {
- interface_section := "[Interface]"
- interface_section_escaped := regexp.QuoteMeta(interface_section)
-
- // (?m) enables multi line mode
- // ^ match from beginning of line
- // $ match till end of line
- // So it matches [Interface] section exactly
- interface_re := regexp.MustCompile(fmt.Sprintf("(?m)^%s$", interface_section_escaped))
- to_replace := fmt.Sprintf("%s\nPrivateKey = %s", interface_section, key.String())
- return interface_re.ReplaceAllString(config, to_replace)
-}
-
-func (server *Server) WireguardGetConfig() (string, error) {
- profile_id := server.Profiles.Current
- wireguardKey, wireguardErr := wireguardGenerateKey()
-
- if wireguardErr != nil {
- return "", wireguardErr
- }
-
- wireguardPublicKey := wireguardKey.PublicKey().String()
- configWireguard, _, configErr := server.APIConnectWireguard(profile_id, wireguardPublicKey)
-
- if configErr != nil {
- return "", configErr
- }
-
- // FIXME: Store expiry
- // This needs the go code a way to identify a connection
- // Use the uuid of the connection e.g. on Linux
- // This needs the client code to call the go code
-
- configWireguardKey := wireguardConfigAddKey(configWireguard, wireguardKey)
-
- return configWireguardKey, nil
-}