The important one is the decryption side one, because when a user types
a password they expect it to both decrypt and authenticate the file.
Moved that one out of Decrypt and into ScryptIdentity, now that
Identities get all the stanzas. special_cases--
This also opens the door to other Identity implementations that do allow
multiple scrypt recipients, if someone really wants that. The CLI will
never allow it, but an explicit choice by an API consumer feels like
something we shouldn't interfere with.
Moreover, this also allows alternative Identity implementations that use
different recipient types to replicate the behavior if they have the
same authentication semantics.
The encryption side one is only a courtesy, to stop API users from
making files that won't decrypt. Unfortunately, that one needs to stay
as a special case in Encrypt, as the Recipient can't see around itself.
However, changed it to a type assertion, so custom recipients can
generate multiple scrypt recipient stanzas, if they really want.
This is a breaking change, but like the other changes to these
interfaces it should not matter to consumers of the API that don't
implement custom Recipients or Identities, which is all of them so far,
as far as I can tell.
It became clear working on plugins that we might want Recipient to
return multiple recipient stanzas, for example if the plugin recipient
is an alias or a group. The Identity side is less important, but it
might help avoid round-trips and it makes sense to keep things
symmetric.
The Type() method was a mistake, as proven by the fact that I can remove
it without losing any functionality. It gives special meaning to the
"0th argument" of recipient stanzas, when actually it should be left up
to Recipient implementations to make their own stanzas recognizable to
their Identity counterparts.
More importantly, there are totally reasonable Identity (and probably
Recipient) implementations that don't know their own stanza type in
advance. For example, a proxy plugin.
Concretely, it was only used to special-case "scrypt" recipients, and to
skip invoking Unwrap. The former can be done based on the returned
recipient stanza, and the latter is best avoided entirely: the Identity
should start by looking at the stanza and returning ErrIncorrectIdentity
if it's of the wrong type.
This is a breaking API change. However, we are still in beta, and none
of the public downstreams look like they would be affected, as they only
use Recipient and Identity implementations from this package, they only
use them with the interfaces defined in this package, and they don't
directly use the Type() method.
It's possible to craft ChaCha20Poly1305 ciphertexts that decrypt under
multiple keys. (I know, it's wild.)
The impact is different for different recipients, but in general only
applies to Chosen Ciphertext Attacks against online decryption oracles:
* With the scrypt recipient, it lets the attacker make a recipient
stanza that decrypts with multiple passwords, speeding up a bruteforce
in terms of oracle queries (but not scrypt work, which can be
precomputed) to logN by binary search.
Limiting the ciphertext size limits the keys to two, which makes this
acceptable: it's a loss of only one bit of security in a scenario
(online decryption oracles) that is not recommended.
* With the X25519 recipient, it lets the attacker search for accepted
public keys without using multiple recipient stanzas in the message.
That lets the attacker bypass the 20 recipients limit (which was not
actually intended to defend against deanonymization attacks).
This is not really in the threat model for age: we make no attempt to
provide anonymity in an online CCA scenario.
Anyway, limiting the keys to two by enforcing short ciphertexts
mitigates the attack: it only lets the attacker test 40 keys per
message instead of 20.
* With the ssh-ed25519 recipient, the attack should be irrelevant, since
the recipient stanza includes a 32-bit hash of the public key, making
it decidedly not anonymous.
Also to avoid breaking the abstraction in the agessh package, we don't
mitigate the attack for this recipient, but we document the lack of
anonymity.
This was reported by Paul Grubbs in the context of the upcoming paper
"Partitioning Oracle Attacks", USENIX Security 2021 (to appear), by
Julia Len, Paul Grubbs, and Thomas Ristenpart.