OpenSSH never generated them (unencrypted, and golang.org/x/crypto/ssh
doesn't support encrypted PKCS#8 for now, so the encrypted_keys.go
change is technically superfluous) but there are other systems that
produce them (for example, 1Password). Unfortunately, ParseRawPrivateKey
returns a value type for PKCS#8 and a pointer type for the OpenSSH
format (golang/go#51974), so we need to handle both.
Fixes#429
The extra dependency makes it harder to package age. Temporarily drop it
to facilitate getting v1.0.0 into distributions.
This reverts commit 53ccaf8b71.
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.