Hi, has anybody tried to use the Keychain Services...
# ios
a
Hi, has anybody tried to use the Keychain Services API from Kotlin? I hoped it'd be possible to translate that to Kotlin: https://agostini.tech/2017/03/06/creating-a-simple-keychain-wrapper/ Here is an example of CFDictionary with Kotlin: https://github.com/JetBrains/kotlin-native/issues/3013 But I'm stuck, here are my thoughts:
Copy code
fun keychainQuery(key: String) = 
    CFDictionaryCreateMutable(null, 5, null, null).also {
        CFDictionaryAddValue(it, kSecClassGenericPassword, kSecClass)
        CFDictionaryAddValue(it, key.cstr, kSecAttrService)
        CFDictionaryAddValue(it, kSecAttrAccessibleWhenUnlockedThisDeviceOnly, kSecAttrAccessible)
    }

fun load(key: String) : String {
    val query = keychainQuery(key)
    
    CFDictionaryAddValue(query, kCFBooleanTrue, kSecReturnData)
    CFDictionaryAddValue(query, kCFBooleanTrue, kSecReturnAttributes)

    memScoped {
        val result: CFTypeRefVar = alloc()
        val status = SecItemCopyMatching(query, result.ptr)
        
        val resultsDict = result as CFDictionaryRef // <-- This cast can never succeed
        val resultsData: COpaquePointer? = CFDictionaryGetValue(result, kSecValueData)
    }
}
@Ellen Shapiro That looks like a cleaner way for sure! I still would like to get this to work, for the sake of learning more about the kotlin-native interop. I tried further and my code now looks like this:
Copy code
fun keychainQuery(key: String) =
    CFDictionaryCreateMutable(null, 5, null, null).also {
        CFDictionaryAddValue(it, kSecClassGenericPassword, kSecClass)
        CFDictionaryAddValue(it, key.cstr, kSecAttrService)
        CFDictionaryAddValue(it, kSecAttrAccessibleWhenUnlockedThisDeviceOnly, kSecAttrAccessible)
    }

fun load(key: String): String = memScoped {
    val query = keychainQuery(key)

    CFDictionaryAddValue(query, kCFBooleanTrue, kSecReturnData)
    CFDictionaryAddValue(query, kCFBooleanTrue, kSecReturnAttributes)

    val result: CFTypeRefVar = alloc()
    val status = SecItemCopyMatching(query, result.ptr)

    Logging.debug("Keychain Load", status.toString())

    val resultsDict = result as CFDictionaryRefVar
    val resultsData = CFDictionaryGetValue(resultsDict.value, kSecValueData) as CFDataRef

    val value = CFStringCreateFromExternalRepresentation(null, resultsData, kCFStringEncodingUTF8)

    return@memScoped CFBridgingRelease(value) as String
}

fun save(value: String, key: String) = memScoped {
    val query = keychainQuery(key)

    val objectData = CFDataCreate(null, value.cstr.ptr as CValuesRef<UInt8Var>, value.cstr.size.toLong())

    if (SecItemCopyMatching(query, null) == noErr.toInt()) {
        val attributes = CFDictionaryCreateMutable(null, 1, null, null)
        CFDictionaryAddValue(attributes, kSecValueData, objectData)
        
        val status = SecItemUpdate(query, attributes)
        Logging.debug("Keychain Update", status.toString())
    } else {
        CFDictionaryAddValue(query, objectData, kSecValueData)
        
        val status = SecItemAdd(query, null)
        Logging.debug("Keychain Add", status.toString())
    }
}
It compiles and there are no "This cast can never succeed"-warnings anymore, only unchecked casts. The load function prints Keychain Load with Status -50 and afterwards: kotlin.ClassCastException: kotlinx.cinterop.NativePointed cannot be cast to kotlinx.cinterop.CPointerVarOf The save process stops with EXC_BAD_ACCESS
e
Yeah the C pointer stuff is…pointy.
it’s
errSecParam
which conveniently has no documentation 🙃
so my guess is something isn’t gettin bridged properly in terms of parameters. What, I have no idea. but maybe that’s at least a place to start?
a
I'm thankful for your input! I was adding a CString instead of a CFString to the CFDictionary, I changed it to:
Copy code
CFDictionaryAddValue(it, CFStringCreateWithCString(null, key, kCFStringEncodingUTF8), kSecAttrService)
Didn't change the result though :(
Well... adding a value to NSMutableDictionary: value, key adding to CFDictionary: key, value So I had to swap every key and value 🙃 Keychain Add returns status 0 now and no exceptions happen, great 🙂 Trying to save the same pair again prints Keychain Update 0, awesome 🙂 Keychain Load returns status 0, BUT followed by the ClassCastException I mentioned before So we aren't there yet, but we are close, I guess 😄
s
result as CFDictionaryRefVar
Use
result.reinterpret<CFDictionaryRefVar>()
t
Hi @Andreas Jost, did you figure this one out, I'm trying to read a string value from keychain, but hasn't succeeded yet 😕