If the implementation re-encodes the header before checking the HMAC,
that would mask malleability issues: the HMAC check would fail because
the tests HMAC'd the original header, but an attacker could also produce
the right HMAC. Instead of duplicating every parsing tests (with the
original and re-encoded HMAC), we make the test framework distinguish
HMAC errors, which ensures bad encodings are recognized as such and not
bypassable HMAC errors.
We are going to reuse the stanza format for IPC in the plugin protocol,
but in that context we need stanzas to be self-closing. Currently they
almost are, but if the body is 0 modulo 48, there is no way to know if
the stanza is over after the last line.
Now, all stanzas have to end with a short line, even if empty.
No ciphertexts generated by age in the past are affected, but 3% of the
ciphertexts generated by rage will now stop working. They are still
supported by rage going forward. If it turns out to be a common issue,
we can add an exception.
Most writes in the cmd/age Writer stack are chunk-sized, so
approximately 64KiB. However, the newlineWriter, which splits lines at
64 columns, was doing a Write on the underlying Writer for each line,
making chunks effectively 48 bytes (before base64). There is no
buffering underneath it, so it was resulting in a lot of write syscalls.
Add a reusable bytes.Buffer to buffer the output of each
(*newlineWriter).Write call, and Write it all at once on the
destination.
This makes --armor just 50% slower than plain, instead of 10x.
Fixes#167
The caller can take care of the armor. For consistency move the
responsibility to close the armor to the caller, and make the stream
Writer not propagate Close.
This also will also allow us to spin the armor implementation out into
its won package that imports format, without getting an import loop from
format.Parse magically invoking armor decoding.
Less magic in the API, more magic in the CLI.
It's with a heavy heart that I admit using the ASCII header as part of
the armor was clever, and you know what we think about being clever
around here.
Still, PEM is so lax, we target a subset without headers, and without
garbage before and after the markers.
-----BEGIN AGE ENCRYPTED FILE-----
VGhpcyBpcyBhIGZpbGUgZW5jcnlwdGVkIHdpdGggYWdlLXRvb2wuY29tLCB2ZXJz
aW9uIDEKLT4gWDI1NTE5IGozWWtNTWtaVGNDc0tKVGtMN29aam9NT2FUaGpBTVdU
Y1k5ZHVNdWJhUlkKb0F5d2N4ZW1lSTM1SkZiWHIxcHRFWW0rMjNzK3RuOTg1OHpN
L0ZkVzNCTQotLS0gQWZqdXFFaXNhbmYxbGpPRVZsSS9QM0wyM0RrTHRWWElsQnFu
ejFmRW4zdwq1FMc+yjVJBDuBUZSPMi0nCAtELIObQOHHQlQnvhk6BCITceOD5DbN
S7b6oumB8i/hEJvTtsOLgTBofzqzB90iAQ==
-----END AGE ENCRYPTED FILE-----
AGE-SECRET-KEY-1Y77J4M9R7GEKMZHR6YFDLDWV74VK2YQV4C7SR2H7SSVVJ05HQS4Q7NNMS3
Now that we don't use Base64 in keys, let's just go back to the standard
alphabet. Still in the spirit of reducing weirdness, use the PEM column
count, so we can also reuse the lineWriter for the PEM armor.
See https://groups.google.com/d/msg/age-dev/UAjkvLoCr9I/l4Q1h3OPAgAJ.