304 lines
7.7 KiB
Go
304 lines
7.7 KiB
Go
package cookie
|
|
|
|
import (
|
|
"crypto/hmac"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"hash/crc32"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
const (
|
|
testCookieKey = "def000008bf3d70e7012b7493c382d561e193218d0c74ab162fb0ea8029ce20e926531b4bcf0aaec9381152e6c161f198e06918b2d1aad67cc7cf40819a51ee328c63830"
|
|
)
|
|
|
|
func TestNativeCodecDecodeFixture(t *testing.T) {
|
|
codec, err := NewCodec(Config{
|
|
CookieName: "PrestaShop-test",
|
|
CookieKey: testCookieKey,
|
|
CookieIV: "vfRFMV42",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("NewCodec() error = %v", err)
|
|
}
|
|
|
|
session, err := codec.Decode(encodeFixtureCookie(t, codec))
|
|
if err != nil {
|
|
t.Fatalf("Decode() error = %v", err)
|
|
}
|
|
|
|
if session.Values["id_lang"] != "1" {
|
|
t.Fatalf("id_lang = %q, want 1", session.Values["id_lang"])
|
|
}
|
|
if session.Values["id_currency"] != "1" {
|
|
t.Fatalf("id_currency = %q, want 1", session.Values["id_currency"])
|
|
}
|
|
if session.Values["checksum"] == "" {
|
|
t.Fatalf("checksum should not be empty")
|
|
}
|
|
if session.Values["detect_language"] != "1" {
|
|
t.Fatalf("detect_language = %q, want 1", session.Values["detect_language"])
|
|
}
|
|
if session.GuestID != nil {
|
|
t.Fatalf("guest_id = %v, want nil", session.GuestID)
|
|
}
|
|
}
|
|
|
|
func TestNativeCodecRoundTrip(t *testing.T) {
|
|
codec, err := NewCodec(Config{
|
|
CookieName: "PrestaShop-test",
|
|
CookieKey: testCookieKey,
|
|
CookieIV: "vfRFMV42",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("NewCodec() error = %v", err)
|
|
}
|
|
|
|
decoded, err := codec.Decode(encodeFixtureCookie(t, codec))
|
|
if err != nil {
|
|
t.Fatalf("Decode() error = %v", err)
|
|
}
|
|
|
|
encoded, err := codec.Encode(decoded)
|
|
if err != nil {
|
|
t.Fatalf("Encode() error = %v", err)
|
|
}
|
|
|
|
redecoded, err := codec.Decode(encoded)
|
|
if err != nil {
|
|
t.Fatalf("Decode(encoded) error = %v", err)
|
|
}
|
|
|
|
if redecoded.Plaintext != decoded.Plaintext {
|
|
t.Fatalf("plaintext mismatch after roundtrip\n got: %s\nwant: %s", redecoded.Plaintext, decoded.Plaintext)
|
|
}
|
|
}
|
|
|
|
func TestNativeCodecEncodeRecomputesPrestashopChecksum(t *testing.T) {
|
|
codec, err := NewCodec(Config{
|
|
CookieName: "PrestaShop-test",
|
|
CookieKey: testCookieKey,
|
|
CookieIV: "vfRFMV42",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("NewCodec() error = %v", err)
|
|
}
|
|
|
|
decoded, err := codec.Decode(encodeFixtureCookie(t, codec))
|
|
if err != nil {
|
|
t.Fatalf("Decode() error = %v", err)
|
|
}
|
|
|
|
decoded.Values["iso_code_country"] = "PL"
|
|
decoded.Values["id_currency"] = "6"
|
|
decoded.Values["checksum"] = "stale"
|
|
decoded.Plaintext = ""
|
|
|
|
encoded, err := codec.Encode(decoded)
|
|
if err != nil {
|
|
t.Fatalf("Encode() error = %v", err)
|
|
}
|
|
|
|
redecoded, err := codec.Decode(encoded)
|
|
if err != nil {
|
|
t.Fatalf("Decode(encoded) error = %v", err)
|
|
}
|
|
|
|
pairs := strings.Split(redecoded.Plaintext, fieldSeparator)
|
|
if len(pairs) < 2 {
|
|
t.Fatalf("plaintext too short: %q", redecoded.Plaintext)
|
|
}
|
|
body := strings.Join(pairs[:len(pairs)-1], fieldSeparator) + fieldSeparator
|
|
wantChecksum := fmt.Sprintf("%d", crc32.ChecksumIEEE([]byte("vfRFMV42"+body)))
|
|
if got := redecoded.Values["checksum"]; got != wantChecksum {
|
|
t.Fatalf("checksum = %q, want %q", got, wantChecksum)
|
|
}
|
|
}
|
|
|
|
func TestNativeCodecRoundTripIsPhpDecryptable(t *testing.T) {
|
|
codec, err := NewCodec(Config{
|
|
CookieName: "PrestaShop-test",
|
|
CookieKey: testCookieKey,
|
|
CookieIV: "vfRFMV42",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("NewCodec() error = %v", err)
|
|
}
|
|
|
|
session := &SessionContext{
|
|
Values: map[string]string{
|
|
"date_add": "2026-05-13 18:51:06",
|
|
"id_lang": "5",
|
|
"id_language": "5",
|
|
"iso_code_country": "CZ",
|
|
"id_currency": "1",
|
|
"id_guest": "39160640",
|
|
"id_connections": "13279441",
|
|
},
|
|
OrderedKeys: []string{
|
|
"date_add",
|
|
"id_lang",
|
|
"id_language",
|
|
"iso_code_country",
|
|
"id_currency",
|
|
"id_guest",
|
|
"id_connections",
|
|
},
|
|
}
|
|
|
|
encoded, err := codec.Encode(session)
|
|
if err != nil {
|
|
t.Fatalf("Encode() error = %v", err)
|
|
}
|
|
|
|
raw, err := hex.DecodeString(encoded)
|
|
if err != nil {
|
|
t.Fatalf("hex.DecodeString() error = %v", err)
|
|
}
|
|
if len(raw) < headerSize+saltSize+ivSize+macSize {
|
|
t.Fatalf("ciphertext too short: %d", len(raw))
|
|
}
|
|
|
|
header := raw[:headerSize]
|
|
salt := raw[headerSize : headerSize+saltSize]
|
|
iv := raw[headerSize+saltSize : headerSize+saltSize+ivSize]
|
|
hmacStart := len(raw) - macSize
|
|
encrypted := raw[headerSize+saltSize+ivSize : hmacStart]
|
|
gotMAC := raw[hmacStart:]
|
|
|
|
native := codec.(*nativeCodec)
|
|
keys, err := native.deriveKeys(salt)
|
|
if err != nil {
|
|
t.Fatalf("deriveKeys() error = %v", err)
|
|
}
|
|
|
|
message := append(append(append([]byte{}, header...), salt...), iv...)
|
|
message = append(message, encrypted...)
|
|
h := hmac.New(sha256.New, keys.akey)
|
|
h.Write(message)
|
|
wantMAC := h.Sum(nil)
|
|
if !hmac.Equal(gotMAC, wantMAC) {
|
|
t.Fatalf("MAC mismatch")
|
|
}
|
|
|
|
redecoded, err := codec.Decode(encoded)
|
|
if err != nil {
|
|
t.Fatalf("Decode(encoded) error = %v", err)
|
|
}
|
|
if redecoded.Plaintext != "date_add|2026-05-13 18:51:06¤id_lang|5¤id_language|5¤iso_code_country|CZ¤id_currency|1¤id_guest|39160640¤id_connections|13279441¤checksum|181610492" {
|
|
t.Fatalf("unexpected plaintext = %q", redecoded.Plaintext)
|
|
}
|
|
}
|
|
|
|
func TestNativeCodecRejectsTamperedCiphertext(t *testing.T) {
|
|
codec, err := NewCodec(Config{
|
|
CookieName: "PrestaShop-test",
|
|
CookieKey: testCookieKey,
|
|
CookieIV: "vfRFMV42",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("NewCodec() error = %v", err)
|
|
}
|
|
|
|
decoded, err := codec.Decode(encodeFixtureCookie(t, codec))
|
|
if err != nil {
|
|
t.Fatalf("Decode() error = %v", err)
|
|
}
|
|
|
|
encoded, err := codec.Encode(decoded)
|
|
if err != nil {
|
|
t.Fatalf("Encode() error = %v", err)
|
|
}
|
|
|
|
raw, err := hex.DecodeString(encoded)
|
|
if err != nil {
|
|
t.Fatalf("hex.DecodeString() error = %v", err)
|
|
}
|
|
raw[len(raw)-1] ^= 0x01
|
|
tampered := hex.EncodeToString(raw)
|
|
|
|
if _, err := codec.Decode(tampered); err == nil {
|
|
t.Fatalf("Decode(tampered) error = nil, want integrity failure")
|
|
}
|
|
}
|
|
|
|
func TestNativeCodecRejectsTamperedPlaintextChecksum(t *testing.T) {
|
|
codec, err := NewCodec(Config{
|
|
CookieName: "PrestaShop-test",
|
|
CookieKey: testCookieKey,
|
|
CookieIV: "vfRFMV42",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("NewCodec() error = %v", err)
|
|
}
|
|
|
|
native := codec.(*nativeCodec)
|
|
plaintext := "date_add|2026-05-13 18:51:06¤id_lang|5¤id_language|5¤iso_code_country|CZ¤id_currency|9¤id_guest|39160640¤id_connections|13279441¤checksum|181610492"
|
|
encoded, err := native.encryptInternal(plaintext)
|
|
if err != nil {
|
|
t.Fatalf("encryptInternal() error = %v", err)
|
|
}
|
|
|
|
if _, err := codec.Decode(encoded); err == nil {
|
|
t.Fatalf("Decode() error = nil, want checksum mismatch")
|
|
}
|
|
}
|
|
|
|
func TestSerializeCookieValuesMatchesPrestashopChecksumFormula(t *testing.T) {
|
|
values := map[string]string{
|
|
"date_add": "2026-05-13 18:51:06",
|
|
"id_lang": "5",
|
|
"id_language": "5",
|
|
"iso_code_country": "CZ",
|
|
"id_currency": "1",
|
|
"id_guest": "39160640",
|
|
"id_connections": "13279441",
|
|
"checksum": "stale",
|
|
}
|
|
orderedKeys := []string{
|
|
"date_add",
|
|
"id_lang",
|
|
"id_language",
|
|
"iso_code_country",
|
|
"id_currency",
|
|
"id_guest",
|
|
"id_connections",
|
|
"checksum",
|
|
}
|
|
|
|
got := serializeCookieValues(values, orderedKeys, "vfRFMV42")
|
|
want := "date_add|2026-05-13 18:51:06¤id_lang|5¤id_language|5¤iso_code_country|CZ¤id_currency|1¤id_guest|39160640¤id_connections|13279441¤checksum|181610492"
|
|
if got != want {
|
|
t.Fatalf("serializeCookieValues() = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func encodeFixtureCookie(t *testing.T, codec Codec) string {
|
|
t.Helper()
|
|
|
|
session := &SessionContext{
|
|
Values: map[string]string{
|
|
"id_lang": "1",
|
|
"id_cart": "",
|
|
"id_language": "1",
|
|
"detect_language": "1",
|
|
"id_currency": "1",
|
|
},
|
|
OrderedKeys: []string{
|
|
"id_lang",
|
|
"id_cart",
|
|
"id_language",
|
|
"detect_language",
|
|
"id_currency",
|
|
},
|
|
}
|
|
|
|
encoded, err := codec.Encode(session)
|
|
if err != nil {
|
|
t.Fatalf("Encode() error = %v", err)
|
|
}
|
|
return encoded
|
|
}
|