1package leaf
2
3import (
4	"context"
5	"fmt"
6	"path/filepath"
7
8	"github.com/gopasspw/gopass/internal/backend"
9	"github.com/gopasspw/gopass/internal/backend/crypto/age"
10	"github.com/gopasspw/gopass/internal/out"
11	"github.com/gopasspw/gopass/pkg/ctxutil"
12	"github.com/gopasspw/gopass/pkg/debug"
13)
14
15func (s *Store) initCryptoBackend(ctx context.Context) error {
16	cb, err := backend.DetectCrypto(ctx, s.storage)
17	if err != nil {
18		return err
19	}
20	s.crypto = cb
21	return nil
22}
23
24// Crypto returns the crypto backend
25func (s *Store) Crypto() backend.Crypto {
26	return s.crypto
27}
28
29// ImportMissingPublicKeys will try to import any missing public keys from the
30// .public-keys folder in the password store
31func (s *Store) ImportMissingPublicKeys(ctx context.Context) error {
32	// do not import any keys for age, where public key == key id
33	if _, ok := s.crypto.(*age.Age); ok {
34		debug.Log("not importing public keys for age")
35		return nil
36	}
37	rs, err := s.GetRecipients(ctx, "")
38	if err != nil {
39		return fmt.Errorf("failed to get recipients: %w", err)
40	}
41	for _, r := range rs {
42		debug.Log("Checking recipients %s ...", r)
43		// check if this recipient is missing
44		// we could list all keys outside the loop and just do the lookup here
45		// but this way we ensure to use the exact same lookup logic as
46		// gpg does on encryption
47		kl, err := s.crypto.FindRecipients(ctx, r)
48		if err != nil {
49			out.Errorf(ctx, "[%s] Failed to get public key for %s: %s", s.alias, r, err)
50		}
51		if len(kl) > 0 {
52			debug.Log("[%s] Keyring contains %d public keys for %s", s.alias, len(kl), r)
53			continue
54		}
55
56		// get info about this public key
57		names, err := s.decodePublicKey(ctx, r)
58		if err != nil {
59			out.Errorf(ctx, "[%s] Failed to decode public key %s: %s", s.alias, r, err)
60			continue
61		}
62
63		// we need to ask the user before importing
64		// any key material into his keyring!
65		if imf := ctxutil.GetImportFunc(ctx); imf != nil {
66			if !imf(ctx, r, names) {
67				continue
68			}
69		}
70
71		debug.Log("[%s] Public Key %s not found in keyring, importing", s.alias, r)
72
73		// try to load this recipient
74		if err := s.importPublicKey(ctx, r); err != nil {
75			out.Errorf(ctx, "[%s] Failed to import public key for %s: %s", s.alias, r, err)
76			continue
77		}
78		out.Printf(ctx, "[%s] Imported public key for %s into Keyring", s.alias, r)
79	}
80	return nil
81}
82
83func (s *Store) decodePublicKey(ctx context.Context, r string) ([]string, error) {
84	for _, kd := range []string{keyDir, oldKeyDir} {
85		filename := filepath.Join(kd, r)
86		if !s.storage.Exists(ctx, filename) {
87			debug.Log("Public Key %s not found at %s", r, filename)
88			continue
89		}
90		buf, err := s.storage.Get(ctx, filename)
91		if err != nil {
92			return nil, fmt.Errorf("unable to read Public Key %q %q: %w", r, filename, err)
93		}
94		return s.crypto.ReadNamesFromKey(ctx, buf)
95	}
96	return nil, fmt.Errorf("public key %q not found", r)
97}
98
99// export an ASCII armored public key
100func (s *Store) exportPublicKey(ctx context.Context, exp keyExporter, r string) (string, error) {
101	filename := filepath.Join(keyDir, r)
102
103	// do not overwrite existing keys
104	if s.storage.Exists(ctx, filename) {
105		return "", nil
106	}
107
108	pk, err := exp.ExportPublicKey(ctx, r)
109	if err != nil {
110		return "", fmt.Errorf("failed to export public key: %w", err)
111	}
112
113	// ECC keys are at least 700 byte, RSA should be a lot bigger
114	if len(pk) < 32 {
115		return "", fmt.Errorf("exported key too small")
116	}
117
118	if err := s.storage.Set(ctx, filename, pk); err != nil {
119		return "", fmt.Errorf("failed to write exported public key to store: %w", err)
120	}
121
122	return filename, nil
123}
124
125type keyImporter interface {
126	ImportPublicKey(ctx context.Context, key []byte) error
127}
128
129// import an public key into the default keyring
130func (s *Store) importPublicKey(ctx context.Context, r string) error {
131	im, ok := s.crypto.(keyImporter)
132	if !ok {
133		debug.Log("importing public keys not supported by %T", s.crypto)
134		return nil
135	}
136
137	for _, kd := range []string{keyDir, oldKeyDir} {
138		filename := filepath.Join(kd, r)
139		if !s.storage.Exists(ctx, filename) {
140			debug.Log("Public Key %s not found at %s", r, filename)
141			continue
142		}
143		pk, err := s.storage.Get(ctx, filename)
144		if err != nil {
145			return err
146		}
147		return im.ImportPublicKey(ctx, pk)
148	}
149	return fmt.Errorf("public key not found in store")
150}
151
152type locker interface {
153	Lock()
154}
155
156// Lock clears the credential caches of all supported backends
157func (s *Store) Lock() error {
158	f, ok := s.crypto.(locker)
159	if !ok {
160		debug.Log("locking not supported by %T in %q", s.crypto, s.alias)
161	}
162	if f == nil {
163		debug.Log("backend %q invalid", s.alias)
164		return nil
165	}
166	f.Lock()
167	debug.Log("locked backend %T for %q", s.crypto, s.alias)
168	return nil
169}
170