Unlocking the GNOME keyring

Posted on by Chris Warburton

The GNOME keyring is a service which runs in the background when logged in to a GNOME desktop session (or, in my case, Phosh on my PinePhone; many of its APIs are also compatible with KWallet too). Its job is to store (collections of) secrets, like passwords:

The current setup on my PinePhone was rather akward, having evolved and congealed over the past few years, resulting in the following:

This combination had a rather unfortunate problem: my GNOME keyring must be unlocked before that SystemD service can access those SFTP shares. This causes a password prompt to appear as soon as my phone starts up, and the service itself may retry several times before I’ve managed to type it in correctly.

GCR

Recently, the SSH agent functionality of GNOME keyring has been removed. AFAIK this was due to it essentially re-implementing the functionality of the standard ssh-agent command, but doing so in a way that was less flexible and configurable than the latter. That’s now gone, in favour of GCR which is just a simple wrapper around ssh-agent, allowing its usual configuration, etc. to be used.

Unfortunately, this switch also happened to change the default location used by the agent’s socket, from $XDG_RUNTIME_DIR/keyring/ssh to $XDG_RUNTIME_DIR/gcr/ssh. This required me to update my SystemD service, so I thought it was about time I improved the overall process a little.

A command to prompt unlocking

Since unlocking requires user interaction (to enter the password) I think it’s reasonable to also require the user to initiate the process; rather than being hit with a prompt when logged in, or after connecting to my home WiFi, etc. My idea is to have a script, which can be bound to a .desktop entry, hotkey, etc. which does the following:

It turns out to be surprisingly hard to simply get GNOME keyring to unlock! Commands like secret-tool are designed to look up secrets with a specified name, which I don’t want to do. I eventually tried the dbus-send command, to invoke the collection’s Unlock method directly: that returns a Prompt object, with methods to either show or dismiss it. Unfortunately, that Promp seems to only exist so long as our DBus connection remains open: since dbus-send sends a single message then disconnects, we never get a chance to tell a Prompt to show!

The solution is to use a “proper” programming language, with a DBus library we can use to keep a single connection open for several messages. Python’s secretstorage library is particularly simple, and available in Nixpkg’s python3Packages attrset. Here’s the script I’m now using:

#!/usr/bin/env python3
import secretstorage
connection = secretstorage.dbus_init()

# This should be login keyring, it should have the same password as our user,
# but it will still be locked if we were automatically logged in to our user
# session (since we didn't type in the password in that case).
default = secretstorage.get_default_collection(connection)

# All keyrings (useful if we put all of our credentials in a different keyring)
keyrings = secretstorage.collection.get_all_collections(connection)

# (Try to) unlock them all, starting with default since that can be used to
# store passwords of other keyrings!
for k in ([default] + [kr for kr in keyrings]):
    if k.is_locked():
        # Waits for password to be entered, or dialogue to be dismissed
        k.unlock()

This script successfully unlocks the collections in GNOME keyring (which can be validated by using Seahorse to lock/unlock them manually for testing). Since .unlock() is synchronous, this loop allows multiple collections to be unlocked from a single password prompt, as long as the default collection contains passwords for the others (this was useful to me since I had two keyrings, as a historical accident).

My next task will be to set up the SystemD services appropriately, which is easy enough using Home Manager.