22eae5c820
<!-- For Work In Progress Pull Requests, please use the Draft PR feature, see https://github.blog/2019-02-14-introducing-draft-pull-requests/ for further details. For a timely review/response, please avoid force-pushing additional commits if your PR already received reviews or comments. Before submitting a Pull Request, please ensure that you have: - 📖 Read the Contributing guide: https://github.com/gorilla/.github/blob/main/CONTRIBUTING.md - 📖 Read the Code of Conduct: https://github.com/gorilla/.github/blob/main/CODE_OF_CONDUCT.md - Provide tests for your changes. - Use descriptive commit messages. - Comment your code where appropriate. - Squash your commits - Update any related documentation. - Add gorilla/pull-request-reviewers as a Reviewer --> ## What type of PR is this? (check all applicable) - [ ] Refactor - [ ] Feature - [ ] Bug Fix - [x] Optimization - [ ] Documentation Update ## Description ## Related Tickets & Documents <!-- For pull requests that relate or close an issue, please include them below. We like to follow [Github's guidance on linking issues to pull requests](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue). For example having the text: "closes #1234" would connect the current pull request to issue 1234. And when we merge the pull request, Github will automatically close the issue. --> - Related Issue # - Closes # ## Added/updated tests? - [ ] Yes - [ ] No, and this is why: _please replace this line with details on why tests have not been included_ - [ ] I need help with writing tests ## Run verifications and test - [ ] `make verify` is passing - [ ] `make test` is passing
345 lines
8 KiB
Go
345 lines
8 KiB
Go
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package securecookie
|
|
|
|
import (
|
|
"crypto/aes"
|
|
"crypto/hmac"
|
|
"crypto/sha256"
|
|
"encoding/base64"
|
|
"fmt"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
|
|
fuzz "github.com/google/gofuzz"
|
|
)
|
|
|
|
// Asserts that cookieError and MultiError are Error implementations.
|
|
var _ Error = cookieError{}
|
|
var _ Error = MultiError{}
|
|
|
|
var testCookies = []interface{}{
|
|
map[string]string{"foo": "bar"},
|
|
map[string]string{"baz": "ding"},
|
|
}
|
|
|
|
var testStrings = []string{"foo", "bar", "baz"}
|
|
|
|
func TestSecureCookie(t *testing.T) {
|
|
// TODO test too old / too new timestamps
|
|
s1 := New([]byte("12345"), []byte("1234567890123456"))
|
|
s2 := New([]byte("54321"), []byte("6543210987654321"))
|
|
value := map[string]interface{}{
|
|
"foo": "bar",
|
|
"baz": 128,
|
|
}
|
|
|
|
for i := 0; i < 50; i++ {
|
|
// Running this multiple times to check if any special character
|
|
// breaks encoding/decoding.
|
|
encoded, err1 := s1.Encode("sid", value)
|
|
if err1 != nil {
|
|
t.Error(err1)
|
|
continue
|
|
}
|
|
dst := make(map[string]interface{})
|
|
err2 := s1.Decode("sid", encoded, &dst)
|
|
if err2 != nil {
|
|
t.Fatalf("%v: %v", err2, encoded)
|
|
}
|
|
if !reflect.DeepEqual(dst, value) {
|
|
t.Fatalf("Expected %v, got %v.", value, dst)
|
|
}
|
|
dst2 := make(map[string]interface{})
|
|
err3 := s2.Decode("sid", encoded, &dst2)
|
|
if err3 == nil {
|
|
t.Fatalf("Expected failure decoding.")
|
|
}
|
|
err4, ok := err3.(Error)
|
|
if !ok {
|
|
t.Fatalf("Expected error to implement Error, got: %#v", err3)
|
|
}
|
|
if !err4.IsDecode() {
|
|
t.Fatalf("Expected DecodeError, got: %#v", err4)
|
|
}
|
|
|
|
// Test other error type flags.
|
|
if err4.IsUsage() {
|
|
t.Fatalf("Expected IsUsage() == false, got: %#v", err4)
|
|
}
|
|
if err4.IsInternal() {
|
|
t.Fatalf("Expected IsInternal() == false, got: %#v", err4)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestSecureCookieNilKey(t *testing.T) {
|
|
s1 := New(nil, nil)
|
|
value := map[string]interface{}{
|
|
"foo": "bar",
|
|
"baz": 128,
|
|
}
|
|
_, err := s1.Encode("sid", value)
|
|
if err != errHashKeyNotSet {
|
|
t.Fatal("Wrong error returned:", err)
|
|
}
|
|
}
|
|
|
|
func TestDecodeInvalid(t *testing.T) {
|
|
// List of invalid cookies, which must not be accepted, base64-decoded
|
|
// (they will be encoded before passing to Decode).
|
|
invalidCookies := []string{
|
|
"",
|
|
" ",
|
|
"\n",
|
|
"||",
|
|
"|||",
|
|
"cookie",
|
|
}
|
|
s := New([]byte("12345"), nil)
|
|
var dst string
|
|
for i, v := range invalidCookies {
|
|
for _, enc := range []*base64.Encoding{
|
|
base64.StdEncoding,
|
|
base64.URLEncoding,
|
|
} {
|
|
err := s.Decode("name", enc.EncodeToString([]byte(v)), &dst)
|
|
if err == nil {
|
|
t.Fatalf("%d: expected failure decoding", i)
|
|
}
|
|
err2, ok := err.(Error)
|
|
if !ok || !err2.IsDecode() {
|
|
t.Fatalf("%d: Expected IsDecode(), got: %#v", i, err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestAuthentication(t *testing.T) {
|
|
hash := hmac.New(sha256.New, []byte("secret-key"))
|
|
for _, value := range testStrings {
|
|
hash.Reset()
|
|
signed := createMac(hash, []byte(value))
|
|
hash.Reset()
|
|
err := verifyMac(hash, []byte(value), signed)
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestEncryption(t *testing.T) {
|
|
block, err := aes.NewCipher([]byte("1234567890123456"))
|
|
if err != nil {
|
|
t.Fatalf("Block could not be created")
|
|
}
|
|
var encrypted, decrypted []byte
|
|
for _, value := range testStrings {
|
|
if encrypted, err = encrypt(block, []byte(value)); err != nil {
|
|
t.Error(err)
|
|
} else {
|
|
if decrypted, err = decrypt(block, encrypted); err != nil {
|
|
t.Error(err)
|
|
}
|
|
if string(decrypted) != value {
|
|
t.Errorf("Expected %v, got %v.", value, string(decrypted))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestGobSerialization(t *testing.T) {
|
|
var (
|
|
sz GobEncoder
|
|
serialized []byte
|
|
deserialized map[string]string
|
|
err error
|
|
)
|
|
for _, value := range testCookies {
|
|
if serialized, err = sz.Serialize(value); err != nil {
|
|
t.Error(err)
|
|
} else {
|
|
deserialized = make(map[string]string)
|
|
if err = sz.Deserialize(serialized, &deserialized); err != nil {
|
|
t.Error(err)
|
|
}
|
|
if fmt.Sprintf("%v", deserialized) != fmt.Sprintf("%v", value) {
|
|
t.Errorf("Expected %v, got %v.", value, deserialized)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestJSONSerialization(t *testing.T) {
|
|
var (
|
|
sz JSONEncoder
|
|
serialized []byte
|
|
deserialized map[string]string
|
|
err error
|
|
)
|
|
for _, value := range testCookies {
|
|
if serialized, err = sz.Serialize(value); err != nil {
|
|
t.Error(err)
|
|
} else {
|
|
deserialized = make(map[string]string)
|
|
if err = sz.Deserialize(serialized, &deserialized); err != nil {
|
|
t.Error(err)
|
|
}
|
|
if fmt.Sprintf("%v", deserialized) != fmt.Sprintf("%v", value) {
|
|
t.Errorf("Expected %v, got %v.", value, deserialized)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestNopSerialization(t *testing.T) {
|
|
cookieData := "fooobar123"
|
|
sz := NopEncoder{}
|
|
|
|
if _, err := sz.Serialize(cookieData); err != errValueNotByte {
|
|
t.Fatal("Expected error passing string")
|
|
}
|
|
dat, err := sz.Serialize([]byte(cookieData))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if (string(dat)) != cookieData {
|
|
t.Fatal("Expected serialized data to be same as source")
|
|
}
|
|
|
|
var dst []byte
|
|
if err = sz.Deserialize(dat, dst); err != errValueNotBytePtr {
|
|
t.Fatal("Expect error unless you pass a *[]byte")
|
|
}
|
|
if err = sz.Deserialize(dat, &dst); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if (string(dst)) != cookieData {
|
|
t.Fatal("Expected deserialized data to be same as source")
|
|
}
|
|
}
|
|
|
|
func TestEncoding(t *testing.T) {
|
|
for _, value := range testStrings {
|
|
encoded := encode([]byte(value))
|
|
decoded, err := decode(encoded)
|
|
if err != nil {
|
|
t.Error(err)
|
|
} else if string(decoded) != value {
|
|
t.Errorf("Expected %v, got %s.", value, string(decoded))
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestMultiError(t *testing.T) {
|
|
s1, s2 := New(nil, nil), New(nil, nil)
|
|
_, err := EncodeMulti("sid", "value", s1, s2)
|
|
if len(err.(MultiError)) != 2 {
|
|
t.Errorf("Expected 2 errors, got %s.", err)
|
|
} else {
|
|
if !strings.Contains(err.Error(), "hash key is not set") {
|
|
t.Errorf("Expected missing hash key error, got %s.", err.Error())
|
|
}
|
|
ourErr, ok := err.(Error)
|
|
if !ok || !ourErr.IsUsage() {
|
|
t.Fatalf("Expected error to be a usage error; got %#v", err)
|
|
}
|
|
if ourErr.IsDecode() {
|
|
t.Errorf("Expected error NOT to be a decode error; got %#v", ourErr)
|
|
}
|
|
if ourErr.IsInternal() {
|
|
t.Errorf("Expected error NOT to be an internal error; got %#v", ourErr)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestMultiNoCodecs(t *testing.T) {
|
|
_, err := EncodeMulti("foo", "bar")
|
|
if err != errNoCodecs {
|
|
t.Errorf("EncodeMulti: bad value for error, got: %v", err)
|
|
}
|
|
|
|
var dst []byte
|
|
err = DecodeMulti("foo", "bar", &dst)
|
|
if err != errNoCodecs {
|
|
t.Errorf("DecodeMulti: bad value for error, got: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestMissingKey(t *testing.T) {
|
|
emptyKeys := [][]byte{
|
|
nil,
|
|
[]byte(""),
|
|
}
|
|
|
|
for _, key := range emptyKeys {
|
|
s1 := New(key, nil)
|
|
|
|
var dst []byte
|
|
err := s1.Decode("sid", "value", &dst)
|
|
if err != errHashKeyNotSet {
|
|
t.Fatalf("Expected %#v, got %#v", errHashKeyNotSet, err)
|
|
}
|
|
if err2, ok := err.(Error); !ok || !err2.IsUsage() {
|
|
t.Errorf("Expected missing hash key to be IsUsage(); was %#v", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
type FooBar struct {
|
|
Foo int
|
|
Bar string
|
|
}
|
|
|
|
func TestCustomType(t *testing.T) {
|
|
s1 := New([]byte("12345"), []byte("1234567890123456"))
|
|
// Type is not registered in gob. (!!!)
|
|
src := &FooBar{42, "bar"}
|
|
encoded, _ := s1.Encode("sid", src)
|
|
|
|
dst := &FooBar{}
|
|
_ = s1.Decode("sid", encoded, dst)
|
|
if dst.Foo != 42 || dst.Bar != "bar" {
|
|
t.Fatalf("Expected %#v, got %#v", src, dst)
|
|
}
|
|
}
|
|
|
|
type Cookie struct {
|
|
B bool
|
|
I int
|
|
S string
|
|
}
|
|
|
|
func FuzzEncodeDecode(f *testing.F) {
|
|
fuzzer := fuzz.New()
|
|
s1 := New([]byte("12345"), []byte("1234567890123456"))
|
|
s1.maxLength = 0
|
|
|
|
for i := 0; i < 100000; i++ {
|
|
var c Cookie
|
|
fuzzer.Fuzz(&c)
|
|
f.Add(c.B, c.I, c.S)
|
|
}
|
|
|
|
f.Fuzz(func(t *testing.T, b bool, i int, s string) {
|
|
c := Cookie{b, i, s}
|
|
encoded, err := s1.Encode("sid", c)
|
|
if err != nil {
|
|
t.Errorf("Encode failed: %v", err)
|
|
}
|
|
dc := Cookie{}
|
|
err = s1.Decode("sid", encoded, &dc)
|
|
if err != nil {
|
|
t.Errorf("Decode failed: %v", err)
|
|
}
|
|
if dc != c {
|
|
t.Fatalf("Expected %v, got %v.", s, dc)
|
|
}
|
|
})
|
|
}
|