Files
Dominik Schulz 2d0e678535 fix(age): deterministically order identities for decryption (#3566)
* fix(age): deterministically order identities for decryption

Go map iteration is randomized, so the identity age tried first for
decryption was picked at random, leading to unpredictable prompts
(e.g. sometimes asking for a hardware token PIN, sometimes not).

Fix this by ordering the identities before handing them to age:

1. identities whose recipient is listed in the new age.identities
   config option, in that order,
2. all remaining identities, sorted deterministically (native age
   identities first, mirroring the recipient ordering applied on
   encryption).

Only public recipient strings (age1...) are ever matched against or
stored in the config, never secret key material.

Also add an interactive 'gopass age identities sort' subcommand that
presents the identities in their current effective order and lets the
user reorder them with the keyboard (j/k, u/d, s to save, q to abort),
persisting the result to age.identities. The terminal UI is built only
on pkg/termio and golang.org/x/term, no new dependencies.

Fixes: #3393

* test(age): fix flaky assertion in TestOrderedIdentities

The 'secret identity encodings must not be matched' assertion used
NotEqual(id2, got[0]), but the deterministic fallback sort orders by
recipient string and two random X25519 recipients of equal length sort
in random relative order. Whenever id2's recipient happened to sort
first, id2 was first via the fallback and the test failed (~50% of
runs).

Assert instead that a config entry containing the secret encoding does
not influence the ordering at all, i.e. the result equals the
no-preference baseline.
2026-08-24 20:31:42 +02:00
..