Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cipher/chacha20.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ func (c *XChaCha20Poly1305) Decrypt(ctx context.Context, ciphertext string) ([]b
return nil, errors.WithStack(herodot.ErrInternalServerError.WithWrap(err).WithReason("Unable to instantiate chacha20"))
}

if len(ciphertext) < aead.NonceSize() {
if len(rawCiphertext) < aead.NonceSize() {
return nil, errors.WithStack(herodot.ErrInternalServerError.WithReason("cipher text too short"))
}

Expand Down
19 changes: 19 additions & 0 deletions cipher/cipher_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,25 @@ func TestCipher(t *testing.T) {
_, err = c.Decrypt(contextx.WithConfigValue(ctx, config.ViperKeySecretsCipher, []string{""}), "not-empty")
require.Error(t, err)
})

t.Run("case=short_ciphertext", func(t *testing.T) {
t.Parallel()

// XChaCha20-Poly1305 has 24-byte nonce, hex encoded is 48 chars
// A valid ciphertext needs at least 24 bytes (nonce) + 16 bytes (tag) = 40 bytes minimum
// Hex encoded minimum is 80 chars
// This tests that we don't get panic on short ciphertext

// 24 hex chars is only 12 bytes - less than nonce size (24 bytes)
shortCiphertext := "00112233445566778899aabbccddeeff"
_, err := c.Decrypt(ctx, shortCiphertext)
require.Error(t, err)

// 64 hex chars is 32 bytes - still less than nonce(24)+tag(16)=40
mediumCiphertext := "00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"
_, err = c.Decrypt(ctx, mediumCiphertext)
require.Error(t, err)
})
})
}

Expand Down
8 changes: 7 additions & 1 deletion driver/registry_default.go
Original file line number Diff line number Diff line change
Expand Up @@ -547,7 +547,13 @@ func (m *RegistryDefault) CookieManager(ctx context.Context) sessions.StoreExact

func (m *RegistryDefault) ContinuityCookieManager(ctx context.Context) sessions.StoreExact {
// To support hot reloading, this can not be instantiated only once.
cs := sessions.NewCookieStore(m.Config().SecretsSession(ctx)...)
var keys [][]byte
for _, k := range m.Config().SecretsSession(ctx) {
encrypt := sha256.Sum256(k)
keys = append(keys, k, encrypt[:])
}

cs := sessions.NewCookieStore(keys...)
cs.Options.Secure = m.Config().CookieSecure(ctx)
cs.Options.HttpOnly = true
cs.Options.SameSite = http.SameSiteLaxMode
Expand Down
52 changes: 52 additions & 0 deletions driver/registry_default_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import (
"context"
"fmt"
"net/http"
"net/http/httptest"
"testing"

"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -969,3 +971,53 @@
}
})
}

func TestContinuityCookieManager_Keys(t *testing.T) {
t.Parallel()
ctx := context.Background()
_, reg := internal.NewVeryFastRegistryWithoutDB(t)

t.Run("supports key rotation", func(t *testing.T) {
t.Parallel()

secret := "secret-key-32-bytes-long-xxxxx"

Check failure on line 983 in driver/registry_default_test.go

View workflow job for this annotation

GitHub Actions / Run tests and lints

G101: Potential hardcoded credentials (gosec)
secret2 := "another-32-bytes-secret-keyyyy"

Check failure on line 984 in driver/registry_default_test.go

View workflow job for this annotation

GitHub Actions / Run tests and lints

G101: Potential hardcoded credentials (gosec)

// Step 1: Create registry with first secret and save a cookie
ctx1 := contextx.WithConfigValues(ctx, map[string]any{
config.ViperKeySecretsCookie: []string{secret},
})

store1 := reg.ContinuityCookieManager(ctx1)
require.NotNil(t, store1)

// Save a session cookie
r1 := &http.Request{Header: http.Header{}}
w1 := httptest.NewRecorder()
session1, err := store1.New(r1, "test_session")
require.NoError(t, err)
session1.Values["test_key"] = "test_value"
err = session1.Save(r1, w1)
require.NoError(t, err)

cookies := w1.Result().Cookies()
require.Len(t, cookies, 1)
cookie := cookies[0]

// Step 2: Create registry with two secrets (new + old for rotation)
ctx2 := contextx.WithConfigValues(context.Background(), map[string]any{
config.ViperKeySecretsCookie: []string{secret2, secret},
})
store2 := reg.ContinuityCookieManager(ctx2)
require.NotNil(t, store2)

// Step 3: Read the original cookie with the new registry
// This should work because the second secret matches the original
r2 := &http.Request{Header: http.Header{}}
r2.AddCookie(cookie)

session2, err := store2.Get(r2, "test_session")
require.NoError(t, err, "should be able to read cookie with rotated keys")
assert.Equal(t, "test_value", session2.Values["test_key"])
})
}
Loading