diff --git a/cmd/crypto-vault-cli/README.md b/cmd/crypto-vault-cli/README.md index 09130f5..f4660f2 100644 --- a/cmd/crypto-vault-cli/README.md +++ b/cmd/crypto-vault-cli/README.md @@ -4,14 +4,9 @@ - [Summary](#summary) - [Getting Started](#getting-started) - - [Encryption and Decryption](#encryption-and-decryption) - - [AES Example](#aes-example) - - [RSA Example](#rsa-example) - - [PKCS#11 Encryption and Decryption](#pkcs11-encryption-and-decryption) - - [Signing and Verifying Signatures](#signing-and-verifying-signatures) - - [ECDSA Example](#ecdsa-example) - - [PKCS#11 Signing and Verifying](#pkcs11-signing-and-verifying) - - [PKCS#11 key management operations](#pkcs11-key-management-operations) + - [AES Example](#aes-example) + - [RSA Example](#rsa-example) + - [PKCS#11 Example](#pkcs11-example) - [Running the e2e-test](#running-the-e2e-test) @@ -21,86 +16,55 @@ ## Getting Started -### Encryption and Decryption - -#### AES example - -*NOTE:* Keys will be generated internally during the encryption operations. +### AES example ```sh uuid=$(cat /proc/sys/kernel/random/uuid) +# Generate AES keys +go run crypto_vault_cli.go generate-aes-keys --key-size 16 --key-dir data/ # Encryption -go run crypto_vault_cli.go encrypt-aes --input-file data/input.txt --output-file data/${uuid}-output.enc --key-size 16 --key-dir data/ +go run crypto_vault_cli.go encrypt-aes --input-file data/input.txt --output-file data/${uuid}-output.enc --symmetric-key # Decryption -go run crypto_vault_cli.go decrypt-aes --input-file data/${uuid}-output.enc --output-file data/${uuid}-decrypted.txt --symmetric-key +go run crypto_vault_cli.go decrypt-aes --input-file data/${uuid}-output.enc --output-file data/${uuid}-decrypted.txt --symmetric-key ``` -#### RSA Example - -*NOTE:* Keys will be generated internally during the encryption operations. +### RSA Example ```sh uuid=$(cat /proc/sys/kernel/random/uuid) -# Encryption -go run crypto_vault_cli.go encrypt-rsa --input-file data/input.txt --output-file data/${uuid}-encrypted.txt --key-dir data/ - -# Decryption -go run crypto_vault_cli.go decrypt-rsa --input-file data/${uuid}-encrypted.txt --output-file data/${uuid}-decrypted.txt --private-key -``` - -#### PKCS#11 encryption and decryption +# Generate RSA keys +go run crypto_vault_cli.go generate-rsa-keys --key-size 2048 --key-dir data/ -*NOTE:* Requires RSA keys managed in FIPS-compliant software or hardware trough `pkcs11-tool` or utilize commands in [PKCS#11 key management operations](#pkcs11-key-management-operations): - -```sh -# RSA-PKCS # Encryption -go run crypto_vault_cli.go encrypt --token-label my-token --object-label my-rsa-key --key-type RSA --input-file data/input.txt --output-file data/encrypted-output.enc +go run crypto_vault_cli.go encrypt-rsa --input-file data/input.txt --output-file data/${uuid}-encrypted.txt --public-key # Decryption -go run crypto_vault_cli.go decrypt --token-label my-token --object-label my-rsa-key --key-type RSA --input-file data/encrypted-output.enc --output-file data/decrypted-output.txt -``` - ---- +go run crypto_vault_cli.go decrypt-rsa --input-file data/${uuid}-encrypted.txt --output-file data/${uuid}-decrypted.txt --private-key -### Signing and Verifying signatures +# Sign +go run crypto_vault_cli.go sign-rsa --input-file data/input.txt --output-file data/${uuid}-signature.bin --private-key -#### ECDSA Example - -*NOTE:* Keys will be generated internally during signature generation operations. - -```sh -# Sign a file with a newly generated ECC key pair (internally generated) -go run crypto_vault_cli.go sign-ecc --input-file data/input.txt --key-dir data - -# Verify the signature using the generated public key -go run crypto_vault_cli.go verify-ecc --input-file data/input.txt --public-key --signature-file +# Verify +go run crypto_vault_cli.go verify-rsa --input-file data/input.txt --signature-file data/${uuid}-signature.bin --public-key ``` -#### PKCS#11 signing and verifying - -*NOTE:* Requires RSA or EC keys managed in FIPS-compliant software or hardware trough `pkcs11-tool` or utilize commands in [PKCS#11 key management operations](#pkcs11-key-management-operations): +### ECDSA Example ```sh -# RSA-PSS -# Sign data with a PKCS#11 token -go run crypto_vault_cli.go sign --token-label my-token --object-label my-rsa-key --key-type RSA --data-file data/input.txt --signature-file data/signature.sig +uuid=$(cat /proc/sys/kernel/random/uuid) -# Verify the signature using the generated public key from the PKCS#11 token -go run crypto_vault_cli.go verify --token-label my-token --object-label my-rsa-key --key-type RSA --data-file data/input.txt --signature-file data/signature.sig +# Generate ECC keys +go run crypto_vault_cli.go generate-ecc-keys --key-size 256 --key-dir data/ -# ECDSA -# Sign data with a PKCS#11 token -go run crypto_vault_cli.go sign --token-label my-token --object-label my-ecdsa-key --key-type ECDSA --data-file data/input.txt --signature-file data/signature.sig +# Sign a file with a newly generated ECC key pair (internally generated) +go run crypto_vault_cli.go sign-ecc --input-file data/input.txt --output-file data/${uuid}-signature.bin --private-key -# Verify the signature using the generated public key from the PKCS#11 token -go run crypto_vault_cli.go verify --token-label my-token --object-label my-ecdsa-key --key-type ECDSA --data-file data/input.txt --signature-file data/signature.sig +# Verify the signature using the generated public key +go run crypto_vault_cli.go verify-ecc --input-file data/input.txt --signature-file data/${uuid}-signature.bin --public-key ``` ---- - -### PKCS#11 key management operations +### PKCS#11 example ```sh # Configure settings @@ -125,6 +89,27 @@ go run crypto_vault_cli.go list-objects --token-label "my-token" # Delete an object (e.g., RSA or EC key) from the PKCS#11 token go run crypto_vault_cli.go delete-object --token-label my-token --object-label my-rsa-key --object-type pubkey go run crypto_vault_cli.go delete-object --token-label my-token --object-label my-rsa-key --object-type privkey + +# RSA-PKCS +# Encryption +go run crypto_vault_cli.go encrypt --token-label my-token --object-label my-rsa-key --key-type RSA --input-file data/input.txt --output-file data/encrypted-output.enc + +# Decryption +go run crypto_vault_cli.go decrypt --token-label my-token --object-label my-rsa-key --key-type RSA --input-file data/encrypted-output.enc --output-file data/decrypted-output.txt + +# RSA-PSS +# Sign data with a PKCS#11 token +go run crypto_vault_cli.go sign --token-label my-token --object-label my-rsa-key --key-type RSA --data-file data/input.txt --signature-file data/signature.sig + +# Verify the signature using the generated public key from the PKCS#11 token +go run crypto_vault_cli.go verify --token-label my-token --object-label my-rsa-key --key-type RSA --data-file data/input.txt --signature-file data/signature.sig + +# ECDSA +# Sign data with a PKCS#11 token +go run crypto_vault_cli.go sign --token-label my-token --object-label my-ecdsa-key --key-type ECDSA --data-file data/input.txt --signature-file data/signature.sig + +# Verify the signature using the generated public key from the PKCS#11 token +go run crypto_vault_cli.go verify --token-label my-token --object-label my-ecdsa-key --key-type ECDSA --data-file data/input.txt --signature-file data/signature.sig ``` ## Running the e2e-test diff --git a/cmd/crypto-vault-cli/crypto_vault_cli_test.go b/cmd/crypto-vault-cli/crypto_vault_cli_test.go index 140fc9f..53c540f 100644 --- a/cmd/crypto-vault-cli/crypto_vault_cli_test.go +++ b/cmd/crypto-vault-cli/crypto_vault_cli_test.go @@ -22,13 +22,13 @@ func runCommand(t *testing.T, cmd string, args []string) (string, error) { return out.String(), nil } -func TestEncryptionAndDecryption(t *testing.T) { +func TestAESEncryptionAndDecryption(t *testing.T) { uuid := "test-uuid-1234" inputFile := "data/input.txt" encOutputFile := fmt.Sprintf("data/%s-output.enc", uuid) cmdEncrypt := "go" - argsEncrypt := []string{"run", "crypto_vault_cli.go", "encrypt-aes", "--input-file", inputFile, "--output-file", encOutputFile, "--key-size", "16", "--key-dir", "data/"} + argsEncrypt := []string{"run", "crypto_vault_cli.go", "encrypt-aes", "--input-file", inputFile, "--output-file", encOutputFile, "--symmetric-key", "your-generated-symmetric-key"} _, err := runCommand(t, cmdEncrypt, argsEncrypt) if err != nil { @@ -51,7 +51,7 @@ func TestRSAEncryptionAndDecryption(t *testing.T) { encOutputFile := fmt.Sprintf("data/%s-encrypted.txt", uuid) cmdEncryptRSA := "go" - argsEncryptRSA := []string{"run", "crypto_vault_cli.go", "encrypt-rsa", "--input-file", inputFile, "--output-file", encOutputFile, "--key-dir", "data/"} + argsEncryptRSA := []string{"run", "crypto_vault_cli.go", "encrypt-rsa", "--input-file", inputFile, "--output-file", encOutputFile, "--public-key", "your-generated-public-key"} _, err := runCommand(t, cmdEncryptRSA, argsEncryptRSA) if err != nil { @@ -68,48 +68,50 @@ func TestRSAEncryptionAndDecryption(t *testing.T) { } } -func TestPKCS11EncryptionAndDecryption(t *testing.T) { - uuid := "test-uuid-pkcs11" +func TestRSASignAndVerify(t *testing.T) { + uuid := "test-uuid-5678" inputFile := "data/input.txt" + signatureFile := fmt.Sprintf("data/%s-signature.bin", uuid) - encOutputFile := fmt.Sprintf("data/%s-encrypted-output.enc", uuid) - cmdEncryptPKCS11 := "go" - argsEncryptPKCS11 := []string{ - "run", "crypto_vault_cli.go", "encrypt", "--token-label", "my-token", "--object-label", "my-rsa-key", "--key-type", "RSA", "--input-file", inputFile, "--output-file", encOutputFile, - } + // Sign + cmdSignRSA := "go" + argsSignRSA := []string{"run", "crypto_vault_cli.go", "sign-rsa", "--input-file", inputFile, "--output-file", signatureFile, "--private-key", "your-generated-private-key"} - _, err := runCommand(t, cmdEncryptPKCS11, argsEncryptPKCS11) + _, err := runCommand(t, cmdSignRSA, argsSignRSA) if err != nil { - t.Fatalf("PKCS11 Encryption failed: %v", err) + t.Fatalf("RSA Signing failed: %v", err) } - decOutputFile := fmt.Sprintf("data/%s-decrypted-output.txt", uuid) - cmdDecryptPKCS11 := "go" - argsDecryptPKCS11 := []string{ - "run", "crypto_vault_cli.go", "decrypt", "--token-label", "my-token", "--object-label", "my-rsa-key", "--key-type", "RSA", "--input-file", encOutputFile, "--output-file", decOutputFile, + // Verify + cmdVerifyRSA := "go" + argsVerifyRSA := []string{ + "run", "crypto_vault_cli.go", "verify-rsa", "--input-file", inputFile, "--signature-file", signatureFile, "--public-key", "your-generated-public-key", } - _, err = runCommand(t, cmdDecryptPKCS11, argsDecryptPKCS11) + _, err = runCommand(t, cmdVerifyRSA, argsVerifyRSA) if err != nil { - t.Fatalf("PKCS11 Decryption failed: %v", err) + t.Fatalf("RSA Verification failed: %v", err) } } func TestSigningAndVerificationECDSA(t *testing.T) { + uuid := "test-uuid-ecc" inputFile := "data/input.txt" - signatureFile := "data/signature.sig" + signatureFile := fmt.Sprintf("data/%s-signature.bin", uuid) + // Sign cmdSignECDSA := "go" - argsSignECDSA := []string{"run", "crypto_vault_cli.go", "sign-ecc", "--input-file", inputFile, "--key-dir", "data"} + argsSignECDSA := []string{"run", "crypto_vault_cli.go", "sign-ecc", "--input-file", inputFile, "--output-file", signatureFile, "--private-key", "your-generated-private-key"} _, err := runCommand(t, cmdSignECDSA, argsSignECDSA) if err != nil { t.Fatalf("ECDSA Signing failed: %v", err) } + // Verify cmdVerifyECDSA := "go" argsVerifyECDSA := []string{ - "run", "crypto_vault_cli.go", "verify-ecc", "--input-file", inputFile, "--public-key", "your-generated-public-key", "--signature-file", signatureFile, + "run", "crypto_vault_cli.go", "verify-ecc", "--input-file", inputFile, "--signature-file", signatureFile, "--public-key", "your-generated-public-key", } _, err = runCommand(t, cmdVerifyECDSA, argsVerifyECDSA) @@ -118,8 +120,36 @@ func TestSigningAndVerificationECDSA(t *testing.T) { } } +func TestPKCS11EncryptionAndDecryption(t *testing.T) { + uuid := "test-uuid-pkcs11" + inputFile := "data/input.txt" + + encOutputFile := fmt.Sprintf("data/%s-encrypted-output.enc", uuid) + cmdEncryptPKCS11 := "go" + argsEncryptPKCS11 := []string{ + "run", "crypto_vault_cli.go", "encrypt", "--token-label", "my-token", "--object-label", "my-rsa-key", "--key-type", "RSA", "--input-file", inputFile, "--output-file", encOutputFile, + } + + _, err := runCommand(t, cmdEncryptPKCS11, argsEncryptPKCS11) + if err != nil { + t.Fatalf("PKCS11 Encryption failed: %v", err) + } + + decOutputFile := fmt.Sprintf("data/%s-decrypted-output.txt", uuid) + cmdDecryptPKCS11 := "go" + argsDecryptPKCS11 := []string{ + "run", "crypto_vault_cli.go", "decrypt", "--token-label", "my-token", "--object-label", "my-rsa-key", "--key-type", "RSA", "--input-file", encOutputFile, "--output-file", decOutputFile, + } + + _, err = runCommand(t, cmdDecryptPKCS11, argsDecryptPKCS11) + if err != nil { + t.Fatalf("PKCS11 Decryption failed: %v", err) + } +} + func TestPKCS11KeyManagement(t *testing.T) { + // Store PKCS#11 settings cmdStorePKCS11 := "go" argsStorePKCS11 := []string{ "run", "crypto_vault_cli.go", "store-pkcs11-settings", "--module", "/usr/lib/softhsm/libsofthsm2.so", "--so-pin", "1234", "--user-pin", "5678", "--slot-id", "0x0", @@ -130,6 +160,7 @@ func TestPKCS11KeyManagement(t *testing.T) { t.Fatalf("Storing PKCS#11 settings failed: %v", err) } + // Add RSA Key cmdAddRSAKey := "go" argsAddRSAKey := []string{"run", "crypto_vault_cli.go", "add-key", "--token-label", "my-token", "--object-label", "my-rsa-key", "--key-type", "RSA", "--key-size", "2048"} @@ -138,6 +169,7 @@ func TestPKCS11KeyManagement(t *testing.T) { t.Fatalf("Adding RSA Key to PKCS#11 failed: %v", err) } + // List Objects cmdListObjects := "go" argsListObjects := []string{"run", "crypto_vault_cli.go", "list-objects", "--token-label", "my-token"} @@ -146,6 +178,7 @@ func TestPKCS11KeyManagement(t *testing.T) { t.Fatalf("Listing PKCS#11 objects failed: %v", err) } + // Delete RSA Key cmdDeleteRSAKey := "go" argsDeleteRSAKey := []string{"run", "crypto_vault_cli.go", "delete-object", "--token-label", "my-token", "--object-label", "my-rsa-key", "--object-type", "pubkey"} diff --git a/cmd/crypto-vault-cli/internal/commands/aes_commands.go b/cmd/crypto-vault-cli/internal/commands/aes_commands.go index 41e3ca0..9ce3e66 100644 --- a/cmd/crypto-vault-cli/internal/commands/aes_commands.go +++ b/cmd/crypto-vault-cli/internal/commands/aes_commands.go @@ -43,47 +43,59 @@ func NewAESCommandHandler() *AESCommandHandler { } } -// EncryptAESCmd encrypts a file using AES and saves the symmetric key with a UUID prefix -func (commandHandler *AESCommandHandler) EncryptAESCmd(cmd *cobra.Command, args []string) { - inputFilePath, _ := cmd.Flags().GetString("input-file") - outputFilePath, _ := cmd.Flags().GetString("output-file") +// GenerateAESKeysCmd generates AES key pairs and persists those in a selected directory +func (commandHandler *AESCommandHandler) GenerateAESKeysCmd(cmd *cobra.Command, args []string) { keySize, _ := cmd.Flags().GetInt("key-size") keyDir, _ := cmd.Flags().GetString("key-dir") - key, err := commandHandler.aes.GenerateKey(keySize) + uniqueID := uuid.New() + + secretKey, err := commandHandler.aes.GenerateKey(keySize) if err != nil { commandHandler.Logger.Error(fmt.Sprintf("%v", err)) return } - plainText, err := os.ReadFile(inputFilePath) + keyFilePath := filepath.Join(keyDir, fmt.Sprintf("%s-symmetric-key.bin", uniqueID)) + err = os.WriteFile(keyFilePath, secretKey, 0644) if err != nil { commandHandler.Logger.Error(fmt.Sprintf("%v", err)) return } + commandHandler.Logger.Info(fmt.Sprintf("AES key saved to %s", keyFilePath)) +} - encryptedData, err := commandHandler.aes.Encrypt(plainText, key) +// EncryptAESCmd encrypts a file using AES and saves the symmetric key with a UUID prefix +func (commandHandler *AESCommandHandler) EncryptAESCmd(cmd *cobra.Command, args []string) { + inputFilePath, _ := cmd.Flags().GetString("input-file") + outputFilePath, _ := cmd.Flags().GetString("output-file") + symmetricKey, _ := cmd.Flags().GetString("symmetric-key") + + plainText, err := os.ReadFile(inputFilePath) if err != nil { commandHandler.Logger.Error(fmt.Sprintf("%v", err)) return } - err = os.WriteFile(outputFilePath, encryptedData, 0644) + key, err := os.ReadFile(symmetricKey) if err != nil { commandHandler.Logger.Error(fmt.Sprintf("%v", err)) return } - uniqueID := uuid.New().String() + encryptedData, err := commandHandler.aes.Encrypt(plainText, key) + if err != nil { + commandHandler.Logger.Error(fmt.Sprintf("%v", err)) + return + } - keyFilePath := filepath.Join(keyDir, fmt.Sprintf("%s-symmetric-key.bin", uniqueID)) - err = os.WriteFile(keyFilePath, key, 0644) + err = os.WriteFile(outputFilePath, encryptedData, 0644) if err != nil { commandHandler.Logger.Error(fmt.Sprintf("%v", err)) return } - commandHandler.Logger.Info(fmt.Sprintf("Encrypted data saved to %s. AES key saved to %s", outputFilePath, keyFilePath)) + commandHandler.Logger.Info(fmt.Sprintf("Encrypted data saved to %s", outputFilePath)) } // DecryptAESCmd decrypts a file using AES and reads the corresponding symmetric key with a UUID prefix @@ -122,15 +134,23 @@ func (commandHandler *AESCommandHandler) DecryptAESCmd(cmd *cobra.Command, args func InitAESCommands(rootCmd *cobra.Command) { handler := NewAESCommandHandler() + var generateAESKeysCmd = &cobra.Command{ + Use: "generate-aes-keys", + Short: "Generate AES keys", + Run: handler.GenerateAESKeysCmd, + } + generateAESKeysCmd.Flags().IntP("key-size", "", 16, "AES key size (default 16 bytes for AES-128)") + generateAESKeysCmd.Flags().StringP("key-dir", "", "", "Directory to store the encryption key") + rootCmd.AddCommand(generateAESKeysCmd) + var encryptAESFileCmd = &cobra.Command{ Use: "encrypt-aes", Short: "Encrypt a file using AES", Run: handler.EncryptAESCmd, } - encryptAESFileCmd.Flags().StringP("input-file", "", "", "Input file path") - encryptAESFileCmd.Flags().StringP("output-file", "", "", "Output encrypted file path") - encryptAESFileCmd.Flags().IntP("key-size", "", 16, "AES key size (default 16 bytes for AES-128)") - encryptAESFileCmd.Flags().StringP("key-dir", "", "", "Directory to store the encryption key") + encryptAESFileCmd.Flags().StringP("input-file", "", "", "Path to input file that needs to be encrypted") + encryptAESFileCmd.Flags().StringP("output-file", "", "", "Path to encrypted output file") + encryptAESFileCmd.Flags().StringP("symmetric-key", "", "", "Path to the symmetric key") rootCmd.AddCommand(encryptAESFileCmd) var decryptAESFileCmd = &cobra.Command{ @@ -138,8 +158,8 @@ func InitAESCommands(rootCmd *cobra.Command) { Short: "Decrypt a file using AES", Run: handler.DecryptAESCmd, } - decryptAESFileCmd.Flags().StringP("input-file", "i", "", "Input encrypted file path") - decryptAESFileCmd.Flags().StringP("output-file", "o", "", "Output decrypted file path") - decryptAESFileCmd.Flags().StringP("symmetric-key", "k", "", "Path to the symmetric key") + decryptAESFileCmd.Flags().StringP("input-file", "", "", "Input encrypted file path") + decryptAESFileCmd.Flags().StringP("output-file", "", "", "Path to decrypted output file") + decryptAESFileCmd.Flags().StringP("symmetric-key", "", "", "Path to the symmetric key") rootCmd.AddCommand(decryptAESFileCmd) } diff --git a/cmd/crypto-vault-cli/internal/commands/ec_commands.go b/cmd/crypto-vault-cli/internal/commands/ec_commands.go index 3825b18..b2d59f5 100644 --- a/cmd/crypto-vault-cli/internal/commands/ec_commands.go +++ b/cmd/crypto-vault-cli/internal/commands/ec_commands.go @@ -1,7 +1,6 @@ package commands import ( - "crypto/ecdsa" "crypto/elliptic" "crypto_vault_service/internal/infrastructure/cryptography" "crypto_vault_service/internal/infrastructure/logger" @@ -45,51 +44,71 @@ func NewECCommandHandler() *ECCommandHandler { } } -// SignECCCmd signs the contents of a file with ECDSA -func (commandHandler *ECCommandHandler) SignECCCmd(cmd *cobra.Command, args []string) { - - inputFile, _ := cmd.Flags().GetString("input-file") +// GenerateECKeysCmd generates EC key pairs and persists those in a selected directory +func (commandHandler *ECCommandHandler) GenerateECKeysCmd(cmd *cobra.Command, args []string) { + keySize, _ := cmd.Flags().GetInt("key-size") keyDir, _ := cmd.Flags().GetString("key-dir") - var privateKey *ecdsa.PrivateKey - var publicKey *ecdsa.PublicKey + uniqueID := uuid.New() - privateKey, publicKey, err := commandHandler.ec.GenerateKeys(elliptic.P256()) + var curve elliptic.Curve + if keySize == 224 { + curve = elliptic.P224() + } else if keySize == 256 { + curve = elliptic.P256() + } else if keySize == 384 { + curve = elliptic.P384() + } else if keySize == 521 { + curve = elliptic.P521() + } else { + commandHandler.Logger.Error(fmt.Sprintf("key size %v not supported", keySize)) + return + } + privateKey, publicKey, err := commandHandler.ec.GenerateKeys(curve) if err != nil { commandHandler.Logger.Error(fmt.Sprintf("%v", err)) return } - fileContent, err := os.ReadFile(inputFile) + privateKeyFilePath := fmt.Sprintf("%s/%s-private-key.pem", keyDir, uniqueID.String()) + err = commandHandler.ec.SavePrivateKeyToFile(privateKey, privateKeyFilePath) if err != nil { commandHandler.Logger.Error(fmt.Sprintf("%v", err)) return } - signature, err := commandHandler.ec.Sign(fileContent, privateKey) + publicKeyFilePath := fmt.Sprintf("%s/%s-public-key.pem", keyDir, uniqueID.String()) + err = commandHandler.ec.SavePublicKeyToFile(publicKey, publicKeyFilePath) if err != nil { commandHandler.Logger.Error(fmt.Sprintf("%v", err)) return } +} - uniqueID := uuid.New() +// SignECCCmd signs the contents of a file with ECDSA +func (commandHandler *ECCommandHandler) SignECCCmd(cmd *cobra.Command, args []string) { + inputFilePath, _ := cmd.Flags().GetString("input-file") + privateKeyFilePath, _ := cmd.Flags().GetString("private-key") + signatureFilePath, _ := cmd.Flags().GetString("output-file") - privateKeyFilePath := fmt.Sprintf("%s/%s-private-key.pem", keyDir, uniqueID.String()) + fileContent, err := os.ReadFile(inputFilePath) + if err != nil { + commandHandler.Logger.Error(fmt.Sprintf("%v", err)) + return + } - err = commandHandler.ec.SavePrivateKeyToFile(privateKey, privateKeyFilePath) + privateKey, err := commandHandler.ec.ReadPrivateKey(privateKeyFilePath, elliptic.P256()) if err != nil { commandHandler.Logger.Error(fmt.Sprintf("%v", err)) return } - publicKeyFilePath := fmt.Sprintf("%s/%s-public-key.pem", keyDir, uniqueID.String()) - err = commandHandler.ec.SavePublicKeyToFile(publicKey, publicKeyFilePath) + signature, err := commandHandler.ec.Sign(fileContent, privateKey) if err != nil { commandHandler.Logger.Error(fmt.Sprintf("%v", err)) return } - signatureFilePath := fmt.Sprintf("%s/%s-signature.sig", keyDir, uniqueID.String()) err = commandHandler.ec.SaveSignatureToFile(signatureFilePath, signature) if err != nil { if err != nil { @@ -105,13 +124,6 @@ func (commandHandler *ECCommandHandler) VerifyECCCmd(cmd *cobra.Command, args [] publicKeyPath, _ := cmd.Flags().GetString("public-key") signatureFile, _ := cmd.Flags().GetString("signature-file") - var publicKey *ecdsa.PublicKey - - if publicKeyPath == "" { - commandHandler.Logger.Error("Public key is required for ECC signature verification") - return - } - publicKey, err := commandHandler.ec.ReadPublicKey(publicKeyPath, elliptic.P256()) if err != nil { commandHandler.Logger.Error(fmt.Sprintf("%v", err)) @@ -152,13 +164,23 @@ func (commandHandler *ECCommandHandler) VerifyECCCmd(cmd *cobra.Command, args [] func InitECDSACommands(rootCmd *cobra.Command) { handler := NewECCommandHandler() + var generateECKeysCmd = &cobra.Command{ + Use: "generate-ecc-keys", + Short: "Generate ECC keys", + Run: handler.GenerateECKeysCmd, + } + generateECKeysCmd.Flags().IntP("key-size", "", 256, "ECC key size (default 256 bytes for ECC-256)") + generateECKeysCmd.Flags().StringP("key-dir", "", "", "Directory to store the ECC keys") + rootCmd.AddCommand(generateECKeysCmd) + var signECCMessageCmd = &cobra.Command{ Use: "sign-ecc", Short: "Sign a message using ECC", Run: handler.SignECCCmd, } signECCMessageCmd.Flags().StringP("input-file", "", "", "Path to file that needs to be signed") - signECCMessageCmd.Flags().StringP("key-dir", "", "", "Directory to save generated keys (optional)") + signECCMessageCmd.Flags().StringP("private-key", "", "", "Path to ECC private key") + signECCMessageCmd.Flags().StringP("output-file", "", "", "Path to signature output file") rootCmd.AddCommand(signECCMessageCmd) var verifyECCSignatureCmd = &cobra.Command{ @@ -166,8 +188,8 @@ func InitECDSACommands(rootCmd *cobra.Command) { Short: "Verify a signature using ECC", Run: handler.VerifyECCCmd, } - verifyECCSignatureCmd.Flags().StringP("input-file", "", "", "Path to ECC public key") - verifyECCSignatureCmd.Flags().StringP("public-key", "", "", "The public key used to verify the signature") - verifyECCSignatureCmd.Flags().StringP("signature-file", "", "", "Signature to verify (hex format)") + verifyECCSignatureCmd.Flags().StringP("input-file", "", "", "Path to file which needs to be validated") + verifyECCSignatureCmd.Flags().StringP("public-key", "", "", "Path to ECC public key") + verifyECCSignatureCmd.Flags().StringP("signature-file", "", "", "Path to signature input file") rootCmd.AddCommand(verifyECCSignatureCmd) } diff --git a/cmd/crypto-vault-cli/internal/commands/rsa_commands.go b/cmd/crypto-vault-cli/internal/commands/rsa_commands.go index 77e22e0..6105497 100644 --- a/cmd/crypto-vault-cli/internal/commands/rsa_commands.go +++ b/cmd/crypto-vault-cli/internal/commands/rsa_commands.go @@ -1,8 +1,6 @@ package commands import ( - "crypto/rsa" - "crypto_vault_service/internal/infrastructure/cryptography" "crypto_vault_service/internal/infrastructure/logger" "crypto_vault_service/internal/infrastructure/settings" @@ -44,17 +42,14 @@ func NewRSACommandHandler() *RSACommandHandler { } } -// EncryptRSACmd encrypts a file using RSA and saves asymmetric key pairs -func (commandHandler *RSACommandHandler) EncryptRSACmd(cmd *cobra.Command, args []string) { - inputFile, _ := cmd.Flags().GetString("input-file") - outputFile, _ := cmd.Flags().GetString("output-file") +// GenerateRSAKeysCmd generates RSA key pairs and persists those in a selected directory +func (commandHandler *RSACommandHandler) GenerateRSAKeysCmd(cmd *cobra.Command, args []string) { + keySize, _ := cmd.Flags().GetInt("key-size") keyDir, _ := cmd.Flags().GetString("key-dir") - var publicKey *rsa.PublicKey - uniqueID := uuid.New() - privateKey, publicKey, err := commandHandler.rsa.GenerateKeys(2048) + privateKey, publicKey, err := commandHandler.rsa.GenerateKeys(keySize) if err != nil { commandHandler.Logger.Error(fmt.Sprintf("%v", err)) return @@ -74,6 +69,19 @@ func (commandHandler *RSACommandHandler) EncryptRSACmd(cmd *cobra.Command, args commandHandler.Logger.Error(fmt.Sprintf("%v", err)) return } +} + +// EncryptRSACmd encrypts a file using RSA and saves asymmetric key pairs +func (commandHandler *RSACommandHandler) EncryptRSACmd(cmd *cobra.Command, args []string) { + inputFile, _ := cmd.Flags().GetString("input-file") + outputFile, _ := cmd.Flags().GetString("output-file") + publicKeyPath, _ := cmd.Flags().GetString("public-key") + + publicKey, err := commandHandler.rsa.ReadPublicKey(publicKeyPath) + if err != nil { + commandHandler.Logger.Error(fmt.Sprintf("%v", err)) + return + } plainText, err := os.ReadFile(inputFile) if err != nil { @@ -102,24 +110,6 @@ func (commandHandler *RSACommandHandler) DecryptRSACmd(cmd *cobra.Command, args outputFile, _ := cmd.Flags().GetString("output-file") privateKeyPath, _ := cmd.Flags().GetString("private-key") - var privateKey *rsa.PrivateKey - - if privateKeyPath == "" { - - privKey, _, err := commandHandler.rsa.GenerateKeys(2048) - if err != nil { - commandHandler.Logger.Error(fmt.Sprintf("%v", err)) - return - } - privateKey = privKey - - err = commandHandler.rsa.SavePrivateKeyToFile(privateKey, "private-key.pem") - if err != nil { - commandHandler.Logger.Error(fmt.Sprintf("%v", err)) - return - } - - } privateKey, err := commandHandler.rsa.ReadPrivateKey(privateKeyPath) if err != nil { commandHandler.Logger.Error(fmt.Sprintf("%v", err)) @@ -147,17 +137,103 @@ func (commandHandler *RSACommandHandler) DecryptRSACmd(cmd *cobra.Command, args commandHandler.Logger.Info(fmt.Sprintf("Decrypted data path %s", outputFile)) } +// SignRSACmd signs a file using RSA and saves the signature +func (commandHandler *RSACommandHandler) SignRSACmd(cmd *cobra.Command, args []string) { + inputFilePath, _ := cmd.Flags().GetString("input-file") + signatureFilePath, _ := cmd.Flags().GetString("output-file") + privateKeyPath, _ := cmd.Flags().GetString("private-key") + + // Read private key + privateKey, err := commandHandler.rsa.ReadPrivateKey(privateKeyPath) + if err != nil { + commandHandler.Logger.Error(fmt.Sprintf("%v", err)) + return + } + + // Read data to sign + data, err := os.ReadFile(inputFilePath) + if err != nil { + commandHandler.Logger.Error(fmt.Sprintf("%v", err)) + return + } + + // Sign the data + signature, err := commandHandler.rsa.Sign(data, privateKey) + if err != nil { + commandHandler.Logger.Error(fmt.Sprintf("%v", err)) + return + } + + // Save the signature to a file + err = os.WriteFile(signatureFilePath, signature, 0644) + if err != nil { + commandHandler.Logger.Error(fmt.Sprintf("%v", err)) + return + } + + commandHandler.Logger.Info(fmt.Sprintf("Signature saved at %s", signatureFilePath)) +} + +// VerifyRSACmd verifies a signature using RSA +func (commandHandler *RSACommandHandler) VerifyRSACmd(cmd *cobra.Command, args []string) { + inputFilePath, _ := cmd.Flags().GetString("input-file") + signatureFilePath, _ := cmd.Flags().GetString("signature-file") + publicKeyPath, _ := cmd.Flags().GetString("public-key") + + // Read public key + publicKey, err := commandHandler.rsa.ReadPublicKey(publicKeyPath) + if err != nil { + commandHandler.Logger.Error(fmt.Sprintf("%v", err)) + return + } + + // Read data and signature + data, err := os.ReadFile(inputFilePath) + if err != nil { + commandHandler.Logger.Error(fmt.Sprintf("%v", err)) + return + } + + signature, err := os.ReadFile(signatureFilePath) + if err != nil { + commandHandler.Logger.Error(fmt.Sprintf("%v", err)) + return + } + + // Verify the signature + valid, err := commandHandler.rsa.Verify(data, signature, publicKey) + if err != nil { + commandHandler.Logger.Error(fmt.Sprintf("%v", err)) + return + } + + if valid { + commandHandler.Logger.Info("Signature is valid") + } else { + commandHandler.Logger.Error("Signature is invalid") + } +} + func InitRSACommands(rootCmd *cobra.Command) { handler := NewRSACommandHandler() + var generateRSAKeysCmd = &cobra.Command{ + Use: "generate-rsa-keys", + Short: "Generate RSA keys", + Run: handler.GenerateRSAKeysCmd, + } + generateRSAKeysCmd.Flags().IntP("key-size", "", 2048, "RSA key size (default 2048 bytes for RSA-2048)") + generateRSAKeysCmd.Flags().StringP("key-dir", "", "", "Directory to store the RSA keys") + rootCmd.AddCommand(generateRSAKeysCmd) + var encryptRSAFileCmd = &cobra.Command{ Use: "encrypt-rsa", Short: "Encrypt a file using RSA", Run: handler.EncryptRSACmd, } - encryptRSAFileCmd.Flags().StringP("input-file", "", "", "Input file path") - encryptRSAFileCmd.Flags().StringP("output-file", "", "", "Output encrypted file path") - encryptRSAFileCmd.Flags().StringP("key-dir", "", "", "Directory to store the encryption key") + encryptRSAFileCmd.Flags().StringP("input-file", "", "", "Path to input file which needs to be encrypted") + encryptRSAFileCmd.Flags().StringP("output-file", "", "", "Path to encrypted output file") + encryptRSAFileCmd.Flags().StringP("public-key", "", "", "Path to RSA public private key") rootCmd.AddCommand(encryptRSAFileCmd) var decryptRSAFileCmd = &cobra.Command{ @@ -165,8 +241,30 @@ func InitRSACommands(rootCmd *cobra.Command) { Short: "Decrypt a file using RSA", Run: handler.DecryptRSACmd, } - decryptRSAFileCmd.Flags().StringP("input-file", "", "", "Input encrypted file path") - decryptRSAFileCmd.Flags().StringP("output-file", "", "", "Output decrypted file path") + decryptRSAFileCmd.Flags().StringP("input-file", "", "", "Path to encrypted file") + decryptRSAFileCmd.Flags().StringP("output-file", "", "", "Path to decrypted output file") decryptRSAFileCmd.Flags().StringP("private-key", "", "", "Path to RSA private key") rootCmd.AddCommand(decryptRSAFileCmd) + + var signRSAFileCmd = &cobra.Command{ + Use: "sign-rsa", + Short: "Sign a file using RSA", + Run: handler.SignRSACmd, + } + + signRSAFileCmd.Flags().StringP("input-file", "", "", "Path to file which needs to be signed") + signRSAFileCmd.Flags().StringP("output-file", "", "", "Path to signature output file") + signRSAFileCmd.Flags().StringP("private-key", "", "", "Path to RSA private key") + rootCmd.AddCommand(signRSAFileCmd) + + var verifyRSAFileCmd = &cobra.Command{ + Use: "verify-rsa", + Short: "Verify a file is valid using RSA", + Run: handler.VerifyRSACmd, + } + + verifyRSAFileCmd.Flags().StringP("input-file", "", "", "Path to file which needs to be validated") + verifyRSAFileCmd.Flags().StringP("signature-file", "", "", "Path to signature input file") + verifyRSAFileCmd.Flags().StringP("public-key", "", "", "Path to RSA public key") + rootCmd.AddCommand(verifyRSAFileCmd) } diff --git a/docs/architecture-decision-record/blob-storage-schema-design.md b/docs/architecture-decision-record/blob-storage-schema-design.md index 3357e3e..4f094da 100644 --- a/docs/architecture-decision-record/blob-storage-schema-design.md +++ b/docs/architecture-decision-record/blob-storage-schema-design.md @@ -6,8 +6,8 @@ Blobs will be stored in a Blob Storage using the schema /{ID}/{Name}, where: - **ID:** A unique identifier for the entity (e.g., user ID, product ID). -- **Name:** The name or description of the specific file (e.g., profile_picture.png, invoice.pdf). +- **Name:** The name of the specific file (e.g., profile_picture.png, invoice.pdf). ## Conclusion: -The `/{ID}/{Name}` schema provides a logical, scalable, and efficient way to organize blobs. It ensures clear grouping of related blobs, simplifies retrieval, and leverages a Blob Storage's hierarchical structure for better performance and management. \ No newline at end of file +The `/{ID}/{Name}` schema provides a logical, scalable and efficient way to organize blobs. It ensures clear grouping of related blobs, simplifies retrieval and leverages a Blob Storage's hierarchical structure for better performance and management. \ No newline at end of file diff --git a/internal/app/services/blob_services.go b/internal/app/services/blob_services.go index b8b85c3..6404759 100644 --- a/internal/app/services/blob_services.go +++ b/internal/app/services/blob_services.go @@ -24,23 +24,18 @@ func NewBlobUploadService(blobConnector connector.BlobConnector, blobRepository // Upload handles the upload of blobs and stores their metadata in the database. func (s *BlobUploadService) Upload(filePaths []string, userId string) ([]*blobs.BlobMeta, error) { - // Use the BlobConnector to upload the files to Azure Blob Storage blobMetas, err := s.BlobConnector.Upload(filePaths, userId) if err != nil { return nil, fmt.Errorf("failed to upload blobs: %w", err) } - // If no blobs are uploaded, return early if len(blobMetas) == 0 { return nil, fmt.Errorf("no blobs uploaded") } - // Store the metadata in the database using the BlobRepository for _, blobMeta := range blobMetas { err := s.BlobRepository.Create(blobMeta) if err != nil { - // Rollback any previously uploaded blobs if the metadata fails to store - // (you can call delete method to handle this as needed) return nil, fmt.Errorf("failed to store metadata for blob '%s': %w", blobMeta.Name, err) } } diff --git a/internal/domain/blobs/models.go b/internal/domain/blobs/models.go index acc8ab9..66d6bf5 100644 --- a/internal/domain/blobs/models.go +++ b/internal/domain/blobs/models.go @@ -24,13 +24,12 @@ type BlobMeta struct { // Validate for validating BlobMeta struct func (b *BlobMeta) Validate() error { - // Initialize the validator + validate := validator.New() - // Validate the struct err := validate.Struct(b) if err != nil { - // If validation fails, return a formatted error + var validationErrors []string for _, err := range err.(validator.ValidationErrors) { validationErrors = append(validationErrors, fmt.Sprintf("Field: %s, Tag: %s", err.Field(), err.Tag())) diff --git a/internal/domain/keys/models.go b/internal/domain/keys/models.go index 27d022b..2c505de 100644 --- a/internal/domain/keys/models.go +++ b/internal/domain/keys/models.go @@ -18,13 +18,12 @@ type CryptoKeyMeta struct { // Validate for validating CryptoKeyMeta struct func (k *CryptoKeyMeta) Validate() error { - // Initialize the validator + validate := validator.New() - // Validate the struct err := validate.Struct(k) if err != nil { - // If validation fails, return a formatted error + var validationErrors []string for _, err := range err.(validator.ValidationErrors) { validationErrors = append(validationErrors, fmt.Sprintf("Field: %s, Tag: %s", err.Field(), err.Tag())) diff --git a/internal/infrastructure/connector/blob_connectors.go b/internal/infrastructure/connector/blob_connectors.go index 183c550..c36290d 100644 --- a/internal/infrastructure/connector/blob_connectors.go +++ b/internal/infrastructure/connector/blob_connectors.go @@ -5,6 +5,7 @@ import ( "context" "crypto_vault_service/internal/domain/blobs" "crypto_vault_service/internal/infrastructure/logger" + "crypto_vault_service/internal/infrastructure/settings" "fmt" "os" "path/filepath" @@ -27,26 +28,30 @@ type BlobConnector interface { // AzureBlobConnector is a struct that holds the Azure Blob storage client and implements the BlobConnector interfaces. type AzureBlobConnector struct { Client *azblob.Client - ContainerName string + containerName string Logger logger.Logger } // NewAzureBlobConnector creates a new AzureBlobConnector instance using a connection string. // It returns the connector and any error encountered during the initialization. -func NewAzureBlobConnector(connectionString string, containerName string, logger logger.Logger) (*AzureBlobConnector, error) { - client, err := azblob.NewClientFromConnectionString(connectionString, nil) +func NewAzureBlobConnector(settings *settings.BlobConnectorSettings, logger logger.Logger) (*AzureBlobConnector, error) { + if err := settings.Validate(); err != nil { + return nil, err + } + + client, err := azblob.NewClientFromConnectionString(settings.ConnectionString, nil) if err != nil { return nil, fmt.Errorf("failed to create Azure Blob client: %w", err) } - _, err = client.CreateContainer(context.Background(), containerName, nil) + _, err = client.CreateContainer(context.Background(), settings.ContainerName, nil) if err != nil { - fmt.Printf("Failed to create Azure container: %v\n", err) // The container may already exist, so we should not return an error in this case. + fmt.Printf("Failed to create Azure container: %v\n", err) } return &AzureBlobConnector{ Client: client, - ContainerName: containerName, + containerName: settings.ContainerName, Logger: logger, }, nil } @@ -102,7 +107,7 @@ func (abc *AzureBlobConnector) Upload(filePaths []string, userId string) ([]*blo fullBlobName := fmt.Sprintf("%s/%s", blob.ID, blob.Name) fullBlobName = filepath.ToSlash(fullBlobName) - _, err = abc.Client.UploadBuffer(context.Background(), abc.ContainerName, fullBlobName, buf.Bytes(), nil) + _, err = abc.Client.UploadBuffer(context.Background(), abc.containerName, fullBlobName, buf.Bytes(), nil) if err != nil { err = fmt.Errorf("failed to upload blob '%s': %w", fullBlobName, err) abc.rollbackUploadedBlobs(blobMeta) @@ -135,7 +140,7 @@ func (abc *AzureBlobConnector) Download(blobId, blobName string) ([]byte, error) fullBlobName := fmt.Sprintf("%s/%s", blobId, blobName) - get, err := abc.Client.DownloadStream(ctx, abc.ContainerName, fullBlobName, nil) + get, err := abc.Client.DownloadStream(ctx, abc.containerName, fullBlobName, nil) if err != nil { return nil, fmt.Errorf("failed to download blob '%s': %w", fullBlobName, err) } @@ -163,7 +168,7 @@ func (abc *AzureBlobConnector) Delete(blobId, blobName string) error { fullBlobName := fmt.Sprintf("%s/%s", blobId, blobName) - _, err := abc.Client.DeleteBlob(ctx, abc.ContainerName, fullBlobName, nil) + _, err := abc.Client.DeleteBlob(ctx, abc.containerName, fullBlobName, nil) if err != nil { return fmt.Errorf("failed to delete blob in %s", fullBlobName) } diff --git a/internal/infrastructure/connector/key_connectors.go b/internal/infrastructure/connector/key_connectors.go index f43b0ec..e867df5 100644 --- a/internal/infrastructure/connector/key_connectors.go +++ b/internal/infrastructure/connector/key_connectors.go @@ -5,6 +5,7 @@ import ( "context" "crypto_vault_service/internal/domain/keys" "crypto_vault_service/internal/infrastructure/logger" + "crypto_vault_service/internal/infrastructure/settings" "fmt" "log" "os" @@ -34,26 +35,30 @@ type VaultConnector interface { // like Azure Key Vault or AWS KMS. type AzureVaultConnector struct { Client *azblob.Client - ContainerName string + containerName string Logger logger.Logger } // NewAzureVaultConnector creates a new instance of AzureVaultConnector, which connects to Azure Blob Storage. // This method can be updated in the future to support a more sophisticated key management system like Azure Key Vault. -func NewAzureVaultConnector(connectionString string, containerName string, logger logger.Logger) (*AzureVaultConnector, error) { - client, err := azblob.NewClientFromConnectionString(connectionString, nil) +func NewAzureVaultConnector(settings *settings.KeyConnectorSettings, logger logger.Logger) (*AzureVaultConnector, error) { + if err := settings.Validate(); err != nil { + return nil, err + } + + client, err := azblob.NewClientFromConnectionString(settings.ConnectionString, nil) if err != nil { return nil, fmt.Errorf("failed to create Azure Blob client: %w", err) } - _, err = client.CreateContainer(context.Background(), containerName, nil) + _, err = client.CreateContainer(context.Background(), settings.ContainerName, nil) if err != nil { - log.Printf("Failed to create Azure container: %v\n", err) // The container may already exist, so we should not return an error in this case. + log.Printf("Failed to create Azure container: %v\n", err) } return &AzureVaultConnector{ Client: client, - ContainerName: containerName, + containerName: settings.ContainerName, Logger: logger, }, nil } @@ -85,7 +90,7 @@ func (vc *AzureVaultConnector) Upload(filePath, userId, keyType, keyAlgorihm str fullKeyName := fmt.Sprintf("%s/%s", keyID, keyType) - _, err = vc.Client.UploadBuffer(context.Background(), vc.ContainerName, fullKeyName, buf.Bytes(), nil) + _, err = vc.Client.UploadBuffer(context.Background(), vc.containerName, fullKeyName, buf.Bytes(), nil) if err != nil { return nil, fmt.Errorf("failed to upload blob '%s' to storage: %w", fullKeyName, err) } @@ -100,7 +105,7 @@ func (vc *AzureVaultConnector) Download(keyId, keyType string) ([]byte, error) { fullKeyName := fmt.Sprintf("%s/%s", keyId, keyType) ctx := context.Background() - get, err := vc.Client.DownloadStream(ctx, vc.ContainerName, fullKeyName, nil) + get, err := vc.Client.DownloadStream(ctx, vc.containerName, fullKeyName, nil) if err != nil { return nil, fmt.Errorf("failed to download blob '%s': %w", fullKeyName, err) } @@ -120,7 +125,7 @@ func (vc *AzureVaultConnector) Delete(keyId, keyType string) error { fullKeyName := fmt.Sprintf("%s/%s", keyId, keyType) ctx := context.Background() - _, err := vc.Client.DeleteBlob(ctx, vc.ContainerName, fullKeyName, nil) + _, err := vc.Client.DeleteBlob(ctx, vc.containerName, fullKeyName, nil) if err != nil { return fmt.Errorf("failed to delete blob '%s': %w", fullKeyName, err) } diff --git a/internal/infrastructure/cryptography/rsa.go b/internal/infrastructure/cryptography/rsa.go index 1058c21..8bfddc0 100644 --- a/internal/infrastructure/cryptography/rsa.go +++ b/internal/infrastructure/cryptography/rsa.go @@ -1,8 +1,10 @@ package cryptography import ( + "crypto" "crypto/rand" "crypto/rsa" + "crypto/sha256" "crypto/x509" "crypto_vault_service/internal/infrastructure/logger" "encoding/pem" @@ -15,6 +17,8 @@ import ( type IRSA interface { Encrypt(plainText []byte, publicKey *rsa.PublicKey) ([]byte, error) Decrypt(ciphertext []byte, privateKey *rsa.PrivateKey) ([]byte, error) + Sign(data []byte, privateKey *rsa.PrivateKey) ([]byte, error) + Verify(data []byte, signature []byte, publicKey *rsa.PublicKey) (bool, error) GenerateKeys(bits int) (*rsa.PrivateKey, *rsa.PublicKey, error) SavePrivateKeyToFile(privateKey *rsa.PrivateKey, filename string) error SavePublicKeyToFile(publicKey *rsa.PublicKey, filename string) error @@ -73,6 +77,45 @@ func (r *RSA) Decrypt(ciphertext []byte, privateKey *rsa.PrivateKey) ([]byte, er return decryptedData, nil } +// Sign data using RSA private key +func (r *RSA) Sign(data []byte, privateKey *rsa.PrivateKey) ([]byte, error) { + if privateKey == nil { + return nil, fmt.Errorf("private key cannot be nil") + } + + // Use the SHA-256 hash algorithm for signing + hashed := sha256.Sum256(data) + + // Sign the hashed data with the private key + signature, err := rsa.SignPKCS1v15(rand.Reader, privateKey, crypto.SHA256, hashed[:]) + if err != nil { + return nil, fmt.Errorf("failed to sign data: %v", err) + } + + r.Logger.Info("RSA signing succeeded") + return signature, nil +} + +// Verify RSA signature with public key +func (r *RSA) Verify(data []byte, signature []byte, publicKey *rsa.PublicKey) (bool, error) { + if publicKey == nil { + return false, fmt.Errorf("public key cannot be nil") + } + + // Use the SHA-256 hash algorithm for verification + hashed := sha256.Sum256(data) + + // Verify the signature using the public key + err := rsa.VerifyPKCS1v15(publicKey, crypto.SHA256, hashed[:], signature) + if err != nil { + return false, fmt.Errorf("failed to verify signature: %v", err) + } + + r.Logger.Info("RSA signature verified successfully") + return true, nil +} + +// SavePrivateKeyToFile saves the private key to a PEM file using encoding/pem func (r *RSA) SavePrivateKeyToFile(privateKey *rsa.PrivateKey, filename string) error { privKeyBytes := x509.MarshalPKCS1PrivateKey(privateKey) privKeyPem := &pem.Block{ @@ -95,6 +138,7 @@ func (r *RSA) SavePrivateKeyToFile(privateKey *rsa.PrivateKey, filename string) return nil } +// SavePublicKeyToFile saves the public key to a PEM file using encoding/pem func (r *RSA) SavePublicKeyToFile(publicKey *rsa.PublicKey, filename string) error { pubKeyBytes, err := x509.MarshalPKIXPublicKey(publicKey) if err != nil { diff --git a/internal/infrastructure/settings/blob_connector_settings.go b/internal/infrastructure/settings/blob_connector_settings.go new file mode 100644 index 0000000..3743744 --- /dev/null +++ b/internal/infrastructure/settings/blob_connector_settings.go @@ -0,0 +1,23 @@ +package settings + +import ( + "fmt" + + "github.com/go-playground/validator/v10" +) + +type BlobConnectorSettings struct { + ConnectionString string `validate:"required"` + ContainerName string `validate:"required"` +} + +// Validate checks that all fields in BlobConnectorSettings are valid (non-empty in this case) +func (settings *BlobConnectorSettings) Validate() error { + validate := validator.New() + + err := validate.Struct(settings) + if err != nil { + return fmt.Errorf("validation failed: %v", err) + } + return nil +} diff --git a/internal/infrastructure/settings/key_connector_settings.go b/internal/infrastructure/settings/key_connector_settings.go new file mode 100644 index 0000000..9e59c17 --- /dev/null +++ b/internal/infrastructure/settings/key_connector_settings.go @@ -0,0 +1,23 @@ +package settings + +import ( + "fmt" + + "github.com/go-playground/validator/v10" +) + +type KeyConnectorSettings struct { + ConnectionString string `validate:"required"` + ContainerName string `validate:"required"` +} + +// Validate checks that all fields in KeyConnectorSettings are valid (non-empty in this case) +func (settings *KeyConnectorSettings) Validate() error { + validate := validator.New() + + err := validate.Struct(settings) + if err != nil { + return fmt.Errorf("validation failed: %v", err) + } + return nil +} diff --git a/internal/infrastructure/settings/logger_settings.go b/internal/infrastructure/settings/logger_settings.go index dfd20b6..991ce98 100644 --- a/internal/infrastructure/settings/logger_settings.go +++ b/internal/infrastructure/settings/logger_settings.go @@ -12,7 +12,7 @@ type LoggerSettings struct { FilePath string `validate:"required_if=LogType file"` // File path is required only if LogType is "file" } -// Validate checks that all fields in PKCS11Settings are valid (non-empty in this case) +// Validate checks that all fields in LoggerSettings are valid (non-empty in this case) func (settings *LoggerSettings) Validate() error { validate := validator.New() diff --git a/test/integration/app/services/key_services_test.go b/test/integration/app/services/key_services_test.go index 6bd7a14..c13d684 100644 --- a/test/integration/app/services/key_services_test.go +++ b/test/integration/app/services/key_services_test.go @@ -34,9 +34,11 @@ func TestCryptoKeyUploadService_Upload_Success(t *testing.T) { dbType := "sqlite" defer helpers.TeardownTestDB(t, ctx, dbType) - connectionString := "DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;" - containerName := "testblobs" - vaultConnector, err := connector.NewAzureVaultConnector(connectionString, containerName, logger) + keyConnectorSettings := &settings.KeyConnectorSettings{ + ConnectionString: "DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;", + ContainerName: "testblobs", + } + vaultConnector, err := connector.NewAzureVaultConnector(keyConnectorSettings, logger) require.NoError(t, err, "Error creating vault connector") cryptoKeyUploadService := &services.CryptoKeyUploadService{ @@ -145,9 +147,11 @@ func TestCryptoKeyDownloadService_Download_Success(t *testing.T) { dbType := "sqlite" defer helpers.TeardownTestDB(t, ctx, dbType) - connectionString := "DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;" - containerName := "testblobs" - vaultConnector, err := connector.NewAzureVaultConnector(connectionString, containerName, logger) + blobConnectorSettings := &settings.BlobConnectorSettings{ + ConnectionString: "DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;", + ContainerName: "testblobs", + } + vaultConnector, err := connector.NewAzureVaultConnector((*settings.KeyConnectorSettings)(blobConnectorSettings), logger) require.NoError(t, err, "Error creating vault connector") cryptoKeyUploadService := &services.CryptoKeyUploadService{ diff --git a/test/integration/infrastructure/connector/az_blob_connector_test.go b/test/integration/infrastructure/connector/az_blob_connector_test.go index 8c2e7ef..7b089f9 100644 --- a/test/integration/infrastructure/connector/az_blob_connector_test.go +++ b/test/integration/infrastructure/connector/az_blob_connector_test.go @@ -27,9 +27,12 @@ func TestAzureBlobConnector_Upload(t *testing.T) { log.Fatalf("Error creating logger: %v", err) } - connectionString := "DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;" - containerName := "testblobs" - abc, err := connector.NewAzureBlobConnector(connectionString, containerName, logger) + blobConnectorSettings := &settings.BlobConnectorSettings{ + ConnectionString: "DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;", + ContainerName: "testblobs", + } + + abc, err := connector.NewAzureBlobConnector(blobConnectorSettings, logger) require.NoError(t, err) testFilePath := "testfile.txt" @@ -69,9 +72,11 @@ func TestAzureBlobConnector_Download(t *testing.T) { log.Fatalf("Error creating logger: %v", err) } - connectionString := "DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;" - containerName := "testblobs" - abc, err := connector.NewAzureBlobConnector(connectionString, containerName, logger) + blobConnectorSettings := &settings.BlobConnectorSettings{ + ConnectionString: "DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;", + ContainerName: "testblobs", + } + abc, err := connector.NewAzureBlobConnector(blobConnectorSettings, logger) require.NoError(t, err) testFilePath := "testfile.pem" @@ -110,9 +115,11 @@ func TestAzureBlobConnector_Delete(t *testing.T) { log.Fatalf("Error creating logger: %v", err) } - connectionString := "DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;" - containerName := "testblobs" - abc, err := connector.NewAzureBlobConnector(connectionString, containerName, logger) + blobConnectorSettings := &settings.BlobConnectorSettings{ + ConnectionString: "DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;", + ContainerName: "testblobs", + } + abc, err := connector.NewAzureBlobConnector(blobConnectorSettings, logger) require.NoError(t, err) testFilePath := "testfile.pem" diff --git a/test/integration/infrastructure/connector/az_vault_connector_test.go b/test/integration/infrastructure/connector/az_vault_connector_test.go index 87a784d..28c2c9c 100644 --- a/test/integration/infrastructure/connector/az_vault_connector_test.go +++ b/test/integration/infrastructure/connector/az_vault_connector_test.go @@ -28,9 +28,11 @@ func TestAzureVaultConnector_Upload(t *testing.T) { log.Fatalf("Error creating logger: %v", err) } - connectionString := "DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;" - containerName := "testblobs" - abc, err := connector.NewAzureVaultConnector(connectionString, containerName, logger) + keyConnectorSettings := &settings.KeyConnectorSettings{ + ConnectionString: "DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;", + ContainerName: "testblobs", + } + abc, err := connector.NewAzureVaultConnector(keyConnectorSettings, logger) require.NoError(t, err) testFilePath := "testfile.txt" @@ -69,9 +71,11 @@ func TestAzureVaultConnector_Download(t *testing.T) { log.Fatalf("Error creating logger: %v", err) } - connectionString := "DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;" - containerName := "testblobs" - abc, err := connector.NewAzureVaultConnector(connectionString, containerName, logger) + keyConnectorSettings := &settings.KeyConnectorSettings{ + ConnectionString: "DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;", + ContainerName: "testblobs", + } + abc, err := connector.NewAzureVaultConnector(keyConnectorSettings, logger) require.NoError(t, err) testFilePath := "testfile.pem" @@ -110,9 +114,11 @@ func TestAzureVaultConnector_Delete(t *testing.T) { log.Fatalf("Error creating logger: %v", err) } - connectionString := "DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;" - containerName := "testblobs" - abc, err := connector.NewAzureVaultConnector(connectionString, containerName, logger) + keyConnectorSettings := &settings.KeyConnectorSettings{ + ConnectionString: "DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;", + ContainerName: "testblobs", + } + abc, err := connector.NewAzureVaultConnector(keyConnectorSettings, logger) require.NoError(t, err) testFilePath := "testfile.pem" diff --git a/test/unit/infrastructure/cryptography/rsa_test.go b/test/unit/infrastructure/cryptography/rsa_test.go index c33d271..b145b6e 100644 --- a/test/unit/infrastructure/cryptography/rsa_test.go +++ b/test/unit/infrastructure/cryptography/rsa_test.go @@ -148,16 +148,44 @@ func (rt *RSATests) TestSavePublicKeyInvalidPath(t *testing.T) { assert.Error(t, err, "Saving public key to an invalid path should return an error") } +// TestSignAndVerify tests signing and verification with RSA keys +func (rt *RSATests) TestSignAndVerify(t *testing.T) { + // Generate RSA keys + privateKey, publicKey, err := rt.rsa.GenerateKeys(2048) + assert.NoError(t, err, "Error generating RSA keys") + + // Data to sign + data := []byte("This is a test message") + + // Sign the data + signature, err := rt.rsa.Sign(data, privateKey) + assert.NoError(t, err, "Error signing the data") + assert.NotNil(t, signature, "Signature should not be nil") + + // Verify the signature + valid, err := rt.rsa.Verify(data, signature, publicKey) + if err != nil { + t.Errorf("Error verifying the signature: %v", err) + } + assert.NoError(t, err, "Error verifying the signature") + assert.True(t, valid, "Signature should be valid") + + // Tamper with the data and verify again (should fail) + tamperedData := []byte("This is a tampered message") + valid, err = rt.rsa.Verify(tamperedData, signature, publicKey) + assert.Error(t, err, "Error verifying the tampered signature") + assert.False(t, valid, "Signature should be invalid for tampered data") +} + // TestRSA is the entry point to run the RSA tests func TestRSA(t *testing.T) { - // Create a new RSA test suite instance rt := NewRSATests(t) - // 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) + t.Run("TestSignAndVerify", rt.TestSignAndVerify) }