mirror of https://github.com/gofiber/fiber.git
v3: Enforce key length for EncryptCookie middleware default functions (#3056)
* Support for key length, Add benchmarks for EncryptCookie middleware * Format tests * Add tests for panics and key check in Encryptor and Decryptor functions * Add tests for base64 decoding errors * Update docs/middleware/encryptcookie.md Co-authored-by: Jason McNeil <sixcolors@mac.com> * Update middleware/encryptcookie/utils.go Co-authored-by: Jason McNeil <sixcolors@mac.com> * Add suggestions from code review --------- Co-authored-by: Jason McNeil <sixcolors@mac.com>pull/3062/head
parent
55138fa506
commit
d17eb99377
|
@ -44,9 +44,6 @@ jobs:
|
||||||
slug: gofiber/fiber
|
slug: gofiber/fiber
|
||||||
|
|
||||||
repeated:
|
repeated:
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
go-version: [stable]
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Fetch Repository
|
- name: Fetch Repository
|
||||||
|
@ -55,7 +52,7 @@ jobs:
|
||||||
- name: Install Go
|
- name: Install Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.go-version }}
|
go-version: stable
|
||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
run: go run gotest.tools/gotestsum@latest -f testname -- ./... -race -count=15 -shuffle=on
|
run: go run gotest.tools/gotestsum@latest -f testname -- ./... -race -count=15 -shuffle=on
|
||||||
|
|
|
@ -16,8 +16,10 @@ This middleware encrypts cookie values and not the cookie names.
|
||||||
// Intitializes the middleware
|
// Intitializes the middleware
|
||||||
func New(config ...Config) fiber.Handler
|
func New(config ...Config) fiber.Handler
|
||||||
|
|
||||||
// Returns a random 32 character long string
|
// GenerateKey returns a random string of 16, 24, or 32 bytes.
|
||||||
func GenerateKey() string
|
// The length of the key determines the AES encryption algorithm used:
|
||||||
|
// 16 bytes for AES-128, 24 bytes for AES-192, and 32 bytes for AES-256-GCM.
|
||||||
|
func GenerateKey(length int) string
|
||||||
```
|
```
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
@ -55,9 +57,8 @@ app.Post("/", func(c fiber.Ctx) error {
|
||||||
```
|
```
|
||||||
|
|
||||||
:::note
|
:::note
|
||||||
`Key` must be a 32 character string. It's used to encrypt the values, so make sure it is random and keep it secret.
|
The `Key` parameter requires an encoded string of 16, 24, or 32 bytes for encryption, corresponding to AES-128, AES-192, and AES-256-GCM standards, respectively. Ensure the key is randomly generated and securely stored.
|
||||||
You can run `openssl rand -base64 32` or call `encryptcookie.GenerateKey()` to create a random key for you.
|
To generate a 32 char key, use `openssl rand -base64 32` or `encryptcookie.GenerateKey(32)`. Avoid dynamically generating a new `Key` with `encryptcookie.GenerateKey(32)` at each application startup to prevent rendering previously encrypted data inaccessible.
|
||||||
Make sure not to set `Key` to `encryptcookie.GenerateKey()` because that will create a new key every run.
|
|
||||||
:::
|
:::
|
||||||
|
|
||||||
## Config
|
## Config
|
||||||
|
@ -83,6 +84,7 @@ var ConfigDefault = Config{
|
||||||
```
|
```
|
||||||
|
|
||||||
## Usage With Other Middlewares That Reads Or Modify Cookies
|
## Usage With Other Middlewares That Reads Or Modify Cookies
|
||||||
|
|
||||||
Place the `encryptcookie` middleware before any other middleware that reads or modifies cookies. For example, if you are using the CSRF middleware, ensure that the `encryptcookie` middleware is placed before it. Failure to do so may prevent the CSRF middleware from reading the encrypted cookie.
|
Place the `encryptcookie` middleware before any other middleware that reads or modifies cookies. For example, if you are using the CSRF middleware, ensure that the `encryptcookie` middleware is placed before it. Failure to do so may prevent the CSRF middleware from reading the encrypted cookie.
|
||||||
|
|
||||||
You may also choose to exclude certain cookies from encryption. For instance, if you are using the `CSRF` middleware with a frontend framework like Angular, and the framework reads the token from a cookie, you should exclude that cookie from encryption. This can be achieved by adding the cookie name to the Except array in the configuration:
|
You may also choose to exclude certain cookies from encryption. For instance, if you are using the `CSRF` middleware with a frontend framework like Angular, and the framework reads the token from a cookie, you should exclude that cookie from encryption. This can be achieved by adding the cookie name to the Except array in the configuration:
|
||||||
|
@ -99,3 +101,22 @@ app.Use(csrf.New(csrf.Config{
|
||||||
CookieHTTPOnly: false,
|
CookieHTTPOnly: false,
|
||||||
}))
|
}))
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Encryption Algorithms
|
||||||
|
|
||||||
|
The default Encryptor and Decryptor functions use `AES-256-GCM` for encryption and decryption. If you need to use `AES-128` or `AES-192` instead, you can do so by changing the length of the key when calling `encryptcookie.GenerateKey(length)` or by providing a key of one of the following lengths:
|
||||||
|
- AES-128 requires a 16-byte key.
|
||||||
|
- AES-192 requires a 24-byte key.
|
||||||
|
- AES-256 requires a 32-byte key.
|
||||||
|
|
||||||
|
For example, to generate a key for AES-128:
|
||||||
|
|
||||||
|
```go
|
||||||
|
key := encryptcookie.GenerateKey(16)
|
||||||
|
```
|
||||||
|
|
||||||
|
And for AES-192:
|
||||||
|
|
||||||
|
```go
|
||||||
|
key := encryptcookie.GenerateKey(24)
|
||||||
|
```
|
|
@ -275,6 +275,10 @@ We've updated several fields from a single string (containing comma-separated va
|
||||||
|
|
||||||
We've added support for `zstd` compression on top of `gzip`, `deflate`, and `brotli`.
|
We've added support for `zstd` compression on top of `gzip`, `deflate`, and `brotli`.
|
||||||
|
|
||||||
|
### EncryptCookie
|
||||||
|
|
||||||
|
Added support for specifying Key length when using `encryptcookie.GenerateKey(length)`. This allows the user to generate keys compatible with `AES-128`, `AES-192`, and `AES-256` (Default).
|
||||||
|
|
||||||
### Session
|
### Session
|
||||||
|
|
||||||
:::caution
|
:::caution
|
||||||
|
|
|
@ -18,18 +18,19 @@ type Config struct {
|
||||||
|
|
||||||
// Base64 encoded unique key to encode & decode cookies.
|
// Base64 encoded unique key to encode & decode cookies.
|
||||||
//
|
//
|
||||||
// Required. Key length should be 32 characters.
|
// Required. Key length should be 16, 24, or 32 bytes when decoded
|
||||||
// You may use `encryptcookie.GenerateKey()` to generate a new key.
|
// if using the default EncryptCookie and DecryptCookie functions.
|
||||||
|
// You may use `encryptcookie.GenerateKey(length)` to generate a new key.
|
||||||
Key string
|
Key string
|
||||||
|
|
||||||
// Custom function to encrypt cookies.
|
// Custom function to encrypt cookies.
|
||||||
//
|
//
|
||||||
// Optional. Default: EncryptCookie
|
// Optional. Default: EncryptCookie (using AES-GCM)
|
||||||
Encryptor func(decryptedString, key string) (string, error)
|
Encryptor func(decryptedString, key string) (string, error)
|
||||||
|
|
||||||
// Custom function to decrypt cookies.
|
// Custom function to decrypt cookies.
|
||||||
//
|
//
|
||||||
// Optional. Default: DecryptCookie
|
// Optional. Default: DecryptCookie (using AES-GCM)
|
||||||
Decryptor func(encryptedString, key string) (string, error)
|
Decryptor func(encryptedString, key string) (string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,7 +53,6 @@ func configDefault(config ...Config) Config {
|
||||||
cfg = config[0]
|
cfg = config[0]
|
||||||
|
|
||||||
// Set default values
|
// Set default values
|
||||||
|
|
||||||
if cfg.Next == nil {
|
if cfg.Next == nil {
|
||||||
cfg.Next = ConfigDefault.Next
|
cfg.Next = ConfigDefault.Next
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
package encryptcookie
|
package encryptcookie
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/rand"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v3"
|
"github.com/gofiber/fiber/v3"
|
||||||
|
@ -10,10 +12,92 @@ import (
|
||||||
"github.com/valyala/fasthttp"
|
"github.com/valyala/fasthttp"
|
||||||
)
|
)
|
||||||
|
|
||||||
var testKey = GenerateKey()
|
func Test_Middleware_Panics(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
t.Run("Empty Key", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
app := fiber.New()
|
||||||
|
require.Panics(t, func() {
|
||||||
|
app.Use(New(Config{
|
||||||
|
Key: "",
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Invalid Key", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
require.Panics(t, func() {
|
||||||
|
GenerateKey(11)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_Middleware_InvalidKeys(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
tests := []struct {
|
||||||
|
length int
|
||||||
|
}{
|
||||||
|
{11},
|
||||||
|
{25},
|
||||||
|
{60},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
tt := tt
|
||||||
|
t.Run(strconv.Itoa(tt.length)+"_length_encrypt", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
key := make([]byte, tt.length)
|
||||||
|
_, err := rand.Read(key)
|
||||||
|
require.NoError(t, err)
|
||||||
|
keyString := base64.StdEncoding.EncodeToString(key)
|
||||||
|
|
||||||
|
_, err = EncryptCookie("SomeThing", keyString)
|
||||||
|
require.Error(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run(strconv.Itoa(tt.length)+"_length_decrypt", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
key := make([]byte, tt.length)
|
||||||
|
_, err := rand.Read(key)
|
||||||
|
require.NoError(t, err)
|
||||||
|
keyString := base64.StdEncoding.EncodeToString(key)
|
||||||
|
|
||||||
|
_, err = DecryptCookie("SomeThing", keyString)
|
||||||
|
require.Error(t, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_Middleware_InvalidBase64(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
invalidBase64 := "invalid-base64-string-!@#"
|
||||||
|
|
||||||
|
t.Run("encryptor", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
_, err := EncryptCookie("SomeText", invalidBase64)
|
||||||
|
require.Error(t, err)
|
||||||
|
require.ErrorContains(t, err, "failed to base64-decode key")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("decryptor_key", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
_, err := DecryptCookie("SomeText", invalidBase64)
|
||||||
|
require.Error(t, err)
|
||||||
|
require.ErrorContains(t, err, "failed to base64-decode key")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("decryptor_value", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
_, err := DecryptCookie(invalidBase64, GenerateKey(32))
|
||||||
|
require.Error(t, err)
|
||||||
|
require.ErrorContains(t, err, "failed to base64-decode value")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func Test_Middleware_Encrypt_Cookie(t *testing.T) {
|
func Test_Middleware_Encrypt_Cookie(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
testKey := GenerateKey(32)
|
||||||
app := fiber.New()
|
app := fiber.New()
|
||||||
|
|
||||||
app.Use(New(Config{
|
app.Use(New(Config{
|
||||||
|
@ -75,6 +159,7 @@ func Test_Middleware_Encrypt_Cookie(t *testing.T) {
|
||||||
|
|
||||||
func Test_Encrypt_Cookie_Next(t *testing.T) {
|
func Test_Encrypt_Cookie_Next(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
testKey := GenerateKey(32)
|
||||||
app := fiber.New()
|
app := fiber.New()
|
||||||
|
|
||||||
app.Use(New(Config{
|
app.Use(New(Config{
|
||||||
|
@ -99,6 +184,7 @@ func Test_Encrypt_Cookie_Next(t *testing.T) {
|
||||||
|
|
||||||
func Test_Encrypt_Cookie_Except(t *testing.T) {
|
func Test_Encrypt_Cookie_Except(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
testKey := GenerateKey(32)
|
||||||
app := fiber.New()
|
app := fiber.New()
|
||||||
|
|
||||||
app.Use(New(Config{
|
app.Use(New(Config{
|
||||||
|
@ -143,6 +229,7 @@ func Test_Encrypt_Cookie_Except(t *testing.T) {
|
||||||
|
|
||||||
func Test_Encrypt_Cookie_Custom_Encryptor(t *testing.T) {
|
func Test_Encrypt_Cookie_Custom_Encryptor(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
testKey := GenerateKey(32)
|
||||||
app := fiber.New()
|
app := fiber.New()
|
||||||
|
|
||||||
app.Use(New(Config{
|
app.Use(New(Config{
|
||||||
|
@ -189,3 +276,409 @@ func Test_Encrypt_Cookie_Custom_Encryptor(t *testing.T) {
|
||||||
require.Equal(t, 200, ctx.Response.StatusCode())
|
require.Equal(t, 200, ctx.Response.StatusCode())
|
||||||
require.Equal(t, "value=SomeThing", string(ctx.Response.Body()))
|
require.Equal(t, "value=SomeThing", string(ctx.Response.Body()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_GenerateKey(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
length int
|
||||||
|
}{
|
||||||
|
{16},
|
||||||
|
{24},
|
||||||
|
{32},
|
||||||
|
}
|
||||||
|
|
||||||
|
decodeBase64 := func(t *testing.T, s string) []byte {
|
||||||
|
t.Helper()
|
||||||
|
data, err := base64.StdEncoding.DecodeString(s)
|
||||||
|
require.NoError(t, err)
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
tt := tt
|
||||||
|
t.Run(strconv.Itoa(tt.length)+"_length", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
key := GenerateKey(tt.length)
|
||||||
|
decodedKey := decodeBase64(t, key)
|
||||||
|
require.Len(t, decodedKey, tt.length)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("Invalid Length", func(t *testing.T) {
|
||||||
|
require.Panics(t, func() { GenerateKey(10) })
|
||||||
|
require.Panics(t, func() { GenerateKey(20) })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Benchmark_Middleware_Encrypt_Cookie(b *testing.B) {
|
||||||
|
testKey := GenerateKey(32)
|
||||||
|
app := fiber.New()
|
||||||
|
|
||||||
|
app.Use(New(Config{
|
||||||
|
Key: testKey,
|
||||||
|
}))
|
||||||
|
|
||||||
|
app.Get("/", func(c fiber.Ctx) error {
|
||||||
|
return c.SendString("value=" + c.Cookies("test"))
|
||||||
|
})
|
||||||
|
app.Post("/", func(c fiber.Ctx) error {
|
||||||
|
c.Cookie(&fiber.Cookie{
|
||||||
|
Name: "test",
|
||||||
|
Value: "SomeThing",
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
h := app.Handler()
|
||||||
|
|
||||||
|
b.Run("Empty Cookie", func(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
ctx := &fasthttp.RequestCtx{}
|
||||||
|
ctx.Request.Header.SetMethod(fiber.MethodGet)
|
||||||
|
h(ctx)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
b.Run("Invalid Cookie", func(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
ctx := &fasthttp.RequestCtx{}
|
||||||
|
ctx.Request.Header.SetMethod(fiber.MethodGet)
|
||||||
|
ctx.Request.Header.SetCookie("test", "Invalid")
|
||||||
|
h(ctx)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
b.Run("Valid Cookie", func(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
ctx := &fasthttp.RequestCtx{}
|
||||||
|
ctx.Request.Header.SetMethod(fiber.MethodPost)
|
||||||
|
h(ctx)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Benchmark_Encrypt_Cookie_Next(b *testing.B) {
|
||||||
|
testKey := GenerateKey(32)
|
||||||
|
app := fiber.New()
|
||||||
|
|
||||||
|
app.Use(New(Config{
|
||||||
|
Key: testKey,
|
||||||
|
Next: func(_ fiber.Ctx) bool {
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
app.Get("/", func(c fiber.Ctx) error {
|
||||||
|
c.Cookie(&fiber.Cookie{
|
||||||
|
Name: "test",
|
||||||
|
Value: "SomeThing",
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
h := app.Handler()
|
||||||
|
|
||||||
|
b.Run("Encrypt Cookie Next", func(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
ctx := &fasthttp.RequestCtx{}
|
||||||
|
ctx.Request.Header.SetMethod(fiber.MethodGet)
|
||||||
|
ctx.Request.SetRequestURI("/")
|
||||||
|
h(ctx)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Benchmark_Encrypt_Cookie_Except(b *testing.B) {
|
||||||
|
testKey := GenerateKey(32)
|
||||||
|
app := fiber.New()
|
||||||
|
|
||||||
|
app.Use(New(Config{
|
||||||
|
Key: testKey,
|
||||||
|
Except: []string{
|
||||||
|
"test1",
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
app.Get("/", func(c fiber.Ctx) error {
|
||||||
|
c.Cookie(&fiber.Cookie{
|
||||||
|
Name: "test1",
|
||||||
|
Value: "SomeThing",
|
||||||
|
})
|
||||||
|
c.Cookie(&fiber.Cookie{
|
||||||
|
Name: "test2",
|
||||||
|
Value: "SomeThing",
|
||||||
|
})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
h := app.Handler()
|
||||||
|
|
||||||
|
b.Run("Encrypt Cookie Except", func(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
ctx := &fasthttp.RequestCtx{}
|
||||||
|
ctx.Request.Header.SetMethod(fiber.MethodGet)
|
||||||
|
h(ctx)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Benchmark_Encrypt_Cookie_Custom_Encryptor(b *testing.B) {
|
||||||
|
testKey := GenerateKey(32)
|
||||||
|
app := fiber.New()
|
||||||
|
|
||||||
|
app.Use(New(Config{
|
||||||
|
Key: testKey,
|
||||||
|
Encryptor: func(decryptedString, _ string) (string, error) {
|
||||||
|
return base64.StdEncoding.EncodeToString([]byte(decryptedString)), nil
|
||||||
|
},
|
||||||
|
Decryptor: func(encryptedString, _ string) (string, error) {
|
||||||
|
decodedBytes, err := base64.StdEncoding.DecodeString(encryptedString)
|
||||||
|
return string(decodedBytes), err
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
app.Get("/", func(c fiber.Ctx) error {
|
||||||
|
return c.SendString("value=" + c.Cookies("test"))
|
||||||
|
})
|
||||||
|
app.Post("/", func(c fiber.Ctx) error {
|
||||||
|
c.Cookie(&fiber.Cookie{
|
||||||
|
Name: "test",
|
||||||
|
Value: "SomeThing",
|
||||||
|
})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
h := app.Handler()
|
||||||
|
|
||||||
|
b.Run("Custom Encryptor Post", func(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
ctx := &fasthttp.RequestCtx{}
|
||||||
|
ctx.Request.Header.SetMethod(fiber.MethodPost)
|
||||||
|
h(ctx)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
b.Run("Custom Encryptor Get", func(b *testing.B) {
|
||||||
|
ctx := &fasthttp.RequestCtx{}
|
||||||
|
ctx.Request.Header.SetMethod(fiber.MethodPost)
|
||||||
|
h(ctx)
|
||||||
|
encryptedCookie := fasthttp.Cookie{}
|
||||||
|
encryptedCookie.SetKey("test")
|
||||||
|
require.True(b, ctx.Response.Header.Cookie(&encryptedCookie), "Get cookie value")
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
ctx := &fasthttp.RequestCtx{}
|
||||||
|
ctx.Request.Header.SetMethod(fiber.MethodGet)
|
||||||
|
ctx.Request.Header.SetCookie("test", string(encryptedCookie.Value()))
|
||||||
|
h(ctx)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Benchmark_Middleware_Encrypt_Cookie_Parallel(b *testing.B) {
|
||||||
|
testKey := GenerateKey(32)
|
||||||
|
app := fiber.New()
|
||||||
|
|
||||||
|
app.Use(New(Config{
|
||||||
|
Key: testKey,
|
||||||
|
}))
|
||||||
|
|
||||||
|
app.Get("/", func(c fiber.Ctx) error {
|
||||||
|
return c.SendString("value=" + c.Cookies("test"))
|
||||||
|
})
|
||||||
|
app.Post("/", func(c fiber.Ctx) error {
|
||||||
|
c.Cookie(&fiber.Cookie{
|
||||||
|
Name: "test",
|
||||||
|
Value: "SomeThing",
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
h := app.Handler()
|
||||||
|
|
||||||
|
b.Run("Empty Cookie Parallel", func(b *testing.B) {
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
ctx := &fasthttp.RequestCtx{}
|
||||||
|
ctx.Request.Header.SetMethod(fiber.MethodGet)
|
||||||
|
h(ctx)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
b.Run("Invalid Cookie Parallel", func(b *testing.B) {
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
ctx := &fasthttp.RequestCtx{}
|
||||||
|
ctx.Request.Header.SetMethod(fiber.MethodGet)
|
||||||
|
ctx.Request.Header.SetCookie("test", "Invalid")
|
||||||
|
h(ctx)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
b.Run("Valid Cookie Parallel", func(b *testing.B) {
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
ctx := &fasthttp.RequestCtx{}
|
||||||
|
ctx.Request.Header.SetMethod(fiber.MethodPost)
|
||||||
|
h(ctx)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Benchmark_Encrypt_Cookie_Next_Parallel(b *testing.B) {
|
||||||
|
testKey := GenerateKey(32)
|
||||||
|
app := fiber.New()
|
||||||
|
|
||||||
|
app.Use(New(Config{
|
||||||
|
Key: testKey,
|
||||||
|
Next: func(_ fiber.Ctx) bool {
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
app.Get("/", func(c fiber.Ctx) error {
|
||||||
|
c.Cookie(&fiber.Cookie{
|
||||||
|
Name: "test",
|
||||||
|
Value: "SomeThing",
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
h := app.Handler()
|
||||||
|
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
ctx := &fasthttp.RequestCtx{}
|
||||||
|
ctx.Request.Header.SetMethod(fiber.MethodGet)
|
||||||
|
ctx.Request.SetRequestURI("/")
|
||||||
|
h(ctx)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Benchmark_Encrypt_Cookie_Except_Parallel(b *testing.B) {
|
||||||
|
testKey := GenerateKey(32)
|
||||||
|
app := fiber.New()
|
||||||
|
|
||||||
|
app.Use(New(Config{
|
||||||
|
Key: testKey,
|
||||||
|
Except: []string{
|
||||||
|
"test1",
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
app.Get("/", func(c fiber.Ctx) error {
|
||||||
|
c.Cookie(&fiber.Cookie{
|
||||||
|
Name: "test1",
|
||||||
|
Value: "SomeThing",
|
||||||
|
})
|
||||||
|
c.Cookie(&fiber.Cookie{
|
||||||
|
Name: "test2",
|
||||||
|
Value: "SomeThing",
|
||||||
|
})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
h := app.Handler()
|
||||||
|
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
ctx := &fasthttp.RequestCtx{}
|
||||||
|
ctx.Request.Header.SetMethod(fiber.MethodGet)
|
||||||
|
h(ctx)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Benchmark_Encrypt_Cookie_Custom_Encryptor_Parallel(b *testing.B) {
|
||||||
|
testKey := GenerateKey(32)
|
||||||
|
app := fiber.New()
|
||||||
|
|
||||||
|
app.Use(New(Config{
|
||||||
|
Key: testKey,
|
||||||
|
Encryptor: func(decryptedString, _ string) (string, error) {
|
||||||
|
return base64.StdEncoding.EncodeToString([]byte(decryptedString)), nil
|
||||||
|
},
|
||||||
|
Decryptor: func(encryptedString, _ string) (string, error) {
|
||||||
|
decodedBytes, err := base64.StdEncoding.DecodeString(encryptedString)
|
||||||
|
return string(decodedBytes), err
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
app.Get("/", func(c fiber.Ctx) error {
|
||||||
|
return c.SendString("value=" + c.Cookies("test"))
|
||||||
|
})
|
||||||
|
app.Post("/", func(c fiber.Ctx) error {
|
||||||
|
c.Cookie(&fiber.Cookie{
|
||||||
|
Name: "test",
|
||||||
|
Value: "SomeThing",
|
||||||
|
})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
h := app.Handler()
|
||||||
|
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
ctx := &fasthttp.RequestCtx{}
|
||||||
|
ctx.Request.Header.SetMethod(fiber.MethodPost)
|
||||||
|
h(ctx)
|
||||||
|
encryptedCookie := fasthttp.Cookie{}
|
||||||
|
encryptedCookie.SetKey("test")
|
||||||
|
require.True(b, ctx.Response.Header.Cookie(&encryptedCookie), "Get cookie value")
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for pb.Next() {
|
||||||
|
ctx := &fasthttp.RequestCtx{}
|
||||||
|
ctx.Request.Header.SetMethod(fiber.MethodGet)
|
||||||
|
ctx.Request.Header.SetCookie("test", string(encryptedCookie.Value()))
|
||||||
|
h(ctx)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Benchmark_GenerateKey(b *testing.B) {
|
||||||
|
tests := []struct {
|
||||||
|
length int
|
||||||
|
}{
|
||||||
|
{16},
|
||||||
|
{24},
|
||||||
|
{32},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
b.Run(strconv.Itoa(tt.length), func(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
GenerateKey(tt.length)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Benchmark_GenerateKey_Parallel(b *testing.B) {
|
||||||
|
tests := []struct {
|
||||||
|
length int
|
||||||
|
}{
|
||||||
|
{16},
|
||||||
|
{24},
|
||||||
|
{32},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
b.Run(strconv.Itoa(tt.length), func(b *testing.B) {
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
GenerateKey(tt.length)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -10,6 +10,8 @@ import (
|
||||||
"io"
|
"io"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var ErrInvalidKeyLength = errors.New("encryption key must be 16, 24, or 32 bytes")
|
||||||
|
|
||||||
// EncryptCookie Encrypts a cookie value with specific encryption key
|
// EncryptCookie Encrypts a cookie value with specific encryption key
|
||||||
func EncryptCookie(value, key string) (string, error) {
|
func EncryptCookie(value, key string) (string, error) {
|
||||||
keyDecoded, err := base64.StdEncoding.DecodeString(key)
|
keyDecoded, err := base64.StdEncoding.DecodeString(key)
|
||||||
|
@ -17,6 +19,11 @@ func EncryptCookie(value, key string) (string, error) {
|
||||||
return "", fmt.Errorf("failed to base64-decode key: %w", err)
|
return "", fmt.Errorf("failed to base64-decode key: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
keyLen := len(keyDecoded)
|
||||||
|
if keyLen != 16 && keyLen != 24 && keyLen != 32 {
|
||||||
|
return "", ErrInvalidKeyLength
|
||||||
|
}
|
||||||
|
|
||||||
block, err := aes.NewCipher(keyDecoded)
|
block, err := aes.NewCipher(keyDecoded)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("failed to create AES cipher: %w", err)
|
return "", fmt.Errorf("failed to create AES cipher: %w", err)
|
||||||
|
@ -29,11 +36,10 @@ func EncryptCookie(value, key string) (string, error) {
|
||||||
|
|
||||||
nonce := make([]byte, gcm.NonceSize())
|
nonce := make([]byte, gcm.NonceSize())
|
||||||
if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
|
if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
|
||||||
return "", fmt.Errorf("failed to read: %w", err)
|
return "", fmt.Errorf("failed to read nonce: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ciphertext := gcm.Seal(nonce, nonce, []byte(value), nil)
|
ciphertext := gcm.Seal(nonce, nonce, []byte(value), nil)
|
||||||
|
|
||||||
return base64.StdEncoding.EncodeToString(ciphertext), nil
|
return base64.StdEncoding.EncodeToString(ciphertext), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,6 +49,12 @@ func DecryptCookie(value, key string) (string, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("failed to base64-decode key: %w", err)
|
return "", fmt.Errorf("failed to base64-decode key: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
keyLen := len(keyDecoded)
|
||||||
|
if keyLen != 16 && keyLen != 24 && keyLen != 32 {
|
||||||
|
return "", ErrInvalidKeyLength
|
||||||
|
}
|
||||||
|
|
||||||
enc, err := base64.StdEncoding.DecodeString(value)
|
enc, err := base64.StdEncoding.DecodeString(value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("failed to base64-decode value: %w", err)
|
return "", fmt.Errorf("failed to base64-decode value: %w", err)
|
||||||
|
@ -65,7 +77,6 @@ func DecryptCookie(value, key string) (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
nonce, ciphertext := enc[:nonceSize], enc[nonceSize:]
|
nonce, ciphertext := enc[:nonceSize], enc[nonceSize:]
|
||||||
|
|
||||||
plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
|
plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("failed to decrypt ciphertext: %w", err)
|
return "", fmt.Errorf("failed to decrypt ciphertext: %w", err)
|
||||||
|
@ -74,16 +85,21 @@ func DecryptCookie(value, key string) (string, error) {
|
||||||
return string(plaintext), nil
|
return string(plaintext), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GenerateKey Generates an encryption key
|
// GenerateKey returns a random string of 16, 24, or 32 bytes.
|
||||||
func GenerateKey() string {
|
// The length of the key determines the AES encryption algorithm used:
|
||||||
const keyLen = 32
|
// 16 bytes for AES-128, 24 bytes for AES-192, and 32 bytes for AES-256-GCM.
|
||||||
ret := make([]byte, keyLen)
|
func GenerateKey(length int) string {
|
||||||
|
if length != 16 && length != 24 && length != 32 {
|
||||||
|
panic(ErrInvalidKeyLength)
|
||||||
|
}
|
||||||
|
|
||||||
if _, err := rand.Read(ret); err != nil {
|
key := make([]byte, length)
|
||||||
|
|
||||||
|
if _, err := rand.Read(key); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return base64.StdEncoding.EncodeToString(ret)
|
return base64.StdEncoding.EncodeToString(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check given cookie key is disabled for encryption or not
|
// Check given cookie key is disabled for encryption or not
|
||||||
|
|
Loading…
Reference in New Issue