It's streaming on-line encryption. That's literally the point of streaming encryption: not buffering whole messages. The rest of your point directly follows from "not buffering whole messages".
Indeed. And the readme and the usage output makes no mention of streaming, buffering, on-line, authentication, or anything related.
This is a potential security relevant behavior that most users-- who haven't written or analyzed tools like this-- would find surprising.
For those following along, I went and tested it-- since the behavior wasn't documented or clear from the code. If it encounters midstream corruption it truncates the output, exits with a non-zero return and prints some error text std stderr: "Error: chacha20poly1305: message authentication failed\n[ Did age not do what you expected? Could an error be more useful? Tell us: https://filippo.io/age/report ]"
If the input is truncated, it either does that-- or if the truncation is on a block boundary it prints "Error: unexpected EOF\n[ Did age not do what you expected? Could an error be more useful? Tell us: https://filippo.io/age/report ]" instead.
This is a security footgun and a vulnerability waiting to happen, but bash is at fault, not age. age does the best it can do (while maintaining O(1) memory requirement) by exiting non-zero, but the shell swallows that if it's in the middle of a pipeline.
The footgun you described can still happen if there's a verification error somewhere in the middle. You could still conceivably craft exploits using only truncation of the plaintext, depending on the situation.
No one should "decrypt < file | sh" (or anything | sh without verifying), but they will. Doesn't matter if we have POSIX or non-POSIX shell flags that can fix it, the defaults are bad.
There's nothing tools like age can do about that, though.
Edit: I was thinking more along the lines of
if decrypt < file | postprocess > tempfile
then
sh tempfile
fi
where postprocess exits zero. This is where the default shell behavior fails. The "decrypt < file | sh" antipattern is something not even the shell can do anything about.
> No one should "decrypt < file | sh" (or anything | sh without verifying)
I was thinking of self-prepared scripts, tooling or owner controller distribution. Decrypt+good signature is precisely what I want.
Anyway, as nmadden pointed out, age does not provide source authentication duh. AFAIU that means, all the streaming semantics and blockwise AEAD are practically useless, unless you are using the password encryption, which is helpfully blocked from automation.
The fact that this is the point of streaming encryption does not preclude the usefulness of pointing it out explicitly. It eliminates a reasoning step by spelling it out, which is always a good thing for critical things, IMO.
This is a potential security relevant behavior that most users-- who haven't written or analyzed tools like this-- would find surprising.
For those following along, I went and tested it-- since the behavior wasn't documented or clear from the code. If it encounters midstream corruption it truncates the output, exits with a non-zero return and prints some error text std stderr: "Error: chacha20poly1305: message authentication failed\n[ Did age not do what you expected? Could an error be more useful? Tell us: https://filippo.io/age/report ]"
If the input is truncated, it either does that-- or if the truncation is on a block boundary it prints "Error: unexpected EOF\n[ Did age not do what you expected? Could an error be more useful? Tell us: https://filippo.io/age/report ]" instead.
It's not a problem, but it should be documented.