Open YKdvd opened 1 year ago
Actually, I see the problem - cfstr_to_str is actually dealing with converting the CFData that is represented by "kSecValueData", not CFstring, and the name mislead me.
I found some ancient but still useful stuff at https://github.com/mountainstorm/MobileDevice/blob/41645fd0e7e3e674f0e963daaa374e3f44f18d67/CoreFoundation.py and have something that can get the attributes of a "kSecClassInternetPassword". I might try and clean it up and submit something as a suggestion if I ever get a chance.
Here is a working stand-alone version of the above code:
from keyring.backends.macOS import api
CFTypeRef = api.c_void_p
CFDictionaryRef = api.c_void_p
CFDictionaryGetValue = api._found.CFDictionaryGetValue
CFDictionaryGetValue.restype = CFTypeRef
CFDictionaryGetValue.argtypes = [CFDictionaryRef, CFTypeRef]
def find_internet_password(server, protocol, username, not_found_ok=False):
# https://developer.apple.com/documentation/security/keychain_services/keychain_items/searching_for_keychain_items?language=objc
q = api.create_query(
kSecClass=api.k_('kSecClassInternetPassword'),
kSecMatchLimit=api.k_('kSecMatchLimitOne'),
kSecAttrServer=server,
kSecAttrProtocol=protocol,
kSecAttrAccount=username,
kSecReturnAttributes=api.create_cfbool(True),
kSecReturnData=api.create_cfbool(True),
)
data = api.c_void_p()
status = api.SecItemCopyMatching(q, api.byref(data))
if status == api.error.item_not_found and not_found_ok:
return
api.Error.raise_for_status(status)
password = CFDictionaryGetValue(data, api.k_('kSecValueData'))
password = api.cfstr_to_str(password)
return password
password = find_internet_password('http://proxy.com', 'htpx', 'username')
print(password)
This is very useful in automatically getting proxy credentials from the machine.
Ideally, I'd like to be able to query only for the protocol (htpx
is an http proxy), and then retrieve the server name, port, user and password.
It is possible to query just by the protocol type by removing the server and username from the create_query
parameters (simply passing null does not work).
I still did not manage to parse the server, port, and username from the result though... if anyone is up to the task.
As a workaround, one can simply call:
security find-internet-password -r htpx
And parse the results.
Nice work. I'm very interested in providing a more complete interface. I'd very much like to build up the API module and possibly expose some of that through the Keyring (there's a mechanism by which environment variables can set properties on a keyring and thus affect behavior, so e.g. something like KEYRING_PROPERTY_KEY_CLASS="internet"
would cause macOS.Keyring.key_class = "internet"
and thus could signal this different kSecClass.
I recommend to start with expanding the APIs to support the functionality as best as possible and second to capture what are the use-cases that we're trying to support (do we want this to work with the CLI (keyring get ...
), the API (keyring.get/set_password
), or something else.
For anyone who ends up here on a similar search: just a big heads-up that the underlying macOS CLI tool, security find-internet-password
, searches all local keychains but not, bafflingly, iCloud Keychain. So as far as I can tell, there is no headless way to get internet passwords stored in iCloud Keychain.
The implementation above does provide value as it finds internet passwords from the login keychain (or any other local keychain), but if we were ever to support iCloud Keychain as well, it sounds like it would have to involve some GUI-based flow.
I was hoping to use keyring to retrieve existing internet passwords on the MacOS keychain for things like ssh server passwords, etc. It looks like keyring deals with "application password" items ("kSecClassGenericPassword"), and can't retrieve "Internet password" items ("kSecClassInternetPassword"). I came up with a variation on your routine, and while I was at it, thought I'd try and retrieve the attributes on the item as well, by setting "kSecReturnAttributes" in the query as well.
It seemed to work - I apparently get the promised CFDictionary back, and I cobbled together a CFDictionaryGetValue() routine from other sources. I can get the item data value (the password) from the dictionary entry "kSecValueData", and convert it as you do with cfstr_to_str(). But while I can successfully retrieve other keys like "kSecAttrServer", which should be the name of the server and also a CFString, cfstr_to_str() crashes with "[__NSCFString bytes]: unrecognized selector sent to instance", so it doesn't seem to be a CFString as expected, or something?
I don't really know the MacOS APIs (at least since MacOS 9 or so), and I may be missing something obvious between that and the whole coercing into Python, so I thought I'd check if anyone here might have an idea.
Also, while the keyring API doesn't seem to be set up to handle multiple flavours of passwords like this, would there be any interest in having the macOS.api have superset functions that can handle some of the other types as a convenience for MacOS folks?