summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormo khan <mo@mokhan.ca>2025-05-07 10:30:59 -0700
committermo khan <mo@mokhan.ca>2025-05-07 10:30:59 -0700
commitf0fbdab72254d68d0a3a4a49a4a1646f89f0f913 (patch)
treea6ede6841714a16fa9ac61ae28575a1f749ad547
parent61598cf8c8a2dbae368f3f8b15393c70d2e4fa9d (diff)
feat: digitally sign and verify cookie using randomly generated key
-rw-r--r--app/controllers/sessions/controller_test.go2
-rw-r--r--app/controllers/sessions/service.go3
-rw-r--r--app/middleware/token_parser.go4
-rw-r--r--go.mod2
-rw-r--r--go.sum2
-rw-r--r--pkg/web/cookie.go44
6 files changed, 52 insertions, 5 deletions
diff --git a/app/controllers/sessions/controller_test.go b/app/controllers/sessions/controller_test.go
index a29041e..a1158da 100644
--- a/app/controllers/sessions/controller_test.go
+++ b/app/controllers/sessions/controller_test.go
@@ -127,7 +127,7 @@ func TestSessions(t *testing.T) {
cookie, err := http.ParseSetCookie(w.Header().Get("Set-Cookie"))
require.NoError(t, err)
require.NotZero(t, cookie)
- data, err := base64.URLEncoding.DecodeString(cookie.Value)
+ data, err := base64.URLEncoding.DecodeString(web.CookieValueFrom(cookie))
require.NoError(t, err)
tokens := map[string]interface{}{}
require.NoError(t, json.Unmarshal(data, &tokens))
diff --git a/app/controllers/sessions/service.go b/app/controllers/sessions/service.go
index 0ee692a..af1512c 100644
--- a/app/controllers/sessions/service.go
+++ b/app/controllers/sessions/service.go
@@ -7,6 +7,7 @@ import (
"gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/pkg/oidc"
"gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/pkg/pls"
+ "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/pkg/web"
"golang.org/x/oauth2"
)
@@ -38,7 +39,7 @@ func (svc *Service) Exchange(r *http.Request) (*oidc.Tokens, error) {
}
state := r.URL.Query().Get("state")
- if state != cookies[0].Value {
+ if state != web.CookieValueFrom(cookies[0]) {
return nil, errors.New("Invalid CSRF token")
}
diff --git a/app/middleware/token_parser.go b/app/middleware/token_parser.go
index 8d81aab..6047a94 100644
--- a/app/middleware/token_parser.go
+++ b/app/middleware/token_parser.go
@@ -5,6 +5,7 @@ import (
"gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/pkg/oidc"
"gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/pkg/pls"
+ "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/pkg/web"
)
type TokenParser func(*http.Request) oidc.RawToken
@@ -16,7 +17,8 @@ func IDTokenFromSessionCookie(r *http.Request) oidc.RawToken {
return ""
}
- tokens, err := oidc.TokensFromBase64String(cookies[0].Value)
+ value := web.CookieValueFrom(cookies[0])
+ tokens, err := oidc.TokensFromBase64String(value)
if err != nil {
pls.LogError(r.Context(), err)
return ""
diff --git a/go.mod b/go.mod
index 125aea5..6b3c9fc 100644
--- a/go.mod
+++ b/go.mod
@@ -9,7 +9,7 @@ require (
github.com/rs/zerolog v1.34.0
github.com/stretchr/testify v1.10.0
github.com/testcontainers/testcontainers-go v0.36.0
- github.com/xlgmokha/x v0.0.0-20250507143633-104ccfbf81d1
+ github.com/xlgmokha/x v0.0.0-20250507172007-444d6e509eb4
golang.org/x/oauth2 v0.30.0
)
diff --git a/go.sum b/go.sum
index 8b0718a..77d9166 100644
--- a/go.sum
+++ b/go.sum
@@ -140,6 +140,8 @@ github.com/xlgmokha/x v0.0.0-20250430185455-b691eda27477 h1:oeKrn9BSDSic5MTXKhQe
github.com/xlgmokha/x v0.0.0-20250430185455-b691eda27477/go.mod h1:axGPKzoJCNTmPJxYqN5l+Z9gGbPe0yolkT61a5p3QiI=
github.com/xlgmokha/x v0.0.0-20250507143633-104ccfbf81d1 h1:iW9a6GiZ8kYduHkUXl6v81CpKyBzSqBMh/fBMqBStKk=
github.com/xlgmokha/x v0.0.0-20250507143633-104ccfbf81d1/go.mod h1:FhvU8e/Zcpo0Lw8n5WkPtqQwzGrLD7FhFEi0MbEBLGk=
+github.com/xlgmokha/x v0.0.0-20250507172007-444d6e509eb4 h1:5o6TNNvh9RH+/W/XdlNETOgPyUvw9JYESz6IBhjWTKw=
+github.com/xlgmokha/x v0.0.0-20250507172007-444d6e509eb4/go.mod h1:FhvU8e/Zcpo0Lw8n5WkPtqQwzGrLD7FhFEi0MbEBLGk=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
diff --git a/pkg/web/cookie.go b/pkg/web/cookie.go
index 1377274..4795f96 100644
--- a/pkg/web/cookie.go
+++ b/pkg/web/cookie.go
@@ -1,18 +1,28 @@
package web
import (
+ "crypto/sha256"
+ "encoding/base64"
+ "fmt"
"net/http"
+ "strings"
"github.com/xlgmokha/x/pkg/cookie"
+ "github.com/xlgmokha/x/pkg/crypt"
"github.com/xlgmokha/x/pkg/env"
+ "github.com/xlgmokha/x/pkg/pls"
"github.com/xlgmokha/x/pkg/x"
)
+var key []byte = x.Must(pls.GenerateRandomBytes(32)) // TODO:: Read fix key from environment variable
+var Signer *crypt.HMACSigner = x.New[*crypt.HMACSigner](crypt.WithKey(key), crypt.WithAlgorithm(sha256.New))
+var delimiter string = "--"
+
func NewCookie(name, value string, options ...x.Option[*http.Cookie]) *http.Cookie {
return x.New[*http.Cookie](x.Prepend[x.Option[*http.Cookie]](
options,
cookie.WithName(name),
- cookie.WithValue(value), // TODO:: digitally sign the value
+ withSignedValue(value),
cookie.WithPath("/"),
cookie.WithHttpOnly(true),
cookie.WithSecure(true),
@@ -21,6 +31,19 @@ func NewCookie(name, value string, options ...x.Option[*http.Cookie]) *http.Cook
)...)
}
+func withSignedValue(value string) x.Option[*http.Cookie] {
+ signature, err := Signer.Sign([]byte(value))
+ if err != nil {
+ return nil
+ }
+ return cookie.WithValue(fmt.Sprintf(
+ "%v%v%v",
+ value,
+ delimiter,
+ base64.URLEncoding.EncodeToString(signature),
+ ))
+}
+
func ExpireCookie(w http.ResponseWriter, name string) {
cookie.Expire(w, name,
cookie.WithPath("/"),
@@ -29,3 +52,22 @@ func ExpireCookie(w http.ResponseWriter, name string) {
cookie.WithSecure(true),
)
}
+
+func CookieValueFrom(c *http.Cookie) string {
+ segments := strings.SplitN(c.Value, delimiter, 2)
+ if len(segments) != 2 {
+ return ""
+ }
+
+ data := segments[0]
+ signature, err := base64.URLEncoding.DecodeString(segments[1])
+ if err != nil {
+ return ""
+ }
+
+ if !Signer.Verify([]byte(data), []byte(signature)) {
+ return ""
+ }
+
+ return data
+}