mirror of
https://github.com/FiloSottile/age.git
synced 2026-01-03 19:03:57 +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
|
// An Identity is a private key or other value that can decrypt an opaque file
|
||||||
// key from a recipient stanza.
|
// key from a recipient stanza.
|
||||||
//
|
//
|
||||||
// Unwrap must return ErrIncorrectIdentity for recipient stanzas that don't
|
// Unwrap must return an error wrapping ErrIncorrectIdentity for recipient
|
||||||
// match the identity, any other error might be considered fatal.
|
// stanzas that don't match the identity, any other error will be considered
|
||||||
|
// fatal.
|
||||||
type Identity interface {
|
type Identity interface {
|
||||||
Unwrap(block *Stanza) (fileKey []byte, err error)
|
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)
|
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.
|
// Decrypt decrypts a file encrypted to one or more identities.
|
||||||
//
|
//
|
||||||
// It returns a Reader reading the decrypted plaintext of the age file read
|
// 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")
|
return nil, errors.New("too many recipients")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
errNoMatch := &NoIdentityMatchError{}
|
||||||
var fileKey []byte
|
var fileKey []byte
|
||||||
RecipientsLoop:
|
RecipientsLoop:
|
||||||
for _, r := range hdr.Recipients {
|
for _, r := range hdr.Recipients {
|
||||||
@@ -151,6 +165,7 @@ RecipientsLoop:
|
|||||||
for _, i := range identities {
|
for _, i := range identities {
|
||||||
fileKey, err = i.Unwrap((*Stanza)(r))
|
fileKey, err = i.Unwrap((*Stanza)(r))
|
||||||
if errors.Is(err, ErrIncorrectIdentity) {
|
if errors.Is(err, ErrIncorrectIdentity) {
|
||||||
|
errNoMatch.Errors = append(errNoMatch.Errors, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -161,7 +176,7 @@ RecipientsLoop:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if fileKey == nil {
|
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 {
|
if mac, err := headerMAC(fileKey, hdr); err != nil {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
@@ -33,11 +34,12 @@ func (i *LazyScryptIdentity) Unwrap(block *age.Stanza) (fileKey []byte, err erro
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
fileKey, err = ii.Unwrap(block)
|
fileKey, err = ii.Unwrap(block)
|
||||||
if err == age.ErrIncorrectIdentity {
|
if errors.Is(err, age.ErrIncorrectIdentity) {
|
||||||
// The API will just ignore the identity if the passphrase is wrong, and
|
// ScryptIdentity returns ErrIncorrectIdentity for an incorrect
|
||||||
// move on, eventually returning "no identity matched a recipient".
|
// passphrase, which would lead Decrypt to returning "no identity
|
||||||
// Since we only supply one identity from the CLI, make it a fatal
|
// matched any recipient". That makes sense in the API, where there
|
||||||
// error with a better message.
|
// 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 nil, fmt.Errorf("incorrect passphrase")
|
||||||
}
|
}
|
||||||
return fileKey, err
|
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
|
// This AEAD is not robust, so an attacker could craft a message that
|
||||||
// decrypts under two different keys (meaning two different passphrases) and
|
// decrypts under two different keys (meaning two different passphrases) and
|
||||||
// then use an error side-channel in an online decryption oracle to learn if
|
// 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
|
// 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.
|
// can be precomputed in an online oracle scenario.
|
||||||
fileKey, err := aeadDecrypt(k, fileKeySize, block.Body)
|
fileKey, err := aeadDecrypt(k, fileKeySize, block.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
Reference in New Issue
Block a user