diff --git a/repo/content/blob_crypto_test.go b/repo/content/blob_crypto_test.go new file mode 100644 index 000000000..fe522f21e --- /dev/null +++ b/repo/content/blob_crypto_test.go @@ -0,0 +1,105 @@ +package content_test + +import ( + "strings" + "testing" + + "github.com/pkg/errors" + "github.com/stretchr/testify/require" + + "github.com/kopia/kopia/internal/gather" + "github.com/kopia/kopia/repo/content" + "github.com/kopia/kopia/repo/encryption" + "github.com/kopia/kopia/repo/hashing" +) + +func TestBlobCrypto(t *testing.T) { + f := &content.FormattingOptions{ + Hash: hashing.DefaultAlgorithm, + Encryption: encryption.DefaultAlgorithm, + } + hf, err := hashing.CreateHashFunc(f) + require.NoError(t, err) + enc, err := encryption.CreateEncryptor(f) + require.NoError(t, err) + + cr := &content.Crypter{ + HashFunction: hf, + Encryptor: enc, + } + + var tmp, tmp2, tmp3 gather.WriteBuffer + defer tmp.Close() + defer tmp2.Close() + defer tmp3.Close() + + id, err := cr.EncryptBLOB(gather.FromSlice([]byte{1, 2, 3}), "n", "mysessionid", &tmp) + require.NoError(t, err) + + id2, err := cr.EncryptBLOB(gather.FromSlice([]byte{1, 2, 4}), "n", "mysessionid", &tmp2) + require.NoError(t, err) + + require.NotEqual(t, id, id2) + + require.NoError(t, cr.DecryptBLOB(tmp.Bytes(), id, &tmp3)) + require.Equal(t, []byte{1, 2, 3}, tmp3.ToByteSlice()) + require.NoError(t, cr.DecryptBLOB(tmp2.Bytes(), id2, &tmp3)) + require.Equal(t, []byte{1, 2, 4}, tmp3.ToByteSlice()) + + // decrypting using invalid ID fails + require.Error(t, cr.DecryptBLOB(tmp.Bytes(), id2, &tmp3)) + require.Error(t, cr.DecryptBLOB(tmp2.Bytes(), id, &tmp3)) + + require.True(t, strings.HasPrefix(string(id), "n")) + require.True(t, strings.HasSuffix(string(id), "-mysessionid"), id) + + // negative cases + require.Error(t, cr.DecryptBLOB(tmp.Bytes(), "invalid-blob-id", &tmp3)) + require.Error(t, cr.DecryptBLOB(tmp.Bytes(), "zzz0123456789abcdef0123456789abcde-suffix", &tmp3)) + require.Error(t, cr.DecryptBLOB(tmp.Bytes(), id2, &tmp3)) + require.Error(t, cr.DecryptBLOB(gather.FromSlice([]byte{2, 3, 4}), id, &tmp2)) +} + +type badEncryptor struct{} + +func (badEncryptor) Encrypt(input gather.Bytes, contentID []byte, output *gather.WriteBuffer) error { + return errors.Errorf("some error") +} + +func (badEncryptor) Decrypt(input gather.Bytes, contentID []byte, output *gather.WriteBuffer) error { + return errors.Errorf("some error") +} + +func (badEncryptor) Overhead() int { return 0 } + +func TestBlobCrypto_Invalid(t *testing.T) { + cr := &content.Crypter{ + HashFunction: func(output []byte, data gather.Bytes) []byte { + // invalid hash + return append(output, 9, 9, 9, 9) + }, + Encryptor: badEncryptor{}, + } + + var tmp, tmp2, tmp3 gather.WriteBuffer + defer tmp.Close() + defer tmp2.Close() + defer tmp3.Close() + + _, err := cr.EncryptBLOB(gather.FromSlice([]byte{1, 2, 3}), "n", "mysessionid", &tmp) + require.Error(t, err) + + f := &content.FormattingOptions{ + Hash: hashing.DefaultAlgorithm, + Encryption: encryption.DefaultAlgorithm, + } + + // now fix HashFunction but encryption still fails. + hf, err := hashing.CreateHashFunc(f) + require.NoError(t, err) + + cr.HashFunction = hf + + _, err = cr.EncryptBLOB(gather.FromSlice([]byte{1, 2, 3}), "n", "mysessionid", &tmp) + require.Error(t, err) +} diff --git a/repo/content/encrypted_blob_mgr_test.go b/repo/content/encrypted_blob_mgr_test.go new file mode 100644 index 000000000..f3056bb32 --- /dev/null +++ b/repo/content/encrypted_blob_mgr_test.go @@ -0,0 +1,92 @@ +package content + +import ( + "testing" + + "github.com/pkg/errors" + "github.com/stretchr/testify/require" + + "github.com/kopia/kopia/internal/blobtesting" + "github.com/kopia/kopia/internal/gather" + "github.com/kopia/kopia/internal/testlogging" + "github.com/kopia/kopia/repo/blob" + "github.com/kopia/kopia/repo/encryption" + "github.com/kopia/kopia/repo/hashing" + "github.com/kopia/kopia/repo/logging" +) + +type failingEncryptor struct { + encryption.Encryptor + err error +} + +func (f failingEncryptor) Encrypt(input gather.Bytes, contentID []byte, output *gather.WriteBuffer) error { + return f.err +} + +func TestEncryptedBlobManager(t *testing.T) { + data := blobtesting.DataMap{} + st := blobtesting.NewMapStorage(data, nil, nil) + fs := blobtesting.NewFaultyStorage(st) + f := &FormattingOptions{ + Hash: hashing.DefaultAlgorithm, + Encryption: encryption.DefaultAlgorithm, + } + hf, err := hashing.CreateHashFunc(f) + require.NoError(t, err) + enc, err := encryption.CreateEncryptor(f) + require.NoError(t, err) + + cr := &Crypter{ + HashFunction: hf, + Encryptor: enc, + } + + ebm := encryptedBlobMgr{ + st: fs, + crypter: cr, + indexBlobCache: passthroughContentCache{fs}, + log: logging.NullLogger, + } + + ctx := testlogging.Context(t) + + bm, err := ebm.encryptAndWriteBlob(ctx, gather.FromSlice([]byte{1, 2, 3}), "x", "session1") + require.NoError(t, err) + + stbm, err := st.GetMetadata(ctx, bm.BlobID) + require.NoError(t, err) + + require.Equal(t, stbm, bm) + + var tmp gather.WriteBuffer + defer tmp.Close() + + require.NoError(t, ebm.getEncryptedBlob(ctx, bm.BlobID, &tmp)) + + // data corruption + data[bm.BlobID][0] ^= 1 + + require.Error(t, ebm.getEncryptedBlob(ctx, bm.BlobID, &tmp)) + + require.ErrorIs(t, ebm.getEncryptedBlob(ctx, "no-such-blob", &tmp), blob.ErrBlobNotFound) + + someError := errors.Errorf("some error") + + fs.AddFault(blobtesting.MethodPutBlob).ErrorInstead(someError) + + _, err = ebm.encryptAndWriteBlob(ctx, gather.FromSlice([]byte{1, 2, 3, 4}), "x", "session1") + require.ErrorIs(t, err, someError) + + fs.AddFault(blobtesting.MethodGetMetadata).ErrorInstead(someError) + + _, err = ebm.encryptAndWriteBlob(ctx, gather.FromSlice([]byte{1, 2, 3, 4}), "x", "session1") + require.ErrorIs(t, err, someError) + + someError2 := errors.Errorf("some error 2") + + cr.Encryptor = failingEncryptor{nil, someError2} + + _, err = ebm.encryptAndWriteBlob(ctx, gather.FromSlice([]byte{1, 2, 3, 4}), "x", "session1") + require.ErrorIs(t, err, someError2) +}