From 56f6acca37fd4bcb57846c1653ec2c0fc046afd4 Mon Sep 17 00:00:00 2001 From: Filippo Valsorda Date: Tue, 26 Apr 2022 20:37:10 +0200 Subject: [PATCH] cmd/age: reject passphrase-encrypted files if -i is used Passphrase-encrypted files make age(1) block, which would be unexpected when decrypting files in a script using -i. --- cmd/age/age.go | 48 ++++++++++++++++++++++++++++++++++++------------ doc/age.1.ronn | 13 +++++++------ 2 files changed, 43 insertions(+), 18 deletions(-) diff --git a/cmd/age/age.go b/cmd/age/age.go index bcddbb7..648a9eb 100644 --- a/cmd/age/age.go +++ b/cmd/age/age.go @@ -265,14 +265,12 @@ func main() { } switch { + case decryptFlag && len(identityFlags) == 0: + decryptPass(in, out) case decryptFlag: - decrypt(identityFlags, in, out) + decryptNotPass(identityFlags, in, out) case passFlag: - pass, err := passphrasePromptForEncryption() - if err != nil { - errorf("%v", err) - } - encryptPass(pass, in, out, armorFlag) + encryptPass(in, out, armorFlag) default: encryptNotPass(recipientFlags, recipientsFileFlags, identityFlags, in, out, armorFlag) } @@ -352,7 +350,12 @@ func encryptNotPass(recs, files []string, identities identityFlags, in io.Reader encrypt(recipients, in, out, armor) } -func encryptPass(pass string, in io.Reader, out io.Writer, armor bool) { +func encryptPass(in io.Reader, out io.Writer, armor bool) { + pass, err := passphrasePromptForEncryption() + if err != nil { + errorf("%v", err) + } + r, err := age.NewScryptRecipient(pass) if err != nil { errorf("%v", err) @@ -388,12 +391,19 @@ func encrypt(recipients []age.Recipient, in io.Reader, out io.Writer, withArmor const crlfMangledIntro = "age-encryption.org/v1" + "\r" const utf16MangledIntro = "\xff\xfe" + "a\x00g\x00e\x00-\x00e\x00n\x00c\x00r\x00y\x00p\x00" -func decrypt(flags identityFlags, in io.Reader, out io.Writer) { - identities := []age.Identity{ - // If there is an scrypt recipient (it will have to be the only one and) - // this identity will be invoked. - &LazyScryptIdentity{passphrasePromptForDecryption}, +type rejectScryptIdentity struct{} + +func (rejectScryptIdentity) Unwrap(stanzas []*age.Stanza) ([]byte, error) { + if len(stanzas) != 1 || stanzas[0].Type != "scrypt" { + return nil, age.ErrIncorrectIdentity } + errorWithHint("file is passphrase-encrypted but identities were specified with -i/--identity or -j", + "remove all -i/--identity/-j flags to decrypt passphrase-encrypted files") + panic("unreachable") +} + +func decryptNotPass(flags identityFlags, in io.Reader, out io.Writer) { + identities := []age.Identity{rejectScryptIdentity{}} for _, f := range flags { switch f.Type { @@ -412,6 +422,20 @@ func decrypt(flags identityFlags, in io.Reader, out io.Writer) { } } + decrypt(identities, in, out) +} + +func decryptPass(in io.Reader, out io.Writer) { + identities := []age.Identity{ + // If there is an scrypt recipient (it will have to be the only one and) + // this identity will be invoked. + &LazyScryptIdentity{passphrasePromptForDecryption}, + } + + decrypt(identities, in, out) +} + +func decrypt(identities []age.Identity, in io.Reader, out io.Writer) { rr := bufio.NewReader(in) if intro, _ := rr.Peek(len(crlfMangledIntro)); string(intro) == crlfMangledIntro || string(intro) == utf16MangledIntro { diff --git a/doc/age.1.ronn b/doc/age.1.ronn index 2a8f5f8..5add740 100644 --- a/doc/age.1.ronn +++ b/doc/age.1.ronn @@ -13,13 +13,13 @@ age(1) -- simple, modern, and secure file encryption optional and defaults to standard input. Only a single file may be specified. If `-o` is not specified, defaults to standard output. -If `--passphrase` is specified, the file is encrypted with a passphrase +If `-p`/`--passphrase` is specified, the file is encrypted with a passphrase requested interactively. Otherwise, it's encrypted to one or more [RECIPIENTS][RECIPIENTS AND IDENTITIES] specified with `-r`/`--recipient` or `-R`/`--recipients-file`. Every recipient can decrypt the file. -In `--decrypt` mode, passphrase-encrypted files are detected automatically and -the passphrase is requested interactively. Otherwise, one or more +In `-d`/`--decrypt` mode, passphrase-encrypted files are detected automatically +and the passphrase is requested interactively. Otherwise, one or more [IDENTITIES][RECIPIENTS AND IDENTITIES] specified with `-i`/`--identity` are used to decrypt the file. @@ -111,9 +111,10 @@ overhead per recipient, plus 16 bytes every 64KiB of plaintext. d\. "`-`", causing one of the options above to be read from standard input. In this case, the argument must be specified. - This option can be repeated. Identities are tried in the order in which - are provided, and the first one matching one of the file's recipients is - used. Unused identities are ignored. + This option can be repeated. Identities are tried in the order in which are + provided, and the first one matching one of the file's recipients is used. + Unused identities are ignored, but it is an error if the file is + passphrase-encrypted and `-i`/`--identity` is specified. If `-e`/`--encrypt` is explicitly specified (to avoid confusion), `-i`/`--identity` may also be used to encrypt to the `RECIPIENTS`