From the number of people visiting this blog to read about the lack of tools for dealing with keychain problems, there is a need for better tools. With the simple KeychainCheck as my starting point, I have launched into producing something better and more capable.
The snag with trying to use a scripting approach is that, while the security
command offers a rich range of tools, there’s the ever-present problem of authentication. Browsing the features offered to apps, it seems possible to produce an app which can do much more, with less grief in terms of development.
That was my intention, but reality went downhill from there.
My first discovery was that the calls for working with keychains have not yet been made fully friendly to Swift. This came as something of a shock, as Swift has been available for almost three years, has enjoyed very substantial resourcing within Apple, and is now at version 3.1. Despite that, the Swift interface to the many function calls to work with keychains are crude kludges from the Objective-C interfaces.
There have been several attempts to make Swiftian wrappers, but most are aimed primarily at iOS, none is comprehensive, and I decided, for better or worse, that I should develop my own solution.
I also feared that anything to do with keychains would be way off limits for Xcode’s Swift playgrounds. On that I was wrong: everything that I have tried so far in a Swift playground has worked, once I have bludgeoned my way through the business of passing it the right parameters. My major problem, as seems always to be the case, has been the confusing documentation and lack of example code. That Apple recommends its open source security
command tool (written mainly in C) as example code is very telling for someone working in Swift.
The first task which I set myself was producing code to lock and unlock the default user login
keychain, using the standard system-supplied dialogs. The sequence of calls is thus going to be:
SecKeychainSetUserInteractionAllowed()
to ensure that macOS displays its standard dialogs,SecKeychainUnlock()
to unlock thelogin
keychain,SecCopyErrorMessageString()
to convert any resulting error into a more informative text message.
So I browsed the documentation using Dash, and discovered that SecKeychainUnlock()
would have four parameters:
- a reference to the keychain to unlock, for which I would “pass NULL to specify the default keychain.”
- an unsigned 32-bit integer giving the length of the password buffer. As I was going to leave macOS to prompt for the password, this would be 0.
- a buffer containing the password, or in my case “NULL if the user password is unknown” to display the Unlock Keychain dialog.
- a Boolean value, which would be
false
as I am not passing a password.
Only that doesn’t mean that you would code the call as
let theResult2 = SecKeychainUnlock(NULL, 0, NULL, false)
When the documentation refers to “passing NULL”, it actually means something more like
let theChain: SecKeychain? = nil
let theAccess: UnsafeRawPointer? = nil
let theResult2 = SecKeychainUnlock(theChain, 0, theAccess, false)
only this is made clear hardly anywhere.
Then, we have to deal with SecCopyErrorMessageString()
, which doesn’t return a Swift String at all, but CFString?
The docs assure us that “You must call the CFRelease function to release this object when you are finished using it.” Only there isn’t a CFRelease
function. And as I delve deeper, other docs tell me that I should not try to release a CFString
at all.
My first lesson in digging this particularly long and twisty trench is best summarised in the playground code which works:
let theChain: SecKeychain? = nil
let theAccess: UnsafeRawPointer? = nil
let theReserved: UnsafeMutableRawPointer? = nil
var theResString: String = ""
let theResult1 = SecKeychainSetUserInteractionAllowed(true)
let theResult2 = SecKeychainUnlock(theChain, 0, theAccess, false)
print(theResult2)
if (theResult2 != 0) {
let theSctring = SecCopyErrorMessageString(theResult2, theReserved)
theResString = theSctring! as String
print(theResString)
}
or, properly formatted:
Perhaps I’m naïve, but that doesn’t seem to me to be anything like what the docs were telling me to do; it does, though, work a treat.