From ebcf6e6aa67fcfc546fc0f1bd0b9560f5cf8dcdd Mon Sep 17 00:00:00 2001 From: Stavros Date: Fri, 4 Jul 2025 01:08:17 +0300 Subject: [PATCH] fix: encrypt the cookie in sessions --- cmd/root.go | 12 +++++++++++- internal/auth/auth.go | 18 +++++++++++++++--- internal/types/config.go | 3 ++- internal/utils/utils.go | 33 +++++++++++++++++++++++++++++++++ 4 files changed, 61 insertions(+), 5 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index 278a511..a15bec7 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -74,6 +74,15 @@ var rootCmd = &cobra.Command{ csrfCookieName := fmt.Sprintf("%s-%s", constants.CsrfCookieName, cookieId) redirectCookieName := fmt.Sprintf("%s-%s", constants.RedirectCookieName, cookieId) + // Generate HMAC and encryption secrets + log.Debug().Msg("Deriving HMAC and encryption secrets") + + hmacSecret, err := utils.DeriveKey(config.Secret, "hmac") + HandleError(err, "Failed to derive HMAC secret") + + encryptionSecret, err := utils.DeriveKey(config.Secret, "encryption") + HandleError(err, "Failed to derive encryption secret") + // Create OAuth config oauthConfig := types.OAuthConfig{ GithubClientId: config.GithubClientId, @@ -115,13 +124,14 @@ var rootCmd = &cobra.Command{ authConfig := types.AuthConfig{ Users: users, OauthWhitelist: config.OAuthWhitelist, - Secret: config.Secret, CookieSecure: config.CookieSecure, SessionExpiry: config.SessionExpiry, Domain: domain, LoginTimeout: config.LoginTimeout, LoginMaxRetries: config.LoginMaxRetries, SessionCookieName: sessionCookieName, + HMACSecret: hmacSecret, + EncryptionSecret: encryptionSecret, } // Create hooks config diff --git a/internal/auth/auth.go b/internal/auth/auth.go index 55a6622..7044141 100644 --- a/internal/auth/auth.go +++ b/internal/auth/auth.go @@ -33,7 +33,7 @@ type Auth struct { func (auth *Auth) GetSession(c *gin.Context) (*sessions.Session, error) { // Create cookie store - store := sessions.NewCookieStore([]byte(auth.Config.Secret)) + store := sessions.NewCookieStore([]byte(auth.Config.HMACSecret), []byte(auth.Config.EncryptionSecret)) // Configure cookie store store.Options = &sessions.Options{ @@ -46,9 +46,21 @@ func (auth *Auth) GetSession(c *gin.Context) (*sessions.Session, error) { // Get session session, err := store.Get(c.Request, auth.Config.SessionCookieName) + if err != nil { - log.Error().Err(err).Msg("Failed to get session") - return nil, err + log.Warn().Err(err).Msg("Invalid session, clearing cookie and retrying") + + // Delete the session cookie if there is an error + c.SetCookie(auth.Config.SessionCookieName, "", -1, "/", auth.Config.Domain, auth.Config.CookieSecure, true) + + // Try to get the session again + session, err = store.Get(c.Request, auth.Config.SessionCookieName) + + if err != nil { + // If we still can't get the session, log the error and return nil + log.Error().Err(err).Msg("Failed to get session") + return nil, err + } } return session, nil diff --git a/internal/types/config.go b/internal/types/config.go index edb7123..12c560d 100644 --- a/internal/types/config.go +++ b/internal/types/config.go @@ -80,12 +80,13 @@ type AuthConfig struct { Users Users OauthWhitelist string SessionExpiry int - Secret string CookieSecure bool Domain string LoginTimeout int LoginMaxRetries int SessionCookieName string + HMACSecret string + EncryptionSecret string } // HooksConfig is the configuration for the hooks service diff --git a/internal/utils/utils.go b/internal/utils/utils.go index 8209802..07957ff 100644 --- a/internal/utils/utils.go +++ b/internal/utils/utils.go @@ -1,8 +1,11 @@ package utils import ( + "bytes" + "crypto/sha256" "encoding/base64" "errors" + "io" "net" "net/url" "os" @@ -11,6 +14,7 @@ import ( "tinyauth/internal/types" "github.com/traefik/paerser/parser" + "golang.org/x/crypto/hkdf" "github.com/google/uuid" "github.com/rs/zerolog/log" @@ -405,3 +409,32 @@ func FilterIP(filter string, ip string) (bool, error) { // If the filter is not a CIDR range or a single IP, return false return false, nil } + +func DeriveKey(secret string, info string) (string, error) { + // Create hashing function + hash := sha256.New + + // Create a new key using the secret and info + hkdf := hkdf.New(hash, []byte(secret), nil, []byte(info)) // I am not using a salt because I just want two different keys from one secret, maybe bad practice + + // Create a new key + key := make([]byte, 32) + + // Read the key from the HKDF + _, err := io.ReadFull(hkdf, key) + + if err != nil { + return "", err + } + + // Verify the key is not empty + if bytes.Equal(key, make([]byte, 32)) { + return "", errors.New("derived key is empty") + } + + // Encode the key to base64 + encodedKey := base64.StdEncoding.EncodeToString(key) + + // Return the key as a base64 encoded string + return encodedKey[:32], nil +}