Skip to content

Commit 33a52f1

Browse files
committed
store/keychain: internalize store.ID mapping to attributes
Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com>
1 parent 5f3e3b2 commit 33a52f1

5 files changed

Lines changed: 106 additions & 107 deletions

File tree

store/keychain/keychain.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,3 +71,29 @@ func matchesFilter(filter, secretAttributes map[string]string) bool {
7171
}
7272
return true
7373
}
74+
75+
// convertSecretID takes in a valid [store.ID] and converts it into a map
76+
// stored inside the secret attributes.
77+
// This allows partial ID matching.
78+
func convertSecretID(id store.ID) map[string]string {
79+
attributes := make(map[string]string)
80+
parts := strings.SplitSeq(id.String(), "/")
81+
for p := range parts {
82+
if p == "" {
83+
continue
84+
}
85+
attributes["internal_secret_id_"+p] = p
86+
}
87+
return attributes
88+
}
89+
90+
// cleanSecretAttributes strips any attributes mapped to the secret for
91+
// internal use
92+
func cleanSecretAttributes(attributes map[string]string) map[string]string {
93+
for k := range attributes {
94+
if strings.HasPrefix(k, "internal_secret_id_") {
95+
delete(attributes, k)
96+
}
97+
}
98+
return attributes
99+
}

store/keychain/keychain_darwin.go

