Include gpg's stderr in the error when gpg fails (closes #146)
check / check (push) Waiting to run
check / check (push) Waiting to run
When gpg failed while decrypting, encrypting or looking up a key fingerprint, the error gave only gpg's exit status, so the user could not tell why. cmd.Output keeps gpg's stderr in the *exec.ExitError it returns; the three errors now end with that stderr, trimmed. When gpg wrote nothing to stderr, the error is left as it was. gpg's stderr can run to more than one line, and the error then does too. The regression test gives gpg data that is not an OpenPGP message instead of one encrypted to a missing key: gpg reports both the same way, and this case starts no gpg-agent the test would have to stop. Model: opus-5-5
This commit was merged in pull request #179.
This commit is contained in:
@@ -388,7 +388,8 @@ func ResolveGPGKeyFingerprint(keyID string) (string, error) {
|
||||
return "", fmt.Errorf("%w: %s", ErrGPGKeyNotFound, keyID)
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("failed to resolve GPG key fingerprint: %w", err)
|
||||
return "", fmt.Errorf(
|
||||
"failed to resolve GPG key fingerprint: %w", withGPGStderr(err))
|
||||
}
|
||||
|
||||
// Parse the output to extract the fingerprint
|
||||
@@ -418,6 +419,24 @@ func checkGPGAvailable() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// withGPGStderr appends gpg's stderr, which says why gpg failed, to err
|
||||
// from cmd.Output. cmd.Output keeps that stderr in the *exec.ExitError.
|
||||
// err is returned unchanged when gpg wrote nothing to stderr.
|
||||
func withGPGStderr(err error) error {
|
||||
var exitErr *exec.ExitError
|
||||
|
||||
if !errors.As(err, &exitErr) {
|
||||
return err
|
||||
}
|
||||
|
||||
stderr := strings.TrimSpace(string(exitErr.Stderr))
|
||||
if stderr == "" {
|
||||
return err
|
||||
}
|
||||
|
||||
return fmt.Errorf("%w: %s", err, stderr)
|
||||
}
|
||||
|
||||
// gpgEncryptDefault is the default implementation of GPG encryption
|
||||
func gpgEncryptDefault(data *memguard.LockedBuffer, keyID string) ([]byte, error) {
|
||||
if data == nil {
|
||||
@@ -437,7 +456,7 @@ func gpgEncryptDefault(data *memguard.LockedBuffer, keyID string) ([]byte, error
|
||||
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GPG encryption failed: %w", err)
|
||||
return nil, fmt.Errorf("GPG encryption failed: %w", withGPGStderr(err))
|
||||
}
|
||||
|
||||
return output, nil
|
||||
@@ -450,7 +469,7 @@ func gpgDecryptDefault(encryptedData []byte) (*memguard.LockedBuffer, error) {
|
||||
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GPG decryption failed: %w", err)
|
||||
return nil, fmt.Errorf("GPG decryption failed: %w", withGPGStderr(err))
|
||||
}
|
||||
|
||||
// Create a secure buffer for the decrypted data
|
||||
|
||||
@@ -102,3 +102,27 @@ func TestPGPUnlockerAddedTwiceKeepsFirst(t *testing.T) {
|
||||
assert.NotEqual(t, first.GetDirectory(), second.GetDirectory())
|
||||
assert.Equal(t, firstFiles, dirFiles(t, fs, first.GetDirectory()))
|
||||
}
|
||||
|
||||
// TestGPGDecryptErrorHasGPGReason runs the real gpg on data that is not an
|
||||
// OpenPGP message and checks that the error carries the reason gpg writes
|
||||
// to stderr.
|
||||
func TestGPGDecryptErrorHasGPGReason(t *testing.T) {
|
||||
// The empty GNUPGHOME keeps gpg away from the user's own keyring;
|
||||
// LC_ALL=C keeps gpg's message in English.
|
||||
t.Setenv("GNUPGHOME", t.TempDir())
|
||||
t.Setenv("LC_ALL", "C")
|
||||
|
||||
_, err := secret.GPGDecryptFunc([]byte("not an OpenPGP message"))
|
||||
require.ErrorContains(t, err, "gpg: no valid OpenPGP data found")
|
||||
}
|
||||
|
||||
// TestGPGDecryptErrorWithoutGPGStderr checks that when gpg fails without
|
||||
// writing to stderr, the error ends with the exit status and nothing after it.
|
||||
//
|
||||
//nolint:paralleltest // installFakeGPG uses t.Setenv
|
||||
func TestGPGDecryptErrorWithoutGPGStderr(t *testing.T) {
|
||||
installFakeGPG(t)
|
||||
|
||||
_, err := secret.GPGDecryptFunc([]byte("not an OpenPGP message"))
|
||||
require.EqualError(t, err, "GPG decryption failed: exit status 1")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user