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