Mind Bending

Gnome Keyring

In the last post I’ve shown how to create keyrings using python and mentioned a slightly difference from the "seahorse password storing process". Well, it happens that, when we start to dig this difference isn’t so small. Using seahorse every keyring item is created with the "Update if Exists" flag as False, so you can create identical keyring items. This is not a safe approach and can result in an inconsistent keyring. But as we use the "Update if Exists" flag set as True, something unexpected happens:

#!/usr/bin/env python

import gnomekeyring as gk
import glib

APP_NAME = 'MyApp'
KEYRING_NAME = 'MyKeyring'

glib.set_application_name(APP_NAME)

keyrings = gk.list_keyring_names_sync()

# If this keyring already exist, let's remove it
if KEYRING_NAME in keyrings:
    # Gnome Keyring Daemon may ask for a password here
    gk.delete_sync(KEYRING_NAME)

# If anyone asks, the password is 'mypasswd'
gk.create_sync(KEYRING_NAME, 'mypasswd')

id = gk.item_create_sync(KEYRING_NAME, gk.ITEM_GENERIC_SECRET, 'magnun@Neptune:22', {'application':APP_NAME}, 'passwd', True)
print 'New host added (key=%i)'%(key)

id = gk.item_create_sync(KEYRING_NAME, gk.ITEM_GENERIC_SECRET, 'guest@Neptune:22', {'application':APP_NAME}, 'passwd', True)
print 'New host added (key=%i)'%(key)

id = gk.item_create_sync(KEYRING_NAME, gk.ITEM_GENERIC_SECRET, 'magnun@Jupiter:22', {'application':APP_NAME}, 'passwd', True)
print 'New host added (id=%i)'%(id)

Save this as my_keyring_creator.py and run it.

Note that the printed id is always the same. Open Seahorse after executing this script and you’ll notice that exists only one keyring item, "magnun@Jupiter:22". This happens because the Gnome Keyring Daemon uses the attributes to define when a keyring item is duplicated or not. So we need to define some attributes to uniquely identify each keyring item. The following script show a good example of this:

#!/usr/bin/env python

import gnomekeyring as gk
import glib

APP_NAME = 'MyApp'
KEYRING_NAME = 'MyKeyring'

glib.set_application_name(APP_NAME)

keyrings = gk.list_keyring_names_sync()

# If this keyring already exist, let's remove it
if KEYRING_NAME in keyrings:
    # Gnome Keyring Daemon may ask for a password here
    gk.delete_sync(KEYRING_NAME)

# If anyone asks, the password is 'mypasswd'
gk.create_sync(KEYRING_NAME, 'mypasswd')

def add_item(username, password, server, protocol, port):
    attr = {
            'application':APP_NAME,
            'username':username,
            'server':server,
            'protocol':protocol,
            'port':str(port),
           }
    name = username+'@'+server+':'+port
    id = gk.item_create_sync(KEYRING_NAME, gk.ITEM_GENERIC_SECRET, name, attr, password, True)
    return id

id = add_item('magnun', 'mypasswd', 'Neptune', 'ssh', '22')
print 'New host added (id=%i)'%(id)
id = add_item('guest', 'mypasswd', 'Neptune', 'ssh', '22')
print 'New host added (id=%i)'%(id)
id = add_item('magnun', 'mypasswd', 'Jupiter', 'ssh','22')
print 'New host added (id=%i)'%(id)

Save this as my_keyring_creator.py and run it.

Now is prompted different ids for each keyring items, each item is unique and are correctly stored. This attributes have another functionality beyond indicates uniqueness.

Searching for items

Let’s say again that you have a keyring named ‘MyKeyring’ for your application called ‘MyApp’ and a bunch of keyring items. If you want to test this environment use the last snipper to create it. During a certain part of your application you need to search for a specific keyring item, let’s suppose you need to search for the magnun@Neptune:22 password. Using only the methods I explained we’ll need to use the following algorithm:

