An address key's token was decrypted with the active account keys only.
A token encrypted to an account key that is now inactive never opened,
although that key still opens with the password, so the address failed
with "has no primary key". proton-drive-linux issue #40 shows this as a
mount that retries "missing key" forever. Tokens are now tried with
every account key that opens, active ones first; signing still uses
active keys only. The key report walks the same chain.
addresses() also failed as a whole when one address did not open. That
address is now left out with a warning and the rest work; the call fails
only when none opens. address_private_keys and the new address(id) name
the reason for a left-out address, and default_address fails with it
when the left-out address is the default rather than signing with
another one.
AccountClient::key_report walks the user and address keys like the
unlock path, but records each step instead of skipping what fails:
whether each user key opens (inactive ones included), and the
recipients of each address key token. ProtonDriveClient::
my_files_share_recipients names the keys the My Files share passphrase
is encrypted to without decrypting anything. Together they trace a
"missing key" to the key it was meant for (proton-drive-linux#40).
crypto::recipients and crypto::key_ids read OpenPGP identifiers from
messages and keys, so a recipient can be matched to a key.
A wrong mailbox password surfaced as InvalidOperation("no active user key could
be unlocked"), which a caller could only recognise by its text. It is now
ProtonError::KeysLocked, and AccountClient::unlock() checks a secret with a
single core/v4/users request instead of loading every address.
Derived passphrases, the mailbox password and the cache encryption key were
plain Vec<u8>s, cloned freely and never wiped. They are Zeroizing now, and the
passphrase map is KeyPassphrases: wiped on drop, redacted in Debug, with
derive_secret() for a client that wants a key only an unlocked account can
reproduce. with_key_passphrases takes it.
Breaking: ProtonError gains a variant, key_passphrases() and
with_key_passphrases() change type, and derive_key_passphrase returns
Zeroizing<Vec<u8>>.
`AccountClient::key_passphrases` is now public, and
`AccountClient::with_key_passphrases` / `ProtonDriveClient::with_key_passphrases`
build a client from the per-key passphrases alone. A client that persists a
session can store those instead of the mailbox password, which on a
single-password account is the login password itself.
`ProtonApiSession::second_factor_methods` exposes the login's 2FA bitmask
(TOTP, FIDO2), so a client without WebAuthn can tell an account that only has
a security key apart from a wrong code.