Mar 21

Inching towards a Pythonic Keychain wrapper

I've started adding some more advanced ctypes wrappers for the OS X Keychain to PyMacAdmin. There's still a bunch of work to do but the upshot is that you can write code like this and expect it to work:

try:
 keychain = Keychain(options.keychain)
 item = keychain.find_generic_password(
service_name=options.service,
account_name=options.account
 )
print "Removing %s" % item
 keychain.remove(item)
except KeyError, exc:
 print >>sys.stderr, exc.message
except RuntimeError, exc:
 print >>sys.stderr, "Unable to delete keychain item: %s" % exc

and get output like this:

chris@Enceladus:~/Development/pymacadmin [git master] $ ./bin/keychain-delete.py -a "acdha"
Removing GenericPassword(service_name='', account_name='acdha', label='Audioscrobbler: acdha')

There's a bunch of stuff going on behind the scenes now to make things easier than the sadly-unimproved state discussed at length by Wil Shipley back in 2006:

  • PyMacAdmin.Security now defines many of the kSec* defines from SecKeychainItem.h, simplifying many calls which either required magic numbers or complicated struct.unpack() calls.
  • PyMacAdmin.Security.Keychain now defines a few classes which represent some of the native typedefs: SecKeychainAttribute, SecKeychainAttributeList and SecKeychainAttributeInfo.
  • Core PyMacAdmin improvements to make it a little easier to work with Carbon-style APIs:
  • mac_strerror() provides a way to lookup the error message for Carbon return codes
  • carbon_call() simplifies the process of calling a Carbon function by automatically checking its return code and throwing an exception any time the return code is negative.
  • carbon_errcheck() can be used as a ctypes errcheck function using the same logic as carbon_call()
  • load_carbon_framework() wraps the ctypes LoadLibrary() method to automatically use carbon_errcheck() for every function in the loaded library - this won't work for functions which use negative return codes for non-error results but the Carbon APIs are pretty consistent in that regard.
  • All of the above combines to allow PyMacAdmin.Security.Keychain's find_generic_password() to call SecKeychainItemCopyAttributesAndData behind the scenes to collect the item's label and will allow arbitrary other attributes in the future.
  • If you have any interest in wrapping native Mac APIs with Python please join the discussion over on the PyMacAdmin group - any sort of Python-OS X integration discussion is welcome.