#!/usr/bin/env python

import gnomekeyring as gk
import glib

APP_NAME = 'MyApp'
KEYRING_NAME = 'MyKeyring'

glib.set_application_name(APP_NAME)

keyrings = gk.list_keyring_names_sync()

# Quit if the keyring don't exist
if KEYRING_NAME not in keyrings:
    print 'Keyring',KEYRING_NAME,'not found'
    print 'Exiting...'
    exit()

def search_secret(username, server, port):
    name = username+'@'+server+':'+port
    items_ids = gk.list_item_ids_sync(KEYRING_NAME)
    for item_id in items_ids:
        item_info = gk.item_get_info_sync(KEYRING_NAME, item_id)
        if name == item_info.get_display_name():
            secret = item_info.get_secret()
            return secret
    else:
        return None

print 'Searching magnun@Neptune:22 secret:',
print search_secret('magnun', 'Neptune', '22')

print 'Searching guest@Neptune:22 secret:',
print search_secret('magnun', 'Neptune', '22')

print 'Searching magnun@Jupiter:22 secret:',
print search_secret('magnun', 'Jupiter', '22')

print 'Searching guest@Jupiter:22 secret:',
print search_secret('guest', 'Jupiter', '22')

Save this as my_keyring_inspector.py and run it.

There is another approach tho solve this problem, we can use the method find_items_sync. This method return a list with GnomeKeyringFound type, which holds its the keyring items attributes, secret, parent keyring and id. This method is faster than the presented above and has the flexibility to do custom searches. With the find_items_sync we can search for all stored keyring items with and specific username or server. Let’s see an example:

#!/usr/bin/env python

import gnomekeyring as gk
import glib

APP_NAME = 'MyApp'
KEYRING_NAME = 'MyKeyring'

glib.set_application_name(APP_NAME)

keyrings = gk.list_keyring_names_sync()

# Quit if the keyring don't exist
if KEYRING_NAME not in keyrings:
    print 'Keyring',KEYRING_NAME,'not found'
    print 'Exiting...'
    exit()

def search_secret(username, server, port):
    attr = {
            'username':username,
            'server':server,
            'port':port,
            'application':APP_NAME,
           }
    try:
        result_list = gk.find_items_sync(gk.ITEM_GENERIC_SECRET, attr)
    except gk.NoMatchError:
        return None

    secrets = [result.secret for result in result_list]
    if len(secrets) == 1:
        secrets = secrets[0]
    return secrets

print 'Searching magnun@Neptune:22 secret:',
print search_secret('magnun', 'Neptune', '22')

print 'Searching guest@Neptune:22 secret:',
print search_secret('magnun', 'Neptune', '22')

print 'Searching magnun@Jupiter:22 secret:',
print search_secret('magnun', 'Jupiter', '22')

print 'Searching guest@Jupiter:22 secret:',
print search_secret('guest', 'Jupiter', '22')

Save this as my_keyring_inspector.py and run it.

As all may know, every bright side has a dark side. As this method search through all keyrings, it request access for all keyrings, what can be annoying once Gnome Keyring presents a dialog to ensure the need of this access. But as said before this approach is faster, in a simple benchmark with 500 keyring items, the executions my_keyring_inspector.py with list_item_ids_sync method took approximately 0.615 seconds while the my_keyring_inspector.py with find_items_sync method executions took approximately 0.037 seconds. It’s almost 16 times faster.

The search method decision is up you, each has its advantages and disadvantages, I’m just presenting possibilities and basic analysis. If I don’t left some mind effort up to you I couldn’t call this blog "Mind Bending".

Magnun

Magnun

Graduated in Telecommunication Engineering, but currently working with GNU/Linux infrastructure and in the spare time I'm an Open Source programmer (Python and C), a drawer and author in the Mind Bending Blog.


Comments

comments powered by Disqus