Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Decrypting with multiple identities #178

Open
kukovecz opened this issue Apr 16, 2024 · 9 comments
Open

Decrypting with multiple identities #178

kukovecz opened this issue Apr 16, 2024 · 9 comments
Labels
S-needs-thought Status: There are aspects of this issue that need some thought.

Comments

@kukovecz
Copy link

kukovecz commented Apr 16, 2024

Hello,

Thanks for this plugin!

My scenario is:

  • I am encrypting a secret with 2 recipients (returned by age-plugin-yubikey --list-all)
  • I would like it to be decryptable by either one of the identities (returned by age-plugin-yubikey --identity)

For this, I am using https://github.com/ryantm/agenix and I realized it does the following:

age --decrypt -i <identity-1> -i <identity-2> FILE

The problem is:
If I have the yubikey with <identity-1> plugged in, it works, but if I have the one with <identity-2> it does not.
Executing the above command manually brings up a popup to choose between skipping the yubikey or inserting it and trying it again, so I got this error:

age: warning: could not read value for age-plugin-yubikey: standard input is not a terminal, and /dev/tty is not available: open /dev/tty: no such device or address
age: error: yubikey plugin: Could not open YubiKey with serial <serial>

If I change the order to (still: yubikey with <identity-2> is plugged in), it works:

age --decrypt -i <identity-2> -i <identity-1> FILE

Could this be handled to make the decryption successful if any of the provided identities are ok? Am I missing something?

@DanNixon
Copy link

The underlying issue is the interactive prompt generated when the plugin fails to find the YubiKey for the identity it is attempting to use.

i.e.

Please insert YubiKey with serial xxxxxxxx (press [1] for "YubiKey is plugged in" or [2] for "Skip this YubiKey")

I would have voiced my desire for this prompt to be made optional, with the alternative being automatically skip an indentoity if the YubiKey it is associated to is not present.

However, I think doing so would cause another issue whereby the PIN would be required for each decryption (at least for all but the first key in the identity file). I imagine the issue arises from an attempt to access a key that is not present closing the existing session on the present key (see also).

@str4d
Copy link
Owner

str4d commented Aug 4, 2024

The UX issue you are encountering is a side-effect of a UX decision made in the age clients.

In the distant past, some age clients (naming no names but rage, my client) would take the -i flags, coalesce them, and then for plugin identities call the plugin once with all identities. Then the plugin could make decisions such as "you've given me identities for two Yubikeys, and I can see one of them is already plugged in, so let's try that one first".

