I’ve never liked programming with pointers and handles. I’ve done plenty, even cheating with fictional zero-length arrays in Object Pascal, but I know how easy it is to screw up. The sustained torrent of vulnerabilities and bugs attributable to pointer and handle use is testimony that I am not the only developer who screws them up, and the efforts of modern language designers to move away from them is indication of where we should be heading.
I did not expect to be grovelling around with pointers and buffers in Swift 3.1, but perhaps I should have kept away from Keychain Services if I had wanted a quiet life.
Having had that opening catharsis, yesterday’s session working on KeychainCheck 2 was peaceful, productive, and almost crash-free – so long as I don’t trust the documentation too much.
The first step was creating my MacAppScaffold, so that I wouldn’t have to keep wiring up text boxes before I can start productive coding. I have made that project available here separately, and hope that you find it as useful as I have already.
For the moment, during alpha development, I am going to pour out text information into the bottom scrolling text box in the window; when it goes into beta, those messages will be divided between alert sheets (for errors and warnings), and the same text area to build a report. So my first task was to build a function to take a Keychain Services numeric error code and the name of the call generating it, get the code converted into a text message, and write the text out in the bottom text box:
func displayError(status: OSStatus, fname: String) {
let theReserved: UnsafeMutableRawPointer? = nil
let theSctring = SecCopyErrorMessageString(status, theReserved)
let theErrorStr = fname + " returned an error. " + (theSctring! as String) + "\n"
let theAttrStr = NSAttributedString(string: theErrorStr)
textScrollContent1.textStorage?.append(theAttrStr)
}
This is a bit cryptic and messy, as SecCopyErrorMessageString()
has to be passed a Swift ‘NULL’ as its second parameter, which has to be generated using
let theReserved: UnsafeMutableRawPointer? = nil
It also doesn’t return a normal String, but a CFString, which has to be forced into being a proper Swift String, then turned into an Attributed String to go into that text box, as shown.
Another important function, which I’ll need to call repeatedly, is to display the status of the default keychain: whether locked, readable, and writeable. My inelegant Swift – and I welcome ideas for its improvement – to do that is:
func displayDefaultKCStatus() {
let theChain: SecKeychain? = nil
var theStatus: SecKeychainStatus = 0
let theResult4 = SecKeychainGetStatus(theChain, &theStatus)
if (theResult4 != 0) {
displayError(status: theResult4, fname: "SecKeychainGetStatus")
} else {
var theCompStr = "The default keychain is "
if ((theStatus & kSecUnlockStateStatus) > 0) {
theCompStr += "unlocked, "
} else {
theCompStr += "locked, "
}
if ((theStatus & kSecReadPermStatus) > 0) {
theCompStr += "readable, "
} else {
theCompStr += "not readable, "
}
if ((theStatus & kSecWritePermStatus) > 0) {
theCompStr += "writeable.\n"
} else {
theCompStr += "not writeable.\n"
}
let theAttrStr = NSAttributedString(string: theCompStr)
textScrollContent1.textStorage?.append(theAttrStr)
}
}
or formatted as:
This centres on the call SecKeychainGetStatus()
, which takes another Swift ‘NULL’ generated by
let theChain: SecKeychain? = nil
and a pointer to what is really a UInt32, dressed up as a SecKeychainStatus type. So the call is actually
let theResult4 = SecKeychainGetStatus(theChain, &theStatus)
This returns the status in theChain
. The following series of conditionals tests the three bit flags for unlock, read permission, and write permission, and builds the line of text to be written into the bottom text box.
The most exciting code so far is that which has gone into viewDidLoad()
, which is run when each window is opened. This starts gently, by ensuring that Keychain Services displays its normal password entry dialogs, etc. using SecKeychainSetUserInteractionAllowed(true)
. It then gets the version number of Keychain Services, which seems of little relevance to anything but has to be done, using SecKeychainGetVersion()
. Then it get positively hairy as we get a pointer to the default keychain, which is stored in a variable declared by
var theDefKeychain: SecKeychain? = nil
let theResult3 = SecKeychainCopyDefault(&theDefKeychain)
if (theResult3 != 0) {
displayError(status: theResult3, fname: "SecKeychainCopyDefault")
} else {
let theAttrStr = NSAttributedString(string: ("Found default keychain.\n"))
textScrollContent1.textStorage?.append(theAttrStr)
displayDefaultKCStatus()
announces success, then fetches and displays the default keychain status.
The next thing that I want is the path to that default keychain, for which we have to write C in Swift to pass SecKeychainGetPath()
a suitable buffer into which to write a null-terminated string. Although all the official advice is to create an UnsafeMutablePointer
, that really upset the Swift parser, which kept insisting that it couldn’t pass an UnsafeMutablePointer
for an UnsafeMutablePointer
.
This is the technique which works, and has the advantage that the buffer does not have to be explicitly deallocated when it is no longer needed:
var theStrBuff = Array(repeating: 0 as Int8, count: 1024)
var theLength: UInt32 = 1024
let theResult4 = SecKeychainGetPath(theDefKeychain, &theLength, &theStrBuff)
if (theResult4 != 0) {
displayError(status: theResult4, fname: "SecKeychainGetPath")
} else {
Getting that C string into a proper Swift string also seemed tough. At first, I tried a conversion using
let thePathStr = String(validatingUTF8: theStrBuff)
but the Swift parser was none too happy with that. So I looked around, and was prompted (albeit in Swift 2 code) to look at what used to be called NSFileManager:
theDefKeychainPath = FileManager().string(withFileSystemRepresentation: theStrBuff, length: Int(theLength))
let theAttrStr = NSAttributedString(string: ("Default keychain path: " + String(theDefKeychainPath) + "\n"))
textScrollContent1.textStorage?.append(theAttrStr)
}
}
So my 2.0a1 code now finds out quite a bit about the default keychain, and can lock and unlock it with ease. Although it is by no means impossible to negotiate your way through an unported interface like that of Keychain Services, if Apple seriously expects developers to use Swift for substantial projects, it needs to make all the interfaces to macOS features and functions properly Swiftian. Otherwise it defeats the purpose of switching to Swift in the first place.