package main import ( "crypto/rand" "encoding/hex" "io" "net/http" "net/http/httptest" "testing" "testing/quick" "git.tjp.lol/authentic_kate" "github.com/jamesruan/sodium" ) type ByteSerDes struct{} func (ByteSerDes) Serialize(w io.Writer, data []byte) error { _, err := w.Write(data) return err } func (ByteSerDes) Deserialize(r io.Reader, data *[]byte) error { buf, err := io.ReadAll(r) if err != nil { return err } *data = buf return nil } func kateSeal(key [32]byte, plaintext []byte) []byte { keyHex := hex.EncodeToString(key[:]) auth := kate.New(keyHex, kate.AuthConfig[[]byte]{ SerDes: ByteSerDes{}, CookieName: "test", }) w := httptest.NewRecorder() if err := auth.Set(w, plaintext); err != nil { panic(err) } cookies := w.Result().Cookies() if len(cookies) == 0 { panic("No cookie set") } encryptedBytes, err := hex.DecodeString(cookies[0].Value) if err != nil { panic(err) } return encryptedBytes } func libsodiumSeal(key [32]byte, plaintext []byte) []byte { var nonce [24]byte if _, err := rand.Read(nonce[:]); err != nil { panic(err) } ciphertextAndMac := sodium.Bytes(plaintext).SecretBox( sodium.SecretBoxNonce{Bytes: nonce[:]}, sodium.SecretBoxKey{Bytes: key[:]}, ) result := make([]byte, 24+len(ciphertextAndMac)) copy(result[:24], nonce[:]) copy(result[24:], ciphertextAndMac) return result } func kateOpen(key [32]byte, box []byte) ([]byte, bool) { keyHex := hex.EncodeToString(key[:]) auth := kate.New(keyHex, kate.AuthConfig[[]byte]{ SerDes: ByteSerDes{}, CookieName: "test", }) cookieValue := hex.EncodeToString(box) req := httptest.NewRequest("GET", "/", nil) req.AddCookie(&http.Cookie{Name: "test", Value: cookieValue}) var decryptedData []byte var success bool handler := auth.Optional(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { data, ok := auth.Get(r.Context()) if ok { decryptedData = data success = true } })) testW := httptest.NewRecorder() handler.ServeHTTP(testW, req) return decryptedData, success } func libsodiumOpen(key [32]byte, box []byte) ([]byte, bool) { if len(box) < 24 { return nil, false } nonce := box[:24] ciphertextAndMac := box[24:] decrypted, err := sodium.Bytes(ciphertextAndMac).SecretBoxOpen( sodium.SecretBoxNonce{Bytes: nonce}, sodium.SecretBoxKey{Bytes: key[:]}, ) if err != nil { return nil, false } return decrypted, true } func TestCrossLibraryCompatibility(t *testing.T) { t.Run("KateEncrypt_LibsodiumDecrypt", func(t *testing.T) { f := func(data []byte) bool { if len(data) == 0 { return true } var key [32]byte if _, err := rand.Read(key[:]); err != nil { return false } kateBox := kateSeal(key, data) decrypted, ok := libsodiumOpen(key, kateBox) return ok && string(decrypted) == string(data) } if err := quick.Check(f, nil); err != nil { t.Errorf("Property test failed: %v", err) } }) t.Run("LibsodiumEncrypt_KateDecrypt", func(t *testing.T) { f := func(data []byte) bool { if len(data) == 0 { return true } var key [32]byte if _, err := rand.Read(key[:]); err != nil { return false } libsodiumBox := libsodiumSeal(key, data) decrypted, ok := kateOpen(key, libsodiumBox) return ok && string(decrypted) == string(data) } if err := quick.Check(f, nil); err != nil { t.Errorf("Property test failed: %v", err) } }) }