hwchen/keyring-rs

Keyring doesn't know how to disambiguate elements from locked collections.

Closed this issue · 6 comments

As reported in the commentary on #201, the Gnome implementation of the secret service obfuscates the attribute values of searched-for credentials in locked collections. This causes keyring to fail at disambiguating these results based on the target attribute, and it falsely reports ambiguous results when it should not do so.

Here is a sample use case. I used keyring to create two entries. Both entries had empty user and service names, but each entry was given a different target (so they were each created by keyring in a separate collection named for the target). When I use secret-tool to search for all entries with an empty username attribute, both entries are returned, but their actual attribute values are obscured because their respective collections are both locked:

secret-tool search --all username ""
secret-tool: Cannot get secret of a locked object
[/1]
label = 
created = 
modified = 
schema = org.freedesktop.Secret.Generic
attribute.gkr:compat:hashed:application = 79d05a70f3e1b0a4365c7912ee036a95
attribute.gkr:compat:hashed:service = d41d8cd98f00b204e9800998ecf8427e
attribute.gkr:compat:hashed:target = 317a8c66319e867ba9cbe4067fdf951d
attribute.gkr:compat:hashed:username = d41d8cd98f00b204e9800998ecf8427e
secret-tool: Cannot get secret of a locked object
[/1]
label = 
created = 
modified = 
schema = org.freedesktop.Secret.Generic
attribute.gkr:compat:hashed:application = 79d05a70f3e1b0a4365c7912ee036a95
attribute.gkr:compat:hashed:service = d41d8cd98f00b204e9800998ecf8427e
attribute.gkr:compat:hashed:target = a6e156c65c6804d9bdb95f480310ea36
attribute.gkr:compat:hashed:username = d41d8cd98f00b204e9800998ecf8427e

If I run the keyring-cli at this time, I get an ambiguous result, because all the attributes have been obscured:

$ keyring-cli -v --target firezone-test --user "" --service "" password 
More than one credential found for '[firezone-test]@': [SsCredential { attributes: {"gkr:compat:hashed:username": "d41d8cd98f00b204e9800998ecf8427e", "gkr:compat:hashed:application": "79d05a70f3e1b0a4365c7912ee036a95", "gkr:compat:hashed:service": "d41d8cd98f00b204e9800998ecf8427e", "xdg:schema": "org.freedesktop.Secret.Generic", "gkr:compat:hashed:target": "a6e156c65c6804d9bdb95f480310ea36"}, label: "", target: None }, SsCredential { attributes: {"gkr:compat:hashed:service": "d41d8cd98f00b204e9800998ecf8427e", "gkr:compat:hashed:username": "d41d8cd98f00b204e9800998ecf8427e", "gkr:compat:hashed:target": "317a8c66319e867ba9cbe4067fdf951d", "gkr:compat:hashed:application": "79d05a70f3e1b0a4365c7912ee036a95", "xdg:schema": "org.freedesktop.Secret.Generic"}, label: "", target: None }]

If I then unlock both collections and run the search again, I get a different result:

$ secret-tool search --all --unlock username ""
[/1]
label = keyring-rs v3.0.5 for target 'dev.firezone.client/token', service '', user ''
secret = this is a third test
created = 2024-08-06 17:02:58
modified = 2024-08-06 17:02:58
schema = org.freedesktop.Secret.Generic
attribute.application = rust-keyring
attribute.service = 
attribute.target = dev.firezone.client/token
attribute.username = 
[/1]
label = keyring-rs v3.0.5 for target 'firezone-test', service '', user ''
secret = this is a second test
created = 2024-08-06 16:55:37
modified = 2024-08-06 17:01:27
schema = org.freedesktop.Secret.Generic
attribute.application = rust-keyring
attribute.service = 
attribute.target = firezone-test
attribute.username = 

And, as you might expect, running the keyring-cli at this time gets the expected result:

$ keyring-cli -v --target firezone-test --user "" --service "" password
this is a second test
Password for '[firezone-test]@' is 'this is a second test'

Interestingly, the secret service seems to have a cache of some kind in place, because even if I re-lock the two collections both secret-tool and keyring-cli are still able to read the credentials:

$ secret-tool search --all username ""
secret-tool: Cannot get secret of a locked object
[/1]
label = keyring-rs v3.0.5 for target 'dev.firezone.client/token', service '', user ''
created = 2024-08-06 17:02:58
modified = 2024-08-06 17:02:58
schema = org.freedesktop.Secret.Generic
attribute.application = rust-keyring
attribute.service = 
attribute.target = dev.firezone.client/token
attribute.username = 
secret-tool: Cannot get secret of a locked object
[/1]
label = keyring-rs v3.0.5 for target 'firezone-test', service '', user ''
created = 2024-08-06 16:55:37
modified = 2024-08-06 17:01:27
schema = org.freedesktop.Secret.Generic
attribute.application = rust-keyring
attribute.service = 
attribute.target = firezone-test
attribute.username = 

and (after throwing a prompt to unlock the collection)

$ keyring-cli -v --target firezone-test --user "" --service "" password
this is a second test
Password for '[firezone-test]@' is 'this is a second test'

FWIW, I suspect the locked behavior is related to this issue and this issue in the Gnome secret service implementation, and the fix for them probably introduced the caching behavior.

Given that keyring v2.0 was released on 18 Feb 2023 (18 months ago), it's likely that any v1-written credentials in the wild are at least a year old. So I think it's probably safe now to include the target attribute in the original search, which would fix this bug. Then, to maintain backward compatibility with v1, if the target being searched for is the default (the v1 secret-service implementation broke on non-default targets), a follow-up search with no target can be done.

the secret service seems to have a cache of some kind in place

That would explain a lot. I'll try to leave my dev VM in this broken state as long as I can for debugging. In fact, I'll see if I can snapshot it.

I've decided that it's better to keep the code simple (and efficient) by always including the target in the secret-service search unless the entry that's being searched for was explicitly created with no target. This means that those clients who want their secret-service v1 credentials found by v3 must use platform-specific code to create their entries (because all the platform independent code will use a default target).

To reduce the burden on secret-service client code that cares about v1 credentials, I have added an entry to the secret-service implementation that makes it easy for them to create "no-target" (v1-compatible) credentials, and I have added code to the CLI the uses this new entry if a new --v1 argument is provided. Note that v1-compatible credentials can only be read or deleted, not set, but this was true in the v2 secret-service implementation as well, so it's not a compatibility break.

My recommendation to secret-service clients of keyring is that they upgrade their v1 credentials automatically. This can be done by finding them, reading their passwords, deleting them, and finally creating v2/v3 entries with the default target and matching service/user names that have the matching passwords. Note that the order of these operations is important in order to avoid ambiguity when looking for the v1 credentials. There are also functions get_all_passwords and delete_all_passwords in the secret-service keystore implementation that can help in cases where ambiguous credentials already exist: these entries can read or delete all the matching credentials found (including both the v1 and the v2/v3 credentials).

@ReactorScram I've released v3.2 which fixes this bug. I believe the keyring client code in Firezone will just work correctly on your dev machine once you integrate the new release.

Yep that fixed it on the dev machine! I'm getting a PR open for Firezone, thanks so much!