mirror of
https://github.com/gopasspw/gopass.git
synced 2026-09-30 11:16:53 +02:00
* Adding regression test Signed-off-by: Yolan Romailler <AnomalRoil@users.noreply.github.com> * fixing recipient logic to honor subkeys Signed-off-by: Yolan Romailler <AnomalRoil@users.noreply.github.com> * Revert "Adding regression test" This reverts commit fcb85c9d2ee4ce3b1d53f934338c6a33e18d7d9d. Signed-off-by: Yolan Romailler <AnomalRoil@users.noreply.github.com> * adding comment about noop Signed-off-by: Yolan Romailler <AnomalRoil@users.noreply.github.com> * Linting Signed-off-by: Yolan Romailler <AnomalRoil@users.noreply.github.com> * Addressing review comments Signed-off-by: Yolan Romailler <AnomalRoil@users.noreply.github.com> * avoid noise debug logs Signed-off-by: Yolan Romailler <AnomalRoil@users.noreply.github.com> --------- Signed-off-by: Yolan Romailler <AnomalRoil@users.noreply.github.com>
194 lines
4.9 KiB
Go
194 lines
4.9 KiB
Go
package cli
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"os/exec"
|
|
"strings"
|
|
"text/template"
|
|
|
|
"github.com/ProtonMail/go-crypto/openpgp"
|
|
"github.com/gopasspw/gopass/internal/backend/crypto/gpg"
|
|
"github.com/gopasspw/gopass/internal/backend/crypto/gpg/colons"
|
|
"github.com/gopasspw/gopass/internal/out"
|
|
"github.com/gopasspw/gopass/pkg/debug"
|
|
)
|
|
|
|
// listKey lists all keys of the given type and matching the search strings.
|
|
func (g *GPG) listKeys(ctx context.Context, typ string, search ...string) (gpg.KeyList, error) {
|
|
debug.Log("listing %s keys for %v", typ, search)
|
|
ctx, cancel := context.WithTimeout(ctx, Timeout)
|
|
defer cancel()
|
|
|
|
args := []string{"--with-colons", "--with-fingerprint", "--fixed-list-mode", "--list-" + typ + "-keys"}
|
|
args = append(args, search...)
|
|
if e, found := g.listCache.Get(strings.Join(args, ",")); found && gpg.UseCache(ctx) {
|
|
debug.Log("listed cached keys: %q", strings.Join(e.Recipients(), ","))
|
|
|
|
return e, nil
|
|
}
|
|
|
|
cmd := exec.CommandContext(ctx, g.binary, args...)
|
|
errBuf := bytes.Buffer{}
|
|
cmd.Stderr = &errBuf
|
|
|
|
debug.V(1).Log("%s %+v\n", cmd.Path, cmd.Args)
|
|
cmdout, err := cmd.Output()
|
|
if err != nil {
|
|
if bytes.Contains(cmdout, []byte("secret key not available")) || bytes.Contains(errBuf.Bytes(), []byte("No secret key")) {
|
|
debug.Log("secret key not available for %v", search)
|
|
|
|
return gpg.KeyList{}, nil
|
|
}
|
|
errStr := fmt.Errorf("%w: %s|%s", err, cmdout, errBuf.String())
|
|
debug.Log("cmd error listing %s keys: %q", typ, errStr)
|
|
|
|
return gpg.KeyList{}, errStr
|
|
}
|
|
|
|
kl := colons.Parse(bytes.NewBuffer(cmdout))
|
|
g.listCache.Add(strings.Join(args, ","), kl)
|
|
|
|
debug.Log("listed non-cached keys: %q", strings.Join(kl.Recipients(), ","))
|
|
|
|
return kl, nil
|
|
}
|
|
|
|
// Fingerprint returns the fingerprint.
|
|
func (g *GPG) Fingerprint(ctx context.Context, id string) string {
|
|
k, found := g.findKey(ctx, id)
|
|
if !found {
|
|
return ""
|
|
}
|
|
|
|
return k.Fingerprint
|
|
}
|
|
|
|
// FormatKey formats the details of a key id
|
|
// Examples:
|
|
// - NameFromKey: {{ .Name }}
|
|
// - EmailFromKey: {{ .Email }}.
|
|
func (g *GPG) FormatKey(ctx context.Context, id, tpl string) string {
|
|
if tpl == "" {
|
|
k, found := g.findKey(ctx, id)
|
|
if !found {
|
|
return ""
|
|
}
|
|
|
|
return k.OneLine()
|
|
}
|
|
|
|
tmpl, err := template.New(tpl).Parse(tpl)
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
|
|
var gid gpg.Identity
|
|
k, found := g.findKey(ctx, id)
|
|
if found {
|
|
gid = k.Identity()
|
|
}
|
|
|
|
buf := &bytes.Buffer{}
|
|
if err := tmpl.Execute(buf, gid); err != nil {
|
|
debug.Log("Failed to render template %q: %s", tpl, err)
|
|
|
|
return ""
|
|
}
|
|
|
|
return buf.String()
|
|
}
|
|
|
|
// ReadNamesFromKey unmarshals and returns the names associated with the given public key.
|
|
func (g *GPG) ReadNamesFromKey(ctx context.Context, buf []byte) ([]string, error) {
|
|
el, err := openpgp.ReadArmoredKeyRing(bytes.NewReader(buf))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read key ring: %w", err)
|
|
}
|
|
|
|
if len(el) != 1 {
|
|
return nil, fmt.Errorf("public Key must contain exactly one Entity")
|
|
}
|
|
|
|
names := make([]string, 0, len(el[0].Identities))
|
|
for _, v := range el[0].Identities {
|
|
names = append(names, v.Name)
|
|
}
|
|
|
|
return names, nil
|
|
}
|
|
|
|
// ImportPublicKey will import a key from the given location into the keyring.
|
|
func (g *GPG) ImportPublicKey(ctx context.Context, buf []byte) error {
|
|
if len(buf) < 1 {
|
|
return fmt.Errorf("empty input")
|
|
}
|
|
|
|
outBuf := &bytes.Buffer{}
|
|
|
|
args := append(g.args, "--import")
|
|
cmd := exec.CommandContext(ctx, g.binary, args...)
|
|
cmd.Stdin = bytes.NewReader(buf)
|
|
cmd.Stdout = outBuf
|
|
cmd.Stderr = outBuf
|
|
|
|
debug.Log("gpg.ImportPublicKey: %s %+v", cmd.Path, cmd.Args)
|
|
if err := cmd.Run(); err != nil {
|
|
out.Printf(ctx, "GPG import failed: %s", outBuf.String())
|
|
|
|
return fmt.Errorf("failed to run command: '%s %+v': %w - %q", cmd.Path, cmd.Args, err, outBuf.String())
|
|
}
|
|
|
|
// clear key cache
|
|
g.privKeys = nil
|
|
g.pubKeys = nil
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetFingerprint returns the fingerprint of a key.
|
|
func (g *GPG) GetFingerprint(ctx context.Context, buf []byte) (string, error) {
|
|
if len(buf) < 1 {
|
|
return "", fmt.Errorf("empty input")
|
|
}
|
|
|
|
el, err := openpgp.ReadArmoredKeyRing(bytes.NewReader(buf))
|
|
if err != nil {
|
|
// maybe it's a non-armored key?
|
|
el, err = openpgp.ReadKeyRing(bytes.NewReader(buf))
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to read key ring: %w", err)
|
|
}
|
|
}
|
|
|
|
if len(el) != 1 {
|
|
return "", fmt.Errorf("public Key must contain exactly one Entity")
|
|
}
|
|
|
|
return strings.ToUpper(hex.EncodeToString(el[0].PrimaryKey.Fingerprint[:])), nil
|
|
}
|
|
|
|
// ExportPublicKey will export the named public key to the location given.
|
|
func (g *GPG) ExportPublicKey(ctx context.Context, id string) ([]byte, error) {
|
|
if id == "" {
|
|
return nil, fmt.Errorf("id is empty")
|
|
}
|
|
|
|
args := append(g.args, "--armor", "--export", id)
|
|
cmd := exec.CommandContext(ctx, g.binary, args...)
|
|
|
|
debug.Log("%s %+v", cmd.Path, cmd.Args)
|
|
out, err := cmd.Output()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to run command '%s %+v': %w", cmd.Path, cmd.Args, err)
|
|
}
|
|
|
|
if len(out) < 1 {
|
|
return nil, fmt.Errorf("key not found")
|
|
}
|
|
|
|
return out, nil
|
|
}
|