Skip to content

Commit da093bb

Browse files
authored
Merge pull request #77 from gBasil/build/ios
build: implement ios builds
2 parents 505a3f7 + e0baaa3 commit da093bb

File tree

3 files changed

+201
-5
lines changed

3 files changed

+201
-5
lines changed

keychain_ios.go

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
//go:build ios && cgo
2+
// NOTE: Due to newer versions of Go's crypto library requiring SecTrustCopyCertificateChain,
3+
// this requires iOS 15.0 or later.
4+
5+
package keyring
6+
7+
import (
8+
"errors"
9+
"fmt"
10+
11+
gokeychain "github.com/byteness/go-keychain"
12+
)
13+
14+
type keychain struct {
15+
service string
16+
17+
passwordFunc PromptFunc
18+
19+
isSynchronizable bool
20+
isAccessibleWhenUnlocked bool
21+
}
22+
23+
func init() {
24+
supportedBackends[KeychainBackend] = opener(func(cfg Config) (Keyring, error) {
25+
kc := &keychain{
26+
service: cfg.ServiceName,
27+
passwordFunc: cfg.KeychainPasswordFunc,
28+
29+
isSynchronizable: cfg.KeychainSynchronizable,
30+
31+
// Set isAccessibleWhenUnlocked to the boolean value of KeychainAccessibleWhenUnlocked,
32+
// which is a shorthand for setting the accessibility value.
33+
// See: https://developer.apple.com/documentation/security/ksecattraccessiblewhenunlocked
34+
isAccessibleWhenUnlocked: cfg.KeychainAccessibleWhenUnlocked,
35+
}
36+
return kc, nil
37+
})
38+
}
39+
40+
func (k *keychain) Get(key string) (Item, error) {
41+
query := gokeychain.NewItem()
42+
query.SetSecClass(gokeychain.SecClassGenericPassword)
43+
query.SetService(k.service)
44+
query.SetAccount(key)
45+
query.SetMatchLimit(gokeychain.MatchLimitOne)
46+
query.SetReturnAttributes(true)
47+
query.SetReturnData(true)
48+
49+
debugf("Querying keychain for service=%q, account=%q", k.service, key)
50+
results, err := gokeychain.QueryItem(query)
51+
if err == gokeychain.ErrorItemNotFound || len(results) == 0 {
52+
debugf("No results found")
53+
return Item{}, ErrKeyNotFound
54+
}
55+
56+
if err != nil {
57+
debugf("Error: %#v", err)
58+
return Item{}, err
59+
}
60+
61+
item := Item{
62+
Key: key,
63+
Data: results[0].Data,
64+
Label: results[0].Label,
65+
Description: results[0].Description,
66+
}
67+
68+
debugf("Found item %q", results[0].Label)
69+
return item, nil
70+
}
71+
72+
func (k *keychain) GetMetadata(key string) (Metadata, error) {
73+
query := gokeychain.NewItem()
74+
query.SetSecClass(gokeychain.SecClassGenericPassword)
75+
query.SetService(k.service)
76+
query.SetAccount(key)
77+
query.SetMatchLimit(gokeychain.MatchLimitOne)
78+
query.SetReturnAttributes(true)
79+
query.SetReturnData(false)
80+
query.SetReturnRef(true)
81+
82+
debugf("Querying keychain for metadata of service=%q, account=%q", k.service, key)
83+
results, err := gokeychain.QueryItem(query)
84+
if err == gokeychain.ErrorItemNotFound || len(results) == 0 {
85+
debugf("No results found")
86+
return Metadata{}, ErrKeyNotFound
87+
} else if err != nil {
88+
debugf("Error: %#v", err)
89+
return Metadata{}, err
90+
}
91+
92+
md := Metadata{
93+
Item: &Item{
94+
Key: key,
95+
Label: results[0].Label,
96+
Description: results[0].Description,
97+
},
98+
ModificationTime: results[0].ModificationDate,
99+
}
100+
101+
debugf("Found metadata for %q", md.Item.Label)
102+
103+
return md, nil
104+
}
105+
106+
func (k *keychain) updateItem(kcItem gokeychain.Item, account string) error {
107+
queryItem := gokeychain.NewItem()
108+
queryItem.SetSecClass(gokeychain.SecClassGenericPassword)
109+
queryItem.SetService(k.service)
110+
queryItem.SetAccount(account)
111+
queryItem.SetMatchLimit(gokeychain.MatchLimitOne)
112+
queryItem.SetReturnAttributes(true)
113+
114+
results, err := gokeychain.QueryItem(queryItem)
115+
if err != nil {
116+
return fmt.Errorf("Failed to query keychain: %v", err)
117+
}
118+
if len(results) == 0 {
119+
return errors.New("no results")
120+
}
121+
122+
if err := gokeychain.UpdateItem(queryItem, kcItem); err != nil {
123+
return fmt.Errorf("Failed to update item in keychain: %v", err)
124+
}
125+
126+
return nil
127+
}
128+
129+
func (k *keychain) Set(item Item) error {
130+
kcItem := gokeychain.NewItem()
131+
kcItem.SetSecClass(gokeychain.SecClassGenericPassword)
132+
kcItem.SetService(k.service)
133+
kcItem.SetAccount(item.Key)
134+
kcItem.SetLabel(item.Label)
135+
kcItem.SetDescription(item.Description)
136+
kcItem.SetData(item.Data)
137+
138+
if k.isSynchronizable && !item.KeychainNotSynchronizable {
139+
kcItem.SetSynchronizable(gokeychain.SynchronizableYes)
140+
}
141+
142+
if k.isAccessibleWhenUnlocked {
143+
kcItem.SetAccessible(gokeychain.AccessibleWhenUnlocked)
144+
}
145+
146+
err := gokeychain.AddItem(kcItem)
147+
148+
if err == gokeychain.ErrorDuplicateItem {
149+
debugf("Item already exists, updating")
150+
err = k.updateItem(kcItem, item.Key)
151+
}
152+
153+
if err != nil {
154+
return err
155+
}
156+
157+
return nil
158+
}
159+
160+
func (k *keychain) Remove(key string) error {
161+
item := gokeychain.NewItem()
162+
item.SetSecClass(gokeychain.SecClassGenericPassword)
163+
item.SetService(k.service)
164+
item.SetAccount(key)
165+
166+
debugf("Removing keychain item service=%q, account=%q", k.service, key)
167+
err := gokeychain.DeleteItem(item)
168+
if err == gokeychain.ErrorItemNotFound {
169+
return ErrKeyNotFound
170+
}
171+
172+
return err
173+
}
174+
175+
func (k *keychain) Keys() ([]string, error) {
176+
query := gokeychain.NewItem()
177+
query.SetSecClass(gokeychain.SecClassGenericPassword)
178+
query.SetService(k.service)
179+
query.SetMatchLimit(gokeychain.MatchLimitAll)
180+
query.SetReturnAttributes(true)
181+
182+
debugf("Querying keychain for service=%q", k.service)
183+
results, err := gokeychain.QueryItem(query)
184+
if err != nil {
185+
return nil, err
186+
}
187+
188+
debugf("Found %d results", len(results))
189+
accountNames := make([]string, len(results))
190+
for idx, r := range results {
191+
accountNames[idx] = r.Account
192+
}
193+
194+
return accountNames, nil
195+
}

keychain.go renamed to keychain_macos.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
//go:build darwin && cgo
2-
// +build darwin,cgo
1+
//go:build darwin && !ios && cgo
32

43
package keyring
54

@@ -41,8 +40,10 @@ func init() {
4140
service: cfg.ServiceName,
4241
passwordFunc: cfg.KeychainPasswordFunc,
4342

44-
// Set the isAccessibleWhenUnlocked to the boolean value of
45-
// KeychainAccessibleWhenUnlocked is a shorthand for setting the accessibility value.
43+
isSynchronizable: cfg.KeychainSynchronizable,
44+
45+
// Set isAccessibleWhenUnlocked to the boolean value of KeychainAccessibleWhenUnlocked,
46+
// which is a shorthand for setting the accessibility value.
4647
// See: https://developer.apple.com/documentation/security/ksecattraccessiblewhenunlocked
4748
isAccessibleWhenUnlocked: cfg.KeychainAccessibleWhenUnlocked,
4849

keyring.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ const (
3535
var backendOrder = []BackendType{
3636
// Windows
3737
WinCredBackend,
38-
// MacOS
38+
// MacOS & iOS
3939
KeychainBackend,
4040
// Linux
4141
SecretServiceBackend,

0 commit comments

Comments
 (0)