Due to a small project I’m developing at the moment I had to access the Mac OS X keyring over python. There is some code of seen around but was not very ‘clean’. The following is my attempt to access the Mac OS X Keyring via python using ctypes, I hope it helps someone out there:
from ctypes import ( byref, c_int32, c_uint32, c_void_p, create_string_buffer, memmove, cdll, ) from ctypes.util import find_library from secrets.utils import are_not_none, valid_args # Types OSStatus = c_int32 SecKeychainItemRef = c_void_p SecKeychainRef = c_void_p # Constants errSecSuccess = 0 errSecItemNotFound = -25300 # Native libraries _security = cdll.LoadLibrary(find_library('Security')) _core = cdll.LoadLibrary(find_library('CoreServices')) def keyring_is_open(): """Assert that the keyring is not None.""" def keyring_not_none(name, value, method_name='Method'): """Ensure that the keyring is open.""" if not getattr(value, '', None): raise OSError('Keyring most be open.') return valid_args(keyring_not_none, (('Keyring', 0))) class Backend(object): """Keyring backend for Mac Os X.""" def __init__(self): """Create a new Mac OS X backend.""" super(Backend, self).__init__() self.keychain = None def _get_item(self, realm, username): """Return the item that matched the given info.""" item = SecKeychainItemRef() status = _security.SecKeychainFindGenericPassword(self.keychain, len(realm), realm, len(username), username, None, None, byref(item)) return item, status def open(self): """Open the keyring.""" # we should not be open if self.keychain is not None: raise OSError() self.keychain = SecKeychainRef() # try to open the keyring if _security.SecKeychainOpen('login.keychain', byref(self.keychain)): raise OSError("Can't access the keychain") @keyring_is_open def close(self): """Close the keyring.""" _core.CFRelease(self.keychain) self.keychain = None @keyring_is_open @are_not_none(( ('realm', 1), ('username', 2), ('password', 3))) def set_password(self, realm, username, password): """Set the password for the given real and username.""" item, status = self._get_item(realm, username) if status == errSecItemNotFound: status = _security.SecKeychainAddGenericPassword(self.keychain, len(realm), realm, len(username), username, len(password), password, None) if status: raise OSError() else: raise OSError() @keyring_is_open @are_not_none(( ('realm', 1), ('username', 2), ('password', 3))) def update_password(self, realm, username, password): """Update the password for the given realm.""" item, status = self._get_item(realm, username) if status and status == errSecItemNotFound: raise OSError("Can't store password in keychain") else: status = _security.SecKeychainItemModifyAttributesAndData(item, None, len(password), password) _core.CFRelease(item) if status: raise OSError() if status: raise OSError("Can't store password in keychain") @keyring_is_open @are_not_none(( ('realm', 1), ('username', 2))) def get_password(self, realm, username): """Get the password for the given real and username.""" length = c_uint32() data = c_void_p() status = _security.SecKeychainFindGenericPassword(self.keychain, len(realm), realm, len(username), username, byref(length), byref(data), None) if status == errSecSuccess: password = create_string_buffer(length.value) memmove(password, data.value, length.value) password = password.raw _security.SecKeychainItemFreeContent(None, data) elif status == errSecItemNotFound: password = None else: raise OSError("Can't fetch password from system") return password @keyring_is_open @are_not_none(( ('realm', 1), ('username', 2))) def delete_password(self, realm, username): """Delete the password of the given real and username.""" item, status = self._get_item(realm, username) if status == errSecSuccess: _security.SecKeychainItemDelete(item) _core.CFRelease(item) else: raise OSError() |
I have not added the code of the decorator because they are just noise, the only thing they do is to check that the keyring was indeed opened (self.keyring != None) and that the parameters with the given index are not None (I’m lazy and I prefer to use decorators for this mundane tasks that are done everywhere.
Read more
Latest Official Posts