cyberark/secretless-broker

View on GitHub
internal/providers/keychain/osx.go

Summary

Maintainability
A
0 mins
Test Coverage
// +build darwin

package keychain

// See https://github.com/keybase/go-keychain/blob/master/keychain.go

// 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 <stdlib.h>
#include <CoreFoundation/CoreFoundation.h>
#include <Security/Security.h>
*/
import "C"

import (
    "fmt"
    "unsafe"
)

// NULL is just an int representation of C NULL
const NULL = 0

func release(ref C.CFTypeRef) {
    C.CFRelease(ref)
}

// GetGenericPassword returns password data for service and account
func GetGenericPassword(service string, account string) ([]byte, error) {
    serviceC := C.CString(service)
    accountC := C.CString(account)

    for _, ptr := range []*C.char{serviceC, accountC} {
        defer C.free(unsafe.Pointer(ptr))
    }

    serviceCf := C.CFStringCreateWithCString(NULL, serviceC, C.kCFStringEncodingUTF8)
    accountCf := C.CFStringCreateWithCString(NULL, accountC, C.kCFStringEncodingUTF8)

    for _, ptr := range []C.CFStringRef{serviceCf, accountCf} {
        defer release(C.CFTypeRef(ptr))
    }

    keys := make([]C.CFTypeRef, 4)
    values := make([]C.CFTypeRef, 4)

    keys[0] = C.CFTypeRef(C.kSecAttrService)
    keys[1] = C.CFTypeRef(C.kSecAttrAccount)
    keys[2] = C.CFTypeRef(C.kSecClass)
    keys[3] = C.CFTypeRef(C.kSecReturnData)

    values[0] = C.CFTypeRef(serviceCf)
    values[1] = C.CFTypeRef(accountCf)
    values[2] = C.CFTypeRef(C.kSecClassGenericPassword)
    values[3] = C.CFTypeRef(unsafe.Pointer(C.kCFBooleanTrue))

    keyCallbacks := (*C.CFDictionaryKeyCallBacks)(&C.kCFTypeDictionaryKeyCallBacks)
    valCallbacks := (*C.CFDictionaryValueCallBacks)(&C.kCFTypeDictionaryValueCallBacks)

    queryCf := C.CFDictionaryCreate(NULL, (*unsafe.Pointer)(unsafe.Pointer(&keys[0])), (*unsafe.Pointer)(unsafe.Pointer(&values[0])), C.CFIndex(len(keys)), keyCallbacks, valCallbacks)

    defer release(C.CFTypeRef(unsafe.Pointer(queryCf)))

    var resultsRef C.CFTypeRef
    errCode := C.SecItemCopyMatching(queryCf, &resultsRef)
    if errCode != 0 {
        errorMessageCf := C.SecCopyErrorMessageString(errCode, nil)
        defer release(C.CFTypeRef(errorMessageCf))
        // Whether or not this function returns a valid pointer or NULL depends on many factors, ...
        errorMessageC := C.CFStringGetCStringPtr(errorMessageCf, C.kCFStringEncodingUTF8)
        var message string
        if errorMessageC != nil {
            message = C.GoString(errorMessageC)
        } else {
            C.CFShow(C.CFTypeRef(errorMessageCf))
            message = fmt.Sprintf("An unknown error occurred : %d", int(errCode))
        }
        return nil, fmt.Errorf(message)
    }

    defer release(C.CFTypeRef(resultsRef))

    cfData := C.CFDataRef(resultsRef)
    bytes := C.GoBytes(unsafe.Pointer(C.CFDataGetBytePtr(cfData)), C.int(C.CFDataGetLength(cfData)))

    return bytes, nil
}