1313 ErrAuthFailed = errors .New ("user incorrectly enterered their credentials" )
1414)
1515
16- func newKeychainItem [T store.Secret ](id store.ID , k * keychainStore [T ]) kc.Item {
16+ // newKeychainItem creates a new keychain item with valid default parameters.
17+ //
18+ // It uses a generic password class, which is suitable for most use cases.
19+ //
20+ // By default, the item will only return one secret when queried.
21+ //
22+ // The id parameter can be empty, in which case the item will search based on
23+ // the service name and group, but not the item label or account.
24+ func newKeychainItem [T store.Secret ](id string , k * keychainStore [T ]) kc.Item {
1725 item := kc .NewItem ()
1826 // generic password is used here as we don't know what we are storing
1927 // the main difference between a generic and internet password is the
@@ -23,27 +31,27 @@ func newKeychainItem[T store.Secret](id store.ID, k *keychainStore[T]) kc.Item {
2331 // set this to MatchLimitAll if you want to retrieve all items
2432 item .SetMatchLimit (kc .MatchLimitOne )
2533 item .SetAccessible (kc .AccessibleAfterFirstUnlock )
26- item .SetReturnData (true )
2734 item .SetReturnAttributes (true )
2835
2936 item .SetService (k .serviceName )
3037 item .SetAccessGroup (k .serviceGroup )
3138
32- if id .String () != "" {
33- item .SetLabel (k .itemLabel (id ))
34- item .SetAccount (id .String ())
39+ if id != "" {
40+ item .SetAccount (id )
3541 }
3642
3743 return item
3844}
3945
40- func (k * keychainStore [T ]) Delete (ctx context.Context , id store.ID ) error {
46+ // getItemWithData retrieves a keychain item with its data.
47+ //
48+ // It uses the SetReturnData attribute to query for an item with its data.
49+ // It cannot be used with MatchLimitAll, as it will return an error.
50+ // https://developer.apple.com/documentation/security/secitemcopymatching(_:_:)#Discussion
51+ func getItemWithData [T store.Secret ](id string , k * keychainStore [T ]) (* kc.QueryResult , error ) {
4152 item := newKeychainItem (id , k )
42- return mapKeychainError (kc .DeleteItem (item ))
43- }
53+ item .SetReturnData (true )
4454
45- func (k * keychainStore [T ]) Get (ctx context.Context , id store.ID ) (store.Secret , error ) {
46- item := newKeychainItem (id , k )
4755 results , err := kc .QueryItem (item )
4856 if err != nil {
4957 return nil , mapKeychainError (err )
@@ -52,40 +60,84 @@ func (k *keychainStore[T]) Get(ctx context.Context, id store.ID) (store.Secret,
5260 return nil , store .ErrCredentialNotFound
5361 }
5462
63+ return & results [0 ], nil
64+ }
65+
66+ func (k * keychainStore [T ]) Delete (ctx context.Context , id store.ID ) error {
67+ if err := id .Valid (); err != nil {
68+ return err
69+ }
70+
71+ item := newKeychainItem (id .String (), k )
72+ return mapKeychainError (kc .DeleteItem (item ))
73+ }
74+
75+ func (k * keychainStore [T ]) Get (ctx context.Context , id store.ID ) (store.Secret , error ) {
76+ if err := id .Valid (); err != nil {
77+ return nil , err
78+ }
79+
80+ result , err := getItemWithData (id .String (), k )
81+ if err != nil {
82+ return nil , err
83+ }
84+
5585 secret := k .factory ()
56- if err := secret .Unmarshal (results [ 0 ] .Data ); err != nil {
86+ if err := secret .Unmarshal (result .Data ); err != nil {
5787 return nil , err
5888 }
5989 return secret , nil
6090}
6191
6292func (k * keychainStore [T ]) GetAll (ctx context.Context ) (map [store.ID ]store.Secret , error ) {
63- item := newKeychainItem (store .ID ("" ), k )
93+ item := newKeychainItem ("" , k )
94+
95+ // We use the MatchLimitAll attribute to query for multiple items from the
96+ // store. It cannot be used with item.SetReturnData.
97+ // https://developer.apple.com/documentation/security/secitemcopymatching(_:_:)#Discussion
6498 item .SetMatchLimit (kc .MatchLimitAll )
6599
66100 results , err := kc .QueryItem (item )
67101 if err != nil {
68102 return nil , mapKeychainError (err )
69103 }
104+
70105 creds := make (map [store.ID ]store.Secret , len (results ))
71106 for _ , result := range results {
107+ id , err := store .ParseID (result .Account )
108+ if err != nil {
109+ return nil , err
110+ }
111+
112+ i , err := getItemWithData (id .String (), k )
113+ if err != nil {
114+ return nil , err
115+ }
116+
72117 secret := k .factory ()
73- if err := secret .Unmarshal (result .Data ); err != nil {
118+ if err := secret .Unmarshal (i .Data ); err != nil {
74119 return nil , err
75120 }
76- id := store .ID (result .Label )
77121 creds [id ] = secret
78122 }
79123 return creds , nil
80124}
81125
82126func (k * keychainStore [T ]) Save (ctx context.Context , id store.ID , secret store.Secret ) error {
127+ if err := id .Valid (); err != nil {
128+ return err
129+ }
130+
83131 data , err := secret .Marshal ()
84132 if err != nil {
85133 return err
86134 }
87- item := newKeychainItem (id , k )
135+ item := newKeychainItem (id . String () , k )
88136 item .SetData (data )
137+ // only creation of a secret needs the label attribute.
138+ // it is a user-friendly name for the item, which is displayed in the keychain UI.
139+ // https://developer.apple.com/documentation/security/ksecattrlabel
140+ item .SetLabel (k .itemLabel (id ))
89141 return mapKeychainError (kc .AddItem (item ))
90142}
91143
0 commit comments