gopass/internal/backend/crypto.go
google-labs-jules[bot] 01bc3cde22
fix(gpg): Opportunistic key comparison on import (#3230)
* fix(gpg): Opportunistic key comparison on import

When importing keys gopass was already checking if the key was already present.
However, GPG is very flexible in which kind of key IDs it accepts. So it could happen that gopass would ask to import a key that is already present in the keyring, but referenced by a different ID.

This change makes the check more robust by checking the key's fingerprint before asking for import. If a key with the same fingerprint is already present, the import is skipped.

* fix(gpg): Opportunistic key comparison on import

When importing keys gopass was already checking if the key was already present.
However, GPG is very flexible in which kind of key IDs it accepts. So it could happen that gopass would ask to import a key that is already present in the keyring, but referenced by a different ID.

This change makes the check more robust by checking the key's fingerprint before asking for import. If a key with the same fingerprint is already present, the import is skipped.

---------

Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
2025-09-19 10:54:20 +02:00

95 lines
2.6 KiB
Go

package backend
import (
"context"
"fmt"
"github.com/blang/semver/v4"
"github.com/gopasspw/gopass/pkg/debug"
)
// CryptoBackend is a cryptographic backend.
type CryptoBackend int
const (
// Plain is a no-op crypto backend.
Plain CryptoBackend = iota
// GPGCLI is a gpg-cli based crypto backend.
GPGCLI
// Age - age-encryption.org.
Age
)
func (c CryptoBackend) String() string {
if be, err := CryptoRegistry.BackendName(c); err == nil {
return be
}
return ""
}
// Keyring is a public/private key manager.
type Keyring interface {
ListRecipients(ctx context.Context) ([]string, error)
ListIdentities(ctx context.Context) ([]string, error)
FindRecipients(ctx context.Context, needles ...string) ([]string, error)
FindIdentities(ctx context.Context, needles ...string) ([]string, error)
Fingerprint(ctx context.Context, id string) string
FormatKey(ctx context.Context, id, tpl string) string
ReadNamesFromKey(ctx context.Context, buf []byte) ([]string, error)
GetFingerprint(ctx context.Context, key []byte) (string, error)
GenerateIdentity(ctx context.Context, name, email, passphrase string) (string, error)
}
// Crypto is a crypto backend.
type Crypto interface {
Keyring
Encrypt(ctx context.Context, plaintext []byte, recipients []string) ([]byte, error)
Decrypt(ctx context.Context, ciphertext []byte) ([]byte, error)
RecipientIDs(ctx context.Context, ciphertext []byte) ([]string, error)
Name() string
Version(context.Context) semver.Version
Initialized(ctx context.Context) error
Ext() string // filename extension.
IDFile() string // recipient IDs.
Concurrency() int
}
// NewCrypto instantiates a new crypto backend.
func NewCrypto(ctx context.Context, id CryptoBackend) (Crypto, error) {
if be, err := CryptoRegistry.Get(id); err == nil {
return be.New(ctx)
}
return nil, fmt.Errorf("unknown backend %d: %w", id, ErrNotFound)
}
// DetectCrypto tries to detect the crypto backend used.
func DetectCrypto(ctx context.Context, storage Storage) (Crypto, error) {
if HasCryptoBackend(ctx) {
if be, err := CryptoRegistry.Get(GetCryptoBackend(ctx)); err == nil {
return be.New(ctx)
}
}
for _, be := range CryptoRegistry.Prioritized() {
debug.Log("Trying %s for %s", be, storage)
if err := be.Handles(ctx, storage); err != nil {
debug.Log("failed to use crypto %s for %s", be, storage)
continue
}
debug.Log("Using %s for %s", be, storage)
return be.New(ctx)
}
debug.Log("No valid crypto provider found for %s", storage)
// TODO: this should return ErrNotSupported, but need to fix some tests for that
return nil, nil //nolint:nilnil
}