Files
woodpecker/server/services/encryption/aes_test.go
2025-12-20 09:23:09 +01:00

165 lines
4.8 KiB
Go

// Copyright 2023 Woodpecker Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package encryption
import (
"encoding/base64"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/tink-crypto/tink-go/v2/subtle/random"
)
func TestShortMessageLongKey(t *testing.T) {
aes := &aesEncryptionService{}
err := aes.loadCipher(string(random.GetRandomBytes(32)))
assert.NoError(t, err)
input := string(random.GetRandomBytes(4))
cipher, err := aes.Encrypt(input, "")
assert.NoError(t, err)
output, err := aes.Decrypt(cipher, "")
assert.NoError(t, err)
assert.Equal(t, input, output)
}
func TestLongMessageShortKey(t *testing.T) {
aes := &aesEncryptionService{}
err := aes.loadCipher(string(random.GetRandomBytes(12)))
assert.NoError(t, err)
input := string(random.GetRandomBytes(1024))
cipher, err := aes.Encrypt(input, "")
assert.NoError(t, err)
output, err := aes.Decrypt(cipher, "")
assert.NoError(t, err)
assert.Equal(t, input, output)
}
func TestEncryptDecryptWithAssociatedData(t *testing.T) {
aes := &aesEncryptionService{}
err := aes.loadCipher(string(random.GetRandomBytes(32)))
require.NoError(t, err)
plaintext := "secret-value-12345"
associatedData := "repo:123"
ciphertext, err := aes.Encrypt(plaintext, associatedData)
require.NoError(t, err)
// Decrypt with correct associated data should succeed
decrypted, err := aes.Decrypt(ciphertext, associatedData)
require.NoError(t, err)
assert.Equal(t, plaintext, decrypted)
// Decrypt with wrong associated data should fail
_, err = aes.Decrypt(ciphertext, "repo:456")
assert.Error(t, err, "decryption should fail with wrong associated data")
// Decrypt with empty associated data should fail
_, err = aes.Decrypt(ciphertext, "")
assert.Error(t, err, "decryption should fail with missing associated data")
}
func TestEncryptProducesUniqueCiphertexts(t *testing.T) {
aes := &aesEncryptionService{}
err := aes.loadCipher(string(random.GetRandomBytes(32)))
require.NoError(t, err)
plaintext := "same-message"
ciphertexts := make(map[string]bool)
// Encrypt the same message multiple times
for range 100 {
ct, err := aes.Encrypt(plaintext, "")
require.NoError(t, err)
assert.False(t, ciphertexts[ct], "ciphertext should be unique due to random nonce")
ciphertexts[ct] = true
}
}
func TestDecryptTamperedCiphertext(t *testing.T) {
aes := &aesEncryptionService{}
err := aes.loadCipher(string(random.GetRandomBytes(32)))
require.NoError(t, err)
plaintext := "sensitive-data"
ciphertext, err := aes.Encrypt(plaintext, "")
require.NoError(t, err)
// Decode, tamper, re-encode
decoded, err := base64.StdEncoding.DecodeString(ciphertext)
require.NoError(t, err)
// Tamper with the ciphertext (flip a bit in the middle)
if len(decoded) > AES_GCM_SIV_NonceSize+1 {
decoded[AES_GCM_SIV_NonceSize+1] ^= 0xFF
}
tampered := base64.StdEncoding.EncodeToString(decoded)
_, err = aes.Decrypt(tampered, "")
assert.Error(t, err, "decryption of tampered ciphertext should fail")
}
func TestDecryptInvalidBase64(t *testing.T) {
aes := &aesEncryptionService{}
err := aes.loadCipher(string(random.GetRandomBytes(32)))
require.NoError(t, err)
_, err = aes.Decrypt("not-valid-base64!!!", "")
assert.Error(t, err, "decryption of invalid base64 should fail")
}
func TestDecryptTruncatedCiphertext(t *testing.T) {
aes := &aesEncryptionService{}
err := aes.loadCipher(string(random.GetRandomBytes(32)))
require.NoError(t, err)
plaintext := "test-message"
ciphertext, err := aes.Encrypt(plaintext, "")
require.NoError(t, err)
// Truncate the ciphertext
decoded, err := base64.StdEncoding.DecodeString(ciphertext)
require.NoError(t, err)
truncated := base64.StdEncoding.EncodeToString(decoded[:len(decoded)/2])
_, err = aes.Decrypt(truncated, "")
assert.Error(t, err, "decryption of truncated ciphertext should fail")
}
func TestRandomBytesUniqueness(t *testing.T) {
seen := make(map[string]bool)
for range 1000 {
bytes := random.GetRandomBytes(32)
key := string(bytes)
assert.False(t, seen[key], "random bytes should be unique")
seen[key] = true
}
}
func TestRandomBytesLength(t *testing.T) {
tests := []uint32{1, 12, 16, 32, 64, 128, 256}
for _, length := range tests {
bytes := random.GetRandomBytes(length)
assert.Len(t, bytes, int(length), "random bytes should have requested length")
}
}