diff options
| author | jwijenbergh <jeroenwijenbergh@protonmail.com> | 2022-04-21 14:39:49 +0200 |
|---|---|---|
| committer | jwijenbergh <jeroenwijenbergh@protonmail.com> | 2022-04-21 14:39:49 +0200 |
| commit | f794ea95eb49a4f92ddd16307d70f6cad0f6a768 (patch) | |
| tree | 5eb56dcda154375eedc2478055cfe07d0b4b7686 /src | |
| parent | 7c284019f1e48ddd37b0f047b50564f069caf379 (diff) | |
Discovery: Rollback preventions and hourly updates for servers
Diffstat (limited to 'src')
| -rw-r--r-- | src/discovery.go | 97 | ||||
| -rw-r--r-- | src/oauth.go | 6 | ||||
| -rw-r--r-- | src/state.go | 2 | ||||
| -rw-r--r-- | src/util.go | 6 |
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() +} |
