feat: 보안 수정 + Prometheus 메트릭 + 단위 테스트 추가
보안: - Zip Bomb 방어 (io.LimitReader 100MB) - Redis Del 에러 로깅 (auth, idempotency) - 로그인 실패 로그에서 username 제거 - os.Remove 에러 로깅 모니터링: - Prometheus 메트릭 미들웨어 + /metrics 엔드포인트 - http_requests_total, http_request_duration_seconds 등 4개 메트릭 테스트: - download (11), chain (10), bossraid (20) = 41개 단위 테스트 기타: - DB 모델 GORM 인덱스 태그 추가 - launcherHash 필드 + hashFileToHex() 추가 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
234
internal/chain/service_test.go
Normal file
234
internal/chain/service_test.go
Normal file
@@ -0,0 +1,234 @@
|
||||
package chain
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
|
||||
tocrypto "github.com/tolelom/tolchain/crypto"
|
||||
)
|
||||
|
||||
// testEncKey returns a valid 32-byte AES-256 key for testing.
|
||||
func testEncKey() []byte {
|
||||
key := make([]byte, 32)
|
||||
for i := range key {
|
||||
key[i] = byte(i)
|
||||
}
|
||||
return key
|
||||
}
|
||||
|
||||
// newTestService creates a minimal Service with only the encryption key set.
|
||||
// No DB, Redis, or chain client — only suitable for testing pure crypto functions.
|
||||
func newTestService() *Service {
|
||||
return &Service{
|
||||
encKeyBytes: testEncKey(),
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncryptDecryptRoundTrip(t *testing.T) {
|
||||
svc := newTestService()
|
||||
|
||||
// Generate a real ed25519 private key
|
||||
privKey, _, err := tocrypto.GenerateKeyPair()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to generate key pair: %v", err)
|
||||
}
|
||||
|
||||
// Encrypt
|
||||
cipherHex, nonceHex, err := svc.encryptPrivKey(tocrypto.PrivateKey(privKey))
|
||||
if err != nil {
|
||||
t.Fatalf("encryptPrivKey failed: %v", err)
|
||||
}
|
||||
|
||||
if cipherHex == "" || nonceHex == "" {
|
||||
t.Fatal("encryptPrivKey returned empty strings")
|
||||
}
|
||||
|
||||
// Verify ciphertext is valid hex
|
||||
if _, err := hex.DecodeString(cipherHex); err != nil {
|
||||
t.Errorf("cipherHex is not valid hex: %v", err)
|
||||
}
|
||||
if _, err := hex.DecodeString(nonceHex); err != nil {
|
||||
t.Errorf("nonceHex is not valid hex: %v", err)
|
||||
}
|
||||
|
||||
// Decrypt
|
||||
decrypted, err := svc.decryptPrivKey(cipherHex, nonceHex)
|
||||
if err != nil {
|
||||
t.Fatalf("decryptPrivKey failed: %v", err)
|
||||
}
|
||||
|
||||
// Compare
|
||||
if hex.EncodeToString(decrypted) != hex.EncodeToString(privKey) {
|
||||
t.Error("decrypted key does not match original")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncryptDecrypt_DifferentKeysProduceDifferentCiphertext(t *testing.T) {
|
||||
svc := newTestService()
|
||||
|
||||
privKey1, _, err := tocrypto.GenerateKeyPair()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to generate key pair 1: %v", err)
|
||||
}
|
||||
privKey2, _, err := tocrypto.GenerateKeyPair()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to generate key pair 2: %v", err)
|
||||
}
|
||||
|
||||
cipher1, _, err := svc.encryptPrivKey(tocrypto.PrivateKey(privKey1))
|
||||
if err != nil {
|
||||
t.Fatalf("encryptPrivKey 1 failed: %v", err)
|
||||
}
|
||||
cipher2, _, err := svc.encryptPrivKey(tocrypto.PrivateKey(privKey2))
|
||||
if err != nil {
|
||||
t.Fatalf("encryptPrivKey 2 failed: %v", err)
|
||||
}
|
||||
|
||||
if cipher1 == cipher2 {
|
||||
t.Error("different private keys should produce different ciphertexts")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncryptSameKey_DifferentNonces(t *testing.T) {
|
||||
svc := newTestService()
|
||||
|
||||
privKey, _, err := tocrypto.GenerateKeyPair()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to generate key pair: %v", err)
|
||||
}
|
||||
|
||||
cipher1, nonce1, err := svc.encryptPrivKey(tocrypto.PrivateKey(privKey))
|
||||
if err != nil {
|
||||
t.Fatalf("encryptPrivKey 1 failed: %v", err)
|
||||
}
|
||||
cipher2, nonce2, err := svc.encryptPrivKey(tocrypto.PrivateKey(privKey))
|
||||
if err != nil {
|
||||
t.Fatalf("encryptPrivKey 2 failed: %v", err)
|
||||
}
|
||||
|
||||
// Each encryption should use a different random nonce
|
||||
if nonce1 == nonce2 {
|
||||
t.Error("encrypting the same key twice should use different nonces")
|
||||
}
|
||||
|
||||
// So ciphertext should also differ (AES-GCM is nonce-dependent)
|
||||
if cipher1 == cipher2 {
|
||||
t.Error("encrypting the same key with different nonces should produce different ciphertexts")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecryptWithWrongKey(t *testing.T) {
|
||||
svc := newTestService()
|
||||
|
||||
privKey, _, err := tocrypto.GenerateKeyPair()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to generate key pair: %v", err)
|
||||
}
|
||||
|
||||
cipherHex, nonceHex, err := svc.encryptPrivKey(tocrypto.PrivateKey(privKey))
|
||||
if err != nil {
|
||||
t.Fatalf("encryptPrivKey failed: %v", err)
|
||||
}
|
||||
|
||||
// Create a service with a different encryption key
|
||||
wrongKey := make([]byte, 32)
|
||||
for i := range wrongKey {
|
||||
wrongKey[i] = byte(255 - i)
|
||||
}
|
||||
wrongSvc := &Service{encKeyBytes: wrongKey}
|
||||
|
||||
_, err = wrongSvc.decryptPrivKey(cipherHex, nonceHex)
|
||||
if err == nil {
|
||||
t.Error("decryptPrivKey with wrong key should fail")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecryptWithInvalidHex(t *testing.T) {
|
||||
svc := newTestService()
|
||||
|
||||
_, err := svc.decryptPrivKey("not-hex", "also-not-hex")
|
||||
if err == nil {
|
||||
t.Error("decryptPrivKey with invalid hex should fail")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecryptWithTamperedCiphertext(t *testing.T) {
|
||||
svc := newTestService()
|
||||
|
||||
privKey, _, err := tocrypto.GenerateKeyPair()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to generate key pair: %v", err)
|
||||
}
|
||||
|
||||
cipherHex, nonceHex, err := svc.encryptPrivKey(tocrypto.PrivateKey(privKey))
|
||||
if err != nil {
|
||||
t.Fatalf("encryptPrivKey failed: %v", err)
|
||||
}
|
||||
|
||||
// Tamper with the ciphertext by flipping a byte
|
||||
cipherBytes, _ := hex.DecodeString(cipherHex)
|
||||
cipherBytes[0] ^= 0xFF
|
||||
tamperedHex := hex.EncodeToString(cipherBytes)
|
||||
|
||||
_, err = svc.decryptPrivKey(tamperedHex, nonceHex)
|
||||
if err == nil {
|
||||
t.Error("decryptPrivKey with tampered ciphertext should fail")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewService_InvalidEncryptionKey(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
encKey string
|
||||
}{
|
||||
{"too short", "aabbccdd"},
|
||||
{"not hex", "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz"},
|
||||
{"empty", ""},
|
||||
{"odd length", "aabbccddeeff00112233445566778899aabbccddeeff0011223344556677889"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
_, err := NewService(nil, nil, "test-chain", "", tt.encKey)
|
||||
if err == nil {
|
||||
t.Error("NewService should fail with invalid encryption key")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewService_ValidEncryptionKey(t *testing.T) {
|
||||
// 64 hex chars = 32 bytes
|
||||
validKey := "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
|
||||
|
||||
svc, err := NewService(nil, nil, "test-chain", "", validKey)
|
||||
if err != nil {
|
||||
t.Fatalf("NewService with valid key should succeed: %v", err)
|
||||
}
|
||||
if svc == nil {
|
||||
t.Fatal("NewService returned nil service")
|
||||
}
|
||||
if svc.chainID != "test-chain" {
|
||||
t.Errorf("chainID = %q, want %q", svc.chainID, "test-chain")
|
||||
}
|
||||
// No operator key provided, so operatorWallet should be nil
|
||||
if svc.operatorWallet != nil {
|
||||
t.Error("operatorWallet should be nil when no operator key is provided")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnsureOperator_NilWallet(t *testing.T) {
|
||||
svc := newTestService()
|
||||
err := svc.ensureOperator()
|
||||
if err == nil {
|
||||
t.Error("ensureOperator should fail when operatorWallet is nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveUsername_NoResolver(t *testing.T) {
|
||||
svc := newTestService()
|
||||
_, err := svc.resolveUsername("testuser")
|
||||
if err == nil {
|
||||
t.Error("resolveUsername should fail when userResolver is nil")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user