diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 418e9b5..6d284a8 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,5 +1,5 @@ { - // Docker images officially provided by Microsoft that can utilized as base images + // Docker images officially provided by Microsoft that can be utilized as base images // https://hub.docker.com/_/microsoft-vscode-devcontainers "name": "Tools for building and running Go projects", "image": "mcr.microsoft.com/vscode/devcontainers/go:1.21", diff --git a/README.md b/README.md index b2ca834..ca7e1e6 100644 --- a/README.md +++ b/README.md @@ -44,4 +44,22 @@ TBD ## Getting Started -TBD \ No newline at end of file +### Run Tests + +To run `unit` tests on Unix systems execute: + +```sh +cd scripts +./run-test.sh -u +``` + +**TBD** To run `integration` tests on Unix systems execute: + +```sh +cd scripts +./run-test.sh -i +``` + +### Applications + +You can find applications utilizing [internal packages](./internal/) in the [cmd folder](./cmd/). \ No newline at end of file diff --git a/test/integration/infrastructure/cryptography/io_test.go b/cmd/crypto-vault-cli/Dockerfile similarity index 100% rename from test/integration/infrastructure/cryptography/io_test.go rename to cmd/crypto-vault-cli/Dockerfile diff --git a/cmd/crypto-vault-cli/crypto-vault-cli.go b/cmd/crypto-vault-cli/crypto-vault-cli.go index 8589ba4..8eb60de 100644 --- a/cmd/crypto-vault-cli/crypto-vault-cli.go +++ b/cmd/crypto-vault-cli/crypto-vault-cli.go @@ -14,6 +14,7 @@ import ( "github.com/spf13/cobra" cryptography "crypto_vault_service/internal/infrastructure/cryptography" + utils "crypto_vault_service/internal/infrastructure/utils" ) // Encrypts a file using AES and saves the encryption key @@ -37,7 +38,7 @@ func encryptAESCmd(cmd *cobra.Command, args []string) { } // Encrypt the file - plainText, err := cryptography.ReadFile(inputFile) + plainText, err := utils.ReadFile(inputFile) if err != nil { log.Fatalf("Error reading input file: %v\n", err) } @@ -48,7 +49,7 @@ func encryptAESCmd(cmd *cobra.Command, args []string) { } // Save encrypted file - err = cryptography.WriteFile(outputFile, encryptedData) + err = utils.WriteFile(outputFile, encryptedData) if err != nil { log.Fatalf("Error writing encrypted file: %v\n", err) } @@ -56,7 +57,7 @@ func encryptAESCmd(cmd *cobra.Command, args []string) { // Save the AES key to the specified key directory keyFilePath := filepath.Join(keyDir, "encryption_key.bin") - err = cryptography.WriteFile(keyFilePath, key) + err = utils.WriteFile(keyFilePath, key) if err != nil { log.Fatalf("Error writing AES key to file: %v\n", err) } @@ -81,7 +82,7 @@ func decryptAESCmd(cmd *cobra.Command, args []string) { } // Decrypt the file - encryptedData, err := cryptography.ReadFile(inputFile) + encryptedData, err := utils.ReadFile(inputFile) if err != nil { log.Fatalf("Error reading encrypted file: %v\n", err) } @@ -94,7 +95,7 @@ func decryptAESCmd(cmd *cobra.Command, args []string) { } // Save decrypted file - err = cryptography.WriteFile(outputFile, decryptedData) + err = utils.WriteFile(outputFile, decryptedData) if err != nil { log.Fatalf("Error writing decrypted file: %v\n", err) } @@ -139,7 +140,7 @@ func encryptRSACmd(cmd *cobra.Command, args []string) { } // Encrypt the file - plainText, err := cryptography.ReadFile(inputFile) + plainText, err := utils.ReadFile(inputFile) if err != nil { log.Fatalf("Error reading input file: %v\n", err) } @@ -150,7 +151,7 @@ func encryptRSACmd(cmd *cobra.Command, args []string) { } // Save encrypted file - err = cryptography.WriteFile(outputFile, encryptedData) + err = utils.WriteFile(outputFile, encryptedData) if err != nil { log.Fatalf("Error writing encrypted file: %v\n", err) } @@ -189,7 +190,7 @@ func decryptRSACmd(cmd *cobra.Command, args []string) { } // Decrypt the file - encryptedData, err := cryptography.ReadFile(inputFile) + encryptedData, err := utils.ReadFile(inputFile) if err != nil { log.Fatalf("Error reading encrypted file: %v\n", err) } @@ -200,7 +201,7 @@ func decryptRSACmd(cmd *cobra.Command, args []string) { } // Save decrypted file - err = cryptography.WriteFile(outputFile, decryptedData) + err = utils.WriteFile(outputFile, decryptedData) if err != nil { log.Fatalf("Error writing decrypted file: %v\n", err) } @@ -226,7 +227,7 @@ func signECCCmd(cmd *cobra.Command, args []string) { } // Read the file content - fileContent, err := cryptography.ReadFile(inputFile) + fileContent, err := utils.ReadFile(inputFile) if err != nil { log.Fatalf("Error reading input file: %v\n", err) } @@ -292,7 +293,7 @@ func verifyECCCmd(cmd *cobra.Command, args []string) { } // Read the file content (optional: you can also hash the content before verifying) - fileContent, err := cryptography.ReadFile(inputFile) + fileContent, err := utils.ReadFile(inputFile) if err != nil { log.Fatalf("Error reading input file: %v\n", err) } diff --git a/cmd/crypto-vault-service/README.md b/cmd/crypto-vault-service/README.md new file mode 100644 index 0000000..3a14dc9 --- /dev/null +++ b/cmd/crypto-vault-service/README.md @@ -0,0 +1,4 @@ +# crypto-vault-service + +TBD + diff --git a/go.mod b/go.mod index 0532410..0906c4a 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/bytedance/sonic/loader v0.2.1 // indirect github.com/cloudwego/base64x v0.1.4 // indirect github.com/cloudwego/iasm v0.2.0 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/gabriel-vasile/mimetype v1.4.6 // indirect github.com/gin-contrib/sse v0.1.0 // indirect @@ -26,6 +27,7 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pelletier/go-toml/v2 v2.2.3 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect @@ -34,6 +36,7 @@ require ( github.com/spf13/cobra v1.8.1 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/viper v1.19.0 // indirect + github.com/stretchr/testify v1.9.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.12 // indirect diff --git a/go.sum b/go.sum index fab9a15..9cc2a1d 100644 --- a/go.sum +++ b/go.sum @@ -10,6 +10,8 @@ github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQ github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/gabriel-vasile/mimetype v1.4.6 h1:3+PzJTKLkvgjeTbts6msPJt4DixhT4YtFNf1gtGe3zc= @@ -53,6 +55,8 @@ github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjY github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= @@ -78,6 +82,7 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= diff --git a/internal/infrastructure/connector/az_blob.go b/internal/infrastructure/connector/az_blob.go index e69de29..9e29bcb 100644 --- a/internal/infrastructure/connector/az_blob.go +++ b/internal/infrastructure/connector/az_blob.go @@ -0,0 +1 @@ +package connector diff --git a/internal/infrastructure/connector/az_postgres.go b/internal/infrastructure/connector/az_postgres.go index e69de29..9e29bcb 100644 --- a/internal/infrastructure/connector/az_postgres.go +++ b/internal/infrastructure/connector/az_postgres.go @@ -0,0 +1 @@ +package connector diff --git a/internal/infrastructure/connector/az_vault.go b/internal/infrastructure/connector/az_vault.go index e69de29..9e29bcb 100644 --- a/internal/infrastructure/connector/az_vault.go +++ b/internal/infrastructure/connector/az_vault.go @@ -0,0 +1 @@ +package connector diff --git a/internal/infrastructure/cryptography/aes.go b/internal/infrastructure/cryptography/aes.go index c5dc0fe..dc13f4e 100644 --- a/internal/infrastructure/cryptography/aes.go +++ b/internal/infrastructure/cryptography/aes.go @@ -48,6 +48,10 @@ func (a *AESImpl) GenerateKey(keySize int) ([]byte, error) { // Encrypt data using AES in CBC mode func (a *AESImpl) Encrypt(plainText, key []byte) ([]byte, error) { + if key == nil { + return nil, fmt.Errorf("key key cannot be nil") + } + block, err := aes.NewCipher(key) if err != nil { return nil, err @@ -69,6 +73,10 @@ func (a *AESImpl) Encrypt(plainText, key []byte) ([]byte, error) { // Decrypt data using AES in CBC mode func (a *AESImpl) Decrypt(ciphertext, key []byte) ([]byte, error) { + if key == nil { + return nil, fmt.Errorf("key key cannot be nil") + } + block, err := aes.NewCipher(key) if err != nil { return nil, err diff --git a/internal/infrastructure/cryptography/ecdsa.go b/internal/infrastructure/cryptography/ecdsa.go index e7079cb..4e7bf43 100644 --- a/internal/infrastructure/cryptography/ecdsa.go +++ b/internal/infrastructure/cryptography/ecdsa.go @@ -41,6 +41,15 @@ func (e *ECDSAImpl) GenerateKeys(curve elliptic.Curve) (*ecdsa.PrivateKey, *ecds // Sign signs a message with the private key func (e *ECDSAImpl) Sign(message []byte, privateKey *ecdsa.PrivateKey) ([]byte, error) { + if privateKey == nil { + return nil, fmt.Errorf("private key cannot be nil") + } + + // Check if the private key is valid (D should not be zero) + if privateKey.D.Sign() == 0 { + return nil, fmt.Errorf("invalid private key: D cannot be zero") + } + // Hash the message before signing it hash := sha256.Sum256(message) r, s, err := ecdsa.Sign(rand.Reader, privateKey, hash[:]) @@ -55,6 +64,10 @@ func (e *ECDSAImpl) Sign(message []byte, privateKey *ecdsa.PrivateKey) ([]byte, // Verify verifies the signature of a message with the public key func (e *ECDSAImpl) Verify(message, signature []byte, publicKey *ecdsa.PublicKey) (bool, error) { + if publicKey == nil { + return false, fmt.Errorf("public key cannot be nil") + } + // Hash the message before verifying it hash := sha256.Sum256(message) diff --git a/internal/infrastructure/cryptography/rsa.go b/internal/infrastructure/cryptography/rsa.go index e898fb9..0ff6fba 100644 --- a/internal/infrastructure/cryptography/rsa.go +++ b/internal/infrastructure/cryptography/rsa.go @@ -5,6 +5,7 @@ import ( "crypto/rsa" "crypto/x509" "encoding/pem" + "errors" "fmt" "io/ioutil" "os" @@ -36,6 +37,10 @@ func (r *RSAImpl) GenerateKeys(bits int) (*rsa.PrivateKey, *rsa.PublicKey, error // Encrypt data using RSA public key func (r *RSAImpl) Encrypt(plainText []byte, publicKey *rsa.PublicKey) ([]byte, error) { + if publicKey == nil { + return nil, errors.New("public key cannot be nil") + } + encryptedData, err := rsa.EncryptPKCS1v15(rand.Reader, publicKey, plainText) if err != nil { return nil, fmt.Errorf("failed to encrypt data: %v", err) @@ -45,6 +50,10 @@ func (r *RSAImpl) Encrypt(plainText []byte, publicKey *rsa.PublicKey) ([]byte, e // Decrypt data using RSA private key func (r *RSAImpl) Decrypt(ciphertext []byte, privateKey *rsa.PrivateKey) ([]byte, error) { + if privateKey == nil { + return nil, fmt.Errorf("private key cannot be nil") + } + decryptedData, err := rsa.DecryptPKCS1v15(rand.Reader, privateKey, ciphertext) if err != nil { return nil, fmt.Errorf("failed to decrypt data: %v", err) diff --git a/internal/infrastructure/cryptography/io.go b/internal/infrastructure/utils/io.go similarity index 86% rename from internal/infrastructure/cryptography/io.go rename to internal/infrastructure/utils/io.go index 6aa0b25..0d1cc5c 100644 --- a/internal/infrastructure/cryptography/io.go +++ b/internal/infrastructure/utils/io.go @@ -1,4 +1,4 @@ -package cryptography +package utils import ( "io/ioutil" diff --git a/scripts/run-test.sh b/scripts/run-test.sh new file mode 100644 index 0000000..5d251de --- /dev/null +++ b/scripts/run-test.sh @@ -0,0 +1,48 @@ +#!/bin/bash + +set -euo pipefail + +SCRIPT_DIR=$(dirname "$BASH_SOURCE") +ROOT_PROJECT_DIR=$SCRIPT_DIR/.. + +cd $ROOT_PROJECT_DIR + +BLUE='\033[0;34m' +NC='\033[0m' + +# Default flag values +RUN_UNIT_TESTS=true +RUN_INTEGRATION_TESTS=true + +# Parse arguments +while getopts "ui" opt; do + case ${opt} in + u) + RUN_UNIT_TESTS=true + RUN_INTEGRATION_TESTS=false + ;; + i) + RUN_UNIT_TESTS=false + RUN_INTEGRATION_TESTS=true + ;; + *) + echo "Usage: $0 [-u] (for unit tests) [-i] (for integration tests)" + exit 1 + ;; + esac +done + +echo "#####################################################################################################" +echo -e "$BLUE INFO: $NC About to run tests based on the flags" + +if [ "$RUN_UNIT_TESTS" = true ]; then + echo -e "$BLUE INFO: $NC Running unit tests..." + go test ./test/unit/... +fi + +if [ "$RUN_INTEGRATION_TESTS" = true ]; then + echo -e "$BLUE INFO: $NC Running integration tests..." + go test ./test/integration/... +fi + +cd $SCRIPT_DIR diff --git a/test/unit/infrastructure/cryptography/io_test.go b/test/integration/infrastructure/utils/io_test.go similarity index 100% rename from test/unit/infrastructure/cryptography/io_test.go rename to test/integration/infrastructure/utils/io_test.go diff --git a/test/unit/infrastructure/connector/az_blob_test.go b/test/unit/infrastructure/connector/az_blob_test.go index e69de29..9e29bcb 100644 --- a/test/unit/infrastructure/connector/az_blob_test.go +++ b/test/unit/infrastructure/connector/az_blob_test.go @@ -0,0 +1 @@ +package connector diff --git a/test/unit/infrastructure/connector/az_postgres_test.go b/test/unit/infrastructure/connector/az_postgres_test.go index e69de29..9e29bcb 100644 --- a/test/unit/infrastructure/connector/az_postgres_test.go +++ b/test/unit/infrastructure/connector/az_postgres_test.go @@ -0,0 +1 @@ +package connector diff --git a/test/unit/infrastructure/connector/az_vault_test.go b/test/unit/infrastructure/connector/az_vault_test.go index e69de29..9e29bcb 100644 --- a/test/unit/infrastructure/connector/az_vault_test.go +++ b/test/unit/infrastructure/connector/az_vault_test.go @@ -0,0 +1 @@ +package connector diff --git a/test/unit/infrastructure/cryptography/aes_test.go b/test/unit/infrastructure/cryptography/aes_test.go index ba9890f..1f4c575 100644 --- a/test/unit/infrastructure/cryptography/aes_test.go +++ b/test/unit/infrastructure/cryptography/aes_test.go @@ -1 +1,113 @@ -// tbd \ No newline at end of file +package cryptography + +import ( + "testing" + + cryptography "crypto_vault_service/internal/infrastructure/cryptography" + + "github.com/stretchr/testify/assert" +) + +// AES struct to encapsulate AES-related test cases +type AESTests struct { + aesImpl *cryptography.AESImpl +} + +// NewAESTests is a constructor that creates a new instance of AESTests +func NewAESTests() *AESTests { + return &AESTests{ + aesImpl: &cryptography.AESImpl{}, + } +} + +// TestEncryptDecrypt tests the encryption and decryption functionality +func (at *AESTests) TestEncryptDecrypt(t *testing.T) { + // Generate a random key of 16 bytes (128-bit AES) + key, err := at.aesImpl.GenerateKey(16) + assert.NoError(t, err) + + // Define a plaintext to encrypt and decrypt + plainText := []byte("This is a test message.") + + // Encrypt the plaintext + ciphertext, err := at.aesImpl.Encrypt(plainText, key) + assert.NoError(t, err) + assert.NotNil(t, ciphertext) + assert.Greater(t, len(ciphertext), 0, "Ciphertext should be longer than 0") + + // Decrypt the ciphertext + decryptedText, err := at.aesImpl.Decrypt(ciphertext, key) + assert.NoError(t, err) + assert.NotNil(t, decryptedText) + + // Assert the decrypted text is the same as the original plaintext + assert.Equal(t, plainText, decryptedText) +} + +// TestEncryptionWithInvalidKey tests encryption with invalid key sizes +func (at *AESTests) TestEncryptionWithInvalidKey(t *testing.T) { + // Try generating an invalid key (e.g., 8 bytes instead of a standard AES size) + key := []byte("shortkey") + plainText := []byte("This is a test.") + + // Try encrypting with an invalid key + _, err := at.aesImpl.Encrypt(plainText, key) + assert.Error(t, err) +} + +// TestGenerateKey tests key generation functionality +func (at *AESTests) TestGenerateKey(t *testing.T) { + // Generate a random AES key with 16 bytes (128-bit AES) + key, err := at.aesImpl.GenerateKey(16) + assert.NoError(t, err) + assert.Equal(t, len(key), 16) + + // Try generating a 32-byte AES key (256-bit AES) + key256, err := at.aesImpl.GenerateKey(32) + assert.NoError(t, err) + assert.Equal(t, len(key256), 32) +} + +// TestDecryptWithWrongKey tests decryption with a wrong key +func (at *AESTests) TestDecryptWithWrongKey(t *testing.T) { + // Generate a random 16-byte AES key + key, err := at.aesImpl.GenerateKey(16) + assert.NoError(t, err) + + // Encrypt the data + plainText := []byte("Test decryption with wrong key.") + ciphertext, err := at.aesImpl.Encrypt(plainText, key) + assert.NoError(t, err) + + // Generate a new, different key for decryption + anotherKey, err := at.aesImpl.GenerateKey(16) + assert.NoError(t, err) + + // Try to decrypt with the wrong key + _, err = at.aesImpl.Decrypt(ciphertext, anotherKey) + assert.Error(t, err, "Decryption with the wrong key should fail") +} + +// TestDecryptShortCiphertext tests the case where the ciphertext is too short +func (at *AESTests) TestDecryptShortCiphertext(t *testing.T) { + // Generate a random key + key, err := at.aesImpl.GenerateKey(16) + assert.NoError(t, err) + + // Attempt to decrypt a too-short ciphertext + _, err = at.aesImpl.Decrypt([]byte("short"), key) + assert.Error(t, err, "Decrypting a ciphertext that's too short should fail") +} + +// TestAES is the entry point to run the AES tests +func TestAES(t *testing.T) { + // Create a new AES test suite instance + at := NewAESTests() + + // Run each test method + t.Run("TestEncryptDecrypt", at.TestEncryptDecrypt) + t.Run("TestEncryptionWithInvalidKey", at.TestEncryptionWithInvalidKey) + t.Run("TestGenerateKey", at.TestGenerateKey) + t.Run("TestDecryptWithWrongKey", at.TestDecryptWithWrongKey) + t.Run("TestDecryptShortCiphertext", at.TestDecryptShortCiphertext) +} diff --git a/test/unit/infrastructure/cryptography/ecdsa_test.go b/test/unit/infrastructure/cryptography/ecdsa_test.go index e69de29..d994939 100644 --- a/test/unit/infrastructure/cryptography/ecdsa_test.go +++ b/test/unit/infrastructure/cryptography/ecdsa_test.go @@ -0,0 +1,184 @@ +package cryptography + +import ( + "crypto/ecdsa" + "crypto/elliptic" + cryptography "crypto_vault_service/internal/infrastructure/cryptography" + "io/ioutil" + "math/big" + "os" + "testing" + + "encoding/hex" + + "github.com/stretchr/testify/assert" +) + +type ECDSATests struct { + ecc *cryptography.ECDSAImpl +} + +// NewECDSATests is a constructor that creates a new instance of ECDSATests +func NewECDSATests() *ECDSATests { + return &ECDSATests{ + ecc: &cryptography.ECDSAImpl{}, + } +} + +// TestGenerateKeys tests the key generation functionality +func (et *ECDSATests) TestGenerateKeys(t *testing.T) { + // Generate ECDSA keys using P256 curve + privateKey, publicKey, err := et.ecc.GenerateKeys(elliptic.P256()) + assert.NoError(t, err) + assert.NotNil(t, privateKey) + assert.NotNil(t, publicKey) + assert.Equal(t, elliptic.P256(), privateKey.PublicKey.Curve) + assert.Equal(t, elliptic.P256(), publicKey.Curve) +} + +// TestSignVerify tests signing and verifying functionality +func (et *ECDSATests) TestSignVerify(t *testing.T) { + // Generate ECDSA keys + privateKey, publicKey, err := et.ecc.GenerateKeys(elliptic.P256()) + assert.NoError(t, err) + + // Message to sign + message := []byte("This is a test message.") + + // Sign the message + signature, err := et.ecc.Sign(message, privateKey) + assert.NoError(t, err) + assert.NotNil(t, signature) + + // Verify the signature + valid, err := et.ecc.Verify(message, signature, publicKey) + assert.NoError(t, err) + assert.True(t, valid, "The signature should be valid") + + // Modify the message and try verifying the signature + modifiedMessage := []byte("This is a modified message.") + valid, err = et.ecc.Verify(modifiedMessage, signature, publicKey) + assert.NoError(t, err) + assert.False(t, valid, "The signature should not be valid for a modified message") +} + +// TestSaveAndReadKeys tests saving and reading the private and public keys from PEM files +func (et *ECDSATests) TestSaveAndReadKeys(t *testing.T) { + // Generate ECDSA keys + privateKey, publicKey, err := et.ecc.GenerateKeys(elliptic.P256()) + assert.NoError(t, err) + + // Save private and public keys to files + privateKeyFile := "private.pem" + publicKeyFile := "public.pem" + err = et.ecc.SavePrivateKeyToFile(privateKey, privateKeyFile) + assert.NoError(t, err) + + err = et.ecc.SavePublicKeyToFile(publicKey, publicKeyFile) + assert.NoError(t, err) + + // Read the private and public keys from the files + readPrivateKey, err := et.ecc.ReadPrivateKey(privateKeyFile) + assert.NoError(t, err) + assert.Equal(t, privateKey.D, readPrivateKey.D) + assert.Equal(t, privateKey.PublicKey.X, readPrivateKey.PublicKey.X) + assert.Equal(t, privateKey.PublicKey.Y, readPrivateKey.PublicKey.Y) + + readPublicKey, err := et.ecc.ReadPublicKey(publicKeyFile) + assert.NoError(t, err) + assert.Equal(t, publicKey.X, readPublicKey.X) + assert.Equal(t, publicKey.Y, readPublicKey.Y) + + // Clean up the generated files + os.Remove(privateKeyFile) + os.Remove(publicKeyFile) +} + +// TestSaveSignatureToFile tests saving a signature to a file +func (et *ECDSATests) TestSaveSignatureToFile(t *testing.T) { + // Generate ECDSA keys + privateKey, _, err := et.ecc.GenerateKeys(elliptic.P256()) + assert.NoError(t, err) + + // Message to sign + message := []byte("This is a test message.") + + // Sign the message + signature, err := et.ecc.Sign(message, privateKey) + assert.NoError(t, err) + assert.NotNil(t, signature) + + // Save the signature to a file + signatureFile := "signature.hex" + err = et.ecc.SaveSignatureToFile(signatureFile, signature) + assert.NoError(t, err) + + // Read the saved signature from the file + hexData, err := ioutil.ReadFile(signatureFile) + assert.NoError(t, err) + + // Decode the hex signature + decodedSignature, err := hex.DecodeString(string(hexData)) + assert.NoError(t, err) + assert.Equal(t, signature, decodedSignature) + + // Clean up the generated signature file + os.Remove(signatureFile) +} + +// TestSignWithInvalidPrivateKey tests signing with an invalid private key +func (et *ECDSATests) TestSignWithInvalidPrivateKey(t *testing.T) { + // Generate ECDSA keys (valid ones) + _, _, err := et.ecc.GenerateKeys(elliptic.P256()) + assert.NoError(t, err) + + // Modify the private key to make it invalid (e.g., set D to 0) + invalidPrivateKey := &ecdsa.PrivateKey{ + D: new(big.Int).SetInt64(0), // Invalid key with D = 0 + PublicKey: ecdsa.PublicKey{ + Curve: elliptic.P256(), + }, + } + + // Attempt to sign a message with the invalid private key + message := []byte("This message will fail to sign") + _, err = et.ecc.Sign(message, invalidPrivateKey) + assert.Error(t, err, "Signing with an invalid private key should fail") +} + +// TestVerifyWithInvalidPublicKey tests verifying with an invalid public key +func (et *ECDSATests) TestVerifyWithInvalidPublicKey(t *testing.T) { + // Generate ECDSA keys + privateKey, _, err := et.ecc.GenerateKeys(elliptic.P256()) + assert.NoError(t, err) + + // Sign the message + message := []byte("This is a test message.") + signature, err := et.ecc.Sign(message, privateKey) + assert.NoError(t, err) + + // Create an invalid public key (e.g., public key X = 0) + invalidPublicKey := &ecdsa.PublicKey{ + Curve: elliptic.P256(), + X: new(big.Int).SetInt64(0), + Y: new(big.Int).SetInt64(0), + } + + // Attempt to verify the signature with the invalid public key + valid, err := et.ecc.Verify(message, signature, invalidPublicKey) + assert.NoError(t, err) + assert.False(t, valid, "Verification with an invalid public key should fail") +} + +func TestECDSA(t *testing.T) { + // Create a new ECDSA test suite instance + et := NewECDSATests() + + // Run each test method + t.Run("TestGenerateKeys", et.TestGenerateKeys) + t.Run("TestSignVerify", et.TestSignVerify) + t.Run("TestSaveAndReadKeys", et.TestSaveAndReadKeys) + t.Run("TestSaveSignatureToFile", et.TestSaveSignatureToFile) + t.Run("TestSignWithInvalidPrivateKey", et.TestSignWithInvalidPrivateKey) + t.Run("TestVerifyWithInvalidPublicKey", et.TestVerifyWithInvalidPublicKey) +} diff --git a/test/unit/infrastructure/cryptography/rsa_test.go b/test/unit/infrastructure/cryptography/rsa_test.go index e69de29..741c7fd 100644 --- a/test/unit/infrastructure/cryptography/rsa_test.go +++ b/test/unit/infrastructure/cryptography/rsa_test.go @@ -0,0 +1,144 @@ +package cryptography + +import ( + "crypto/rsa" + cryptography "crypto_vault_service/internal/infrastructure/cryptography" + "os" + "testing" + + "github.com/stretchr/testify/assert" +) + +// RSATests struct to encapsulate RSA-related test cases +type RSATests struct { + rsaImpl *cryptography.RSAImpl +} + +// NewRSATests is a constructor that creates a new instance of RSATests +func NewRSATests() *RSATests { + return &RSATests{ + rsaImpl: &cryptography.RSAImpl{}, + } +} + +// TestGenerateRSAKeys tests the generation of RSA keys +func (rt *RSATests) TestGenerateRSAKeys(t *testing.T) { + // Generate RSA keys with 2048-bit size + privateKey, publicKey, err := rt.rsaImpl.GenerateKeys(2048) + assert.NoError(t, err, "Error generating RSA keys") + assert.NotNil(t, privateKey, "Private key should not be nil") + assert.NotNil(t, publicKey, "Public key should not be nil") + + // Ensure the public key's type is *rsa.PublicKey + assert.IsType(t, &rsa.PublicKey{}, publicKey) + assert.Equal(t, 2048, privateKey.N.BitLen(), "Private key bit length should be 2048") +} + +// TestEncryptDecrypt tests the encryption and decryption methods of RSA +func (rt *RSATests) TestEncryptDecrypt(t *testing.T) { + // Generate RSA keys + privateKey, publicKey, err := rt.rsaImpl.GenerateKeys(2048) + assert.NoError(t, err) + + // Message to encrypt + plainText := []byte("This is a secret message") + + // Encrypt the message + encryptedData, err := rt.rsaImpl.Encrypt(plainText, publicKey) + assert.NoError(t, err, "Error encrypting data") + + // Decrypt the message + decryptedData, err := rt.rsaImpl.Decrypt(encryptedData, privateKey) + assert.NoError(t, err, "Error decrypting data") + + // Ensure the decrypted data matches the original message + assert.Equal(t, plainText, decryptedData, "Decrypted data should match the original plaintext") +} + +// TestSaveAndReadKeys tests saving and reading RSA keys to and from files +func (rt *RSATests) TestSaveAndReadKeys(t *testing.T) { + // Generate RSA keys + privateKey, publicKey, err := rt.rsaImpl.GenerateKeys(2048) + assert.NoError(t, err) + + // Save keys to files + privateKeyFile := "private.pem" + publicKeyFile := "public.pem" + + err = rt.rsaImpl.SavePrivateKeyToFile(privateKey, privateKeyFile) + assert.NoError(t, err, "Error saving private key to file") + + err = rt.rsaImpl.SavePublicKeyToFile(publicKey, publicKeyFile) + assert.NoError(t, err, "Error saving public key to file") + + // Read the keys back from the files + readPrivateKey, err := rt.rsaImpl.ReadPrivateKey(privateKeyFile) + assert.NoError(t, err, "Error reading private key from file") + assert.Equal(t, privateKey.N, readPrivateKey.N, "Private key N component should match") + assert.Equal(t, privateKey.E, readPrivateKey.E, "Private key E component should match") + + readPublicKey, err := rt.rsaImpl.ReadPublicKey(publicKeyFile) + assert.NoError(t, err, "Error reading public key from file") + assert.Equal(t, publicKey.N, readPublicKey.N, "Public key N component should match") + assert.Equal(t, publicKey.E, readPublicKey.E, "Public key E component should match") + + // Clean up the generated files + os.Remove(privateKeyFile) + os.Remove(publicKeyFile) +} + +// TestEncryptWithInvalidKey tests encryption with an invalid public key +func (rt *RSATests) TestEncryptWithInvalidKey(t *testing.T) { + // Generate RSA keys + _, _, err := rt.rsaImpl.GenerateKeys(2048) + assert.NoError(t, err) + + // Attempt to encrypt with a nil public key (invalid case) + plainText := []byte("This should fail encryption") + _, err = rt.rsaImpl.Encrypt(plainText, nil) + assert.Error(t, err, "Encryption should fail with an invalid public key") + + // Attempt to decrypt with a nil private key (invalid case) + _, err = rt.rsaImpl.Decrypt(plainText, nil) + assert.Error(t, err, "Decryption should fail with an invalid private key") + + // Attempt to decrypt with a different private key (invalid case) + _, err = rt.rsaImpl.Decrypt(plainText, &rsa.PrivateKey{}) + assert.Error(t, err, "Decryption should fail with an invalid private key") +} + +// TestSavePrivateKeyInvalidPath tests saving a private key to an invalid path +func (rt *RSATests) TestSavePrivateKeyInvalidPath(t *testing.T) { + // Generate RSA keys + privateKey, _, err := rt.rsaImpl.GenerateKeys(2048) + assert.NoError(t, err) + + // Try saving the private key to an invalid file path + err = rt.rsaImpl.SavePrivateKeyToFile(privateKey, "/invalid/path/private.pem") + assert.Error(t, err, "Saving private key to an invalid path should return an error") +} + +// TestSavePublicKeyInvalidPath tests saving a public key to an invalid path +func (rt *RSATests) TestSavePublicKeyInvalidPath(t *testing.T) { + // Generate RSA keys + _, publicKey, err := rt.rsaImpl.GenerateKeys(2048) + assert.NoError(t, err) + + // Try saving the public key to an invalid file path + err = rt.rsaImpl.SavePublicKeyToFile(publicKey, "/invalid/path/public.pem") + assert.Error(t, err, "Saving public key to an invalid path should return an error") +} + +// TestRSA is the entry point to run the RSA tests +func TestRSA(t *testing.T) { + // Create a new RSA test suite instance + rt := NewRSATests() + + // Run each test method + t.Run("TestGenerateRSAKeys", rt.TestGenerateRSAKeys) + t.Run("TestEncryptDecrypt", rt.TestEncryptDecrypt) + t.Run("TestSaveAndReadKeys", rt.TestSaveAndReadKeys) + t.Run("TestEncryptWithInvalidKey", rt.TestEncryptWithInvalidKey) + t.Run("TestSavePrivateKeyInvalidPath", rt.TestSavePrivateKeyInvalidPath) + t.Run("TestSavePublicKeyInvalidPath", rt.TestSavePublicKeyInvalidPath) +} diff --git a/test/unit/infrastructure/utils/io_test.go b/test/unit/infrastructure/utils/io_test.go new file mode 100644 index 0000000..b360ea7 --- /dev/null +++ b/test/unit/infrastructure/utils/io_test.go @@ -0,0 +1,45 @@ +package utils + +import ( + "os" + "testing" + + utils "crypto_vault_service/internal/infrastructure/utils" + + "github.com/stretchr/testify/assert" +) + +// TestWriteFileAndReadFile tests the WriteFile and ReadFile functions +func TestWriteFileAndReadFile(t *testing.T) { + // Prepare a test file path + testFilePath := "testfile.txt" + testData := []byte("This is a test message.") + + // Write data to the file + err := utils.WriteFile(testFilePath, testData) + assert.NoError(t, err, "Error writing to file") + + // Read data from the file + readData, err := utils.ReadFile(testFilePath) + assert.NoError(t, err, "Error reading from file") + + // Ensure that the written and read data are the same + assert.Equal(t, testData, readData, "The data read from the file should match the data written") + + // Clean up the test file + err = os.Remove(testFilePath) + assert.NoError(t, err, "Error cleaning up the test file") +} + +// TestReadFileWithNonExistentFile tests the ReadFile function with a non-existent file +func TestReadFileWithNonExistentFile(t *testing.T) { + // Use a file path that doesn't exist + testFilePath := "non_existent_file.txt" + + // Attempt to read from the non-existent file + data, err := utils.ReadFile(testFilePath) + + // We expect an error because the file does not exist + assert.Error(t, err, "Reading from a non-existent file should return an error") + assert.Nil(t, data, "No data should be returned for a non-existent file") +}