mirror of
https://github.com/ProtonMail/go-keychain.git
synced 2026-01-11 19:58:20 +00:00
653 lines
20 KiB
Go
653 lines
20 KiB
Go
//go:build darwin
|
|
// +build darwin
|
|
|
|
package keychain
|
|
|
|
// See https://developer.apple.com/library/ios/documentation/Security/Reference/keychainservices/index.html for the APIs used below.
|
|
|
|
// Also see https://developer.apple.com/library/ios/documentation/Security/Conceptual/keychainServConcepts/01introduction/introduction.html .
|
|
|
|
/*
|
|
#cgo LDFLAGS: -framework CoreFoundation -framework Security
|
|
|
|
#include <CoreFoundation/CoreFoundation.h>
|
|
#include <Security/Security.h>
|
|
*/
|
|
import "C"
|
|
import (
|
|
"fmt"
|
|
"time"
|
|
)
|
|
|
|
// Error defines keychain errors
|
|
type Error int
|
|
|
|
var (
|
|
// ErrorUnimplemented corresponds to errSecUnimplemented result code
|
|
ErrorUnimplemented = Error(C.errSecUnimplemented)
|
|
// ErrorParam corresponds to errSecParam result code
|
|
ErrorParam = Error(C.errSecParam)
|
|
// ErrorAllocate corresponds to errSecAllocate result code
|
|
ErrorAllocate = Error(C.errSecAllocate)
|
|
// ErrorNotAvailable corresponds to errSecNotAvailable result code
|
|
ErrorNotAvailable = Error(C.errSecNotAvailable)
|
|
// ErrorAuthFailed corresponds to errSecAuthFailed result code
|
|
ErrorAuthFailed = Error(C.errSecAuthFailed)
|
|
// ErrorDuplicateItem corresponds to errSecDuplicateItem result code
|
|
ErrorDuplicateItem = Error(C.errSecDuplicateItem)
|
|
// ErrorItemNotFound corresponds to errSecItemNotFound result code
|
|
ErrorItemNotFound = Error(C.errSecItemNotFound)
|
|
// ErrorInteractionNotAllowed corresponds to errSecInteractionNotAllowed result code
|
|
ErrorInteractionNotAllowed = Error(C.errSecInteractionNotAllowed)
|
|
// ErrorDecode corresponds to errSecDecode result code
|
|
ErrorDecode = Error(C.errSecDecode)
|
|
// ErrorNoSuchKeychain corresponds to errSecNoSuchKeychain result code
|
|
ErrorNoSuchKeychain = Error(C.errSecNoSuchKeychain)
|
|
// ErrorNoAccessForItem corresponds to errSecNoAccessForItem result code
|
|
ErrorNoAccessForItem = Error(C.errSecNoAccessForItem)
|
|
// ErrorReadOnly corresponds to errSecReadOnly result code
|
|
ErrorReadOnly = Error(C.errSecReadOnly)
|
|
// ErrorInvalidKeychain corresponds to errSecInvalidKeychain result code
|
|
ErrorInvalidKeychain = Error(C.errSecInvalidKeychain)
|
|
// ErrorDuplicateKeyChain corresponds to errSecDuplicateKeychain result code
|
|
ErrorDuplicateKeyChain = Error(C.errSecDuplicateKeychain)
|
|
// ErrorWrongVersion corresponds to errSecWrongSecVersion result code
|
|
ErrorWrongVersion = Error(C.errSecWrongSecVersion)
|
|
// ErrorReadonlyAttribute corresponds to errSecReadOnlyAttr result code
|
|
ErrorReadonlyAttribute = Error(C.errSecReadOnlyAttr)
|
|
// ErrorInvalidSearchRef corresponds to errSecInvalidSearchRef result code
|
|
ErrorInvalidSearchRef = Error(C.errSecInvalidSearchRef)
|
|
// ErrorInvalidItemRef corresponds to errSecInvalidItemRef result code
|
|
ErrorInvalidItemRef = Error(C.errSecInvalidItemRef)
|
|
// ErrorDataNotAvailable corresponds to errSecDataNotAvailable result code
|
|
ErrorDataNotAvailable = Error(C.errSecDataNotAvailable)
|
|
// ErrorDataNotModifiable corresponds to errSecDataNotModifiable result code
|
|
ErrorDataNotModifiable = Error(C.errSecDataNotModifiable)
|
|
// ErrorInvalidOwnerEdit corresponds to errSecInvalidOwnerEdit result code
|
|
ErrorInvalidOwnerEdit = Error(C.errSecInvalidOwnerEdit)
|
|
// ErrorUserCanceled corresponds to errSecUserCanceled result code
|
|
ErrorUserCanceled = Error(C.errSecUserCanceled)
|
|
)
|
|
|
|
func checkError(errCode C.OSStatus) error {
|
|
if errCode == C.errSecSuccess {
|
|
return nil
|
|
}
|
|
return Error(errCode)
|
|
}
|
|
|
|
func (k Error) Error() (msg string) {
|
|
// SecCopyErrorMessageString is only available on OSX, so derive manually.
|
|
// Messages derived from `$ security error $errcode`.
|
|
switch k {
|
|
case ErrorUnimplemented:
|
|
msg = "Function or operation not implemented."
|
|
case ErrorParam:
|
|
msg = "One or more parameters passed to the function were not valid."
|
|
case ErrorAllocate:
|
|
msg = "Failed to allocate memory."
|
|
case ErrorNotAvailable:
|
|
msg = "No keychain is available. You may need to restart your computer."
|
|
case ErrorAuthFailed:
|
|
msg = "The user name or passphrase you entered is not correct."
|
|
case ErrorDuplicateItem:
|
|
msg = "The specified item already exists in the keychain."
|
|
case ErrorItemNotFound:
|
|
msg = "The specified item could not be found in the keychain."
|
|
case ErrorInteractionNotAllowed:
|
|
msg = "User interaction is not allowed."
|
|
case ErrorDecode:
|
|
msg = "Unable to decode the provided data."
|
|
case ErrorNoSuchKeychain:
|
|
msg = "The specified keychain could not be found."
|
|
case ErrorNoAccessForItem:
|
|
msg = "The specified item has no access control."
|
|
case ErrorReadOnly:
|
|
msg = "Read-only error."
|
|
case ErrorReadonlyAttribute:
|
|
msg = "The attribute is read-only."
|
|
case ErrorInvalidKeychain:
|
|
msg = "The keychain is not valid."
|
|
case ErrorDuplicateKeyChain:
|
|
msg = "A keychain with the same name already exists."
|
|
case ErrorWrongVersion:
|
|
msg = "The version is incorrect."
|
|
case ErrorInvalidItemRef:
|
|
msg = "The item reference is invalid."
|
|
case ErrorInvalidSearchRef:
|
|
msg = "The search reference is invalid."
|
|
case ErrorDataNotAvailable:
|
|
msg = "The data is not available."
|
|
case ErrorDataNotModifiable:
|
|
msg = "The data is not modifiable."
|
|
case ErrorInvalidOwnerEdit:
|
|
msg = "An invalid attempt to change the owner of an item."
|
|
case ErrorUserCanceled:
|
|
msg = "User canceled the operation."
|
|
default:
|
|
msg = "Keychain Error."
|
|
}
|
|
return fmt.Sprintf("%s (%d)", msg, k)
|
|
}
|
|
|
|
// SecClass is the items class code
|
|
type SecClass int
|
|
|
|
// Keychain Item Classes
|
|
var (
|
|
/*
|
|
kSecClassGenericPassword item attributes:
|
|
kSecAttrAccess (OS X only)
|
|
kSecAttrAccessGroup (iOS; also OS X if kSecAttrSynchronizable specified)
|
|
kSecAttrAccessible (iOS; also OS X if kSecAttrSynchronizable specified)
|
|
kSecAttrAccount
|
|
kSecAttrService
|
|
*/
|
|
SecClassGenericPassword SecClass = 1
|
|
SecClassInternetPassword SecClass = 2
|
|
)
|
|
|
|
// SecClassKey is the key type for SecClass
|
|
var SecClassKey = attrKey(C.CFTypeRef(C.kSecClass))
|
|
var secClassTypeRef = map[SecClass]C.CFTypeRef{
|
|
SecClassGenericPassword: C.CFTypeRef(C.kSecClassGenericPassword),
|
|
SecClassInternetPassword: C.CFTypeRef(C.kSecClassInternetPassword),
|
|
}
|
|
|
|
var (
|
|
// ServiceKey is for kSecAttrService
|
|
ServiceKey = attrKey(C.CFTypeRef(C.kSecAttrService))
|
|
|
|
// ServerKey is for kSecAttrServer
|
|
ServerKey = attrKey(C.CFTypeRef(C.kSecAttrServer))
|
|
// ProtocolKey is for kSecAttrProtocol
|
|
ProtocolKey = attrKey(C.CFTypeRef(C.kSecAttrProtocol))
|
|
// AuthenticationTypeKey is for kSecAttrAuthenticationType
|
|
AuthenticationTypeKey = attrKey(C.CFTypeRef(C.kSecAttrAuthenticationType))
|
|
// PortKey is for kSecAttrPort
|
|
PortKey = attrKey(C.CFTypeRef(C.kSecAttrPort))
|
|
// PathKey is for kSecAttrPath
|
|
PathKey = attrKey(C.CFTypeRef(C.kSecAttrPath))
|
|
|
|
// LabelKey is for kSecAttrLabel
|
|
LabelKey = attrKey(C.CFTypeRef(C.kSecAttrLabel))
|
|
// AccountKey is for kSecAttrAccount
|
|
AccountKey = attrKey(C.CFTypeRef(C.kSecAttrAccount))
|
|
// AccessGroupKey is for kSecAttrAccessGroup
|
|
AccessGroupKey = attrKey(C.CFTypeRef(C.kSecAttrAccessGroup))
|
|
// DataKey is for kSecValueData
|
|
DataKey = attrKey(C.CFTypeRef(C.kSecValueData))
|
|
// DescriptionKey is for kSecAttrDescription
|
|
DescriptionKey = attrKey(C.CFTypeRef(C.kSecAttrDescription))
|
|
// CommentKey is for kSecAttrComment
|
|
CommentKey = attrKey(C.CFTypeRef(C.kSecAttrComment))
|
|
// CreationDateKey is for kSecAttrCreationDate
|
|
CreationDateKey = attrKey(C.CFTypeRef(C.kSecAttrCreationDate))
|
|
// ModificationDateKey is for kSecAttrModificationDate
|
|
ModificationDateKey = attrKey(C.CFTypeRef(C.kSecAttrModificationDate))
|
|
)
|
|
|
|
// Synchronizable is the items synchronizable status
|
|
type Synchronizable int
|
|
|
|
const (
|
|
// SynchronizableDefault is the default setting
|
|
SynchronizableDefault Synchronizable = 0
|
|
// SynchronizableAny is for kSecAttrSynchronizableAny
|
|
SynchronizableAny = 1
|
|
// SynchronizableYes enables synchronization
|
|
SynchronizableYes = 2
|
|
// SynchronizableNo disables synchronization
|
|
SynchronizableNo = 3
|
|
)
|
|
|
|
// SynchronizableKey is the key type for Synchronizable
|
|
var SynchronizableKey = attrKey(C.CFTypeRef(C.kSecAttrSynchronizable))
|
|
var syncTypeRef = map[Synchronizable]C.CFTypeRef{
|
|
SynchronizableAny: C.CFTypeRef(C.kSecAttrSynchronizableAny),
|
|
SynchronizableYes: C.CFTypeRef(C.kCFBooleanTrue),
|
|
SynchronizableNo: C.CFTypeRef(C.kCFBooleanFalse),
|
|
}
|
|
|
|
// Accessible is the items accessibility
|
|
type Accessible int
|
|
|
|
const (
|
|
// AccessibleDefault is the default
|
|
AccessibleDefault Accessible = 0
|
|
// AccessibleWhenUnlocked is when unlocked
|
|
AccessibleWhenUnlocked = 1
|
|
// AccessibleAfterFirstUnlock is after first unlock
|
|
AccessibleAfterFirstUnlock = 2
|
|
// AccessibleAlways is always
|
|
AccessibleAlways = 3
|
|
// AccessibleWhenPasscodeSetThisDeviceOnly is when passcode is set
|
|
AccessibleWhenPasscodeSetThisDeviceOnly = 4
|
|
// AccessibleWhenUnlockedThisDeviceOnly is when unlocked for this device only
|
|
AccessibleWhenUnlockedThisDeviceOnly = 5
|
|
// AccessibleAfterFirstUnlockThisDeviceOnly is after first unlock for this device only
|
|
AccessibleAfterFirstUnlockThisDeviceOnly = 6
|
|
// AccessibleAccessibleAlwaysThisDeviceOnly is always for this device only
|
|
AccessibleAccessibleAlwaysThisDeviceOnly = 7
|
|
)
|
|
|
|
// MatchLimit is whether to limit results on query
|
|
type MatchLimit int
|
|
|
|
const (
|
|
// MatchLimitDefault is the default
|
|
MatchLimitDefault MatchLimit = 0
|
|
// MatchLimitOne limits to one result
|
|
MatchLimitOne = 1
|
|
// MatchLimitAll is no limit
|
|
MatchLimitAll = 2
|
|
)
|
|
|
|
// MatchLimitKey is key type for MatchLimit
|
|
var MatchLimitKey = attrKey(C.CFTypeRef(C.kSecMatchLimit))
|
|
var matchTypeRef = map[MatchLimit]C.CFTypeRef{
|
|
MatchLimitOne: C.CFTypeRef(C.kSecMatchLimitOne),
|
|
MatchLimitAll: C.CFTypeRef(C.kSecMatchLimitAll),
|
|
}
|
|
|
|
// ReturnAttributesKey is key type for kSecReturnAttributes
|
|
var ReturnAttributesKey = attrKey(C.CFTypeRef(C.kSecReturnAttributes))
|
|
|
|
// ReturnDataKey is key type for kSecReturnData
|
|
var ReturnDataKey = attrKey(C.CFTypeRef(C.kSecReturnData))
|
|
|
|
// ReturnRefKey is key type for kSecReturnRef
|
|
var ReturnRefKey = attrKey(C.CFTypeRef(C.kSecReturnRef))
|
|
|
|
// Item for adding, querying or deleting.
|
|
type Item struct {
|
|
// Values can be string, []byte, Convertable or CFTypeRef (constant).
|
|
attr map[string]interface{}
|
|
}
|
|
|
|
// SetSecClass sets the security class
|
|
func (k *Item) SetSecClass(sc SecClass) {
|
|
k.attr[SecClassKey] = secClassTypeRef[sc]
|
|
}
|
|
|
|
// SetInt32 sets an int32 attribute for a string key
|
|
func (k *Item) SetInt32(key string, v int32) {
|
|
if v != 0 {
|
|
k.attr[key] = v
|
|
} else {
|
|
delete(k.attr, key)
|
|
}
|
|
}
|
|
|
|
// SetString sets a string attibute for a string key
|
|
func (k *Item) SetString(key string, s string) {
|
|
if s != "" {
|
|
k.attr[key] = s
|
|
} else {
|
|
delete(k.attr, key)
|
|
}
|
|
}
|
|
|
|
// SetService sets the service attribute (for generic application items)
|
|
func (k *Item) SetService(s string) {
|
|
k.SetString(ServiceKey, s)
|
|
}
|
|
|
|
// SetServer sets the server attribute (for internet password items)
|
|
func (k *Item) SetServer(s string) {
|
|
k.SetString(ServerKey, s)
|
|
}
|
|
|
|
// SetProtocol sets the protocol attribute (for internet password items)
|
|
// Example values are: "htps", "http", "smb "
|
|
func (k *Item) SetProtocol(s string) {
|
|
k.SetString(ProtocolKey, s)
|
|
}
|
|
|
|
// SetAuthenticationType sets the authentication type attribute (for internet password items)
|
|
func (k *Item) SetAuthenticationType(s string) {
|
|
k.SetString(AuthenticationTypeKey, s)
|
|
}
|
|
|
|
// SetPort sets the port attribute (for internet password items)
|
|
func (k *Item) SetPort(v int32) {
|
|
k.SetInt32(PortKey, v)
|
|
}
|
|
|
|
// SetPath sets the path attribute (for internet password items)
|
|
func (k *Item) SetPath(s string) {
|
|
k.SetString(PathKey, s)
|
|
}
|
|
|
|
// SetAccount sets the account attribute
|
|
func (k *Item) SetAccount(a string) {
|
|
k.SetString(AccountKey, a)
|
|
}
|
|
|
|
// SetLabel sets the label attribute
|
|
func (k *Item) SetLabel(l string) {
|
|
k.SetString(LabelKey, l)
|
|
}
|
|
|
|
// SetDescription sets the description attribute
|
|
func (k *Item) SetDescription(s string) {
|
|
k.SetString(DescriptionKey, s)
|
|
}
|
|
|
|
// SetComment sets the comment attribute
|
|
func (k *Item) SetComment(s string) {
|
|
k.SetString(CommentKey, s)
|
|
}
|
|
|
|
// SetData sets the data attribute
|
|
func (k *Item) SetData(b []byte) {
|
|
if b != nil {
|
|
k.attr[DataKey] = b
|
|
} else {
|
|
delete(k.attr, DataKey)
|
|
}
|
|
}
|
|
|
|
// SetAccessGroup sets the access group attribute
|
|
func (k *Item) SetAccessGroup(ag string) {
|
|
k.SetString(AccessGroupKey, ag)
|
|
}
|
|
|
|
// SetSynchronizable sets the synchronizable attribute
|
|
func (k *Item) SetSynchronizable(sync Synchronizable) {
|
|
if sync != SynchronizableDefault {
|
|
k.attr[SynchronizableKey] = syncTypeRef[sync]
|
|
} else {
|
|
delete(k.attr, SynchronizableKey)
|
|
}
|
|
}
|
|
|
|
// SetAccessible sets the accessible attribute
|
|
func (k *Item) SetAccessible(accessible Accessible) {
|
|
if accessible != AccessibleDefault {
|
|
k.attr[AccessibleKey] = accessibleTypeRef[accessible]
|
|
} else {
|
|
delete(k.attr, AccessibleKey)
|
|
}
|
|
}
|
|
|
|
// SetMatchLimit sets the match limit
|
|
func (k *Item) SetMatchLimit(matchLimit MatchLimit) {
|
|
if matchLimit != MatchLimitDefault {
|
|
k.attr[MatchLimitKey] = matchTypeRef[matchLimit]
|
|
} else {
|
|
delete(k.attr, MatchLimitKey)
|
|
}
|
|
}
|
|
|
|
// SetReturnAttributes sets the return value type on query
|
|
func (k *Item) SetReturnAttributes(b bool) {
|
|
k.attr[ReturnAttributesKey] = b
|
|
}
|
|
|
|
// SetReturnData enables returning data on query
|
|
func (k *Item) SetReturnData(b bool) {
|
|
k.attr[ReturnDataKey] = b
|
|
}
|
|
|
|
// SetReturnRef enables returning references on query
|
|
func (k *Item) SetReturnRef(b bool) {
|
|
k.attr[ReturnRefKey] = b
|
|
}
|
|
|
|
// NewItem is a new empty keychain item
|
|
func NewItem() Item {
|
|
return Item{make(map[string]interface{})}
|
|
}
|
|
|
|
// NewGenericPassword creates a generic password item with the default keychain. This is a convenience method.
|
|
func NewGenericPassword(service string, account string, label string, data []byte, accessGroup string) Item {
|
|
item := NewItem()
|
|
item.SetSecClass(SecClassGenericPassword)
|
|
item.SetService(service)
|
|
item.SetAccount(account)
|
|
item.SetLabel(label)
|
|
item.SetData(data)
|
|
item.SetAccessGroup(accessGroup)
|
|
return item
|
|
}
|
|
|
|
// AddItem adds a Item to a Keychain
|
|
func AddItem(item Item) error {
|
|
cfDict, err := ConvertMapToCFDictionary(item.attr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer Release(C.CFTypeRef(cfDict))
|
|
|
|
errCode := C.SecItemAdd(cfDict, nil)
|
|
err = checkError(errCode)
|
|
return err
|
|
}
|
|
|
|
// UpdateItem updates the queryItem with the parameters from updateItem
|
|
func UpdateItem(queryItem Item, updateItem Item) error {
|
|
cfDict, err := ConvertMapToCFDictionary(queryItem.attr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer Release(C.CFTypeRef(cfDict))
|
|
cfDictUpdate, err := ConvertMapToCFDictionary(updateItem.attr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer Release(C.CFTypeRef(cfDictUpdate))
|
|
errCode := C.SecItemUpdate(cfDict, cfDictUpdate)
|
|
err = checkError(errCode)
|
|
return err
|
|
}
|
|
|
|
// QueryResult stores all possible results from queries.
|
|
// Not all fields are applicable all the time. Results depend on query.
|
|
type QueryResult struct {
|
|
// For generic application items
|
|
Service string
|
|
|
|
// For internet password items
|
|
Server string
|
|
Protocol string
|
|
AuthenticationType string
|
|
Port int32
|
|
Path string
|
|
|
|
Account string
|
|
AccessGroup string
|
|
Label string
|
|
Description string
|
|
Comment string
|
|
Data []byte
|
|
CreationDate time.Time
|
|
ModificationDate time.Time
|
|
}
|
|
|
|
// QueryItemRef returns query result as CFTypeRef. You must release it when you are done.
|
|
func QueryItemRef(item Item) (C.CFTypeRef, error) {
|
|
cfDict, err := ConvertMapToCFDictionary(item.attr)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
defer Release(C.CFTypeRef(cfDict))
|
|
|
|
var resultsRef C.CFTypeRef
|
|
errCode := C.SecItemCopyMatching(cfDict, &resultsRef) //nolint
|
|
if Error(errCode) == ErrorItemNotFound {
|
|
return 0, nil
|
|
}
|
|
err = checkError(errCode)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return resultsRef, nil
|
|
}
|
|
|
|
// QueryItem returns a list of query results.
|
|
func QueryItem(item Item) ([]QueryResult, error) {
|
|
resultsRef, err := QueryItemRef(item)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if resultsRef == 0 {
|
|
return nil, nil
|
|
}
|
|
defer Release(resultsRef)
|
|
|
|
results := make([]QueryResult, 0, 1)
|
|
|
|
typeID := C.CFGetTypeID(resultsRef)
|
|
if typeID == C.CFArrayGetTypeID() {
|
|
arr := CFArrayToArray(C.CFArrayRef(resultsRef))
|
|
for _, ref := range arr {
|
|
elementTypeID := C.CFGetTypeID(ref)
|
|
if elementTypeID == C.CFDictionaryGetTypeID() {
|
|
item, err := convertResult(C.CFDictionaryRef(ref))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
results = append(results, *item)
|
|
} else {
|
|
return nil, fmt.Errorf("invalid result type (If you SetReturnRef(true) you should use QueryItemRef directly)")
|
|
}
|
|
}
|
|
} else if typeID == C.CFDictionaryGetTypeID() {
|
|
item, err := convertResult(C.CFDictionaryRef(resultsRef))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
results = append(results, *item)
|
|
} else if typeID == C.CFDataGetTypeID() {
|
|
b, err := CFDataToBytes(C.CFDataRef(resultsRef))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
item := QueryResult{Data: b}
|
|
results = append(results, item)
|
|
} else {
|
|
return nil, fmt.Errorf("Invalid result type: %s", CFTypeDescription(resultsRef))
|
|
}
|
|
|
|
return results, nil
|
|
}
|
|
|
|
func attrKey(ref C.CFTypeRef) string {
|
|
return CFStringToString(C.CFStringRef(ref))
|
|
}
|
|
|
|
func convertResult(d C.CFDictionaryRef) (*QueryResult, error) {
|
|
m := CFDictionaryToMap(d)
|
|
result := QueryResult{}
|
|
for k, v := range m {
|
|
switch attrKey(k) {
|
|
case ServiceKey:
|
|
result.Service = CFStringToString(C.CFStringRef(v))
|
|
case ServerKey:
|
|
result.Server = CFStringToString(C.CFStringRef(v))
|
|
case ProtocolKey:
|
|
result.Protocol = CFStringToString(C.CFStringRef(v))
|
|
case AuthenticationTypeKey:
|
|
result.AuthenticationType = CFStringToString(C.CFStringRef(v))
|
|
case PortKey:
|
|
val := CFNumberToInterface(C.CFNumberRef(v))
|
|
result.Port = val.(int32)
|
|
case PathKey:
|
|
result.Path = CFStringToString(C.CFStringRef(v))
|
|
case AccountKey:
|
|
result.Account = CFStringToString(C.CFStringRef(v))
|
|
case AccessGroupKey:
|
|
result.AccessGroup = CFStringToString(C.CFStringRef(v))
|
|
case LabelKey:
|
|
result.Label = CFStringToString(C.CFStringRef(v))
|
|
case DescriptionKey:
|
|
result.Description = CFStringToString(C.CFStringRef(v))
|
|
case CommentKey:
|
|
result.Comment = CFStringToString(C.CFStringRef(v))
|
|
case DataKey:
|
|
b, err := CFDataToBytes(C.CFDataRef(v))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
result.Data = b
|
|
case CreationDateKey:
|
|
result.CreationDate = CFDateToTime(C.CFDateRef(v))
|
|
case ModificationDateKey:
|
|
result.ModificationDate = CFDateToTime(C.CFDateRef(v))
|
|
// default:
|
|
// fmt.Printf("Unhandled key in conversion: %v = %v\n", cfTypeValue(k), cfTypeValue(v))
|
|
}
|
|
}
|
|
return &result, nil
|
|
}
|
|
|
|
// DeleteGenericPasswordItem removes a generic password item.
|
|
func DeleteGenericPasswordItem(service string, account string) error {
|
|
item := NewItem()
|
|
item.SetSecClass(SecClassGenericPassword)
|
|
item.SetService(service)
|
|
item.SetAccount(account)
|
|
return DeleteItem(item)
|
|
}
|
|
|
|
// DeleteItem removes a Item
|
|
func DeleteItem(item Item) error {
|
|
cfDict, err := ConvertMapToCFDictionary(item.attr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer Release(C.CFTypeRef(cfDict))
|
|
|
|
errCode := C.SecItemDelete(cfDict)
|
|
return checkError(errCode)
|
|
}
|
|
|
|
// GetAccountsForService is deprecated
|
|
func GetAccountsForService(service string) ([]string, error) {
|
|
return GetGenericPasswordAccounts(service)
|
|
}
|
|
|
|
// GetGenericPasswordAccounts returns generic password accounts for service. This is a convenience method.
|
|
func GetGenericPasswordAccounts(service string) ([]string, error) {
|
|
query := NewItem()
|
|
query.SetSecClass(SecClassGenericPassword)
|
|
query.SetService(service)
|
|
query.SetMatchLimit(MatchLimitAll)
|
|
query.SetReturnAttributes(true)
|
|
results, err := QueryItem(query)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
accounts := make([]string, 0, len(results))
|
|
for _, r := range results {
|
|
accounts = append(accounts, r.Account)
|
|
}
|
|
|
|
return accounts, nil
|
|
}
|
|
|
|
// GetGenericPassword returns password data for service and account. This is a convenience method.
|
|
// If item is not found returns nil, nil.
|
|
func GetGenericPassword(service string, account string, label string, accessGroup string) ([]byte, error) {
|
|
query := NewItem()
|
|
query.SetSecClass(SecClassGenericPassword)
|
|
query.SetService(service)
|
|
query.SetAccount(account)
|
|
query.SetLabel(label)
|
|
query.SetAccessGroup(accessGroup)
|
|
query.SetMatchLimit(MatchLimitOne)
|
|
query.SetReturnData(true)
|
|
results, err := QueryItem(query)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(results) > 1 {
|
|
return nil, fmt.Errorf("Too many results")
|
|
}
|
|
if len(results) == 1 {
|
|
return results[0].Data, nil
|
|
}
|
|
return nil, nil
|
|
}
|