mirror of
https://github.com/gopasspw/gopass.git
synced 2026-05-30 11:18:48 +02:00
0a10134d40
Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org>
146 lines
3.7 KiB
Go
146 lines
3.7 KiB
Go
package age
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"strings"
|
|
|
|
"filippo.io/age"
|
|
"github.com/gopasspw/gopass/internal/backend/crypto/age/agent"
|
|
|
|
"github.com/gopasspw/gopass/internal/config"
|
|
"github.com/gopasspw/gopass/pkg/ctxutil"
|
|
"github.com/gopasspw/gopass/pkg/debug"
|
|
)
|
|
|
|
// Decrypt will attempt to decrypt the given payload.
|
|
func (a *Age) Decrypt(ctx context.Context, ciphertext []byte) ([]byte, error) {
|
|
if config.Bool(ctx, "age.agent-enabled") {
|
|
plaintext, err := a.decryptWithAgent(ctx, ciphertext)
|
|
if err == nil {
|
|
return plaintext, nil
|
|
}
|
|
debug.Log("failed to decrypt with agent: %s", err)
|
|
debug.Log("falling back to direct decryption")
|
|
}
|
|
|
|
if !ctxutil.HasPasswordCallback(ctx) {
|
|
debug.Log("no password callback found, redirecting to askPass")
|
|
ctx = ctxutil.WithPasswordCallback(ctx, func(prompt string, _ bool) ([]byte, error) {
|
|
pw, err := a.askPass.Passphrase(prompt, fmt.Sprintf("to load the keyring at %s", a.identity), false)
|
|
|
|
return []byte(pw), err
|
|
})
|
|
ctx = ctxutil.WithPasswordPurgeCallback(ctx, a.askPass.Remove)
|
|
}
|
|
|
|
ids, err := a.getAllIds(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return a.decrypt(ciphertext, ids...)
|
|
}
|
|
|
|
func (a *Age) decryptWithAgent(ctx context.Context, ciphertext []byte) ([]byte, error) {
|
|
client := agent.NewClient()
|
|
plaintext, err := client.Decrypt(ciphertext)
|
|
if err == nil {
|
|
return plaintext, nil
|
|
}
|
|
|
|
if !strings.Contains(err.Error(), "agent is locked") {
|
|
debug.Log("failed to decrypt with agent: %s", err)
|
|
|
|
return nil, err
|
|
}
|
|
|
|
debug.Log("agent is locked, trying to unlock")
|
|
// unlock the agent
|
|
if err := client.Unlock(); err != nil {
|
|
debug.Log("failed to unlock agent: %s", err)
|
|
}
|
|
// get identities
|
|
ids, err := a.getAllIds(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// send identities to agent
|
|
sIds, err := a.identitiesToString(ids)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err := client.SendIdentities(sIds); err != nil {
|
|
debug.Log("failed to send identities to agent: %s", err)
|
|
}
|
|
// set timeout
|
|
if timeout := config.AsInt(config.String(ctx, "age.agent-timeout")); timeout > 0 {
|
|
if err := client.SetTimeout(timeout); err != nil {
|
|
debug.Log("failed to set agent timeout: %s", err)
|
|
}
|
|
}
|
|
// retry decryption
|
|
return client.Decrypt(ciphertext)
|
|
}
|
|
|
|
func (a *Age) decrypt(ciphertext []byte, ids ...age.Identity) ([]byte, error) {
|
|
debug.V(1).Log("decrypting with %d ids", len(ids))
|
|
|
|
out := &bytes.Buffer{}
|
|
f := bytes.NewReader(ciphertext)
|
|
r, err := age.Decrypt(f, ids...)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to decrypt: %w", err)
|
|
}
|
|
n, err := io.Copy(out, r)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to write plaintext to buffer: %w", err)
|
|
}
|
|
debug.V(1).Log("Decrypted %d bytes of ciphertext to %d bytes of plaintext", len(ciphertext), n)
|
|
|
|
return out.Bytes(), nil
|
|
}
|
|
|
|
// decryptFile is used to decrypt a scrypt encrypted age keyring/identity file.
|
|
func (a *Age) decryptFile(ctx context.Context, filename string) ([]byte, error) {
|
|
ciphertext, err := os.ReadFile(filename)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
debug.V(1).Log("read %d bytes from %s", len(ciphertext), filename)
|
|
|
|
pw, err := ctxutil.GetPasswordCallback(ctx)(filename, false)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// debug.Log("deriving scrypt identity from password: %q", out.Secret(pw))
|
|
id, err := age.NewScryptIdentity(string(pw))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
plaintext, err := a.decrypt(ciphertext, id)
|
|
if err != nil {
|
|
ctxutil.GetPasswordPurgeCallback(ctx)(filename)
|
|
}
|
|
|
|
return plaintext, err
|
|
}
|
|
|
|
func (a *Age) getAllIds(ctx context.Context) ([]age.Identity, error) {
|
|
ids, err := a.getAllIdentities(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
idl := make([]age.Identity, 0, len(ids))
|
|
for _, id := range ids {
|
|
idl = append(idl, id)
|
|
}
|
|
|
|
return idl, nil
|
|
}
|