mirror of
https://github.com/FiloSottile/age.git
synced 2025-12-23 05:25:14 +00:00
21
age.go
21
age.go
@@ -51,8 +51,9 @@ import (
|
||||
// An Identity is a private key or other value that can decrypt an opaque file
|
||||
// key from a recipient stanza.
|
||||
//
|
||||
// Unwrap must return ErrIncorrectIdentity for recipient stanzas that don't
|
||||
// match the identity, any other error might be considered fatal.
|
||||
// Unwrap must return an error wrapping ErrIncorrectIdentity for recipient
|
||||
// stanzas that don't match the identity, any other error will be considered
|
||||
// fatal.
|
||||
type Identity interface {
|
||||
Unwrap(block *Stanza) (fileKey []byte, err error)
|
||||
}
|
||||
@@ -125,6 +126,18 @@ func Encrypt(dst io.Writer, recipients ...Recipient) (io.WriteCloser, error) {
|
||||
return stream.NewWriter(streamKey(fileKey, nonce), dst)
|
||||
}
|
||||
|
||||
// NoIdentityMatchError is returned by Decrypt when none of the supplied
|
||||
// identities match the encrypted file.
|
||||
type NoIdentityMatchError struct {
|
||||
// Errors is a slice of all the errors returned to Decrypt by the Unwrap
|
||||
// calls it made. They all wrap ErrIncorrectIdentity.
|
||||
Errors []error
|
||||
}
|
||||
|
||||
func (*NoIdentityMatchError) Error() string {
|
||||
return "no identity matched any of the recipients"
|
||||
}
|
||||
|
||||
// Decrypt decrypts a file encrypted to one or more identities.
|
||||
//
|
||||
// It returns a Reader reading the decrypted plaintext of the age file read
|
||||
@@ -142,6 +155,7 @@ func Decrypt(src io.Reader, identities ...Identity) (io.Reader, error) {
|
||||
return nil, errors.New("too many recipients")
|
||||
}
|
||||
|
||||
errNoMatch := &NoIdentityMatchError{}
|
||||
var fileKey []byte
|
||||
RecipientsLoop:
|
||||
for _, r := range hdr.Recipients {
|
||||
@@ -151,6 +165,7 @@ RecipientsLoop:
|
||||
for _, i := range identities {
|
||||
fileKey, err = i.Unwrap((*Stanza)(r))
|
||||
if errors.Is(err, ErrIncorrectIdentity) {
|
||||
errNoMatch.Errors = append(errNoMatch.Errors, err)
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
@@ -161,7 +176,7 @@ RecipientsLoop:
|
||||
}
|
||||
}
|
||||
if fileKey == nil {
|
||||
return nil, errors.New("no identity matched any of the recipients")
|
||||
return nil, errNoMatch
|
||||
}
|
||||
|
||||
if mac, err := headerMAC(fileKey, hdr); err != nil {
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
@@ -33,11 +34,12 @@ func (i *LazyScryptIdentity) Unwrap(block *age.Stanza) (fileKey []byte, err erro
|
||||
return nil, err
|
||||
}
|
||||
fileKey, err = ii.Unwrap(block)
|
||||
if err == age.ErrIncorrectIdentity {
|
||||
// The API will just ignore the identity if the passphrase is wrong, and
|
||||
// move on, eventually returning "no identity matched a recipient".
|
||||
// Since we only supply one identity from the CLI, make it a fatal
|
||||
// error with a better message.
|
||||
if errors.Is(err, age.ErrIncorrectIdentity) {
|
||||
// ScryptIdentity returns ErrIncorrectIdentity for an incorrect
|
||||
// passphrase, which would lead Decrypt to returning "no identity
|
||||
// matched any recipient". That makes sense in the API, where there
|
||||
// might be multiple configured ScryptIdentity. Since in cmd/age there
|
||||
// can be only one, return a better error message.
|
||||
return nil, fmt.Errorf("incorrect passphrase")
|
||||
}
|
||||
return fileKey, err
|
||||
|
||||
@@ -155,9 +155,9 @@ func (i *ScryptIdentity) Unwrap(block *Stanza) ([]byte, error) {
|
||||
// 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
|
||||
// either key is correct. This is deemed acceptable because the usa case (an
|
||||
// either key is correct. This is deemed acceptable because the use case (an
|
||||
// online decryption oracle) is not recommended, and the security loss is
|
||||
// only one bit. This also does not bypass any scrypt work, but that work
|
||||
// only one bit. This also does not bypass any scrypt work, although that work
|
||||
// can be precomputed in an online oracle scenario.
|
||||
fileKey, err := aeadDecrypt(k, fileKeySize, block.Body)
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user