1// +build darwin
2
3package keychain
4
5// See https://developer.apple.com/library/ios/documentation/Security/Reference/keychainservices/index.html for the APIs used below.
6
7// Also see https://developer.apple.com/library/ios/documentation/Security/Conceptual/keychainServConcepts/01introduction/introduction.html .
8
9/*
10#cgo LDFLAGS: -framework CoreFoundation -framework Security
11
12#include <CoreFoundation/CoreFoundation.h>
13#include <Security/Security.h>
14*/
15import "C"
16import (
17	"fmt"
18	"time"
19)
20
21// Error defines keychain errors
22type Error int
23
24var (
25	// ErrorUnimplemented corresponds to errSecUnimplemented result code
26	ErrorUnimplemented = Error(C.errSecUnimplemented)
27	// ErrorParam corresponds to errSecParam result code
28	ErrorParam = Error(C.errSecParam)
29	// ErrorAllocate corresponds to errSecAllocate result code
30	ErrorAllocate = Error(C.errSecAllocate)
31	// ErrorNotAvailable corresponds to errSecNotAvailable result code
32	ErrorNotAvailable = Error(C.errSecNotAvailable)
33	// ErrorAuthFailed corresponds to errSecAuthFailed result code
34	ErrorAuthFailed = Error(C.errSecAuthFailed)
35	// ErrorDuplicateItem corresponds to errSecDuplicateItem result code
36	ErrorDuplicateItem = Error(C.errSecDuplicateItem)
37	// ErrorItemNotFound corresponds to errSecItemNotFound result code
38	ErrorItemNotFound = Error(C.errSecItemNotFound)
39	// ErrorInteractionNotAllowed corresponds to errSecInteractionNotAllowed result code
40	ErrorInteractionNotAllowed = Error(C.errSecInteractionNotAllowed)
41	// ErrorDecode corresponds to errSecDecode result code
42	ErrorDecode = Error(C.errSecDecode)
43	// ErrorNoSuchKeychain corresponds to errSecNoSuchKeychain result code
44	ErrorNoSuchKeychain = Error(C.errSecNoSuchKeychain)
45	// ErrorNoAcccessForItem corresponds to errSecNoAccessForItem result code
46	ErrorNoAccessForItem = Error(C.errSecNoAccessForItem)
47	// ErrorReadOnly corresponds to errSecReadOnly result code
48	ErrorReadOnly = Error(C.errSecReadOnly)
49	// ErrorInvalidKeychain corresponds to errSecInvalidKeychain result code
50	ErrorInvalidKeychain = Error(C.errSecInvalidKeychain)
51	// ErrorDuplicateKeyChain corresponds to errSecDuplicateKeychain result code
52	ErrorDuplicateKeyChain = Error(C.errSecDuplicateKeychain)
53	// ErrorWrongVersion corresponds to errSecWrongSecVersion result code
54	ErrorWrongVersion = Error(C.errSecWrongSecVersion)
55	// ErrorReadonlyAttribute corresponds to errSecReadOnlyAttr result code
56	ErrorReadonlyAttribute = Error(C.errSecReadOnlyAttr)
57	// ErrorInvalidSearchRef corresponds to errSecInvalidSearchRef result code
58	ErrorInvalidSearchRef = Error(C.errSecInvalidSearchRef)
59	// ErrorInvalidItemRef corresponds to errSecInvalidItemRef result code
60	ErrorInvalidItemRef = Error(C.errSecInvalidItemRef)
61	// ErrorDataNotAvailable corresponds to errSecDataNotAvailable result code
62	ErrorDataNotAvailable = Error(C.errSecDataNotAvailable)
63	// ErrorDataNotModifiable corresponds to errSecDataNotModifiable result code
64	ErrorDataNotModifiable = Error(C.errSecDataNotModifiable)
65	// ErrorInvalidOwnerEdit corresponds to errSecInvalidOwnerEdit result code
66	ErrorInvalidOwnerEdit = Error(C.errSecInvalidOwnerEdit)
67)
68
69func checkError(errCode C.OSStatus) error {
70	if errCode == C.errSecSuccess {
71		return nil
72	}
73	return Error(errCode)
74}
75
76func (k Error) Error() (msg string) {
77	// SecCopyErrorMessageString is only available on OSX, so derive manually.
78	// Messages derived from `$ security error $errcode`.
79	switch k {
80	case ErrorUnimplemented:
81		msg = "Function or operation not implemented."
82	case ErrorParam:
83		msg = "One or more parameters passed to the function were not valid."
84	case ErrorAllocate:
85		msg = "Failed to allocate memory."
86	case ErrorNotAvailable:
87		msg = "No keychain is available. You may need to restart your computer."
88	case ErrorAuthFailed:
89		msg = "The user name or passphrase you entered is not correct."
90	case ErrorDuplicateItem:
91		msg = "The specified item already exists in the keychain."
92	case ErrorItemNotFound:
93		msg = "The specified item could not be found in the keychain."
94	case ErrorInteractionNotAllowed:
95		msg = "User interaction is not allowed."
96	case ErrorDecode:
97		msg = "Unable to decode the provided data."
98	case ErrorNoSuchKeychain:
99		msg = "The specified keychain could not be found."
100	case ErrorNoAccessForItem:
101		msg = "The specified item has no access control."
102	case ErrorReadOnly:
103		msg = "Read-only error."
104	case ErrorReadonlyAttribute:
105		msg = "The attribute is read-only."
106	case ErrorInvalidKeychain:
107		msg = "The keychain is not valid."
108	case ErrorDuplicateKeyChain:
109		msg = "A keychain with the same name already exists."
110	case ErrorWrongVersion:
111		msg = "The version is incorrect."
112	case ErrorInvalidItemRef:
113		msg = "The item reference is invalid."
114	case ErrorInvalidSearchRef:
115		msg = "The search reference is invalid."
116	case ErrorDataNotAvailable:
117		msg = "The data is not available."
118	case ErrorDataNotModifiable:
119		msg = "The data is not modifiable."
120	case ErrorInvalidOwnerEdit:
121		msg = "An invalid attempt to change the owner of an item."
122	default:
123		msg = "Keychain Error."
124	}
125	return fmt.Sprintf("%s (%d)", msg, k)
126}
127
128// SecClass is the items class code
129type SecClass int
130
131// Keychain Item Classes
132var (
133	/*
134		kSecClassGenericPassword item attributes:
135		 kSecAttrAccess (OS X only)
136		 kSecAttrAccessGroup (iOS; also OS X if kSecAttrSynchronizable specified)
137		 kSecAttrAccessible (iOS; also OS X if kSecAttrSynchronizable specified)
138		 kSecAttrAccount
139		 kSecAttrService
140	*/
141	SecClassGenericPassword  SecClass = 1
142	SecClassInternetPassword SecClass = 2
143)
144
145// SecClassKey is the key type for SecClass
146var SecClassKey = attrKey(C.CFTypeRef(C.kSecClass))
147var secClassTypeRef = map[SecClass]C.CFTypeRef{
148	SecClassGenericPassword:  C.CFTypeRef(C.kSecClassGenericPassword),
149	SecClassInternetPassword: C.CFTypeRef(C.kSecClassInternetPassword),
150}
151
152var (
153	// ServiceKey is for kSecAttrService
154	ServiceKey = attrKey(C.CFTypeRef(C.kSecAttrService))
155	// LabelKey is for kSecAttrLabel
156	LabelKey = attrKey(C.CFTypeRef(C.kSecAttrLabel))
157	// AccountKey is for kSecAttrAccount
158	AccountKey = attrKey(C.CFTypeRef(C.kSecAttrAccount))
159	// AccessGroupKey is for kSecAttrAccessGroup
160	AccessGroupKey = attrKey(C.CFTypeRef(C.kSecAttrAccessGroup))
161	// DataKey is for kSecValueData
162	DataKey = attrKey(C.CFTypeRef(C.kSecValueData))
163	// DescriptionKey is for kSecAttrDescription
164	DescriptionKey = attrKey(C.CFTypeRef(C.kSecAttrDescription))
165	// CreationDateKey is for kSecAttrCreationDate
166	CreationDateKey = attrKey(C.CFTypeRef(C.kSecAttrCreationDate))
167	// ModificationDateKey is for kSecAttrModificationDate
168	ModificationDateKey = attrKey(C.CFTypeRef(C.kSecAttrModificationDate))
169)
170
171// Synchronizable is the items synchronizable status
172type Synchronizable int
173
174const (
175	// SynchronizableDefault is the default setting
176	SynchronizableDefault Synchronizable = 0
177	// SynchronizableAny is for kSecAttrSynchronizableAny
178	SynchronizableAny = 1
179	// SynchronizableYes enables synchronization
180	SynchronizableYes = 2
181	// SynchronizableNo disables synchronization
182	SynchronizableNo = 3
183)
184
185// SynchronizableKey is the key type for Synchronizable
186var SynchronizableKey = attrKey(C.CFTypeRef(C.kSecAttrSynchronizable))
187var syncTypeRef = map[Synchronizable]C.CFTypeRef{
188	SynchronizableAny: C.CFTypeRef(C.kSecAttrSynchronizableAny),
189	SynchronizableYes: C.CFTypeRef(C.kCFBooleanTrue),
190	SynchronizableNo:  C.CFTypeRef(C.kCFBooleanFalse),
191}
192
193// Accessible is the items accessibility
194type Accessible int
195
196const (
197	// AccessibleDefault is the default
198	AccessibleDefault Accessible = 0
199	// AccessibleWhenUnlocked is when unlocked
200	AccessibleWhenUnlocked = 1
201	// AccessibleAfterFirstUnlock is after first unlock
202	AccessibleAfterFirstUnlock = 2
203	// AccessibleAlways is always
204	AccessibleAlways = 3
205	// AccessibleWhenPasscodeSetThisDeviceOnly is when passcode is set
206	AccessibleWhenPasscodeSetThisDeviceOnly = 4
207	// AccessibleWhenUnlockedThisDeviceOnly is when unlocked for this device only
208	AccessibleWhenUnlockedThisDeviceOnly = 5
209	// AccessibleAfterFirstUnlockThisDeviceOnly is after first unlock for this device only
210	AccessibleAfterFirstUnlockThisDeviceOnly = 6
211	// AccessibleAccessibleAlwaysThisDeviceOnly is always for this device only
212	AccessibleAccessibleAlwaysThisDeviceOnly = 7
213)
214
215// MatchLimit is whether to limit results on query
216type MatchLimit int
217
218const (
219	// MatchLimitDefault is the default
220	MatchLimitDefault MatchLimit = 0
221	// MatchLimitOne limits to one result
222	MatchLimitOne = 1
223	// MatchLimitAll is no limit
224	MatchLimitAll = 2
225)
226
227// MatchLimitKey is key type for MatchLimit
228var MatchLimitKey = attrKey(C.CFTypeRef(C.kSecMatchLimit))
229var matchTypeRef = map[MatchLimit]C.CFTypeRef{
230	MatchLimitOne: C.CFTypeRef(C.kSecMatchLimitOne),
231	MatchLimitAll: C.CFTypeRef(C.kSecMatchLimitAll),
232}
233
234// ReturnAttributesKey is key type for kSecReturnAttributes
235var ReturnAttributesKey = attrKey(C.CFTypeRef(C.kSecReturnAttributes))
236
237// ReturnDataKey is key type for kSecReturnData
238var ReturnDataKey = attrKey(C.CFTypeRef(C.kSecReturnData))
239
240// ReturnRefKey is key type for kSecReturnRef
241var ReturnRefKey = attrKey(C.CFTypeRef(C.kSecReturnRef))
242
243// Item for adding, querying or deleting.
244type Item struct {
245	// Values can be string, []byte, Convertable or CFTypeRef (constant).
246	attr map[string]interface{}
247}
248
249// SetSecClass sets the security class
250func (k *Item) SetSecClass(sc SecClass) {
251	k.attr[SecClassKey] = secClassTypeRef[sc]
252}
253
254// SetString sets a string attibute for a string key
255func (k *Item) SetString(key string, s string) {
256	if s != "" {
257		k.attr[key] = s
258	} else {
259		delete(k.attr, key)
260	}
261}
262
263// SetService sets the service attribute
264func (k *Item) SetService(s string) {
265	k.SetString(ServiceKey, s)
266}
267
268// SetAccount sets the account attribute
269func (k *Item) SetAccount(a string) {
270	k.SetString(AccountKey, a)
271}
272
273// SetLabel sets the label attribute
274func (k *Item) SetLabel(l string) {
275	k.SetString(LabelKey, l)
276}
277
278// SetDescription sets the description attribute
279func (k *Item) SetDescription(s string) {
280	k.SetString(DescriptionKey, s)
281}
282
283// SetData sets the data attribute
284func (k *Item) SetData(b []byte) {
285	if b != nil {
286		k.attr[DataKey] = b
287	} else {
288		delete(k.attr, DataKey)
289	}
290}
291
292// SetAccessGroup sets the access group attribute
293func (k *Item) SetAccessGroup(ag string) {
294	k.SetString(AccessGroupKey, ag)
295}
296
297// SetSynchronizable sets the synchronizable attribute
298func (k *Item) SetSynchronizable(sync Synchronizable) {
299	if sync != SynchronizableDefault {
300		k.attr[SynchronizableKey] = syncTypeRef[sync]
301	} else {
302		delete(k.attr, SynchronizableKey)
303	}
304}
305
306// SetAccessible sets the accessible attribute
307func (k *Item) SetAccessible(accessible Accessible) {
308	if accessible != AccessibleDefault {
309		k.attr[AccessibleKey] = accessibleTypeRef[accessible]
310	} else {
311		delete(k.attr, AccessibleKey)
312	}
313}
314
315// SetMatchLimit sets the match limit
316func (k *Item) SetMatchLimit(matchLimit MatchLimit) {
317	if matchLimit != MatchLimitDefault {
318		k.attr[MatchLimitKey] = matchTypeRef[matchLimit]
319	} else {
320		delete(k.attr, MatchLimitKey)
321	}
322}
323
324// SetReturnAttributes sets the return value type on query
325func (k *Item) SetReturnAttributes(b bool) {
326	k.attr[ReturnAttributesKey] = b
327}
328
329// SetReturnData enables returning data on query
330func (k *Item) SetReturnData(b bool) {
331	k.attr[ReturnDataKey] = b
332}
333
334// SetReturnRef enables returning references on query
335func (k *Item) SetReturnRef(b bool) {
336	k.attr[ReturnRefKey] = b
337}
338
339// NewItem is a new empty keychain item
340func NewItem() Item {
341	return Item{make(map[string]interface{})}
342}
343
344// NewGenericPassword creates a generic password item with the default keychain. This is a convenience method.
345func NewGenericPassword(service string, account string, label string, data []byte, accessGroup string) Item {
346	item := NewItem()
347	item.SetSecClass(SecClassGenericPassword)
348	item.SetService(service)
349	item.SetAccount(account)
350	item.SetLabel(label)
351	item.SetData(data)
352	item.SetAccessGroup(accessGroup)
353	return item
354}
355
356// AddItem adds a Item to a Keychain
357func AddItem(item Item) error {
358	cfDict, err := ConvertMapToCFDictionary(item.attr)
359	if err != nil {
360		return err
361	}
362	defer Release(C.CFTypeRef(cfDict))
363
364	errCode := C.SecItemAdd(cfDict, nil)
365	err = checkError(errCode)
366	return err
367}
368
369// UpdateItem updates the queryItem with the parameters from updateItem
370func UpdateItem(queryItem Item, updateItem Item) error {
371	cfDict, err := ConvertMapToCFDictionary(queryItem.attr)
372	if err != nil {
373		return err
374	}
375	defer Release(C.CFTypeRef(cfDict))
376	cfDictUpdate, err := ConvertMapToCFDictionary(updateItem.attr)
377	if err != nil {
378		return err
379	}
380	defer Release(C.CFTypeRef(cfDictUpdate))
381	errCode := C.SecItemUpdate(cfDict, cfDictUpdate)
382	err = checkError(errCode)
383	return err
384}
385
386// QueryResult stores all possible results from queries.
387// Not all fields are applicable all the time. Results depend on query.
388type QueryResult struct {
389	Service          string
390	Account          string
391	AccessGroup      string
392	Label            string
393	Description      string
394	Data             []byte
395	CreationDate     time.Time
396	ModificationDate time.Time
397}
398
399// QueryItemRef returns query result as CFTypeRef. You must release it when you are done.
400func QueryItemRef(item Item) (C.CFTypeRef, error) {
401	cfDict, err := ConvertMapToCFDictionary(item.attr)
402	if err != nil {
403		return 0, err
404	}
405	defer Release(C.CFTypeRef(cfDict))
406
407	var resultsRef C.CFTypeRef
408	errCode := C.SecItemCopyMatching(cfDict, &resultsRef) //nolint
409	if Error(errCode) == ErrorItemNotFound {
410		return 0, nil
411	}
412	err = checkError(errCode)
413	if err != nil {
414		return 0, err
415	}
416	return resultsRef, nil
417}
418
419// QueryItem returns a list of query results.
420func QueryItem(item Item) ([]QueryResult, error) {
421	resultsRef, err := QueryItemRef(item)
422	if err != nil {
423		return nil, err
424	}
425	if resultsRef == 0 {
426		return nil, nil
427	}
428	defer Release(resultsRef)
429
430	results := make([]QueryResult, 0, 1)
431
432	typeID := C.CFGetTypeID(resultsRef)
433	if typeID == C.CFArrayGetTypeID() {
434		arr := CFArrayToArray(C.CFArrayRef(resultsRef))
435		for _, ref := range arr {
436			elementTypeID := C.CFGetTypeID(ref)
437			if elementTypeID == C.CFDictionaryGetTypeID() {
438				item, err := convertResult(C.CFDictionaryRef(ref))
439				if err != nil {
440					return nil, err
441				}
442				results = append(results, *item)
443			} else {
444				return nil, fmt.Errorf("invalid result type (If you SetReturnRef(true) you should use QueryItemRef directly)")
445			}
446		}
447	} else if typeID == C.CFDictionaryGetTypeID() {
448		item, err := convertResult(C.CFDictionaryRef(resultsRef))
449		if err != nil {
450			return nil, err
451		}
452		results = append(results, *item)
453	} else if typeID == C.CFDataGetTypeID() {
454		b, err := CFDataToBytes(C.CFDataRef(resultsRef))
455		if err != nil {
456			return nil, err
457		}
458		item := QueryResult{Data: b}
459		results = append(results, item)
460	} else {
461		return nil, fmt.Errorf("Invalid result type: %s", CFTypeDescription(resultsRef))
462	}
463
464	return results, nil
465}
466
467func attrKey(ref C.CFTypeRef) string {
468	return CFStringToString(C.CFStringRef(ref))
469}
470
471func convertResult(d C.CFDictionaryRef) (*QueryResult, error) {
472	m := CFDictionaryToMap(d)
473	result := QueryResult{}
474	for k, v := range m {
475		switch attrKey(k) {
476		case ServiceKey:
477			result.Service = CFStringToString(C.CFStringRef(v))
478		case AccountKey:
479			result.Account = CFStringToString(C.CFStringRef(v))
480		case AccessGroupKey:
481			result.AccessGroup = CFStringToString(C.CFStringRef(v))
482		case LabelKey:
483			result.Label = CFStringToString(C.CFStringRef(v))
484		case DescriptionKey:
485			result.Description = CFStringToString(C.CFStringRef(v))
486		case DataKey:
487			b, err := CFDataToBytes(C.CFDataRef(v))
488			if err != nil {
489				return nil, err
490			}
491			result.Data = b
492		case CreationDateKey:
493			result.CreationDate = CFDateToTime(C.CFDateRef(v))
494		case ModificationDateKey:
495			result.ModificationDate = CFDateToTime(C.CFDateRef(v))
496			// default:
497			// fmt.Printf("Unhandled key in conversion: %v = %v\n", cfTypeValue(k), cfTypeValue(v))
498		}
499	}
500	return &result, nil
501}
502
503// DeleteGenericPasswordItem removes a generic password item.
504func DeleteGenericPasswordItem(service string, account string) error {
505	item := NewItem()
506	item.SetSecClass(SecClassGenericPassword)
507	item.SetService(service)
508	item.SetAccount(account)
509	return DeleteItem(item)
510}
511
512// DeleteItem removes a Item
513func DeleteItem(item Item) error {
514	cfDict, err := ConvertMapToCFDictionary(item.attr)
515	if err != nil {
516		return err
517	}
518	defer Release(C.CFTypeRef(cfDict))
519
520	errCode := C.SecItemDelete(cfDict)
521	return checkError(errCode)
522}
523
524// GetAccountsForService is deprecated
525func GetAccountsForService(service string) ([]string, error) {
526	return GetGenericPasswordAccounts(service)
527}
528
529// GetGenericPasswordAccounts returns generic password accounts for service. This is a convenience method.
530func GetGenericPasswordAccounts(service string) ([]string, error) {
531	query := NewItem()
532	query.SetSecClass(SecClassGenericPassword)
533	query.SetService(service)
534	query.SetMatchLimit(MatchLimitAll)
535	query.SetReturnAttributes(true)
536	results, err := QueryItem(query)
537	if err != nil {
538		return nil, err
539	}
540
541	accounts := make([]string, 0, len(results))
542	for _, r := range results {
543		accounts = append(accounts, r.Account)
544	}
545
546	return accounts, nil
547}
548
549// GetGenericPassword returns password data for service and account. This is a convenience method.
550// If item is not found returns nil, nil.
551func GetGenericPassword(service string, account string, label string, accessGroup string) ([]byte, error) {
552	query := NewItem()
553	query.SetSecClass(SecClassGenericPassword)
554	query.SetService(service)
555	query.SetAccount(account)
556	query.SetLabel(label)
557	query.SetAccessGroup(accessGroup)
558	query.SetMatchLimit(MatchLimitOne)
559	query.SetReturnData(true)
560	results, err := QueryItem(query)
561	if err != nil {
562		return nil, err
563	}
564	if len(results) > 1 {
565		return nil, fmt.Errorf("Too many results")
566	}
567	if len(results) == 1 {
568		return results[0].Data, nil
569	}
570	return nil, nil
571}
572