package crypto import ( "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func testKey() []byte { // 32 bytes of deterministic test key return []byte("01234567890123456789012345678901") } func otherTestKey() []byte { return []byte("abcdefghijklmnopqrstuvwxyz012345") } func TestNewKeyring_PrimaryAndLookup(t *testing.T) { keyring, err := NewKeyring([]Key{ {ID: "old", Material: testKey()}, {ID: "new", Material: otherTestKey()}, }, "new") require.NoError(t, err) assert.Equal(t, "new", keyring.primaryID) assert.Equal(t, []string{"old", "new"}, keyring.order) } func TestNewKeyring_RejectsUnsafeKeyID(t *testing.T) { _, err := NewKeyring([]Key{{ID: "old.key", Material: testKey()}}, "old.key") require.Error(t, err) assert.Contains(t, err.Error(), "key id") } func TestNewService_NilKey(t *testing.T) { svc, err := NewService(nil) require.NoError(t, err) assert.False(t, svc.Enabled()) } func TestNewService_ValidKey(t *testing.T) { svc, err := NewService(testKey()) require.NoError(t, err) assert.True(t, svc.Enabled()) } func TestNewService_WrongKeyLength(t *testing.T) { _, err := NewService([]byte("too-short")) require.Error(t, err) assert.Contains(t, err.Error(), "32 bytes") } func TestEncryptDecrypt(t *testing.T) { svc, err := NewService(testKey()) require.NoError(t, err) plaintext := []byte("hello, secret headers!") ciphertext, err := svc.EnvelopeEncrypt(plaintext) require.NoError(t, err) // Ciphertext should be different from plaintext assert.NotEqual(t, plaintext, ciphertext) // Decrypt should recover original decrypted, err := svc.EnvelopeDecrypt(ciphertext) require.NoError(t, err) assert.Equal(t, plaintext, decrypted) } func TestEncryptDecrypt_DifferentNonces(t *testing.T) { svc, err := NewService(testKey()) require.NoError(t, err) plaintext := []byte("same input") ct1, err := svc.EnvelopeEncrypt(plaintext) require.NoError(t, err) ct2, err := svc.EnvelopeEncrypt(plaintext) require.NoError(t, err) // Two encryptions of the same plaintext should produce different ciphertext assert.NotEqual(t, ct1, ct2) // Both should decrypt correctly d1, err := svc.EnvelopeDecrypt(ct1) require.NoError(t, err) d2, err := svc.EnvelopeDecrypt(ct2) require.NoError(t, err) assert.Equal(t, d1, d2) } func TestDecrypt_TamperedData(t *testing.T) { svc, err := NewService(testKey()) require.NoError(t, err) ciphertext, err := svc.EnvelopeEncrypt([]byte("sensitive")) require.NoError(t, err) // Tamper with last byte ciphertext[len(ciphertext)-1] ^= 0xFF _, err = svc.EnvelopeDecrypt(ciphertext) assert.Error(t, err) } func TestDecrypt_TooShort(t *testing.T) { svc, err := NewService(testKey()) require.NoError(t, err) _, err = svc.EnvelopeDecrypt([]byte{envelopeVersionWithKeyID}) assert.Error(t, err) assert.Contains(t, err.Error(), "too short") } func TestNoEncryptionKey(t *testing.T) { svc, err := NewService(nil) require.NoError(t, err) _, err = svc.EnvelopeEncrypt([]byte("data")) assert.ErrorIs(t, err, ErrNoEncryptionKey) _, err = svc.EnvelopeDecrypt([]byte("data")) assert.ErrorIs(t, err, ErrNoEncryptionKey) } func TestEncryptDecryptJSON(t *testing.T) { svc, err := NewService(testKey()) require.NoError(t, err) original := map[string]string{ "Authorization": "Bearer sk-secret-token", "X-Api-Key": "key-12345", } ciphertext, err := svc.EncryptJSON(original) require.NoError(t, err) var decrypted map[string]string err = svc.DecryptJSON(ciphertext, &decrypted) require.NoError(t, err) assert.Equal(t, original, decrypted) } func TestParseKey(t *testing.T) { tests := []struct { name string input string wantNil bool wantErr bool }{ {"empty returns nil", "", true, false}, {"valid 64 hex chars", "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", false, false}, {"invalid hex", "not-hex-at-all!", false, true}, {"wrong length", "0123456789abcdef", false, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { key, err := ParseKey(tt.input) if tt.wantErr { assert.Error(t, err) return } require.NoError(t, err) if tt.wantNil { assert.Nil(t, key) } else { assert.Len(t, key, 32) } }) } } func TestDecrypt_WrongKey(t *testing.T) { svc1, err := NewService(testKey()) require.NoError(t, err) svc2, err := NewService([]byte("different-key-0123456789abcdefgh")) require.NoError(t, err) ciphertext, err := svc1.EnvelopeEncrypt([]byte("secret")) require.NoError(t, err) // Decrypting with a different key should fail _, err = svc2.EnvelopeDecrypt(ciphertext) assert.Error(t, err) } func TestEncryptString_DecryptString(t *testing.T) { svc, err := NewService(testKey()) require.NoError(t, err) // Normal string ct, err := svc.EncryptString("whsec_abc123") require.NoError(t, err) assert.NotNil(t, ct) decrypted, err := svc.DecryptString(ct) require.NoError(t, err) assert.Equal(t, "whsec_abc123", decrypted) // Empty string returns nil ct, err = svc.EncryptString("") require.NoError(t, err) assert.Nil(t, ct) decrypted, err = svc.DecryptString(nil) require.NoError(t, err) assert.Equal(t, "", decrypted) } func TestGenerateKey(t *testing.T) { hexKey, rawKey, err := GenerateKey() require.NoError(t, err) assert.Len(t, rawKey, 32) assert.Len(t, hexKey, 64) // Should be usable to create a service svc, err := NewService(rawKey) require.NoError(t, err) assert.True(t, svc.Enabled()) // Hex should round-trip parsed, err := ParseKey(hexKey) require.NoError(t, err) assert.Equal(t, rawKey, parsed) }