// QueryItem returns a list of query results. func QueryItem(item Item) ([]QueryResult, error) { cfDict, err := ConvertMapToCFDictionary(item.attr) if err != nil { return nil, err } defer Release(C.CFTypeRef(cfDict)) var resultsRef C.CFTypeRef errCode := C.SecItemCopyMatching(cfDict, &resultsRef) if Error(errCode) == ErrorItemNotFound { return nil, nil } err = checkError(errCode) if err != nil { return nil, err } defer Release(resultsRef) results := make([]QueryResult, 0, 1) typeID := C.CFGetTypeID(resultsRef) if typeID == C.CFArrayGetTypeID() { arr := CFArrayToArray(C.CFArrayRef(resultsRef)) for _, dictRef := range arr { item, err := convertResult(C.CFDictionaryRef(dictRef)) if err != nil { return nil, err } results = append(results, *item) } } 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 }
// Convert converts a CFTypeRef to a go instance. func Convert(ref C.CFTypeRef) (interface{}, error) { typeID := C.CFGetTypeID(ref) if typeID == C.CFStringGetTypeID() { return CFStringToString(C.CFStringRef(ref)), nil } else if typeID == C.CFDictionaryGetTypeID() { return ConvertCFDictionary(C.CFDictionaryRef(ref)) } else if typeID == C.CFArrayGetTypeID() { arr := CFArrayToArray(C.CFArrayRef(ref)) results := make([]interface{}, 0, len(arr)) for _, ref := range arr { v, err := Convert(ref) if err != nil { return nil, err } results = append(results, v) return results, nil } } else if typeID == C.CFDataGetTypeID() { b, err := CFDataToBytes(C.CFDataRef(ref)) if err != nil { return nil, err } return b, nil } else if typeID == C.CFNumberGetTypeID() { return CFNumberToInterface(C.CFNumberRef(ref)), nil } else if typeID == C.CFBooleanGetTypeID() { if C.CFBooleanGetValue(C.CFBooleanRef(ref)) != 0 { return true, nil } return false, nil } return nil, fmt.Errorf("Invalid type: %s", CFTypeDescription(ref)) }
func convertResult(d C.CFDictionaryRef) (*QueryResult, error) { m := CFDictionaryToMap(C.CFDictionaryRef(d)) result := QueryResult{} for k, v := range m { switch attrKey(k) { case ServiceKey: result.Service = 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 DataKey: b, err := CFDataToBytes(C.CFDataRef(v)) if err != nil { return nil, err } result.Data = b // default: // fmt.Printf("Unhandled key in conversion: %v = %v\n", cfTypeValue(k), cfTypeValue(v)) } } return &result, 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 == nil { 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 { typeID := C.CFGetTypeID(ref) if typeID == 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 }
// GetAllAccountNames returns a list of all account names for the // given service name in the default keychain. func GetAllAccountNames(serviceName string) (accountNames []string, err error) { var serviceNameString C.CFStringRef if serviceNameString, err = _UTF8StringToCFString(serviceName); err != nil { return } defer C.CFRelease(C.CFTypeRef(serviceNameString)) query := map[C.CFTypeRef]C.CFTypeRef{ secClass: secClassGenericPassword, secAttrService: C.CFTypeRef(serviceNameString), secMatchLimit: secMatchLimitAll, secReturnAttributes: C.CFTypeRef(C.kCFBooleanTrue), } queryDict := mapToCFDictionary(query) defer C.CFRelease(C.CFTypeRef(queryDict)) var resultsRef C.CFTypeRef errCode := C.SecItemCopyMatching(queryDict, &resultsRef) err = newKeychainError(errCode) if err == ErrItemNotFound { return []string{}, nil } else if err != nil { return nil, err } defer C.CFRelease(resultsRef) // The resultsRef should always be an array (because kSecReturnAttributes is true) // but it's a good sanity check and useful if want to support kSecReturnRef in the future. typeID := C.CFGetTypeID(resultsRef) if typeID != C.CFArrayGetTypeID() { typeDesc := C.CFCopyTypeIDDescription(typeID) defer C.CFRelease(C.CFTypeRef(typeDesc)) err = fmt.Errorf("Invalid result type: %s", _CFStringToUTF8String(typeDesc)) return } results := _CFArrayToArray(C.CFArrayRef(resultsRef)) for _, result := range results { m := _CFDictionaryToMap(C.CFDictionaryRef(result)) resultServiceName := _CFStringToUTF8String(C.CFStringRef(m[secAttrService])) if resultServiceName != serviceName { err = fmt.Errorf("Expected service name %s, got %s", serviceName, resultServiceName) return } accountName := _CFStringToUTF8String(C.CFStringRef(m[secAttrAccount])) accountNames = append(accountNames, accountName) } return }
// ConvertCFDictionary converts a CFDictionary to map (deep). func ConvertCFDictionary(d C.CFDictionaryRef) (map[interface{}]interface{}, error) { m := CFDictionaryToMap(C.CFDictionaryRef(d)) result := make(map[interface{}]interface{}) for k, v := range m { gk, err := Convert(k) if err != nil { return nil, err } gv, err := Convert(v) if err != nil { return nil, err } result[gk] = gv } return result, nil }
func (k *keychain) Keys() ([]string, error) { serviceRef, err := _UTF8StringToCFString(k.Service) if err != nil { return nil, err } defer C.CFRelease(C.CFTypeRef(serviceRef)) query := map[C.CFTypeRef]C.CFTypeRef{ C.CFTypeRef(C.kSecClass): C.CFTypeRef(C.kSecClassGenericPassword), C.CFTypeRef(C.kSecAttrService): C.CFTypeRef(serviceRef), C.CFTypeRef(C.kSecMatchLimit): C.CFTypeRef(C.kSecMatchLimitAll), C.CFTypeRef(C.kSecReturnAttributes): C.CFTypeRef(C.kCFBooleanTrue), } kref, err := openKeychain(k.Path) if err != nil { return nil, err } searchArray := arrayToCFArray([]C.CFTypeRef{C.CFTypeRef(kref)}) defer C.CFRelease(C.CFTypeRef(searchArray)) query[C.CFTypeRef(C.kSecMatchSearchList)] = C.CFTypeRef(searchArray) queryDict := mapToCFDictionary(query) defer C.CFRelease(C.CFTypeRef(queryDict)) var resultsRef C.CFTypeRef if err = newKeychainError(C.SecItemCopyMatching(queryDict, &resultsRef)); err == errItemNotFound { return nil, nil } else if err != nil { return nil, err } defer C.CFRelease(resultsRef) var accountNames = []string{} for _, result := range _CFArrayToArray(C.CFArrayRef(resultsRef)) { m := _CFDictionaryToMap(C.CFDictionaryRef(result)) accountName := _CFStringToUTF8String(C.CFStringRef(m[C.CFTypeRef(C.kSecAttrAccount)])) accountNames = append(accountNames, accountName) } return accountNames, nil }
// we shouldn't ever get an error from this, but I'd rather not panic func convertCFTypeToInterface(cfType cfTypeRef) (interface{}, error) { typeId := C.CFGetTypeID(C.CFTypeRef(cfType)) switch typeId { case C.CFStringGetTypeID(): return convertCFStringToString(C.CFStringRef(cfType)), nil case C.CFNumberGetTypeID(): return convertCFNumberToInterface(C.CFNumberRef(cfType)), nil case C.CFBooleanGetTypeID(): return convertCFBooleanToBool(C.CFBooleanRef(cfType)), nil case C.CFDataGetTypeID(): return convertCFDataToBytes(C.CFDataRef(cfType)), nil case C.CFDateGetTypeID(): return convertCFDateToTime(C.CFDateRef(cfType)), nil case C.CFArrayGetTypeID(): ary, err := convertCFArrayToSlice(C.CFArrayRef(cfType)) return ary, err case C.CFDictionaryGetTypeID(): dict, err := convertCFDictionaryToMap(C.CFDictionaryRef(cfType)) return dict, err } return nil, &UnknownCFTypeError{typeId} }
func (state *unmarshalState) unmarshalValue(cfObj cfTypeRef, v reflect.Value) error { vType := v.Type() var unmarshaler Unmarshaler if u, ok := v.Interface().(Unmarshaler); ok { unmarshaler = u } else if vType.Kind() != reflect.Ptr && vType.Name() != "" && v.CanAddr() { // matching the encoding/json behavior here // If v is a named type and is addressable, check its address for Unmarshaler. vA := v.Addr() if u, ok := vA.Interface().(Unmarshaler); ok { unmarshaler = u } } if unmarshaler != nil { // flip over to the dumb conversion routine so we have something to give UnmarshalPlist() plist, err := convertCFTypeToInterface(cfObj) if err != nil { return err } if vType.Kind() == reflect.Ptr && v.IsNil() { v.Set(reflect.New(vType.Elem())) unmarshaler = v.Interface().(Unmarshaler) } return unmarshaler.UnmarshalPlist(plist) } if vType.Kind() == reflect.Ptr { if v.IsNil() { v.Set(reflect.New(vType.Elem())) } return state.unmarshalValue(cfObj, v.Elem()) } typeID := C.CFGetTypeID(C.CFTypeRef(cfObj)) vSetter := v // receiver of any Set* calls vAddr := v.Addr() // used for re-setting v for maps/slices if vType.Kind() == reflect.Interface { if v.IsNil() { // pick an appropriate type based on the cfobj var typ reflect.Type if typeID == cfNumberTypeID { typ = cfNumberTypeToType(C.CFNumberGetType(C.CFNumberRef(cfObj))) } else { var ok bool typ, ok = cfTypeMap[typeID] if !ok { return &UnknownCFTypeError{typeID} } } if !typ.AssignableTo(vType) { // v must be some interface that our object doesn't conform to state.recordError(&UnmarshalTypeError{cfTypeNames[typeID], vType}) return nil } vSetter.Set(reflect.Zero(typ)) } vAddr = v v = v.Elem() vType = v.Type() } switch typeID { case cfArrayTypeID: if vType.Kind() != reflect.Slice && vType.Kind() != reflect.Array { state.recordError(&UnmarshalTypeError{cfTypeNames[typeID], vType}) return nil } return convertCFArrayToSliceHelper(C.CFArrayRef(cfObj), func(elem cfTypeRef, idx, count int) (bool, error) { if idx == 0 && vType.Kind() == reflect.Slice { vSetter.Set(reflect.MakeSlice(vType, count, count)) v = vAddr.Elem() } else if vType.Kind() == reflect.Array && idx >= v.Len() { return false, nil } if err := state.unmarshalValue(elem, v.Index(idx)); err != nil { return false, err } return true, nil }) case cfBooleanTypeID: if vType.Kind() != reflect.Bool { state.recordError(&UnmarshalTypeError{cfTypeNames[typeID], vType}) return nil } vSetter.Set(reflect.ValueOf(C.CFBooleanGetValue(C.CFBooleanRef(cfObj)) != C.false)) return nil case cfDataTypeID: if !byteSliceType.AssignableTo(vType) { state.recordError(&UnmarshalTypeError{cfTypeNames[typeID], vType}) return nil } vSetter.Set(reflect.ValueOf(convertCFDataToBytes(C.CFDataRef(cfObj)))) return nil case cfDateTypeID: if !timeType.AssignableTo(vType) { state.recordError(&UnmarshalTypeError{cfTypeNames[typeID], vType}) return nil } vSetter.Set(reflect.ValueOf(convertCFDateToTime(C.CFDateRef(cfObj)))) return nil case cfDictionaryTypeID: if vType.Kind() == reflect.Map { // it's a map. Check its key type first if !stringType.AssignableTo(vType.Key()) { state.recordError(&UnmarshalTypeError{cfTypeNames[cfStringTypeID], vType.Key()}) return nil } if v.IsNil() { vSetter.Set(reflect.MakeMap(vType)) v = vAddr.Elem() } return convertCFDictionaryToMapHelper(C.CFDictionaryRef(cfObj), func(key string, value cfTypeRef, count int) error { keyVal := reflect.ValueOf(key) val := reflect.New(vType.Elem()) if err := state.unmarshalValue(value, val); err != nil { return err } v.SetMapIndex(keyVal, val.Elem()) return nil }) } else if vType.Kind() == reflect.Struct { return convertCFDictionaryToMapHelper(C.CFDictionaryRef(cfObj), func(key string, value cfTypeRef, count int) error { // we need to iterate the fields because the tag might rename the key var f reflect.StructField var ok bool for i := 0; i < vType.NumField(); i++ { sf := vType.Field(i) tag := sf.Tag.Get("plist") if tag == "-" { // Pretend this field doesn't exist continue } if sf.Anonymous { // Match encoding/json's behavior here and pretend it doesn't exist continue } name, _ := parseTag(tag) if name == key { f = sf ok = true // This is unambiguously the right match break } if sf.Name == key { f = sf ok = true } // encoding/json does a case-insensitive match. Lets do that too if !ok && strings.EqualFold(sf.Name, key) { f = sf ok = true } } if ok { if f.PkgPath != "" { // this is an unexported field return &UnmarshalFieldError{key, vType, f} } vElem := v.FieldByIndex(f.Index) if err := state.unmarshalValue(value, vElem); err != nil { return err } } return nil }) } state.recordError(&UnmarshalTypeError{cfTypeNames[typeID], vType}) return nil case cfNumberTypeID: switch vType.Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: i := convertCFNumberToInt64(C.CFNumberRef(cfObj)) if v.OverflowInt(i) { state.recordError(&UnmarshalTypeError{cfTypeNames[typeID] + " " + strconv.FormatInt(i, 10), vType}) return nil } if vSetter.Kind() == reflect.Interface { vSetter.Set(reflect.ValueOf(i)) } else { vSetter.SetInt(i) } return nil case reflect.Uint, reflect.Uintptr, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: u := uint64(convertCFNumberToUInt32(C.CFNumberRef(cfObj))) if v.OverflowUint(u) { state.recordError(&UnmarshalTypeError{cfTypeNames[typeID] + " " + strconv.FormatUint(u, 10), vType}) return nil } if vSetter.Kind() == reflect.Interface { vSetter.Set(reflect.ValueOf(u)) } else { vSetter.SetUint(u) } return nil case reflect.Float32, reflect.Float64: f := convertCFNumberToFloat64(C.CFNumberRef(cfObj)) if v.OverflowFloat(f) { state.recordError(&UnmarshalTypeError{cfTypeNames[typeID] + " " + strconv.FormatFloat(f, 'f', -1, 64), vType}) return nil } if vSetter.Kind() == reflect.Interface { vSetter.Set(reflect.ValueOf(f)) } else { vSetter.SetFloat(f) } return nil } state.recordError(&UnmarshalTypeError{cfTypeNames[typeID], vType}) return nil case cfStringTypeID: if vType.Kind() != reflect.String { state.recordError(&UnmarshalTypeError{cfTypeNames[typeID], vType}) return nil } vSetter.Set(reflect.ValueOf(convertCFStringToString(C.CFStringRef(cfObj)))) return nil } return &UnknownCFTypeError{typeID} }
func (k *keychain) Get(key string) (Item, error) { if _, err := os.Stat(k.Path); os.IsNotExist(err) { return Item{}, ErrKeyNotFound } serviceRef, err := _UTF8StringToCFString(k.Service) if err != nil { return Item{}, err } defer C.CFRelease(C.CFTypeRef(serviceRef)) accountRef, err := _UTF8StringToCFString(key) if err != nil { return Item{}, err } defer C.CFRelease(C.CFTypeRef(serviceRef)) query := map[C.CFTypeRef]C.CFTypeRef{ C.CFTypeRef(C.kSecClass): C.CFTypeRef(C.kSecClassGenericPassword), C.CFTypeRef(C.kSecAttrService): C.CFTypeRef(serviceRef), C.CFTypeRef(C.kSecAttrAccount): C.CFTypeRef(accountRef), C.CFTypeRef(C.kSecMatchLimit): C.CFTypeRef(C.kSecMatchLimitOne), C.CFTypeRef(C.kSecReturnAttributes): C.CFTypeRef(C.kCFBooleanTrue), C.CFTypeRef(C.kSecReturnData): C.CFTypeRef(C.kCFBooleanTrue), } kref, err := openKeychain(k.Path) if err != nil { return Item{}, err } searchArray := arrayToCFArray([]C.CFTypeRef{C.CFTypeRef(kref)}) defer C.CFRelease(C.CFTypeRef(searchArray)) query[C.CFTypeRef(C.kSecMatchSearchList)] = C.CFTypeRef(searchArray) queryDict := mapToCFDictionary(query) defer C.CFRelease(C.CFTypeRef(queryDict)) var resultsRef C.CFTypeRef if err = newKeychainError(C.SecItemCopyMatching(queryDict, &resultsRef)); err == errItemNotFound { return Item{}, ErrKeyNotFound } else if err != nil { return Item{}, err } defer C.CFRelease(resultsRef) m := _CFDictionaryToMap(C.CFDictionaryRef(resultsRef)) data := C.CFDataRef(m[C.CFTypeRef(C.kSecValueData)]) dataLen := C.int(C.CFDataGetLength(data)) cdata := C.CFDataGetBytePtr(data) item := Item{ Key: key, Data: C.GoBytes(unsafe.Pointer(cdata), dataLen), } if label, exists := m[C.CFTypeRef(C.kSecAttrLabel)]; exists { item.Label = _CFStringToUTF8String(C.CFStringRef(label)) } if descr, exists := m[C.CFTypeRef(C.kSecAttrDescription)]; exists { item.Description = _CFStringToUTF8String(C.CFStringRef(descr)) } return item, nil }
// FindIdentity ... // IMPORTANT: you have to C.CFRelease the returned items (one-by-one)!! // you can use the ReleaseIdentityWithRefList method to do that func FindIdentity(identityLabel string, isFullLabelMatch bool) ([]IdentityWithRefModel, error) { queryDict := C.CFDictionaryCreateMutable(nil, 0, nil, nil) defer C.CFRelease(C.CFTypeRef(queryDict)) C.CFDictionaryAddValue(queryDict, unsafe.Pointer(C.kSecClass), unsafe.Pointer(C.kSecClassIdentity)) C.CFDictionaryAddValue(queryDict, unsafe.Pointer(C.kSecMatchLimit), unsafe.Pointer(C.kSecMatchLimitAll)) C.CFDictionaryAddValue(queryDict, unsafe.Pointer(C.kSecReturnAttributes), unsafe.Pointer(C.kCFBooleanTrue)) C.CFDictionaryAddValue(queryDict, unsafe.Pointer(C.kSecReturnRef), unsafe.Pointer(C.kCFBooleanTrue)) var resultRefs C.CFTypeRef osStatusCode := C.SecItemCopyMatching(queryDict, &resultRefs) if osStatusCode != C.errSecSuccess { return nil, fmt.Errorf("Failed to call SecItemCopyMatch - OSStatus: %d", osStatusCode) } defer C.CFRelease(C.CFTypeRef(resultRefs)) identitiesArrRef := C.CFArrayRef(resultRefs) identitiesCount := C.CFArrayGetCount(identitiesArrRef) if identitiesCount < 1 { return nil, fmt.Errorf("No Identity (certificate + related private key) found in your Keychain!") } log.Debugf("identitiesCount: %d", identitiesCount) // filter the identities, by label retIdentityRefs := []IdentityWithRefModel{} for i := C.CFIndex(0); i < identitiesCount; i++ { aIdentityRef := C.CFArrayGetValueAtIndex(identitiesArrRef, i) log.Debugf("aIdentityRef: %#v", aIdentityRef) aIdentityDictRef := C.CFDictionaryRef(aIdentityRef) log.Debugf("aIdentityDictRef: %#v", aIdentityDictRef) lablCSting := C.CString("labl") defer C.free(unsafe.Pointer(lablCSting)) vrefCSting := C.CString("v_Ref") defer C.free(unsafe.Pointer(vrefCSting)) labl, err := getCFDictValueUTF8String(aIdentityDictRef, C.CFTypeRef(convertCStringToCFString(lablCSting))) if err != nil { return nil, fmt.Errorf("FindIdentity: failed to get 'labl' property: %s", err) } log.Debugf("labl: %#v", labl) if isFullLabelMatch { if labl != identityLabel { continue } } else { if !strings.Contains(labl, identityLabel) { continue } } log.Debugf("Found identity with label: %s", labl) vrefRef, err := getCFDictValueRef(aIdentityDictRef, C.CFTypeRef(convertCStringToCFString(vrefCSting))) if err != nil { return nil, fmt.Errorf("FindIdentity: failed to get 'v_Ref' property: %s", err) } log.Debugf("vrefRef: %#v", vrefRef) // retain the pointer vrefRef = C.CFRetain(vrefRef) // store it retIdentityRefs = append(retIdentityRefs, IdentityWithRefModel{ KeychainRef: vrefRef, Label: labl, }) } return retIdentityRefs, nil }