mirror of
https://github.com/gopasspw/gopass.git
synced 2026-05-30 11:18:48 +02:00
01bc3cde22
* 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>
262 lines
6.5 KiB
Go
262 lines
6.5 KiB
Go
package leaf
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"path/filepath"
|
|
|
|
"github.com/gopasspw/gopass/internal/backend"
|
|
"github.com/gopasspw/gopass/internal/backend/crypto/age"
|
|
"github.com/gopasspw/gopass/internal/config"
|
|
"github.com/gopasspw/gopass/internal/out"
|
|
"github.com/gopasspw/gopass/internal/store"
|
|
"github.com/gopasspw/gopass/pkg/ctxutil"
|
|
"github.com/gopasspw/gopass/pkg/debug"
|
|
)
|
|
|
|
func (s *Store) initCryptoBackend(ctx context.Context) error {
|
|
cb, err := backend.DetectCrypto(ctx, s.storage)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
s.crypto = cb
|
|
|
|
return nil
|
|
}
|
|
|
|
// Crypto returns the crypto backend.
|
|
func (s *Store) Crypto() backend.Crypto {
|
|
return s.crypto
|
|
}
|
|
|
|
// recipientCheck checks if a recipient is already present in the keyring and up-to-date.
|
|
// It returns true if the recipient is fine and the import can be skipped.
|
|
func (s *Store) recipientCheck(ctx context.Context, r string) bool {
|
|
// check if this recipient is missing
|
|
// we could list all keys outside the loop and just do the lookup here
|
|
// but this way we ensure to use the exact same lookup logic as
|
|
// gpg does on encryption
|
|
kl, err := s.crypto.FindRecipients(ctx, r)
|
|
if err != nil {
|
|
// this is expected if we don't have the key
|
|
debug.Log("Failed to get public key for %s: %s", r, err)
|
|
}
|
|
|
|
if len(kl) > 0 { //nolint:nestif
|
|
debug.Log("Keyring contains %d public keys for %s", len(kl), r)
|
|
if !IsPubkeyUpdate(ctx) {
|
|
return true
|
|
}
|
|
ex, ok := s.crypto.(keyExporter)
|
|
if !ok {
|
|
return true
|
|
}
|
|
pk, err := ex.ExportPublicKey(ctx, r)
|
|
if err != nil {
|
|
return true
|
|
}
|
|
pk2, err2 := s.getPublicKey(ctx, r)
|
|
if err2 != nil {
|
|
return true
|
|
}
|
|
if bytes.Equal(pk, pk2) {
|
|
return true
|
|
}
|
|
} else {
|
|
// if key is not found, try to check by fingerprint
|
|
pk, err := s.getPublicKey(ctx, r)
|
|
if err != nil {
|
|
debug.Log("failed to get public key for %s: %s", r, err)
|
|
|
|
return true
|
|
}
|
|
fp, err := s.crypto.GetFingerprint(ctx, pk)
|
|
if err != nil {
|
|
debug.Log("failed to get fingerprint for %s: %s", r, err)
|
|
|
|
return true
|
|
}
|
|
kl, err = s.crypto.FindRecipients(ctx, fp)
|
|
if err != nil {
|
|
debug.Log("failed to find recipients for %s: %s", fp, err)
|
|
}
|
|
if len(kl) > 0 {
|
|
debug.Log("key %s with fingerprint %s already in keyring", r, fp)
|
|
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// ImportMissingPublicKeys will try to import any missing public keys from the
|
|
// .public-keys folder in the password store.
|
|
func (s *Store) ImportMissingPublicKeys(ctx context.Context, newrs ...string) error {
|
|
// do not import any keys for age, where public key == key id
|
|
// TODO: do not hard code exceptions, ask the backend if it supports it
|
|
if _, ok := s.crypto.(*age.Age); ok {
|
|
debug.Log("not importing public keys for age")
|
|
|
|
return nil
|
|
}
|
|
|
|
rs, err := s.GetRecipients(ctx, "")
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get recipients: %w", err)
|
|
}
|
|
|
|
ids := append(rs.IDs(), newrs...)
|
|
for _, r := range ids {
|
|
debug.Log("Checking recipients %s ...", r)
|
|
if s.recipientCheck(ctx, r) {
|
|
continue
|
|
}
|
|
|
|
// get info about this public key
|
|
names, err := s.decodePublicKey(ctx, r)
|
|
if err != nil {
|
|
out.Errorf(ctx, "Failed to decode public key %s: %s", r, err)
|
|
|
|
continue
|
|
}
|
|
|
|
// we need to ask the user before importing
|
|
// any key material into his keyring!
|
|
if imf := ctxutil.GetImportFunc(ctx); imf != nil && !config.Bool(ctx, "core.autoimport") {
|
|
if !imf(ctx, r, names) {
|
|
continue
|
|
}
|
|
}
|
|
|
|
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, "Failed to import public key for %s: %s", r, err)
|
|
|
|
continue
|
|
}
|
|
out.Printf(ctx, "Imported public key for %s into Keyring", r)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *Store) decodePublicKey(ctx context.Context, r string) ([]string, error) {
|
|
for _, kd := range []string{keyDir, oldKeyDir} {
|
|
filename := filepath.Join(kd, r)
|
|
if !s.storage.Exists(ctx, filename) {
|
|
debug.Log("Public Key %s not found at %s", r, filename)
|
|
|
|
continue
|
|
}
|
|
buf, err := s.storage.Get(ctx, filename)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("unable to read Public Key %q %q: %w", r, filename, err)
|
|
}
|
|
|
|
return s.crypto.ReadNamesFromKey(ctx, buf)
|
|
}
|
|
|
|
return nil, fmt.Errorf("public key %q not found", r)
|
|
}
|
|
|
|
// export an ASCII armored public key.
|
|
func (s *Store) exportPublicKey(ctx context.Context, exp keyExporter, r string) (string, error) {
|
|
filename := filepath.Join(keyDir, r)
|
|
|
|
// 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
|
|
}
|
|
|
|
pk, err := exp.ExportPublicKey(ctx, r)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to export public key: %w", err)
|
|
}
|
|
|
|
// ECC keys are at least 700 byte, RSA should be a lot bigger
|
|
if len(pk) < 32 {
|
|
return "", fmt.Errorf("exported key too small")
|
|
}
|
|
|
|
if err := s.storage.Set(ctx, filename, pk); err != nil {
|
|
if !errors.Is(err, store.ErrMeaninglessWrite) {
|
|
return "", fmt.Errorf("failed to write exported public key to store: %w", err)
|
|
}
|
|
debug.Log("No need to write exported public key %s: already stored", r)
|
|
}
|
|
|
|
debug.Log("exported public keys for %s to %s", r, filename)
|
|
|
|
return filename, nil
|
|
}
|
|
|
|
type keyImporter interface {
|
|
ImportPublicKey(ctx context.Context, key []byte) error
|
|
}
|
|
type keyExporter interface {
|
|
ExportPublicKey(ctx context.Context, id string) ([]byte, error)
|
|
}
|
|
|
|
func (s *Store) getPublicKey(ctx context.Context, r string) ([]byte, error) {
|
|
for _, kd := range []string{keyDir, oldKeyDir} {
|
|
filename := filepath.Join(kd, r)
|
|
if !s.storage.Exists(ctx, filename) {
|
|
debug.Log("Public Key %s not found at %s", r, filename)
|
|
|
|
continue
|
|
}
|
|
pk, err := s.storage.Get(ctx, filename)
|
|
|
|
return pk, err
|
|
}
|
|
|
|
return nil, fmt.Errorf("public key not found in store")
|
|
}
|
|
|
|
// import an public key into the default keyring.
|
|
func (s *Store) importPublicKey(ctx context.Context, r string) error {
|
|
im, ok := s.crypto.(keyImporter)
|
|
if !ok {
|
|
debug.Log("importing public keys not supported by %T", s.crypto)
|
|
|
|
return nil
|
|
}
|
|
|
|
pk, err := s.getPublicKey(ctx, r)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return im.ImportPublicKey(ctx, pk)
|
|
}
|
|
|
|
type locker interface {
|
|
Lock()
|
|
}
|
|
|
|
// Lock clears the credential caches of all supported backends.
|
|
func (s *Store) Lock() error {
|
|
f, ok := s.crypto.(locker)
|
|
if !ok {
|
|
debug.Log("locking not supported by %T in %q", s.crypto, s.alias)
|
|
}
|
|
|
|
if f == nil {
|
|
debug.Log("backend %q invalid", s.alias)
|
|
|
|
return nil
|
|
}
|
|
|
|
f.Lock()
|
|
debug.Log("locked backend %T for %q", s.crypto, s.alias)
|
|
|
|
return nil
|
|
}
|