summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorjwijenbergh <jeroenwijenbergh@protonmail.com>2022-04-21 14:39:49 +0200
committerjwijenbergh <jeroenwijenbergh@protonmail.com>2022-04-21 14:39:49 +0200
commitf794ea95eb49a4f92ddd16307d70f6cad0f6a768 (patch)
tree5eb56dcda154375eedc2478055cfe07d0b4b7686
parent7c284019f1e48ddd37b0f047b50564f069caf379 (diff)
Discovery: Rollback preventions and hourly updates for servers
-rw-r--r--src/discovery.go97
-rw-r--r--src/oauth.go6
-rw-r--r--src/state.go2
-rw-r--r--src/util.go6
4 files changed, 79 insertions, 32 deletions
diff --git a/src/discovery.go b/src/discovery.go
index c7682f0..fa3cac6 100644
--- a/src/discovery.go
+++ b/src/discovery.go
@@ -1,6 +1,7 @@
package eduvpn
import (
+ "encoding/json"
"fmt"
)
@@ -32,20 +33,41 @@ 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 DiscoList struct {
- Organizations string `json:"organizations"`
- Servers string `json:"servers"`
+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) (string, error) {
+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}
+ return &DiscoFileError{fileURL, fileErr}
}
// Get signature
@@ -54,21 +76,26 @@ func getDiscoFile(jsonFile string) (string, error) {
_, sigBody, sigFileErr := HTTPGet(sigURL)
if sigFileErr != nil {
- return "", &DiscoSigFileError{URL: sigURL, Err: sigFileErr}
+ return &DiscoSigFileError{URL: sigURL, Err: sigFileErr}
}
// Verify signature
- // TODO: Handle this by keeping track of the previous sign time
- // Wrappers must do this?
- var previousSigTime uint64 = 0
+ // Set this to true when we want to force prehash
forcePrehash := false
- verifySuccess, verifyErr := Verify(string(sigBody), fileBody, jsonFile, previousSigTime, forcePrehash)
+ verifySuccess, verifyErr := Verify(string(sigBody), fileBody, jsonFile, previousVersion, forcePrehash)
if !verifySuccess || verifyErr != nil {
- return "", &DiscoVerifyError{File: jsonFile, Sigfile: sigFile, Err: verifyErr}
+ return &DiscoVerifyError{File: jsonFile, Sigfile: sigFile, Err: verifyErr}
}
- return string(fileBody), nil
+ // 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 {
@@ -80,39 +107,59 @@ func (e *GetListError) Error() string {
return fmt.Sprintf("failed getting disco list file %s with error %v", e.File, e.Err)
}
-// FIXME: Implement these properly based on version and time info
+// 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 eduvpn.DiscoList.Organizations == ""
+ 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 {
- return eduvpn.DiscoList.Servers == ""
+ // 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 eduvpn.DiscoList.Organizations, nil
+ return string(eduvpn.DiscoList.Organizations.JSON), nil
}
file := "organization_list.json"
- body, err := getDiscoFile(file)
+ err := getDiscoFile(file, eduvpn.DiscoList.Organizations.Version, &eduvpn.DiscoList.Organizations)
if err != nil {
- return "", &GetListError{File: file, Err: err}
+ // Return previous with an error
+ return string(eduvpn.DiscoList.Organizations.JSON), &GetListError{File: file, Err: err}
}
- eduvpn.DiscoList.Organizations = body
- return body, nil
+ return string(eduvpn.DiscoList.Organizations.JSON), nil
}
// Get the server list
func (eduvpn *VPNState) GetServersList() (string, error) {
if !eduvpn.DetermineServersUpdate() {
- return eduvpn.DiscoList.Servers, nil
+ return string(eduvpn.DiscoList.Servers.JSON), nil
}
file := "server_list.json"
- body, err := getDiscoFile("server_list.json")
+ err := getDiscoFile(file, eduvpn.DiscoList.Servers.Version, &eduvpn.DiscoList.Servers)
if err != nil {
- return "", &GetListError{File: file, Err: err}
+ // Return previous with an error
+ return string(eduvpn.DiscoList.Servers.JSON), &GetListError{File: file, Err: err}
}
- eduvpn.DiscoList.Servers = body
- return body, nil
+ // Update servers timestamp
+ eduvpn.DiscoList.Servers.Timestamp = GenerateTimeSeconds()
+ return string(eduvpn.DiscoList.Servers.JSON), nil
}
diff --git a/src/oauth.go b/src/oauth.go
index 96ce8b2..08ec07e 100644
--- a/src/oauth.go
+++ b/src/oauth.go
@@ -9,7 +9,6 @@ import (
"fmt"
"net/http"
"net/url"
- "time"
)
// Generates a random base64 string to be used for state
@@ -74,11 +73,6 @@ type OAuthExchangeSession struct {
Server http.Server
}
-func GenerateTimeSeconds() int64 {
- current := time.Now()
- return current.Unix()
-}
-
// Struct that defines the json format for /.well-known/vpn-user-portal"
type OAuthToken struct {
Access string `json:"access_token"`
diff --git a/src/state.go b/src/state.go
index 121ef6d..52a4861 100644
--- a/src/state.go
+++ b/src/state.go
@@ -15,7 +15,7 @@ type VPNState struct {
Server Server `json:"server"`
// The list of servers and organizations from disco
- DiscoList DiscoList `json:"disco"`
+ DiscoList DiscoLists `json:"-"`
// The file we keep open for logging
LogFile FileLogger `json:"-"`
diff --git a/src/util.go b/src/util.go
index 1d0df91..87340f9 100644
--- a/src/util.go
+++ b/src/util.go
@@ -2,6 +2,7 @@ package eduvpn
import (
"crypto/rand"
+ "time"
)
// Creates a random byteslice of `size`
@@ -13,3 +14,8 @@ func MakeRandomByteSlice(size int) ([]byte, error) {
}
return byteSlice, nil
}
+
+func GenerateTimeSeconds() int64 {
+ current := time.Now()
+ return current.Unix()
+}