Add comprehensive PGP unlock key testing with non-interactive GPG support
This commit is contained in:
parent
85d7ef21eb
commit
1a1b11c5a3
@ -15,6 +15,17 @@ import (
|
|||||||
"github.com/spf13/afero"
|
"github.com/spf13/afero"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Variables to allow overriding in tests
|
||||||
|
var (
|
||||||
|
// GPGEncryptFunc is the function used for GPG encryption
|
||||||
|
// Can be overridden in tests to provide a non-interactive implementation
|
||||||
|
GPGEncryptFunc = gpgEncryptDefault
|
||||||
|
|
||||||
|
// GPGDecryptFunc is the function used for GPG decryption
|
||||||
|
// Can be overridden in tests to provide a non-interactive implementation
|
||||||
|
GPGDecryptFunc = gpgDecryptDefault
|
||||||
|
)
|
||||||
|
|
||||||
// PGPUnlockKeyMetadata extends UnlockKeyMetadata with PGP-specific data
|
// PGPUnlockKeyMetadata extends UnlockKeyMetadata with PGP-specific data
|
||||||
type PGPUnlockKeyMetadata struct {
|
type PGPUnlockKeyMetadata struct {
|
||||||
UnlockKeyMetadata
|
UnlockKeyMetadata
|
||||||
@ -56,7 +67,7 @@ func (p *PGPUnlockKey) GetIdentity() (*age.X25519Identity, error) {
|
|||||||
|
|
||||||
// Step 2: Decrypt the age private key using GPG
|
// Step 2: Decrypt the age private key using GPG
|
||||||
Debug("Decrypting age private key with GPG", "key_id", p.GetID())
|
Debug("Decrypting age private key with GPG", "key_id", p.GetID())
|
||||||
agePrivKeyData, err := gpgDecrypt(encryptedAgePrivKeyData)
|
agePrivKeyData, err := GPGDecryptFunc(encryptedAgePrivKeyData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Debug("Failed to decrypt age private key with GPG", "error", err, "key_id", p.GetID())
|
Debug("Failed to decrypt age private key with GPG", "error", err, "key_id", p.GetID())
|
||||||
return nil, fmt.Errorf("failed to decrypt age private key with GPG: %w", err)
|
return nil, fmt.Errorf("failed to decrypt age private key with GPG: %w", err)
|
||||||
@ -271,7 +282,7 @@ func CreatePGPUnlockKey(fs afero.Fs, stateDir string, gpgKeyID string) (*PGPUnlo
|
|||||||
|
|
||||||
// Step 8: Encrypt age private key to the GPG key ID
|
// Step 8: Encrypt age private key to the GPG key ID
|
||||||
agePrivateKeyBytes := []byte(ageIdentity.String())
|
agePrivateKeyBytes := []byte(ageIdentity.String())
|
||||||
encryptedAgePrivKey, err := gpgEncrypt(agePrivateKeyBytes, gpgKeyID)
|
encryptedAgePrivKey, err := GPGEncryptFunc(agePrivateKeyBytes, gpgKeyID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to encrypt age private key with GPG: %w", err)
|
return nil, fmt.Errorf("failed to encrypt age private key with GPG: %w", err)
|
||||||
}
|
}
|
||||||
@ -322,8 +333,8 @@ func checkGPGAvailable() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// gpgEncrypt encrypts data to the specified GPG key ID
|
// gpgEncryptDefault is the default implementation of GPG encryption
|
||||||
func gpgEncrypt(data []byte, keyID string) ([]byte, error) {
|
func gpgEncryptDefault(data []byte, keyID string) ([]byte, error) {
|
||||||
cmd := exec.Command("gpg", "--trust-model", "always", "--armor", "--encrypt", "-r", keyID)
|
cmd := exec.Command("gpg", "--trust-model", "always", "--armor", "--encrypt", "-r", keyID)
|
||||||
cmd.Stdin = strings.NewReader(string(data))
|
cmd.Stdin = strings.NewReader(string(data))
|
||||||
|
|
||||||
@ -335,8 +346,8 @@ func gpgEncrypt(data []byte, keyID string) ([]byte, error) {
|
|||||||
return output, nil
|
return output, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// gpgDecrypt decrypts GPG-encrypted data
|
// gpgDecryptDefault is the default implementation of GPG decryption
|
||||||
func gpgDecrypt(encryptedData []byte) ([]byte, error) {
|
func gpgDecryptDefault(encryptedData []byte) ([]byte, error) {
|
||||||
cmd := exec.Command("gpg", "--quiet", "--decrypt")
|
cmd := exec.Command("gpg", "--quiet", "--decrypt")
|
||||||
cmd.Stdin = strings.NewReader(string(encryptedData))
|
cmd.Stdin = strings.NewReader(string(encryptedData))
|
||||||
|
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
package secret_test
|
package secret_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@ -10,10 +12,117 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"filippo.io/age"
|
||||||
"git.eeqj.de/sneak/secret/internal/secret"
|
"git.eeqj.de/sneak/secret/internal/secret"
|
||||||
|
"git.eeqj.de/sneak/secret/internal/vault"
|
||||||
|
"git.eeqj.de/sneak/secret/pkg/agehd"
|
||||||
"github.com/spf13/afero"
|
"github.com/spf13/afero"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Register vault with secret package for testing
|
||||||
|
func init() {
|
||||||
|
// Register the vault.GetCurrentVault function with the secret package
|
||||||
|
secret.RegisterGetCurrentVaultFunc(func(fs afero.Fs, stateDir string) (secret.VaultInterface, error) {
|
||||||
|
return vault.GetCurrentVault(fs, stateDir)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// setupNonInteractiveGPG creates a custom GPG environment for testing
|
||||||
|
func setupNonInteractiveGPG(t *testing.T, tempDir, passphrase, gnupgHomeDir string) {
|
||||||
|
// Create GPG config file for non-interactive operation
|
||||||
|
gpgConfPath := filepath.Join(gnupgHomeDir, "gpg.conf")
|
||||||
|
gpgConfContent := `batch
|
||||||
|
no-tty
|
||||||
|
pinentry-mode loopback
|
||||||
|
`
|
||||||
|
if err := os.WriteFile(gpgConfPath, []byte(gpgConfContent), 0600); err != nil {
|
||||||
|
t.Fatalf("Failed to write GPG config file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a test-specific GPG implementation
|
||||||
|
origEncryptFunc := secret.GPGEncryptFunc
|
||||||
|
origDecryptFunc := secret.GPGDecryptFunc
|
||||||
|
|
||||||
|
// Set custom GPG functions for this test
|
||||||
|
secret.GPGEncryptFunc = func(data []byte, keyID string) ([]byte, error) {
|
||||||
|
cmd := exec.Command("gpg",
|
||||||
|
"--homedir", gnupgHomeDir,
|
||||||
|
"--batch",
|
||||||
|
"--yes",
|
||||||
|
"--pinentry-mode", "loopback",
|
||||||
|
"--passphrase", passphrase,
|
||||||
|
"--trust-model", "always",
|
||||||
|
"--armor",
|
||||||
|
"--encrypt",
|
||||||
|
"-r", keyID)
|
||||||
|
|
||||||
|
var stdout, stderr bytes.Buffer
|
||||||
|
cmd.Stdout = &stdout
|
||||||
|
cmd.Stderr = &stderr
|
||||||
|
cmd.Stdin = bytes.NewReader(data)
|
||||||
|
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
return nil, fmt.Errorf("GPG encryption failed: %w\nStderr: %s", err, stderr.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
return stdout.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
secret.GPGDecryptFunc = func(encryptedData []byte) ([]byte, error) {
|
||||||
|
cmd := exec.Command("gpg",
|
||||||
|
"--homedir", gnupgHomeDir,
|
||||||
|
"--batch",
|
||||||
|
"--yes",
|
||||||
|
"--pinentry-mode", "loopback",
|
||||||
|
"--passphrase", passphrase,
|
||||||
|
"--quiet",
|
||||||
|
"--decrypt")
|
||||||
|
|
||||||
|
var stdout, stderr bytes.Buffer
|
||||||
|
cmd.Stdout = &stdout
|
||||||
|
cmd.Stderr = &stderr
|
||||||
|
cmd.Stdin = bytes.NewReader(encryptedData)
|
||||||
|
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
return nil, fmt.Errorf("GPG decryption failed: %w\nStderr: %s", err, stderr.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
return stdout.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore original functions after test
|
||||||
|
t.Cleanup(func() {
|
||||||
|
secret.GPGEncryptFunc = origEncryptFunc
|
||||||
|
secret.GPGDecryptFunc = origDecryptFunc
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// runGPGWithPassphrase executes a GPG command with the specified passphrase
|
||||||
|
func runGPGWithPassphrase(gnupgHome, passphrase string, args []string, input io.Reader) ([]byte, error) {
|
||||||
|
cmdArgs := []string{
|
||||||
|
"--homedir=" + gnupgHome,
|
||||||
|
"--batch",
|
||||||
|
"--yes",
|
||||||
|
"--pinentry-mode", "loopback",
|
||||||
|
"--passphrase", passphrase,
|
||||||
|
}
|
||||||
|
cmdArgs = append(cmdArgs, args...)
|
||||||
|
|
||||||
|
cmd := exec.Command("gpg", cmdArgs...)
|
||||||
|
cmd.Stdin = input
|
||||||
|
|
||||||
|
var stdout, stderr bytes.Buffer
|
||||||
|
cmd.Stdout = &stdout
|
||||||
|
cmd.Stderr = &stderr
|
||||||
|
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("GPG command failed: %w\nStderr: %s", err, stderr.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
return stdout.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
func TestPGPUnlockKeyWithRealFS(t *testing.T) {
|
func TestPGPUnlockKeyWithRealFS(t *testing.T) {
|
||||||
// Skip tests if gpg is not available
|
// Skip tests if gpg is not available
|
||||||
if _, err := exec.LookPath("gpg"); err != nil {
|
if _, err := exec.LookPath("gpg"); err != nil {
|
||||||
@ -48,6 +157,12 @@ func TestPGPUnlockKeyWithRealFS(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
// Test passphrase for GPG key
|
||||||
|
testPassphrase := "test123"
|
||||||
|
|
||||||
|
// Setup non-interactive GPG with custom functions
|
||||||
|
setupNonInteractiveGPG(t, tempDir, testPassphrase, gnupgHomeDir)
|
||||||
|
|
||||||
// Create GPG batch file for key generation
|
// Create GPG batch file for key generation
|
||||||
batchFile := filepath.Join(tempDir, "gen-key-batch")
|
batchFile := filepath.Join(tempDir, "gen-key-batch")
|
||||||
batchContent := `%echo Generating a test key
|
batchContent := `%echo Generating a test key
|
||||||
@ -56,7 +171,7 @@ Key-Length: 2048
|
|||||||
Name-Real: Test User
|
Name-Real: Test User
|
||||||
Name-Email: test@example.com
|
Name-Email: test@example.com
|
||||||
Expire-Date: 0
|
Expire-Date: 0
|
||||||
Passphrase: test123
|
Passphrase: ` + testPassphrase + `
|
||||||
%commit
|
%commit
|
||||||
%echo Key generation completed
|
%echo Key generation completed
|
||||||
`
|
`
|
||||||
@ -64,20 +179,20 @@ Passphrase: test123
|
|||||||
t.Fatalf("Failed to write batch file: %v", err)
|
t.Fatalf("Failed to write batch file: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate GPG key
|
// Generate GPG key with batch mode
|
||||||
t.Log("Generating GPG key...")
|
t.Log("Generating GPG key...")
|
||||||
cmd := exec.Command("gpg", "--batch", "--gen-key", batchFile)
|
output, err := runGPGWithPassphrase(gnupgHomeDir, testPassphrase,
|
||||||
output, err := cmd.CombinedOutput()
|
[]string{"--gen-key", batchFile}, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to generate GPG key: %v\nOutput: %s", err, output)
|
t.Fatalf("Failed to generate GPG key: %v", err)
|
||||||
}
|
}
|
||||||
t.Log("GPG key generated successfully")
|
t.Log("GPG key generated successfully")
|
||||||
|
|
||||||
// Get the key ID
|
// Get the key ID
|
||||||
cmd = exec.Command("gpg", "--list-secret-keys", "--with-colons")
|
output, err = runGPGWithPassphrase(gnupgHomeDir, testPassphrase,
|
||||||
output, err = cmd.CombinedOutput()
|
[]string{"--list-secret-keys", "--with-colons"}, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to list GPG keys: %v\nOutput: %s", err, output)
|
t.Fatalf("Failed to list GPG keys: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse output to get key ID
|
// Parse output to get key ID
|
||||||
@ -98,11 +213,16 @@ Passphrase: test123
|
|||||||
}
|
}
|
||||||
t.Logf("Generated GPG key ID: %s", keyID)
|
t.Logf("Generated GPG key ID: %s", keyID)
|
||||||
|
|
||||||
// Export GNUPGHOME variable to ensure subprocesses inherit it
|
// Set the GPG_AGENT_INFO to empty to ensure gpg-agent doesn't interfere
|
||||||
err = os.Setenv("GNUPGHOME", gnupgHomeDir)
|
oldAgentInfo := os.Getenv("GPG_AGENT_INFO")
|
||||||
if err != nil {
|
os.Setenv("GPG_AGENT_INFO", "")
|
||||||
t.Fatalf("Failed to set GNUPGHOME environment variable: %v", err)
|
defer func() {
|
||||||
}
|
if oldAgentInfo != "" {
|
||||||
|
os.Setenv("GPG_AGENT_INFO", oldAgentInfo)
|
||||||
|
} else {
|
||||||
|
os.Unsetenv("GPG_AGENT_INFO")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
// Use the real filesystem
|
// Use the real filesystem
|
||||||
fs := afero.NewOsFs()
|
fs := afero.NewOsFs()
|
||||||
@ -133,7 +253,158 @@ Passphrase: test123
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Create the directory structure for test
|
// Set up vault structure for testing
|
||||||
|
stateDir := tempDir
|
||||||
|
vaultName := "test-vault"
|
||||||
|
|
||||||
|
// Test creation of a PGP unlock key through a vault
|
||||||
|
t.Run("CreatePGPUnlockKey", func(t *testing.T) {
|
||||||
|
// Set a limited test timeout to avoid hanging
|
||||||
|
timer := time.AfterFunc(30*time.Second, func() {
|
||||||
|
t.Fatalf("Test timed out after 30 seconds")
|
||||||
|
})
|
||||||
|
defer timer.Stop()
|
||||||
|
|
||||||
|
// Create a test vault directory structure
|
||||||
|
vlt, err := vault.CreateVault(fs, stateDir, vaultName)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create vault: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the current vault
|
||||||
|
err = vault.SelectVault(fs, stateDir, vaultName)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to select vault: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Derive long-term key from mnemonic
|
||||||
|
ltIdentity, err := agehd.DeriveIdentity(testMnemonic, 0)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to derive long-term key: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the vault directory
|
||||||
|
vaultDir, err := vlt.GetDirectory()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to get vault directory: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write long-term public key
|
||||||
|
ltPubKeyPath := filepath.Join(vaultDir, "pub.age")
|
||||||
|
if err := afero.WriteFile(fs, ltPubKeyPath, []byte(ltIdentity.Recipient().String()), secret.FilePerms); err != nil {
|
||||||
|
t.Fatalf("Failed to write long-term public key: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unlock the vault
|
||||||
|
vlt.Unlock(ltIdentity)
|
||||||
|
|
||||||
|
// Create a passphrase unlock key first (to have current unlock key)
|
||||||
|
passKey, err := vlt.CreatePassphraseKey("test-passphrase")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create passphrase key: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify passphrase key was created
|
||||||
|
if passKey == nil {
|
||||||
|
t.Fatal("Passphrase key is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now create a PGP unlock key (this will use our custom GPGEncryptFunc)
|
||||||
|
pgpKey, err := secret.CreatePGPUnlockKey(fs, stateDir, keyID)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create PGP unlock key: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the PGP unlock key was created
|
||||||
|
if pgpKey == nil {
|
||||||
|
t.Fatal("PGP unlock key is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the key has the correct type
|
||||||
|
if pgpKey.GetType() != "pgp" {
|
||||||
|
t.Errorf("Expected PGP unlock key type 'pgp', got '%s'", pgpKey.GetType())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the key ID includes the GPG key ID
|
||||||
|
if !strings.Contains(pgpKey.GetID(), keyID) {
|
||||||
|
t.Errorf("PGP unlock key ID '%s' does not contain GPG key ID '%s'", pgpKey.GetID(), keyID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the key directory exists
|
||||||
|
keyDir := pgpKey.GetDirectory()
|
||||||
|
keyExists, err := afero.DirExists(fs, keyDir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to check if PGP key directory exists: %v", err)
|
||||||
|
}
|
||||||
|
if !keyExists {
|
||||||
|
t.Errorf("PGP unlock key directory does not exist: %s", keyDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if required files exist
|
||||||
|
pubKeyPath := filepath.Join(keyDir, "pub.age")
|
||||||
|
pubKeyExists, err := afero.Exists(fs, pubKeyPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to check if public key file exists: %v", err)
|
||||||
|
}
|
||||||
|
if !pubKeyExists {
|
||||||
|
t.Errorf("PGP unlock key public key file does not exist: %s", pubKeyPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
privKeyPath := filepath.Join(keyDir, "priv.age.gpg")
|
||||||
|
privKeyExists, err := afero.Exists(fs, privKeyPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to check if private key file exists: %v", err)
|
||||||
|
}
|
||||||
|
if !privKeyExists {
|
||||||
|
t.Errorf("PGP unlock key private key file does not exist: %s", privKeyPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
metadataPath := filepath.Join(keyDir, "unlock-metadata.json")
|
||||||
|
metadataExists, err := afero.Exists(fs, metadataPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to check if metadata file exists: %v", err)
|
||||||
|
}
|
||||||
|
if !metadataExists {
|
||||||
|
t.Errorf("PGP unlock key metadata file does not exist: %s", metadataPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
longtermPath := filepath.Join(keyDir, "longterm.age")
|
||||||
|
longtermExists, err := afero.Exists(fs, longtermPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to check if longterm key file exists: %v", err)
|
||||||
|
}
|
||||||
|
if !longtermExists {
|
||||||
|
t.Errorf("PGP unlock key longterm key file does not exist: %s", longtermPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read and verify metadata
|
||||||
|
metadataBytes, err := afero.ReadFile(fs, metadataPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to read metadata: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var metadata struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
Flags []string `json:"flags"`
|
||||||
|
GPGKeyID string `json:"gpg_key_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.Unmarshal(metadataBytes, &metadata); err != nil {
|
||||||
|
t.Fatalf("Failed to parse metadata: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if metadata.Type != "pgp" {
|
||||||
|
t.Errorf("Expected metadata type 'pgp', got '%s'", metadata.Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
if metadata.GPGKeyID != keyID {
|
||||||
|
t.Errorf("Expected GPG key ID '%s', got '%s'", keyID, metadata.GPGKeyID)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Set up key directory for individual tests
|
||||||
keyDir := filepath.Join(tempDir, "unlock-key")
|
keyDir := filepath.Join(tempDir, "unlock-key")
|
||||||
if err := os.MkdirAll(keyDir, secret.DirPerms); err != nil {
|
if err := os.MkdirAll(keyDir, secret.DirPerms); err != nil {
|
||||||
t.Fatalf("Failed to create key directory: %v", err)
|
t.Fatalf("Failed to create key directory: %v", err)
|
||||||
@ -147,11 +418,6 @@ Passphrase: test123
|
|||||||
Flags: []string{"gpg", "encrypted"},
|
Flags: []string{"gpg", "encrypted"},
|
||||||
}
|
}
|
||||||
|
|
||||||
// We'll skip the CreatePGPUnlockKey test since it requires registered vault functions
|
|
||||||
t.Run("CreatePGPUnlockKey", func(t *testing.T) {
|
|
||||||
t.Skip("Skipping test that requires registered vault functions")
|
|
||||||
})
|
|
||||||
|
|
||||||
// Create a PGP unlock key for the remaining tests
|
// Create a PGP unlock key for the remaining tests
|
||||||
unlockKey := secret.NewPGPUnlockKey(fs, keyDir, metadata)
|
unlockKey := secret.NewPGPUnlockKey(fs, keyDir, metadata)
|
||||||
|
|
||||||
@ -192,9 +458,43 @@ Passphrase: test123
|
|||||||
|
|
||||||
// Test getting identity from PGP unlock key
|
// Test getting identity from PGP unlock key
|
||||||
t.Run("GetIdentity", func(t *testing.T) {
|
t.Run("GetIdentity", func(t *testing.T) {
|
||||||
// For this test, we'll do a simplified version since GPG operations
|
// Generate an age identity for testing
|
||||||
// can be tricky in automated tests
|
ageIdentity, err := age.GenerateX25519Identity()
|
||||||
t.Skip("Skipping GetIdentity test due to complex GPG operations in automated testing")
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to generate age identity: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the public key
|
||||||
|
pubKeyPath := filepath.Join(keyDir, "pub.age")
|
||||||
|
if err := afero.WriteFile(fs, pubKeyPath, []byte(ageIdentity.Recipient().String()), secret.FilePerms); err != nil {
|
||||||
|
t.Fatalf("Failed to write public key: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GPG encrypt the private key using our custom encrypt function
|
||||||
|
privKeyData := []byte(ageIdentity.String())
|
||||||
|
encryptedOutput, err := secret.GPGEncryptFunc(privKeyData, keyID)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to encrypt with GPG: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the encrypted data to a file
|
||||||
|
encryptedPath := filepath.Join(keyDir, "priv.age.gpg")
|
||||||
|
if err := afero.WriteFile(fs, encryptedPath, encryptedOutput, secret.FilePerms); err != nil {
|
||||||
|
t.Fatalf("Failed to write encrypted private key: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now try to get the identity - this will use our custom GPGDecryptFunc
|
||||||
|
identity, err := unlockKey.GetIdentity()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to get identity: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the identity matches
|
||||||
|
expectedPubKey := ageIdentity.Recipient().String()
|
||||||
|
actualPubKey := identity.Recipient().String()
|
||||||
|
if actualPubKey != expectedPubKey {
|
||||||
|
t.Errorf("Expected public key '%s', got '%s'", expectedPubKey, actualPubKey)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Test removing the unlock key
|
// Test removing the unlock key
|
||||||
|
Loading…
Reference in New Issue
Block a user