diff --git a/AUTHORS b/AUTHORS index eaebc51..1fbcfc4 100644 --- a/AUTHORS +++ b/AUTHORS @@ -3,3 +3,4 @@ # who owns a contribution's copyright. Google LLC +Filippo Valsorda diff --git a/cmd/age/age_test.go b/cmd/age/age_test.go index e0aa32b..fd3c45e 100644 --- a/cmd/age/age_test.go +++ b/cmd/age/age_test.go @@ -65,7 +65,10 @@ func TestVectors(t *testing.T) { r, err := age.Decrypt(in, identities...) if expectFailure { if err == nil { - t.Fatal("expected Decrypt failure") + _, err = io.ReadAll(r) + } + if err == nil { + t.Fatal("expected Decrypt or Read failure") } if e := new(age.NoIdentityMatchError); errors.As(err, &e) { t.Errorf("got ErrIncorrectIdentity, expected more specific error") @@ -87,7 +90,7 @@ func TestVectors(t *testing.T) { } t.Logf("%s", out) } else { - t.Fatal("invalid test vector") + t.Fatal("invalid test vector: missing prefix") } }) } diff --git a/cmd/age/testdata/fail_last_empty.age b/cmd/age/testdata/fail_last_empty.age new file mode 100644 index 0000000..3f67f5b Binary files /dev/null and b/cmd/age/testdata/fail_last_empty.age differ diff --git a/cmd/age/testdata/good_empty_payload.age b/cmd/age/testdata/good_empty_payload.age new file mode 100644 index 0000000..93e5611 --- /dev/null +++ b/cmd/age/testdata/good_empty_payload.age @@ -0,0 +1,5 @@ +age-encryption.org/v1 +-> X25519 JRosIz2avWchP2qSL6wF6U7uzD6kDuJXDbZvN1MOGmo +KpIQxpkbBDHqp+JsHLiTy2d5RYRwp2qzvUrAe0aDOnk +--- orVjbqbzm8U3S9njAs53o4PFi1wK39fIQQ4gRj3i7IU + g 0N'jao&T \ No newline at end of file diff --git a/cmd/age/testdata/good_last_full.age b/cmd/age/testdata/good_last_full.age new file mode 100644 index 0000000..78d62d8 Binary files /dev/null and b/cmd/age/testdata/good_last_full.age differ diff --git a/internal/stream/stream.go b/internal/stream/stream.go index 084bbd1..f6d0602 100644 --- a/internal/stream/stream.go +++ b/internal/stream/stream.go @@ -87,7 +87,11 @@ func (r *Reader) readChunk() (last bool, err error) { // A message can't end without a marked chunk. This message is truncated. return false, io.ErrUnexpectedEOF case err == io.ErrUnexpectedEOF: - // The last chunk can be short. + // The last chunk can be short, but not empty unless it's the first and + // only chunk. + if !nonceIsZero(&r.nonce) && n == r.a.Overhead() { + return false, errors.New("last chunk is empty, try age v1.0.0, and please consider reporting this") + } in = in[:n] last = true setLastChunkFlag(&r.nonce) @@ -128,6 +132,10 @@ func setLastChunkFlag(nonce *[chacha20poly1305.NonceSize]byte) { nonce[len(nonce)-1] = lastChunkFlag } +func nonceIsZero(nonce *[chacha20poly1305.NonceSize]byte) bool { + return *nonce == [chacha20poly1305.NonceSize]byte{} +} + type Writer struct { a cipher.AEAD dst io.Writer