Skip to content

Commit

Permalink
Export and import all recipient public keys during fsck (#2383)
Browse files Browse the repository at this point in the history
Fixes #2369

RELEASE_NOTES=[ENHANCEMENT] Improve key expiration handling

Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org>

Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org>
  • Loading branch information
dominikschulz authored Oct 17, 2022
1 parent 894b624 commit 8926b34
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 14 deletions.
12 changes: 8 additions & 4 deletions internal/backend/crypto/gpg/cli/keyring.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ import (
"bytes"
"context"
"fmt"
"os"
"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"
)

Expand Down Expand Up @@ -115,15 +115,19 @@ func (g *GPG) ImportPublicKey(ctx context.Context, buf []byte) error {
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 = os.Stdout
cmd.Stderr = os.Stderr
cmd.Stdout = outBuf
cmd.Stderr = outBuf

debug.Log("gpg.ImportPublicKey: %s %+v", cmd.Path, cmd.Args)
if err := cmd.Run(); err != nil {
return fmt.Errorf("failed to run command: '%s %+v': %w", cmd.Path, cmd.Args, err)
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
Expand Down
13 changes: 13 additions & 0 deletions internal/store/leaf/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const (
ctxKeyCheckRecipients
ctxKeyFsckDecrypt
ctxKeyNoGitOps
ctxKeyPubkeyUpdate
)

// WithFsckCheck returns a context with the flag for fscks check set.
Expand Down Expand Up @@ -132,6 +133,18 @@ func IsNoGitOps(ctx context.Context) bool {
return is(ctx, ctxKeyNoGitOps, false)
}

// IsPubkeyUpdate returns true if we should update all exported
// recipients pub keys.
func IsPubkeyUpdate(ctx context.Context) bool {
return is(ctx, ctxKeyPubkeyUpdate, false)
}

// WithPubkeyUpdate returns a context with the selection to update
// all exported recipients pub keys set.
func WithPubkeyUpdate(ctx context.Context, d bool) context.Context {
return context.WithValue(ctx, ctxKeyPubkeyUpdate, d)
}

// hasBool is a helper function for checking if a bool has been set in
// the provided context.
func hasBool(ctx context.Context, key contextKey) bool {
Expand Down
20 changes: 11 additions & 9 deletions internal/store/leaf/crypto.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,19 +52,19 @@ func (s *Store) ImportMissingPublicKeys(ctx context.Context, newrs ...string) er
kl, err := s.crypto.FindRecipients(ctx, r)
if err != nil {
// this is expected if we don't have the key
debug.Log("[%s] Failed to get public key for %s: %s", s.alias, r, err)
debug.Log("Failed to get public key for %s: %s", r, err)
}

if len(kl) > 0 {
debug.Log("[%s] Keyring contains %d public keys for %s", s.alias, len(kl), r)
if !IsPubkeyUpdate(ctx) && len(kl) > 0 {
debug.Log("Keyring contains %d public keys for %s", len(kl), r)

continue
}

// get info about this public key
names, err := s.decodePublicKey(ctx, r)
if err != nil {
out.Errorf(ctx, "[%s] Failed to decode public key %s: %s", s.alias, r, err)
out.Errorf(ctx, "Failed to decode public key %s: %s", r, err)

continue
}
Expand All @@ -77,15 +77,15 @@ func (s *Store) ImportMissingPublicKeys(ctx context.Context, newrs ...string) er
}
}

debug.Log("[%s] Public Key %s not found in keyring, importing", s.alias, r)
debug.Log("Public Key %s not found in keyring, importing", r)

// try to load this recipient
if err := s.importPublicKey(ctx, r); err != nil {
out.Errorf(ctx, "[%s] Failed to import public key for %s: %s", s.alias, r, err)
out.Errorf(ctx, "Failed to import public key for %s: %s", r, err)

continue
}
out.Printf(ctx, "[%s] Imported public key for %s into Keyring", s.alias, r)
out.Printf(ctx, "Imported public key for %s into Keyring", r)
}

return nil
Expand Down Expand Up @@ -114,8 +114,10 @@ func (s *Store) decodePublicKey(ctx context.Context, r string) ([]string, error)
func (s *Store) exportPublicKey(ctx context.Context, exp keyExporter, r string) (string, error) {
filename := filepath.Join(keyDir, r)

// do not overwrite existing keys
if s.storage.Exists(ctx, filename) {
// do not overwrite existing keys, unless forced
if !IsPubkeyUpdate(ctx) && s.storage.Exists(ctx, filename) {
debug.Log("leaving existing key for %s at %s alone", filename)

return "", nil
}

Expand Down
27 changes: 26 additions & 1 deletion internal/store/leaf/fsck.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ func (s *Store) Fsck(ctx context.Context, path string) error {
}
}

if err := s.fsckUpdatePublicKeys(ctx); err != nil {
out.Errorf(ctx, "Failed to update public keys: %s", err)
}

if err := s.storage.Push(ctx, "", ""); err != nil {
if !errors.Is(err, store.ErrGitNoRemote) {
out.Printf(ctx, "RCS Push failed: %s", err)
Expand All @@ -69,6 +73,27 @@ func (s *Store) Fsck(ctx context.Context, path string) error {
return nil
}

func (s *Store) fsckUpdatePublicKeys(ctx context.Context) error {
ctx = WithPubkeyUpdate(ctx, true)
rs := s.Recipients(ctx)

// first import possibly new/updated keys to merge any changes
// that might come from others.
if err := s.ImportMissingPublicKeys(ctx, rs...); err != nil {
return fmt.Errorf("failed to import new or updated pubkeys: %w", err)
}

// then export our (possibly updated) keys for consumption
// by others.
exported, err := s.UpdateExportedPublicKeys(ctx, rs)
if err != nil {
return fmt.Errorf("failed to update exported pubkeys: %w", err)
}
debug.Log("Updated exported public keys: %t", exported)

return nil
}

type convertedSecret interface {
gopass.Secret
FromMime() bool
Expand Down Expand Up @@ -112,7 +137,7 @@ func (s *Store) fsckCheckEntry(ctx context.Context, name string) error {

func (s *Store) fsckCheckRecipients(ctx context.Context, name string) error {
// now compare the recipients this secret was encoded for and fix it if
// if doesn't match
// it doesn't match.
ciphertext, err := s.storage.Get(ctx, s.Passfile(name))
if err != nil {
return fmt.Errorf("failed to get raw secret: %w", err)
Expand Down

0 comments on commit 8926b34

Please sign in to comment.