From 865658aa9221f5963aed51c8729959d763cee3b9 Mon Sep 17 00:00:00 2001 From: Mike Sawka Date: Wed, 13 Mar 2024 23:59:20 -0700 Subject: [PATCH] allow hmac authentication for read-file (#449) --- wavesrv/cmd/main-server.go | 32 ++++++++++++++++++++- wavesrv/pkg/promptenc/hmac.go | 52 +++++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 wavesrv/pkg/promptenc/hmac.go diff --git a/wavesrv/cmd/main-server.go b/wavesrv/cmd/main-server.go index c2d13377..8a2123ae 100644 --- a/wavesrv/cmd/main-server.go +++ b/wavesrv/cmd/main-server.go @@ -37,6 +37,7 @@ import ( "github.com/wavetermdev/waveterm/waveshell/pkg/wlog" "github.com/wavetermdev/waveterm/wavesrv/pkg/cmdrunner" "github.com/wavetermdev/waveterm/wavesrv/pkg/pcloud" + "github.com/wavetermdev/waveterm/wavesrv/pkg/promptenc" "github.com/wavetermdev/waveterm/wavesrv/pkg/releasechecker" "github.com/wavetermdev/waveterm/wavesrv/pkg/remote" "github.com/wavetermdev/waveterm/wavesrv/pkg/rtnstate" @@ -695,6 +696,35 @@ func AuthKeyMiddleWare(next http.Handler) http.Handler { }) } +func AuthKeyWrapAllowHmac(fn WebFnType) WebFnType { + return func(w http.ResponseWriter, r *http.Request) { + reqAuthKey := r.Header.Get("X-AuthKey") + if reqAuthKey == "" { + // try hmac + qvals := r.URL.Query() + if !qvals.Has("hmac") { + w.WriteHeader(500) + w.Write([]byte("no x-authkey header")) + return + } + hmacOk, err := promptenc.ValidateUrlHmac([]byte(GlobalAuthKey), r.URL.Path, qvals) + if err != nil || !hmacOk { + w.WriteHeader(500) + w.Write([]byte(fmt.Sprintf("error validating hmac"))) + return + } + // fallthrough (hmac is valid) + } else if reqAuthKey != GlobalAuthKey { + w.WriteHeader(500) + w.Write([]byte("x-authkey header is invalid")) + return + } + w.Header().Set(CacheControlHeaderKey, CacheControlHeaderNoCache) + fn(w, r) + } + +} + func AuthKeyWrap(fn WebFnType) WebFnType { return func(w http.ResponseWriter, r *http.Request) { reqAuthKey := r.Header.Get("X-AuthKey") @@ -921,7 +951,7 @@ func main() { gr.HandleFunc("/api/get-client-data", AuthKeyWrap(HandleGetClientData)) gr.HandleFunc("/api/set-winsize", AuthKeyWrap(HandleSetWinSize)) gr.HandleFunc("/api/log-active-state", AuthKeyWrap(HandleLogActiveState)) - gr.HandleFunc("/api/read-file", AuthKeyWrap(HandleReadFile)) + gr.HandleFunc("/api/read-file", AuthKeyWrapAllowHmac(HandleReadFile)) gr.HandleFunc("/api/write-file", AuthKeyWrap(HandleWriteFile)).Methods("POST") configPath := path.Join(scbase.GetWaveHomeDir(), "config") + "/" log.Printf("[wave] config path: %q\n", configPath) diff --git a/wavesrv/pkg/promptenc/hmac.go b/wavesrv/pkg/promptenc/hmac.go new file mode 100644 index 00000000..53a1c20c --- /dev/null +++ b/wavesrv/pkg/promptenc/hmac.go @@ -0,0 +1,52 @@ +// Copyright 2024, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + +package promptenc + +import ( + "crypto/hmac" + "crypto/sha256" + "encoding/base64" + "fmt" + "net/url" +) + +func ComputeUrlHmac(key []byte, baseUrl string, qvals url.Values) (string, error) { + if qvals.Has("nonce") { + return "", fmt.Errorf("nonce is required for hmac") + } + if qvals.Has("hmac") { + return "", fmt.Errorf("hmac is already present") + } + encStr := baseUrl + "?" + qvals.Encode() + mac := hmac.New(sha256.New, key) + mac.Write([]byte(encStr)) + rtn := mac.Sum(nil) + return base64.URLEncoding.EncodeToString(rtn), nil +} + +func copyUrlValues(src url.Values) url.Values { + rtn := make(url.Values) + for k, v := range src { + rtn[k] = v + } + return rtn +} + +func ValidateUrlHmac(key []byte, baseUrl string, qvalsOrig url.Values) (bool, error) { + qvals := copyUrlValues(qvalsOrig) + hmacStr := qvals.Get("hmac") + if hmacStr == "" { + return false, fmt.Errorf("no hmac key found")) + } + qvals.Del("hmac") + encStr := baseUrl + "?" + qvals.Encode() + mac := hmac.New(sha256.New, key) + mac.Write([]byte(encStr)) + expected := mac.Sum(nil) + actual, err := base64.URLEncoding.DecodeString(hmacStr) + if err != nil { + return false, fmt.Errorf("error decoding hmac: %w", err) + } + return hmac.Equal(expected, actual), nil +}