However, this had a significant UX problem in that the order in which identities were tried was non-deterministic, and could not be predicted by the user. So back in 2021 we decided to make -i flags deterministic during decryption (str4d/rage#236). After this change, -i flags are tried in left-to-right order, and no coalescing is performed.

What this means is, when you call:

age --decrypt -i <identity-1> -i <identity-2> FILE

what you are telling age is "always try to decrypt with identity-1 first, and if that can't be used, only then try with identity-2". age-plugin-yubikey only sees one identity at a time, and because plugin invocations are ephemeral, it has no idea that it might get called a second time, so it asks the user to plug in the Yubikey for identity-1. That is the best thing the plugin can do in that moment, and is a correct thing to do: the user expressed their preference to try identity-1 before identity-2 by way of ordering the -i flags.

What this fundamentally boils down to is two conflicting user desires:

  • 1️⃣ Deterministic handling of identity order, so users can directly express that identity-1 sitting in their desk drawer should be tried before identity-2 sitting in their safe.
  • 2️⃣ Dynamically adapting to the environment, so that identity-2 gets tried because the user happened to have taken it out of the safe and plugged it into their computer, before identity-1 sitting in their desk drawer.

Now, there might be UX tricks we could pull to mitigate this somewhat. But as long as age clients treat -i flags as deterministic during decryption (preferentially handling case 1️⃣ above), there are limits on what age plugins can do here.

@str4d
Copy link
Owner

str4d commented Aug 4, 2024

Now, there might be UX tricks we could pull to mitigate this somewhat.

One such UX trick would be to define an "OR" identity. That is, in addition to the current AGE-PLUGIN-YUBIKEY- identity format, support a format that internally can store multiple Yubikey stubs, effectively re-introducing the coalescing that rage used to have, but done explicitly by the user. It gets a bit weird in that it would need to be an alternative AGE-PLUGIN-YUBIKEY- encoding (as age clients determine which plugin to use via the name in the identity), which would cause errors in old plugin versions (but that's probably fine, as users couldn't generate the new encoding without having updated the plugin on at least one machine), but I think that overall it could maybe be made to work.

The workflow would be something like:

  • User uses age-plugin-yubikey to OR-concatenate identity-1 and identity-2 into identity-1-or-2.
    • The new encoding would probably also include metadata about which order to try the YubiKeys depending on what combinations are plugged in vs not. Maybe also caching in the OR identity flags for "this identity requires a PIN always, vs this other identity only requires a PIN once"? The user mental model could become quite complex here, so we probably want to keep it simple.
  • User calls age --decrypt -i identity-1-or-2 -i identity-3.
  • age calls age-plugin-yubikey with identity-1-or-2.
  • age-plugin-yubikey sees the OR, looks to see if either key is plugged in, and then tries the YubiKeys in the order encoded in the identity.
  • If neither key is available, then age calls age-plugin-yubikey with identity-3 (and the plugin behaves like currently).

Having sketched out the above, I'm not sure about whether this added UX and implementation complexity is worth the flexibility it brings. Thoughts @FiloSottile?

@str4d str4d added the S-needs-thought Status: There are aspects of this issue that need some thought. label Aug 4, 2024
@Tiebe
Copy link

Tiebe commented Aug 5, 2024

I'd prefer this as well, as I have one Yubikey always plugged in my PC at home, and another one on my keychain which I use on my laptop. I'd like to use try the one on my keychain first, if that one isn't plugged in, it'd be great if it could automatically check the other Yubikey.

@str4d
Copy link
Owner

str4d commented Aug 6, 2024

@Tiebe a plain reading of your use case would suggest it can be handled with the existing client and plugin UX, by expressing your preferences thusly:

# Encryption on both PC and laptop (and anywhere else) encrypts to
# both recipients, so you can decrypt on either.
$ age -r RECIPIENT_PC -r RECIPIENT_LAPTOP ...

# Decryption on PC
# No need to include IDENTITY_LAPTOP, because IDENTITY_PC is always
# plugged in and therefore always available to use.
$ age -d -i IDENTITY_PC ...

# Decryption on laptop
# No need to include IDENTITY_PC, because it is always plugged into
# your PC, and therefore never plugged into your laptop. You'll be
# prompted to plug in the keychain Yubikey if it isn't already.
$ age -d -i IDENTITY_LAPTOP ...

However, I think what you intended to say is (emphasis added):

I'd like to on my PC try the one on my keychain first, if that one isn't plugged in, it'd be great if it could automatically check the other Yubikey.

which is a somewhat unusual use case: if there's a Yubikey always plugged in, why decrypt with the keychain Yubikey first? (The answer is likely "the file is only encrypted to the keychain Yubikey", which per above can be solved by always encrypting to both, but let's assume that isn't done here.)

If you explicitly use age -i IDENTITY_LAPTOP -i IDENTITY_PC to encode this preference, then you're telling age to always try IDENTITY_LAPTOP before trying IDENTITY_PC. age itself has no knowledge that IDENTITY_PC is plugged it, so it does what you told it to do, and tries IDENTITY_LAPTOP first, causing age-plugin-yubikey to ask you for your keyring Yubikey, or tell it to skip. It's the "tell it to skip" that is the UX automation hurdle here, for which we do not have a good solution to.

@sebhoss
Copy link

sebhoss commented Sep 15, 2024

Couldn't age/rage call age-plugin-yubikey --identity first when it encounters an identity that's using the yubikey plugin and only use those identities that are available? There would be no need for an OR identity but maybe another CLI flag to toggle this behavior?

@mntn-xyz

This comment was marked as resolved.

@remko
Copy link

remko commented Dec 12, 2024

@mntn-xyz The PR you reference (and the vars you describe) only relates to Passage plugins. It has no impact on age plugins.

@mntn-xyz
Copy link

@remko Thanks, I misunderstood the PR and thought it could help solve this issue. I see now that it's a problem in other plugins as well and it's not a problem with passage at all.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-needs-thought Status: There are aspects of this issue that need some thought.
Projects
None yet
Development

No branches or pull requests

7 participants