- PKCE (S256) in OAuth flow: code_verifier in oauth_states, code_challenge in auth URL - CryptoService: AES-256-GCM encryption for OAuth provider tokens at rest - OAuth redirect URL validated against OAUTH_ALLOWED_REDIRECT_DOMAINS - CHAT_JWT_SECRET must differ from JWT_SECRET in production - Migration script: cmd/tools/encrypt_oauth_tokens for existing tokens - Fixes: VEZA-SEC-003, VEZA-SEC-004, VEZA-SEC-009, VEZA-SEC-010
161 lines
3.8 KiB
Go
161 lines
3.8 KiB
Go
package services
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestNewCryptoService_InvalidKey(t *testing.T) {
|
|
_, err := NewCryptoService([]byte("short"))
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "32 bytes")
|
|
}
|
|
|
|
func TestNewCryptoService_ValidKey(t *testing.T) {
|
|
key := make([]byte, 32)
|
|
for i := range key {
|
|
key[i] = byte(i)
|
|
}
|
|
svc, err := NewCryptoService(key)
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, svc)
|
|
}
|
|
|
|
func TestCryptoService_EncryptDecrypt_Roundtrip(t *testing.T) {
|
|
key := make([]byte, 32)
|
|
for i := range key {
|
|
key[i] = byte(i)
|
|
}
|
|
svc, err := NewCryptoService(key)
|
|
require.NoError(t, err)
|
|
|
|
plaintext := []byte("sensitive-oauth-token-value")
|
|
enc, err := svc.Encrypt(plaintext)
|
|
require.NoError(t, err)
|
|
assert.NotEqual(t, plaintext, enc)
|
|
assert.NotEmpty(t, enc)
|
|
|
|
dec, err := svc.Decrypt(enc)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, plaintext, dec)
|
|
}
|
|
|
|
func TestCryptoService_EncryptDecrypt_DifferentEachTime(t *testing.T) {
|
|
key := make([]byte, 32)
|
|
for i := range key {
|
|
key[i] = byte(i)
|
|
}
|
|
svc, err := NewCryptoService(key)
|
|
require.NoError(t, err)
|
|
|
|
plaintext := []byte("token")
|
|
enc1, err := svc.Encrypt(plaintext)
|
|
require.NoError(t, err)
|
|
enc2, err := svc.Encrypt(plaintext)
|
|
require.NoError(t, err)
|
|
assert.NotEqual(t, enc1, enc2, "encryption should be non-deterministic (random nonce)")
|
|
|
|
dec1, err := svc.Decrypt(enc1)
|
|
require.NoError(t, err)
|
|
dec2, err := svc.Decrypt(enc2)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, plaintext, dec1)
|
|
assert.Equal(t, plaintext, dec2)
|
|
}
|
|
|
|
func TestCryptoService_EncryptString_DecryptString_Roundtrip(t *testing.T) {
|
|
key := make([]byte, 32)
|
|
for i := range key {
|
|
key[i] = byte(i)
|
|
}
|
|
svc, err := NewCryptoService(key)
|
|
require.NoError(t, err)
|
|
|
|
plaintext := "my-access-token-123"
|
|
enc, err := svc.EncryptString(plaintext)
|
|
require.NoError(t, err)
|
|
assert.True(t, strings.HasPrefix(enc, EncryptedTokenPrefix()))
|
|
assert.NotEqual(t, plaintext, enc)
|
|
|
|
dec, err := svc.DecryptString(enc)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, plaintext, dec)
|
|
}
|
|
|
|
func TestCryptoService_DecryptString_Plaintext(t *testing.T) {
|
|
key := make([]byte, 32)
|
|
for i := range key {
|
|
key[i] = byte(i)
|
|
}
|
|
svc, err := NewCryptoService(key)
|
|
require.NoError(t, err)
|
|
|
|
// Legacy plaintext (no prefix) -> returned as-is
|
|
plain := "legacy-token"
|
|
dec, err := svc.DecryptString(plain)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, plain, dec)
|
|
}
|
|
|
|
func TestCryptoService_DecryptString_Empty(t *testing.T) {
|
|
key := make([]byte, 32)
|
|
svc, err := NewCryptoService(key)
|
|
require.NoError(t, err)
|
|
|
|
dec, err := svc.DecryptString("")
|
|
require.NoError(t, err)
|
|
assert.Empty(t, dec)
|
|
}
|
|
|
|
func TestCryptoService_EncryptString_Empty(t *testing.T) {
|
|
key := make([]byte, 32)
|
|
svc, err := NewCryptoService(key)
|
|
require.NoError(t, err)
|
|
|
|
enc, err := svc.EncryptString("")
|
|
require.NoError(t, err)
|
|
assert.Empty(t, enc)
|
|
}
|
|
|
|
func TestCryptoService_Decrypt_ModifiedCiphertext(t *testing.T) {
|
|
key := make([]byte, 32)
|
|
for i := range key {
|
|
key[i] = byte(i)
|
|
}
|
|
svc, err := NewCryptoService(key)
|
|
require.NoError(t, err)
|
|
|
|
enc, err := svc.Encrypt([]byte("token"))
|
|
require.NoError(t, err)
|
|
enc[20] ^= 0xff // flip a byte
|
|
|
|
_, err = svc.Decrypt(enc)
|
|
require.Error(t, err)
|
|
}
|
|
|
|
func TestNewCryptoServiceFromBase64(t *testing.T) {
|
|
key := make([]byte, 32)
|
|
for i := range key {
|
|
key[i] = byte(i)
|
|
}
|
|
keyB64 := base64.RawStdEncoding.EncodeToString(key)
|
|
|
|
svc, err := NewCryptoServiceFromBase64(keyB64)
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, svc)
|
|
|
|
enc, err := svc.EncryptString("test")
|
|
require.NoError(t, err)
|
|
dec, err := svc.DecryptString(enc)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "test", dec)
|
|
}
|
|
|
|
func TestNewCryptoServiceFromBase64_Empty(t *testing.T) {
|
|
_, err := NewCryptoServiceFromBase64("")
|
|
require.Error(t, err)
|
|
}
|