From 30a84e90035c53079d422ecf4ee0346e6a6c2886 Mon Sep 17 00:00:00 2001
From: Michael Eischer <michael.eischer@fau.de>
Date: Sat, 3 Feb 2024 17:30:58 +0100
Subject: [PATCH] backup: verify unpacked files before upload

---
 internal/repository/repository.go | 27 ++++++++++++++++++++++++++-
 1 file changed, 26 insertions(+), 1 deletion(-)

diff --git a/internal/repository/repository.go b/internal/repository/repository.go
index fadb120ce..2584f42c7 100644
--- a/internal/repository/repository.go
+++ b/internal/repository/repository.go
@@ -500,7 +500,8 @@ func (r *Repository) decompressUnpacked(p []byte) ([]byte, error) {
 
 // SaveUnpacked encrypts data and stores it in the backend. Returned is the
 // storage hash.
-func (r *Repository) SaveUnpacked(ctx context.Context, t restic.FileType, p []byte) (id restic.ID, err error) {
+func (r *Repository) SaveUnpacked(ctx context.Context, t restic.FileType, buf []byte) (id restic.ID, err error) {
+	p := buf
 	if t != restic.ConfigFile {
 		p, err = r.compressUnpacked(p)
 		if err != nil {
@@ -515,6 +516,11 @@ func (r *Repository) SaveUnpacked(ctx context.Context, t restic.FileType, p []by
 
 	ciphertext = r.key.Seal(ciphertext, nonce, p, nil)
 
+	if err := r.verifyUnpacked(ciphertext, t, buf); err != nil {
+		// FIXME call to action
+		return restic.ID{}, fmt.Errorf("detected data corruption while saving file of type %v: %w", t, err)
+	}
+
 	if t == restic.ConfigFile {
 		id = restic.ID{}
 	} else {
@@ -532,6 +538,25 @@ func (r *Repository) SaveUnpacked(ctx context.Context, t restic.FileType, p []by
 	return id, nil
 }
 
+func (r *Repository) verifyUnpacked(buf []byte, t restic.FileType, expected []byte) error {
+	nonce, ciphertext := buf[:r.key.NonceSize()], buf[r.key.NonceSize():]
+	plaintext, err := r.key.Open(nil, nonce, ciphertext, nil)
+	if err != nil {
+		return fmt.Errorf("decryption failed: %w", err)
+	}
+	if t != restic.ConfigFile {
+		plaintext, err = r.decompressUnpacked(plaintext)
+		if err != nil {
+			return fmt.Errorf("decompression failed: %w", err)
+		}
+	}
+
+	if !bytes.Equal(plaintext, expected) {
+		return errors.New("data mismatch")
+	}
+	return nil
+}
+
 // Flush saves all remaining packs and the index
 func (r *Repository) Flush(ctx context.Context) error {
 	if err := r.flushPacks(ctx); err != nil {