Lines changed: 8 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import (
44
"context"
55
"errors"
66
"fmt"
7-
"strings"
7+
"maps"
88

99
kc "github.com/Benehiko/go-keychain/v2"
1010

@@ -108,7 +108,7 @@ func (k *keychainStore[T]) Get(_ context.Context, id store.ID) (store.Secret, er
108108
}
109109

110110
secret := k.factory()
111-
if err := secret.SetMetadata(attr); err != nil {
111+
if err := secret.SetMetadata(cleanSecretAttributes(attr)); err != nil {
112112
return nil, err
113113
}
114114
if err := secret.Unmarshal(result.Data); err != nil {
@@ -141,7 +141,7 @@ func (k *keychainStore[T]) GetAll(context.Context) (map[store.ID]store.Secret, e
141141
return nil, err
142142
}
143143
secret := k.factory()
144-
if err := secret.SetMetadata(attr); err != nil {
144+
if err := secret.SetMetadata(cleanSecretAttributes(attr)); err != nil {
145145
return nil, err
146146
}
147147
creds[id] = secret
@@ -165,18 +165,9 @@ func (k *keychainStore[T]) Save(_ context.Context, id store.ID, secret store.Sec
165165
// https://developer.apple.com/documentation/security/ksecattrlabel
166166
item.SetLabel(k.itemLabel(id))
167167

168-
metadata := make(map[string]any)
169-
for k, v := range secret.Metadata() {
170-
metadata[k] = v
171-
}
172-
parts := strings.SplitSeq(id.String(), "/")
173-
for p := range parts {
174-
if p == "" {
175-
continue
176-
}
177-
metadata[p] = p
178-
}
179-
168+
metadata := make(map[string]string)
169+
maps.Copy(metadata, secret.Metadata())
170+
maps.Copy(metadata, convertSecretID(id))
180171
item.SetGenericMetadata(metadata)
181172

182173
return mapKeychainError(kc.AddItem(item))
@@ -202,9 +193,7 @@ func (k *keychainStore[T]) Filter(_ context.Context, id store.ID, filter map[str
202193
if filter == nil {
203194
filter = make(map[string]string)
204195
}
205-
for p := range strings.SplitSeq(id.String(), "/") {
206-
filter[p] = p
207-
}
196+
maps.Copy(filter, convertSecretID(id))
208197

209198
creds := make(map[store.ID]store.Secret)
210199
for _, result := range results {
@@ -228,7 +217,7 @@ func (k *keychainStore[T]) Filter(_ context.Context, id store.ID, filter map[str
228217
}
229218

230219
secret := k.factory()
231-
if err := secret.SetMetadata(attr); err != nil {
220+
if err := secret.SetMetadata(cleanSecretAttributes(attr)); err != nil {
232221
return nil, err
233222
}
234223
if err := secret.Unmarshal(i.Data); err != nil {

store/keychain/keychain_linux.go

Lines changed: 17 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import (
99
"fmt"
1010
"maps"
1111
"slices"
12-
"strings"
1312

1413
kc "github.com/Benehiko/go-keychain/v2/secretservice"
1514
dbus "github.com/godbus/dbus/v5"
@@ -203,7 +202,7 @@ func (k *keychainStore[T]) Get(_ context.Context, id store.ID) (store.Secret, er
203202
}
204203

205204
secret := k.factory()
206-
if err := secret.SetMetadata(attributes); err != nil {
205+
if err := secret.SetMetadata(cleanSecretAttributes(attributes)); err != nil {
207206
return nil, err
208207
}
209208
if err := secret.Unmarshal(value); err != nil {
@@ -268,7 +267,7 @@ func (k *keychainStore[T]) GetAll(context.Context) (map[store.ID]store.Secret, e
268267
}
269268

270269
secret := k.factory()
271-
if err := secret.SetMetadata(attributes); err != nil {
270+
if err := secret.SetMetadata(cleanSecretAttributes(attributes)); err != nil {
272271
return nil, err
273272
}
274273
credentials[secretID] = secret
@@ -319,16 +318,8 @@ func (k *keychainStore[T]) Save(_ context.Context, id store.ID, secret store.Sec
319318
}
320319

321320
attributes := newItemAttributes(id.String(), k)
322-
323-
parts := strings.SplitSeq(id.String(), "/")
324-
for p := range parts {
325-
if p == "" {
326-
continue
327-
}
328-
attributes[p] = p
329-
}
330-
331321
maps.Copy(attributes, secret.Metadata())
322+
maps.Copy(attributes, convertSecretID(id))
332323

333324
label := k.itemLabel(id)
334325
properties := kc.NewSecretProperties(label, attributes)
@@ -370,16 +361,8 @@ func (k *keychainStore[T]) Filter(_ context.Context, id store.ID, attributes map
370361
}
371362

372363
itemAttr := newItemAttributes("", k)
373-
374364
maps.Copy(itemAttr, attributes)
375-
376-
parts := strings.SplitSeq(id.String(), "/")
377-
for p := range parts {
378-
if p == "" {
379-
continue
380-
}
381-
itemAttr[p] = p
382-
}
365+
maps.Copy(itemAttr, convertSecretID(id))
383366

384367
itemPaths, err := service.SearchCollection(objectPath, itemAttr)
385368
if err != nil {
@@ -390,35 +373,36 @@ func (k *keychainStore[T]) Filter(_ context.Context, id store.ID, attributes map
390373
return nil, store.ErrCredentialNotFound
391374
}
392375

393-
credentials := make(map[store.ID]store.Secret, len(itemPaths))
376+
credentials := make(map[store.ID]store.Secret)
394377
for _, itemPath := range itemPaths {
395-
value, err := service.GetSecret(itemPath, *session)
396-
if err != nil {
397-
return nil, err
398-
}
399-
400378
attributes, err := service.GetAttributes(itemPath)
401379
if err != nil {
402380
return nil, err
403381
}
404382

405383
attrID, ok := attributes["id"]
406384
if !ok {
407-
return nil, errors.New("secret attributes does not contain `id` field")
385+
continue
386+
}
387+
388+
secretID, err := store.ParseID(attrID)
389+
if err != nil {
390+
continue
391+
}
392+
393+
value, err := service.GetSecret(itemPath, *session)
394+
if err != nil {
395+
return nil, err
408396
}
409397

410398
secret := k.factory()
411-
if err := secret.SetMetadata(attributes); err != nil {
399+
if err := secret.SetMetadata(cleanSecretAttributes(attributes)); err != nil {
412400
return nil, err
413401
}
414402
if err := secret.Unmarshal(value); err != nil {
415403
return nil, err
416404
}
417405

418-
secretID, err := store.ParseID(attrID)
419-
if err != nil {
420-
return nil, err
421-
}
422406
credentials[secretID] = secret
423407
}
424408

store/keychain/keychain_test.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,3 +348,25 @@ func TestMatchesFilter(t *testing.T) {
348348
})
349349
}
350350
}
351+
352+
func TestConvertID(t *testing.T) {
353+
id := store.ID("com.test.test/application/username")
354+
attributes := convertSecretID(id)
355+
assert.EqualValues(t, map[string]string{
356+
"internal_secret_id_com.test.test": "com.test.test",
357+
"internal_secret_id_application": "application",
358+
"internal_secret_id_username": "username",
359+
}, attributes)
360+
}
361+
362+
func TestCleanAttributes(t *testing.T) {
363+
attributes := map[string]string{
364+
"internal_secret_id_com.test.test": "com.test.test",
365+
"internal_secret_id_application": "application",
366+
"internal_secret_id_username": "username",
367+
"color": "green",
368+
}
369+
assert.EqualValues(t, map[string]string{
370+
"color": "green",
371+
}, cleanSecretAttributes(attributes))
372+
}

store/keychain/keychain_windows.go

Lines changed: 33 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,25 @@ func decodeSecret(blob []byte, secret store.Secret) error {
5151
return secret.Unmarshal(val)
5252
}
5353

54+
func mapToWindowsAttributes(attributes map[string]string) []wincred.CredentialAttribute {
55+
winAttrs := make([]wincred.CredentialAttribute, 0, len(attributes))
56+
for k, v := range attributes {
57+
winAttrs = append(winAttrs, wincred.CredentialAttribute{
58+
Keyword: k,
59+
Value: []byte(v),
60+
})
61+
}
62+
return winAttrs
63+
}
64+
65+
func mapFromWindowsAttributes(winAttrs []wincred.CredentialAttribute) map[string]string {
66+
attributes := make(map[string]string, len(winAttrs))
67+
for _, attr := range winAttrs {
68+
attributes[attr.Keyword] = string(attr.Value)
69+
}
70+
return attributes
71+
}
72+
5473
func (k *keychainStore[T]) Delete(_ context.Context, id store.ID) error {
5574
if err := id.Valid(); err != nil {
5675
return err
@@ -74,10 +93,7 @@ func (k *keychainStore[T]) Get(_ context.Context, id store.ID) (store.Secret, er
7493
return nil, mapWindowsCredentialError(err)
7594
}
7695

77-
attributes := make(map[string]string)
78-
for _, attr := range gc.Attributes {
79-
attributes[attr.Keyword] = string(attr.Value)
80-
}
96+
attributes := cleanSecretAttributes(mapFromWindowsAttributes(gc.Attributes))
8197

8298
secret := k.factory()
8399
if err := secret.SetMetadata(attributes); err != nil {
@@ -148,12 +164,9 @@ func (k *keychainStore[T]) GetAll(context.Context) (map[store.ID]store.Secret, e
148164
continue
149165
}
150166

151-
secretAttr := make(map[string]string, len(cred.Attributes))
152-
for _, attr := range cred.Attributes {
153-
secretAttr[attr.Keyword] = string(attr.Value)
154-
}
167+
attributes := cleanSecretAttributes(mapFromWindowsAttributes(cred.Attributes))
155168
secret := k.factory()
156-
if err := secret.SetMetadata(secretAttr); err != nil {
169+
if err := secret.SetMetadata(attributes); err != nil {
157170
return nil, err
158171
}
159172
secrets[id] = secret
@@ -172,44 +185,19 @@ func (k *keychainStore[T]) Save(_ context.Context, id store.ID, secret store.Sec
172185
return err
173186
}
174187

175-
attributes := []wincred.CredentialAttribute{}
176-
parts := strings.SplitSeq(id.String(), "/")
177-
for p := range parts {
178-
if p == "" {
179-
continue
180-
}
181-
attributes = append(attributes, wincred.CredentialAttribute{
182-
Keyword: p,
183-
Value: []byte(p),
184-
})
185-
}
186-
for k, v := range secret.Metadata() {
187-
attributes = append(attributes, wincred.CredentialAttribute{
188-
Keyword: k,
189-
Value: []byte(v),
190-
})
191-
}
188+
attributes := make(map[string]string)
189+
maps.Copy(attributes, secret.Metadata())
190+
maps.Copy(attributes, convertSecretID(id))
192191

193-
attributes = append(attributes,
194-
wincred.CredentialAttribute{
195-
Keyword: "id",
196-
Value: []byte(id.String()),
197-
},
198-
wincred.CredentialAttribute{
199-
Keyword: "service:group",
200-
Value: []byte(k.serviceGroup),
201-
},
202-
wincred.CredentialAttribute{
203-
Keyword: "service:name",
204-
Value: []byte(k.serviceName),
205-
},
206-
)
192+
attributes["id"] = id.String()
193+
attributes["service:group"] = k.serviceGroup
194+
attributes["service:name"] = k.serviceName
207195

208196
g := wincred.NewGenericCredential(k.itemLabel(id))
209197
g.UserName = id.String()
210198
g.CredentialBlob = blob
211199
g.Persist = wincred.PersistLocalMachine
212-
g.Attributes = attributes
200+
g.Attributes = mapToWindowsAttributes(attributes)
213201
return mapWindowsCredentialError(g.Write())
214202
}
215203

@@ -223,16 +211,9 @@ func (k *keychainStore[T]) Filter(_ context.Context, id store.ID, attributes map
223211

224212
attrs := make(map[string]string)
225213
maps.Copy(attrs, attributes)
214+
maps.Copy(attrs, convertSecretID(id))
226215

227-
parts := strings.SplitSeq(id.String(), "/")
228-
for p := range parts {
229-
if p == "" {
230-
continue
231-
}
232-
attrs[p] = p
233-
}
234-
235-
secrets := make(map[store.ID]store.Secret, len(credentials))
216+
secrets := make(map[store.ID]store.Secret)
236217
for cred := range findServiceCredentials(k, attrs, credentials) {
237218
id, err := store.ParseID(strings.ReplaceAll(cred.TargetName, onlyLabelPrefix, ""))
238219
if err != nil {
@@ -250,13 +231,10 @@ func (k *keychainStore[T]) Filter(_ context.Context, id store.ID, attributes map
250231
return nil, err
251232
}
252233

253-
gcAttr := make(map[string]string)
254-
for _, attr := range gc.Attributes {
255-
gcAttr[attr.Keyword] = string(attr.Value)
256-
}
234+
gcAttributes := cleanSecretAttributes(mapFromWindowsAttributes(gc.Attributes))
257235

258236
secret := k.factory()
259-
if err := secret.SetMetadata(gcAttr); err != nil {
237+
if err := secret.SetMetadata(gcAttributes); err != nil {
260238
return nil, err
261239
}
262240
if err := secret.Unmarshal(blob); err != nil {

0 commit comments

Comments
 (0)