2021-11-24 10:29:15 +00:00
|
|
|
// Copyright 2019 The age Authors. All rights reserved.
|
2019-10-07 03:16:20 +00:00
|
|
|
// Use of this source code is governed by a BSD-style
|
2021-11-24 10:29:15 +00:00
|
|
|
// license that can be found in the LICENSE file.
|
2019-10-07 03:16:20 +00:00
|
|
|
|
2019-10-07 01:19:04 +00:00
|
|
|
package age
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"crypto/rand"
|
2023-08-05 17:19:26 +00:00
|
|
|
"encoding/hex"
|
2019-10-07 01:19:04 +00:00
|
|
|
"errors"
|
|
|
|
|
"fmt"
|
2022-06-19 15:28:39 +00:00
|
|
|
"regexp"
|
2019-10-07 01:19:04 +00:00
|
|
|
"strconv"
|
|
|
|
|
|
2019-12-07 04:00:57 +00:00
|
|
|
"filippo.io/age/internal/format"
|
2019-10-07 01:19:04 +00:00
|
|
|
"golang.org/x/crypto/chacha20poly1305"
|
|
|
|
|
"golang.org/x/crypto/scrypt"
|
|
|
|
|
)
|
|
|
|
|
|
2019-12-26 17:05:21 +00:00
|
|
|
const scryptLabel = "age-encryption.org/v1/scrypt"
|
|
|
|
|
|
age: mitigate multi-key attacks on ChaCha20Poly1305
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.
2020-09-19 16:18:59 +00:00
|
|
|
// ScryptRecipient is a password-based recipient. Anyone with the password can
|
|
|
|
|
// decrypt the message.
|
2020-05-18 06:53:37 +00:00
|
|
|
//
|
|
|
|
|
// If a ScryptRecipient is used, it must be the only recipient for the file: it
|
|
|
|
|
// can't be mixed with other recipient types and can't be used multiple times
|
|
|
|
|
// for the same file.
|
|
|
|
|
//
|
|
|
|
|
// Its use is not recommended for automated systems, which should prefer
|
2025-11-17 11:32:50 +00:00
|
|
|
// [HybridRecipient] or [X25519Recipient].
|
2019-10-07 01:19:04 +00:00
|
|
|
type ScryptRecipient struct {
|
|
|
|
|
password []byte
|
|
|
|
|
workFactor int
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var _ Recipient = &ScryptRecipient{}
|
|
|
|
|
|
2020-05-18 06:53:37 +00:00
|
|
|
// NewScryptRecipient returns a new ScryptRecipient with the provided password.
|
2019-10-07 01:19:04 +00:00
|
|
|
func NewScryptRecipient(password string) (*ScryptRecipient, error) {
|
|
|
|
|
if len(password) == 0 {
|
2019-12-31 13:01:57 +00:00
|
|
|
return nil, errors.New("passphrase can't be empty")
|
2019-10-07 01:19:04 +00:00
|
|
|
}
|
|
|
|
|
r := &ScryptRecipient{
|
2019-10-09 03:24:13 +00:00
|
|
|
password: []byte(password),
|
|
|
|
|
// TODO: automatically scale this to 1s (with a min) in the CLI.
|
|
|
|
|
workFactor: 18, // 1s on a modern machine
|
2019-10-07 01:19:04 +00:00
|
|
|
}
|
|
|
|
|
return r, nil
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-09 03:24:13 +00:00
|
|
|
// SetWorkFactor sets the scrypt work factor to 2^logN.
|
|
|
|
|
// It must be called before Wrap.
|
2020-05-18 06:53:37 +00:00
|
|
|
//
|
|
|
|
|
// If SetWorkFactor is not called, a reasonable default is used.
|
2019-10-09 03:24:13 +00:00
|
|
|
func (r *ScryptRecipient) SetWorkFactor(logN int) {
|
|
|
|
|
if logN > 30 || logN < 1 {
|
|
|
|
|
panic("age: SetWorkFactor called with illegal value")
|
|
|
|
|
}
|
|
|
|
|
r.workFactor = logN
|
2019-10-07 01:19:04 +00:00
|
|
|
}
|
|
|
|
|
|
age: mitigate multi-key attacks on ChaCha20Poly1305
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.
2020-09-19 16:18:59 +00:00
|
|
|
const scryptSaltSize = 16
|
|
|
|
|
|
2021-01-31 20:59:06 +00:00
|
|
|
func (r *ScryptRecipient) Wrap(fileKey []byte) ([]*Stanza, error) {
|
age: mitigate multi-key attacks on ChaCha20Poly1305
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.
2020-09-19 16:18:59 +00:00
|
|
|
salt := make([]byte, scryptSaltSize)
|
2019-10-07 01:19:04 +00:00
|
|
|
if _, err := rand.Read(salt[:]); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-09 03:24:13 +00:00
|
|
|
logN := r.workFactor
|
2020-06-27 23:44:26 +00:00
|
|
|
l := &Stanza{
|
2019-10-07 01:19:04 +00:00
|
|
|
Type: "scrypt",
|
2019-10-09 03:24:13 +00:00
|
|
|
Args: []string{format.EncodeToString(salt), strconv.Itoa(logN)},
|
2019-10-07 01:19:04 +00:00
|
|
|
}
|
|
|
|
|
|
2019-12-26 17:05:21 +00:00
|
|
|
salt = append([]byte(scryptLabel), salt...)
|
2019-10-09 03:24:13 +00:00
|
|
|
k, err := scrypt.Key(r.password, salt, 1<<logN, 8, 1, chacha20poly1305.KeySize)
|
2019-10-07 01:19:04 +00:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("failed to generate scrypt hash: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
wrappedKey, err := aeadEncrypt(k, fileKey)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2019-10-13 21:24:21 +00:00
|
|
|
l.Body = wrappedKey
|
2019-10-07 01:19:04 +00:00
|
|
|
|
2021-01-31 20:59:06 +00:00
|
|
|
return []*Stanza{l}, nil
|
2019-10-07 01:19:04 +00:00
|
|
|
}
|
|
|
|
|
|
2023-08-05 17:19:26 +00:00
|
|
|
// WrapWithLabels implements [age.RecipientWithLabels], returning a random
|
|
|
|
|
// label. This ensures a ScryptRecipient can't be mixed with other recipients
|
|
|
|
|
// (including other ScryptRecipients).
|
|
|
|
|
//
|
|
|
|
|
// Users reasonably expect files encrypted to a passphrase to be [authenticated]
|
|
|
|
|
// by that passphrase, i.e. for it to be impossible to produce a file that
|
|
|
|
|
// decrypts successfully with a passphrase without knowing it. If a file is
|
|
|
|
|
// encrypted to other recipients, those parties can produce different files that
|
|
|
|
|
// would break that expectation.
|
|
|
|
|
//
|
|
|
|
|
// [authenticated]: https://words.filippo.io/dispatches/age-authentication/
|
|
|
|
|
func (r *ScryptRecipient) WrapWithLabels(fileKey []byte) (stanzas []*Stanza, labels []string, err error) {
|
|
|
|
|
stanzas, err = r.Wrap(fileKey)
|
|
|
|
|
|
|
|
|
|
random := make([]byte, 16)
|
|
|
|
|
if _, err := rand.Read(random); err != nil {
|
|
|
|
|
return nil, nil, err
|
|
|
|
|
}
|
|
|
|
|
labels = []string{hex.EncodeToString(random)}
|
|
|
|
|
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-18 06:53:37 +00:00
|
|
|
// ScryptIdentity is a password-based identity.
|
2019-10-07 01:19:04 +00:00
|
|
|
type ScryptIdentity struct {
|
|
|
|
|
password []byte
|
|
|
|
|
maxWorkFactor int
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var _ Identity = &ScryptIdentity{}
|
|
|
|
|
|
2020-05-18 06:53:37 +00:00
|
|
|
// NewScryptIdentity returns a new ScryptIdentity with the provided password.
|
2019-10-07 01:19:04 +00:00
|
|
|
func NewScryptIdentity(password string) (*ScryptIdentity, error) {
|
|
|
|
|
if len(password) == 0 {
|
2019-12-31 13:01:57 +00:00
|
|
|
return nil, errors.New("passphrase can't be empty")
|
2019-10-07 01:19:04 +00:00
|
|
|
}
|
|
|
|
|
i := &ScryptIdentity{
|
|
|
|
|
password: []byte(password),
|
2019-10-09 03:24:13 +00:00
|
|
|
maxWorkFactor: 22, // 15s on a modern machine
|
2019-10-07 01:19:04 +00:00
|
|
|
}
|
|
|
|
|
return i, nil
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-18 06:53:37 +00:00
|
|
|
// SetMaxWorkFactor sets the maximum accepted scrypt work factor to 2^logN.
|
2019-10-09 03:24:13 +00:00
|
|
|
// It must be called before Unwrap.
|
2020-05-18 06:53:37 +00:00
|
|
|
//
|
|
|
|
|
// This caps the amount of work that Decrypt might have to do to process
|
|
|
|
|
// received files. If SetMaxWorkFactor is not called, a fairly high default is
|
|
|
|
|
// used, which might not be suitable for systems processing untrusted files.
|
2019-10-09 03:24:13 +00:00
|
|
|
func (i *ScryptIdentity) SetMaxWorkFactor(logN int) {
|
|
|
|
|
if logN > 30 || logN < 1 {
|
|
|
|
|
panic("age: SetMaxWorkFactor called with illegal value")
|
|
|
|
|
}
|
|
|
|
|
i.maxWorkFactor = logN
|
2019-10-07 01:19:04 +00:00
|
|
|
}
|
|
|
|
|
|
2021-01-31 20:59:06 +00:00
|
|
|
func (i *ScryptIdentity) Unwrap(stanzas []*Stanza) ([]byte, error) {
|
age: move the scrypt lone recipient check out of Decrypt
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.
2021-06-06 11:42:16 +00:00
|
|
|
for _, s := range stanzas {
|
|
|
|
|
if s.Type == "scrypt" && len(stanzas) != 1 {
|
|
|
|
|
return nil, errors.New("an scrypt recipient must be the only one")
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-01-31 20:59:06 +00:00
|
|
|
return multiUnwrap(i.unwrap, stanzas)
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-19 15:28:39 +00:00
|
|
|
var digitsRe = regexp.MustCompile(`^[1-9][0-9]*$`)
|
|
|
|
|
|
2021-01-31 20:59:06 +00:00
|
|
|
func (i *ScryptIdentity) unwrap(block *Stanza) ([]byte, error) {
|
2019-10-07 01:19:04 +00:00
|
|
|
if block.Type != "scrypt" {
|
2019-11-25 00:15:53 +00:00
|
|
|
return nil, ErrIncorrectIdentity
|
2019-10-07 01:19:04 +00:00
|
|
|
}
|
|
|
|
|
if len(block.Args) != 2 {
|
|
|
|
|
return nil, errors.New("invalid scrypt recipient block")
|
|
|
|
|
}
|
|
|
|
|
salt, err := format.DecodeString(block.Args[0])
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("failed to parse scrypt salt: %v", err)
|
|
|
|
|
}
|
age: mitigate multi-key attacks on ChaCha20Poly1305
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.
2020-09-19 16:18:59 +00:00
|
|
|
if len(salt) != scryptSaltSize {
|
2019-10-07 01:19:04 +00:00
|
|
|
return nil, errors.New("invalid scrypt recipient block")
|
|
|
|
|
}
|
2022-06-19 15:28:39 +00:00
|
|
|
if w := block.Args[1]; !digitsRe.MatchString(w) {
|
|
|
|
|
return nil, fmt.Errorf("scrypt work factor encoding invalid: %q", w)
|
|
|
|
|
}
|
2019-10-09 03:24:13 +00:00
|
|
|
logN, err := strconv.Atoi(block.Args[1])
|
2019-10-07 01:19:04 +00:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("failed to parse scrypt work factor: %v", err)
|
|
|
|
|
}
|
2019-10-09 03:24:13 +00:00
|
|
|
if logN > i.maxWorkFactor {
|
|
|
|
|
return nil, fmt.Errorf("scrypt work factor too large: %v", logN)
|
|
|
|
|
}
|
2022-06-19 15:28:39 +00:00
|
|
|
if logN <= 0 { // unreachable
|
2019-10-09 03:24:13 +00:00
|
|
|
return nil, fmt.Errorf("invalid scrypt work factor: %v", logN)
|
2019-10-07 01:19:04 +00:00
|
|
|
}
|
|
|
|
|
|
2019-12-26 17:05:21 +00:00
|
|
|
salt = append([]byte(scryptLabel), salt...)
|
2019-10-09 03:24:13 +00:00
|
|
|
k, err := scrypt.Key(i.password, salt, 1<<logN, 8, 1, chacha20poly1305.KeySize)
|
2022-06-19 15:28:39 +00:00
|
|
|
if err != nil { // unreachable
|
2019-10-07 01:19:04 +00:00
|
|
|
return nil, fmt.Errorf("failed to generate scrypt hash: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
age: mitigate multi-key attacks on ChaCha20Poly1305
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.
2020-09-19 16:18:59 +00:00
|
|
|
// This AEAD is not robust, so an attacker could craft a message that
|
|
|
|
|
// decrypts under two different keys (meaning two different passphrases) and
|
|
|
|
|
// then use an error side-channel in an online decryption oracle to learn if
|
2021-01-17 11:09:07 +00:00
|
|
|
// either key is correct. This is deemed acceptable because the use case (an
|
age: mitigate multi-key attacks on ChaCha20Poly1305
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.
2020-09-19 16:18:59 +00:00
|
|
|
// online decryption oracle) is not recommended, and the security loss is
|
2021-01-17 11:09:07 +00:00
|
|
|
// only one bit. This also does not bypass any scrypt work, although that work
|
age: mitigate multi-key attacks on ChaCha20Poly1305
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.
2020-09-19 16:18:59 +00:00
|
|
|
// can be precomputed in an online oracle scenario.
|
|
|
|
|
fileKey, err := aeadDecrypt(k, fileKeySize, block.Body)
|
2021-02-02 12:51:35 +00:00
|
|
|
if err == errIncorrectCiphertextSize {
|
|
|
|
|
return nil, errors.New("invalid scrypt recipient block: incorrect file key size")
|
|
|
|
|
} else if err != nil {
|
2019-11-25 00:15:53 +00:00
|
|
|
return nil, ErrIncorrectIdentity
|
2019-10-07 01:19:04 +00:00
|
|
|
}
|
|
|
|
|
return fileKey, nil
|
|
|
|
|
}
|