mirror of
https://github.com/FiloSottile/age.git
synced 2026-01-08 13:01:09 +00:00
@@ -7,6 +7,7 @@ package main
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -406,7 +407,11 @@ func encrypt(recipients []age.Recipient, in io.Reader, out io.Writer, withArmor
|
||||
out = a
|
||||
}
|
||||
w, err := age.Encrypt(out, recipients...)
|
||||
if err != nil {
|
||||
if e := new(plugin.NotFoundError); errors.As(err, &e) {
|
||||
errorWithHint(err.Error(),
|
||||
fmt.Sprintf("you might want to install the %q plugin", e.Name),
|
||||
"visit https://age-encryption.org/awesome#plugins for a list of available plugins")
|
||||
} else if err != nil {
|
||||
errorf("%v", err)
|
||||
}
|
||||
if _, err := io.Copy(w, in); err != nil {
|
||||
@@ -483,7 +488,11 @@ func decrypt(identities []age.Identity, in io.Reader, out io.Writer) {
|
||||
}
|
||||
|
||||
r, err := age.Decrypt(in, identities...)
|
||||
if err != nil {
|
||||
if e := new(plugin.NotFoundError); errors.As(err, &e) {
|
||||
errorWithHint(err.Error(),
|
||||
fmt.Sprintf("you might want to install the %q plugin", e.Name),
|
||||
"visit https://age-encryption.org/awesome#plugins for a list of available plugins")
|
||||
} else if err != nil {
|
||||
errorf("%v", err)
|
||||
}
|
||||
out.Write(nil) // trigger the lazyOpener even if r is empty
|
||||
|
||||
8
cmd/age/testdata/plugin.txt
vendored
8
cmd/age/testdata/plugin.txt
vendored
@@ -19,6 +19,12 @@ cp age-plugin-pwn/pwn $TMPDIR/age-plugin-pwn/pwn
|
||||
! age -d -j pwn/pwn test.age
|
||||
! exists pwn
|
||||
|
||||
# check plugin not found hint
|
||||
! age -r age1nonexistentplugin1pt5d8z -o test1.age
|
||||
stderr /awesome#plugins
|
||||
! age -d -i nonexistent-identity.txt test.age
|
||||
stderr /awesome#plugins
|
||||
|
||||
-- input --
|
||||
test
|
||||
-- key.txt --
|
||||
@@ -32,3 +38,5 @@ AGE-PLUGIN-PWN/PWN-19GYK4WLY
|
||||
-- age-plugin-pwn/pwn --
|
||||
#!/bin/sh
|
||||
touch "$WORK/pwn"
|
||||
-- nonexistent-identity.txt --
|
||||
AGE-PLUGIN-NONEXISTENTPLUGIN-1R4XFW4
|
||||
|
||||
@@ -9,6 +9,7 @@ package plugin
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
mathrand "math/rand/v2"
|
||||
@@ -402,6 +403,24 @@ type clientConnection struct {
|
||||
close func()
|
||||
}
|
||||
|
||||
// NotFoundError is returned by [Recipient.Wrap] and [Identity.Unwrap] when the
|
||||
// plugin binary cannot be found.
|
||||
type NotFoundError struct {
|
||||
// Name is the plugin (not binary) name.
|
||||
Name string
|
||||
// Err is the underlying error, usually an [exec.Error] wrapping
|
||||
// [exec.ErrNotFound].
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e *NotFoundError) Error() string {
|
||||
return fmt.Sprintf("%q plugin not found: %v", e.Name, e.Err)
|
||||
}
|
||||
|
||||
func (e *NotFoundError) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
||||
|
||||
var testOnlyPluginPath string
|
||||
|
||||
func openClientConnection(name, protocol string) (*clientConnection, error) {
|
||||
@@ -444,6 +463,9 @@ func openClientConnection(name, protocol string) (*clientConnection, error) {
|
||||
cmd.Dir = os.TempDir()
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
if errors.Is(err, exec.ErrNotFound) {
|
||||
return nil, &NotFoundError{Name: name, Err: err}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
||||
@@ -7,8 +7,11 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"testing"
|
||||
@@ -108,3 +111,55 @@ func TestLabels(t *testing.T) {
|
||||
t.Errorf("expected one pqc and one normal to fail")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNotFound(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("Windows support is TODO")
|
||||
}
|
||||
|
||||
r := EncodeRecipient("nonexistentplugin", nil)
|
||||
t.Log(r)
|
||||
testPluginRecipient, err := NewRecipient(r, &ClientUI{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var e *NotFoundError
|
||||
if _, err := age.Encrypt(io.Discard, testPluginRecipient); err == nil {
|
||||
t.Errorf("expected error for nonexistent plugin")
|
||||
} else if !errors.As(err, &e) {
|
||||
t.Errorf("expected NotFoundError, got %T: %v", err, err)
|
||||
} else if e.Name != "nonexistentplugin" {
|
||||
t.Errorf("expected NotFoundError.Name to be nonexistentplugin, got %q", e.Name)
|
||||
} else if !errors.Is(err, exec.ErrNotFound) {
|
||||
t.Errorf("expected error to wrap exec.ErrNotFound, got: %v", err)
|
||||
}
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
id, err := age.GenerateHybridIdentity()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
w, err := age.Encrypt(buf, id.Recipient())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
w.Close()
|
||||
|
||||
i := EncodeIdentity("nonexistentplugin", nil)
|
||||
t.Log(i)
|
||||
testPluginIdentity, err := NewIdentity(i, &ClientUI{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := age.Decrypt(buf, testPluginIdentity); err == nil {
|
||||
t.Errorf("expected error for nonexistent plugin")
|
||||
} else if errors.As(err, new(*age.NoIdentityMatchError)) {
|
||||
t.Errorf("expected NotFoundError, got NoIdentityMatchError: %v", err)
|
||||
} else if !errors.As(err, &e) {
|
||||
t.Errorf("expected NotFoundError, got %T: %v", err, err)
|
||||
} else if e.Name != "nonexistentplugin" {
|
||||
t.Errorf("expected NotFoundError.Name to be nonexistentplugin, got %q", e.Name)
|
||||
} else if !errors.Is(err, exec.ErrNotFound) {
|
||||
t.Errorf("expected error to wrap exec.ErrNotFound, got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user