From 30456d13e733989c0d3ee10ac4e60c588a401b9b Mon Sep 17 00:00:00 2001 From: Jarek Kowalski Date: Sat, 30 Jul 2022 07:57:56 -0700 Subject: [PATCH] refactor(repository): refactored Crypter to an unexported interface (#2251) --- cli/command_benchmark_crypto.go | 15 ++++++-- cli/command_benchmark_encryption.go | 4 +- cli/command_benchmark_hashing.go | 7 +--- cli/command_blob_show.go | 3 +- cli/command_index_inspect.go | 2 +- cli/command_logs_show.go | 3 +- cli/command_repository_set_parameters.go | 2 +- repo/content/blob_crypto.go | 34 ++++++----------- repo/content/blob_crypto_test.go | 44 ++++++++++------------ repo/content/committed_read_manager.go | 22 +++-------- repo/content/content_formatter_test.go | 43 +++++---------------- repo/content/content_formatting_options.go | 43 +++++++++++++++++---- repo/content/content_index_recovery.go | 4 +- repo/content/content_manager_indexes.go | 6 +-- repo/content/content_manager_lock_free.go | 30 +-------------- repo/content/encrypted_blob_mgr.go | 12 +++--- repo/content/encrypted_blob_mgr_test.go | 28 +++++++------- repo/content/index_blob_manager_v0.go | 2 +- repo/content/index_blob_manager_v0_test.go | 7 +--- repo/content/index_blob_manager_v1.go | 4 +- repo/content/internal_logger.go | 6 +-- repo/content/sessions.go | 4 +- repo/repository.go | 6 --- 23 files changed, 141 insertions(+), 190 deletions(-) diff --git a/cli/command_benchmark_crypto.go b/cli/command_benchmark_crypto.go index 8ee72ffab..155df89ef 100644 --- a/cli/command_benchmark_crypto.go +++ b/cli/command_benchmark_crypto.go @@ -67,12 +67,19 @@ func (c *commandBenchmarkCrypto) runBenchmark(ctx context.Context) []cryptoBench for _, ha := range hashing.SupportedAlgorithms() { for _, ea := range encryption.SupportedAlgorithms(c.deprecatedAlgorithms) { - cr, err := content.CreateCrypter(&content.FormattingOptions{ + fo := &content.FormattingOptions{ Encryption: ea, Hash: ha, MasterKey: make([]byte, 32), // nolint:gomnd HMACSecret: make([]byte, 32), // nolint:gomnd - }) + } + + hf, err := hashing.CreateHashFunc(fo) + if err != nil { + continue + } + + enc, err := encryption.CreateEncryptor(fo) if err != nil { continue } @@ -91,9 +98,9 @@ func (c *commandBenchmarkCrypto) runBenchmark(ctx context.Context) []cryptoBench defer encryptOutput.Close() for i := 0; i < hashCount; i++ { - contentID := cr.HashFunction(hashOutput[:0], input) + contentID := hf(hashOutput[:0], input) - if encerr := cr.Encryptor.Encrypt(input, contentID, &encryptOutput); encerr != nil { + if encerr := enc.Encrypt(input, contentID, &encryptOutput); encerr != nil { log(ctx).Errorf("encryption failed: %v", encerr) break } diff --git a/cli/command_benchmark_encryption.go b/cli/command_benchmark_encryption.go index ad9d47611..1d6374231 100644 --- a/cli/command_benchmark_encryption.go +++ b/cli/command_benchmark_encryption.go @@ -66,7 +66,7 @@ func (c *commandBenchmarkEncryption) runBenchmark(ctx context.Context) []cryptoB data := make([]byte, c.blockSize) for _, ea := range encryption.SupportedAlgorithms(c.deprecatedAlgorithms) { - cr, err := content.CreateCrypter(&content.FormattingOptions{ + enc, err := encryption.CreateEncryptor(&content.FormattingOptions{ Encryption: ea, Hash: hashing.DefaultAlgorithm, MasterKey: make([]byte, 32), // nolint:gomnd @@ -90,7 +90,7 @@ func (c *commandBenchmarkEncryption) runBenchmark(ctx context.Context) []cryptoB defer encryptOutput.Close() for i := 0; i < hashCount; i++ { - if encerr := cr.Encryptor.Encrypt(input, hashOutput[:32], &encryptOutput); encerr != nil { + if encerr := enc.Encrypt(input, hashOutput[:32], &encryptOutput); encerr != nil { log(ctx).Errorf("encryption failed: %v", encerr) break } diff --git a/cli/command_benchmark_hashing.go b/cli/command_benchmark_hashing.go index 74f2e2d82..9a2b82b06 100644 --- a/cli/command_benchmark_hashing.go +++ b/cli/command_benchmark_hashing.go @@ -10,7 +10,6 @@ "github.com/kopia/kopia/internal/timetrack" "github.com/kopia/kopia/internal/units" "github.com/kopia/kopia/repo/content" - "github.com/kopia/kopia/repo/encryption" "github.com/kopia/kopia/repo/hashing" ) @@ -64,10 +63,8 @@ func (c *commandBenchmarkHashing) runBenchmark(ctx context.Context) []cryptoBenc data := make([]byte, c.blockSize) for _, ha := range hashing.SupportedAlgorithms() { - cr, err := content.CreateCrypter(&content.FormattingOptions{ - Encryption: encryption.DefaultAlgorithm, + hf, err := hashing.CreateHashFunc(&content.FormattingOptions{ Hash: ha, - MasterKey: make([]byte, 32), // nolint:gomnd HMACSecret: make([]byte, 32), // nolint:gomnd }) if err != nil { @@ -85,7 +82,7 @@ func (c *commandBenchmarkHashing) runBenchmark(ctx context.Context) []cryptoBenc var hashOutput [hashing.MaxHashSize]byte for i := 0; i < hashCount; i++ { - cr.HashFunction(hashOutput[:0], input) + hf(hashOutput[:0], input) } return nil diff --git a/cli/command_blob_show.go b/cli/command_blob_show.go index 199cd4b69..e26c9a2fc 100644 --- a/cli/command_blob_show.go +++ b/cli/command_blob_show.go @@ -12,6 +12,7 @@ "github.com/kopia/kopia/internal/iocopy" "github.com/kopia/kopia/repo" "github.com/kopia/kopia/repo/blob" + "github.com/kopia/kopia/repo/content" ) type commandBlobShow struct { @@ -56,7 +57,7 @@ func (c *commandBlobShow) maybeDecryptBlob(ctx context.Context, w io.Writer, rep var tmp gather.WriteBuffer defer tmp.Close() - if err := rep.Crypter().DecryptBLOB(b, blobID, &tmp); err != nil { + if err := content.DecryptBLOB(rep.ContentReader().ContentFormat(), b, blobID, &tmp); err != nil { return errors.Wrap(err, "error decrypting blob") } diff --git a/cli/command_index_inspect.go b/cli/command_index_inspect.go index f6bc8aaf7..7c753feb2 100644 --- a/cli/command_index_inspect.go +++ b/cli/command_index_inspect.go @@ -162,7 +162,7 @@ func (c *commandIndexInspect) inspectSingleIndexBlob(ctx context.Context, rep re return errors.Wrapf(err, "unable to get data for %v", blobID) } - entries, err := content.ParseIndexBlob(ctx, blobID, data.Bytes(), rep.Crypter()) + entries, err := content.ParseIndexBlob(ctx, blobID, data.Bytes(), rep.ContentReader().ContentFormat()) if err != nil { return errors.Wrapf(err, "unable to recover index from %v", blobID) } diff --git a/cli/command_logs_show.go b/cli/command_logs_show.go index d8f0d4683..57195efad 100644 --- a/cli/command_logs_show.go +++ b/cli/command_logs_show.go @@ -7,6 +7,7 @@ "github.com/kopia/kopia/internal/gather" "github.com/kopia/kopia/repo" + "github.com/kopia/kopia/repo/content" ) type commandLogsShow struct { @@ -69,7 +70,7 @@ func (c *commandLogsShow) run(ctx context.Context, rep repo.DirectRepository) er return errors.Wrap(err, "error getting log") } - if err := rep.Crypter().DecryptBLOB(data.Bytes(), bm.BlobID, &decrypted); err != nil { + if err := content.DecryptBLOB(rep.ContentReader().ContentFormat(), data.Bytes(), bm.BlobID, &decrypted); err != nil { return errors.Wrap(err, "error decrypting log") } diff --git a/cli/command_repository_set_parameters.go b/cli/command_repository_set_parameters.go index 49acbac19..0a9d19cb1 100644 --- a/cli/command_repository_set_parameters.go +++ b/cli/command_repository_set_parameters.go @@ -124,7 +124,7 @@ func (c *commandRepositorySetParameters) setRetentionModeParameter(ctx context.C func (c *commandRepositorySetParameters) run(ctx context.Context, rep repo.DirectRepositoryWriter) error { var anyChange bool - mp := rep.ContentReader().ContentFormat().GetMutableParameters() + mp := rep.ContentReader().ContentFormat().Struct().MutableParameters blobcfg := rep.BlobCfg() requiredFeatures, err := rep.RequiredFeatures() diff --git a/repo/content/blob_crypto.go b/repo/content/blob_crypto.go index 95711246e..21fbce163 100644 --- a/repo/content/blob_crypto.go +++ b/repo/content/blob_crypto.go @@ -13,25 +13,15 @@ "github.com/kopia/kopia/repo/hashing" ) -// Crypter ecapsulates hashing and encryption and provides utilities for whole-BLOB encryption. -// Whole-BLOB encryption relies on BLOB identifiers formatted as: -// -// [-optionalSuffix] -// -// Where: -// 'prefix' is arbitrary string without dashes -// 'hash' is base16-encoded 128-bit hash of contents, used as initialization vector (IV) -// for the encryption. In case of longer hash functions, we use last 16 bytes of -// their outputs. -// 'optionalSuffix' can be any string -type Crypter struct { - HashFunction hashing.HashFunc - Encryptor encryption.Encryptor +// crypter ecapsulates hashing and encryption. +type crypter interface { + HashFunc() hashing.HashFunc + Encryptor() encryption.Encryptor } // getIndexBlobIV gets the initialization vector from the provided blob ID by taking // 32 characters immediately preceding the first dash ('-') and decoding them using base16. -func (c *Crypter) getIndexBlobIV(s blob.ID) ([]byte, error) { +func getIndexBlobIV(s blob.ID) ([]byte, error) { if p := strings.Index(string(s), "-"); p >= 0 { // nolint:gocritic s = s[0:p] } @@ -50,24 +40,24 @@ func (c *Crypter) getIndexBlobIV(s blob.ID) ([]byte, error) { // EncryptBLOB encrypts the given data using crypter-defined key and returns a name that should // be used to save the blob in thre repository. -func (c *Crypter) EncryptBLOB(payload gather.Bytes, prefix blob.ID, sessionID SessionID, output *gather.WriteBuffer) (blob.ID, error) { +func EncryptBLOB(c crypter, payload gather.Bytes, prefix blob.ID, sessionID SessionID, output *gather.WriteBuffer) (blob.ID, error) { var hashOutput [hashing.MaxHashSize]byte - hash := c.HashFunction(hashOutput[:0], payload) + hash := c.HashFunc()(hashOutput[:0], payload) blobID := prefix + blob.ID(hex.EncodeToString(hash)) if sessionID != "" { blobID += blob.ID("-" + sessionID) } - iv, err := c.getIndexBlobIV(blobID) + iv, err := getIndexBlobIV(blobID) if err != nil { return "", err } output.Reset() - if err := c.Encryptor.Encrypt(payload, iv, output); err != nil { + if err := c.Encryptor().Encrypt(payload, iv, output); err != nil { return "", errors.Wrapf(err, "error encrypting BLOB %v", blobID) } @@ -75,8 +65,8 @@ func (c *Crypter) EncryptBLOB(payload gather.Bytes, prefix blob.ID, sessionID Se } // DecryptBLOB decrypts the provided data using provided blobID to derive initialization vector. -func (c *Crypter) DecryptBLOB(payload gather.Bytes, blobID blob.ID, output *gather.WriteBuffer) error { - iv, err := c.getIndexBlobIV(blobID) +func DecryptBLOB(c crypter, payload gather.Bytes, blobID blob.ID, output *gather.WriteBuffer) error { + iv, err := getIndexBlobIV(blobID) if err != nil { return errors.Wrap(err, "unable to get index blob IV") } @@ -84,7 +74,7 @@ func (c *Crypter) DecryptBLOB(payload gather.Bytes, blobID blob.ID, output *gath output.Reset() // Decrypt will verify the payload. - if err := c.Encryptor.Decrypt(payload, iv, output); err != nil { + if err := c.Encryptor().Decrypt(payload, iv, output); err != nil { return errors.Wrapf(err, "error decrypting BLOB %v", blobID) } diff --git a/repo/content/blob_crypto_test.go b/repo/content/blob_crypto_test.go index fe522f21e..c22936702 100644 --- a/repo/content/blob_crypto_test.go +++ b/repo/content/blob_crypto_test.go @@ -1,4 +1,4 @@ -package content_test +package content import ( "strings" @@ -8,13 +8,12 @@ "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{ + f := &FormattingOptions{ Hash: hashing.DefaultAlgorithm, Encryption: encryption.DefaultAlgorithm, } @@ -23,41 +22,38 @@ func TestBlobCrypto(t *testing.T) { enc, err := encryption.CreateEncryptor(f) require.NoError(t, err) - cr := &content.Crypter{ - HashFunction: hf, - Encryptor: enc, - } + cr := staticCrypter{hf, 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) + id, err := EncryptBLOB(cr, 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) + id2, err := EncryptBLOB(cr, 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.NoError(t, DecryptBLOB(cr, tmp.Bytes(), id, &tmp3)) require.Equal(t, []byte{1, 2, 3}, tmp3.ToByteSlice()) - require.NoError(t, cr.DecryptBLOB(tmp2.Bytes(), id2, &tmp3)) + require.NoError(t, DecryptBLOB(cr, 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.Error(t, DecryptBLOB(cr, tmp.Bytes(), id2, &tmp3)) + require.Error(t, DecryptBLOB(cr, 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)) + require.Error(t, DecryptBLOB(cr, tmp.Bytes(), "invalid-blob-id", &tmp3)) + require.Error(t, DecryptBLOB(cr, tmp.Bytes(), "zzz0123456789abcdef0123456789abcde-suffix", &tmp3)) + require.Error(t, DecryptBLOB(cr, tmp.Bytes(), id2, &tmp3)) + require.Error(t, DecryptBLOB(cr, gather.FromSlice([]byte{2, 3, 4}), id, &tmp2)) } type badEncryptor struct{} @@ -73,12 +69,12 @@ func (badEncryptor) Decrypt(input gather.Bytes, contentID []byte, output *gather func (badEncryptor) Overhead() int { return 0 } func TestBlobCrypto_Invalid(t *testing.T) { - cr := &content.Crypter{ - HashFunction: func(output []byte, data gather.Bytes) []byte { + cr := staticCrypter{ + func(output []byte, data gather.Bytes) []byte { // invalid hash return append(output, 9, 9, 9, 9) }, - Encryptor: badEncryptor{}, + badEncryptor{}, } var tmp, tmp2, tmp3 gather.WriteBuffer @@ -86,10 +82,10 @@ func TestBlobCrypto_Invalid(t *testing.T) { defer tmp2.Close() defer tmp3.Close() - _, err := cr.EncryptBLOB(gather.FromSlice([]byte{1, 2, 3}), "n", "mysessionid", &tmp) + _, err := EncryptBLOB(cr, gather.FromSlice([]byte{1, 2, 3}), "n", "mysessionid", &tmp) require.Error(t, err) - f := &content.FormattingOptions{ + f := &FormattingOptions{ Hash: hashing.DefaultAlgorithm, Encryption: encryption.DefaultAlgorithm, } @@ -98,8 +94,8 @@ func TestBlobCrypto_Invalid(t *testing.T) { hf, err := hashing.CreateHashFunc(f) require.NoError(t, err) - cr.HashFunction = hf + cr.h = hf - _, err = cr.EncryptBLOB(gather.FromSlice([]byte{1, 2, 3}), "n", "mysessionid", &tmp) + _, err = EncryptBLOB(cr, gather.FromSlice([]byte{1, 2, 3}), "n", "mysessionid", &tmp) require.Error(t, err) } diff --git a/repo/content/committed_read_manager.go b/repo/content/committed_read_manager.go index 9e8d9337f..aac3c7532 100644 --- a/repo/content/committed_read_manager.go +++ b/repo/content/committed_read_manager.go @@ -69,11 +69,6 @@ type indexBlobManager interface { invalidate(ctx context.Context) } -// CrypterProvider returns the crypter to use for encrypting contents and blobs. -type CrypterProvider interface { - Crypter() *Crypter -} - // SharedManager is responsible for read-only access to committed data. type SharedManager struct { // +checkatomic @@ -119,11 +114,6 @@ type SharedManager struct { internalLogger *zap.SugaredLogger // backing logger for 'sharedBaseLogger' } -// Crypter returns the crypter. -func (sm *SharedManager) Crypter() *Crypter { - return sm.format.Crypter() -} - func (sm *SharedManager) readPackFileLocalIndex(ctx context.Context, packFile blob.ID, packFileLength int64, output *gather.WriteBuffer) error { var err error @@ -291,7 +281,7 @@ func (sm *SharedManager) decryptContentAndVerify(payload gather.Bytes, bi Info, } func (sm *SharedManager) decryptAndVerify(encrypted gather.Bytes, iv []byte, output *gather.WriteBuffer) error { - if err := sm.format.Crypter().Encryptor.Decrypt(encrypted, iv, output); err != nil { + if err := sm.format.Encryptor().Decrypt(encrypted, iv, output); err != nil { sm.Stats.foundInvalidContent() return errors.Wrap(err, "decrypt") } @@ -435,10 +425,10 @@ func (sm *SharedManager) setupReadManagerCaches(ctx context.Context, caching *Ca } sm.enc = &encryptedBlobMgr{ - st: cachedSt, - crypterProvider: sm.format, - indexBlobCache: indexBlobCache, - log: sm.namedLogger("encrypted-blob-manager"), + st: cachedSt, + crypter: sm.format, + indexBlobCache: indexBlobCache, + log: sm.namedLogger("encrypted-blob-manager"), } // set up legacy index blob manager @@ -466,7 +456,7 @@ func (sm *SharedManager) setupReadManagerCaches(ctx context.Context, caching *Ca sm.metadataCache = metadataCache sm.indexBlobCache = indexBlobCache sm.committedContents = newCommittedContentIndex(caching, - uint32(sm.format.Crypter().Encryptor.Overhead()), + uint32(sm.format.Encryptor().Overhead()), sm.format.WriteIndexVersion(), sm.enc.getEncryptedBlob, sm.namedLogger("committed-content-index"), diff --git a/repo/content/content_formatter_test.go b/repo/content/content_formatter_test.go index c0f74d78f..287926c1f 100644 --- a/repo/content/content_formatter_test.go +++ b/repo/content/content_formatter_test.go @@ -5,7 +5,6 @@ "context" cryptorand "crypto/rand" "crypto/sha1" - "strings" "testing" "time" @@ -19,18 +18,6 @@ "github.com/kopia/kopia/repo/hashing" ) -// combinations of hash and encryption that are not compatible. -var incompatibleAlgorithms = map[string]string{ - "BLAKE2B-256-128/XSALSA20": "expected >=24 bytes, got 16", - "BLAKE2S-128/XSALSA20": "expected >=24 bytes, got 16", - "HMAC-RIPEMD-160/XSALSA20": "expected >=24 bytes, got 20", - "HMAC-SHA256-128/XSALSA20": "expected >=24 bytes, got 16", - "BLAKE2B-256-128/XSALSA20-HMAC": "expected >=24 bytes, got 16", - "BLAKE2S-128/XSALSA20-HMAC": "expected >=24 bytes, got 16", - "HMAC-RIPEMD-160/XSALSA20-HMAC": "expected >=24 bytes, got 20", - "HMAC-SHA256-128/XSALSA20-HMAC": "expected >=24 bytes, got 16", -} - func TestFormatters(t *testing.T) { secret := []byte("secret") @@ -46,40 +33,30 @@ func TestFormatters(t *testing.T) { t.Run(encryptionAlgo, func(t *testing.T) { ctx := testlogging.Context(t) - cr, err := CreateCrypter(&FormattingOptions{ + fo := &FormattingOptions{ HMACSecret: secret, MasterKey: make([]byte, 32), Hash: hashAlgo, Encryption: encryptionAlgo, - }) - if err != nil { - key := hashAlgo + "/" + encryptionAlgo - - errmsg := incompatibleAlgorithms[key] - if errmsg == "" { - t.Errorf("Algorithm %v not marked as incompatible and failed with %v", key, err) - return - } - - if !strings.HasSuffix(err.Error(), errmsg) { - t.Errorf("unexpected error message %v, wanted %v", err.Error(), errmsg) - return - } - - return } - contentID := cr.HashFunction(nil, gather.FromSlice(data)) + hf, err := hashing.CreateHashFunc(fo) + require.NoError(t, err) + + enc, err := encryption.CreateEncryptor(fo) + require.NoError(t, err) + + contentID := hf(nil, gather.FromSlice(data)) var cipherText gather.WriteBuffer defer cipherText.Close() - require.NoError(t, cr.Encryptor.Encrypt(gather.FromSlice(data), contentID, &cipherText)) + require.NoError(t, enc.Encrypt(gather.FromSlice(data), contentID, &cipherText)) var plainText gather.WriteBuffer defer plainText.Close() - require.NoError(t, cr.Encryptor.Decrypt(cipherText.Bytes(), contentID, &plainText)) + require.NoError(t, enc.Decrypt(cipherText.Bytes(), contentID, &plainText)) h1 := sha1.Sum(plainText.ToByteSlice()) diff --git a/repo/content/content_formatting_options.go b/repo/content/content_formatting_options.go index b91a07216..095121873 100644 --- a/repo/content/content_formatting_options.go +++ b/repo/content/content_formatting_options.go @@ -6,6 +6,7 @@ "github.com/pkg/errors" "github.com/kopia/kopia/internal/epoch" + "github.com/kopia/kopia/internal/gather" "github.com/kopia/kopia/internal/units" "github.com/kopia/kopia/repo/blob" "github.com/kopia/kopia/repo/content/index" @@ -127,12 +128,17 @@ func (f *FormattingOptions) GetHmacSecret() []byte { // should not be cached for more than a few seconds as they are subject to change. type FormattingOptionsProvider interface { epoch.ParametersProvider - IndexFormattingOptions - CrypterProvider + + MaxIndexBlobSize() int64 + WriteIndexVersion() int + IndexShardSize() int encryption.Parameters hashing.Parameters + HashFunc() hashing.HashFunc + Encryptor() encryption.Encryptor + GetMutableParameters() MutableParameters GetMasterKey() []byte SupportsPasswordChange() bool @@ -145,7 +151,8 @@ type FormattingOptionsProvider interface { type formattingOptionsProvider struct { *FormattingOptions - crypter *Crypter + h hashing.HashFunc + e encryption.Encryptor actualFormatVersion FormatVersion actualIndexVersion int formatBytes []byte @@ -221,23 +228,43 @@ func NewFormattingOptionsProvider(f *FormattingOptions, formatBytes []byte) (For return nil, errors.Errorf("index version %v is not supported", actualIndexVersion) } - crypter, err := CreateCrypter(f) + h, err := hashing.CreateHashFunc(f) if err != nil { - return nil, errors.Wrap(err, "unable to create crypter") + return nil, errors.Wrap(err, "unable to create hash") + } + + e, err := encryption.CreateEncryptor(f) + if err != nil { + return nil, errors.Wrap(err, "unable to create encryptor") + } + + contentID := h(nil, gather.FromSlice(nil)) + + var tmp gather.WriteBuffer + defer tmp.Close() + + err = e.Encrypt(gather.FromSlice(nil), contentID, &tmp) + if err != nil { + return nil, errors.Wrap(err, "invalid encryptor") } return &formattingOptionsProvider{ FormattingOptions: f, - crypter: crypter, + h: h, + e: e, actualIndexVersion: actualIndexVersion, actualFormatVersion: f.Version, formatBytes: formatBytes, }, nil } -func (f *formattingOptionsProvider) Crypter() *Crypter { - return f.crypter +func (f *formattingOptionsProvider) Encryptor() encryption.Encryptor { + return f.e +} + +func (f *formattingOptionsProvider) HashFunc() hashing.HashFunc { + return f.h } func (f *formattingOptionsProvider) WriteIndexVersion() int { diff --git a/repo/content/content_index_recovery.go b/repo/content/content_index_recovery.go index ee4781c5f..3b78d9b7b 100644 --- a/repo/content/content_index_recovery.go +++ b/repo/content/content_index_recovery.go @@ -22,7 +22,7 @@ func (bm *WriteManager) RecoverIndexFromPackBlob(ctx context.Context, packFile b return nil, err } - ndx, err := index.Open(localIndexBytes.Bytes().ToByteSlice(), nil, uint32(bm.format.Crypter().Encryptor.Overhead())) + ndx, err := index.Open(localIndexBytes.Bytes().ToByteSlice(), nil, uint32(bm.format.Encryptor().Overhead())) if err != nil { return nil, errors.Errorf("unable to open index in file %v", packFile) } @@ -195,7 +195,7 @@ func (sm *SharedManager) appendPackFileIndexRecoveryData(pending index.Builder, var encryptedLocalIndex gather.WriteBuffer defer encryptedLocalIndex.Close() - if err := sm.format.Crypter().Encryptor.Encrypt(localIndex.Bytes(), localIndexIV, &encryptedLocalIndex); err != nil { + if err := sm.format.Encryptor().Encrypt(localIndex.Bytes(), localIndexIV, &encryptedLocalIndex); err != nil { return errors.Wrap(err, "encryption error") } diff --git a/repo/content/content_manager_indexes.go b/repo/content/content_manager_indexes.go index af3ba7237..32786d8ba 100644 --- a/repo/content/content_manager_indexes.go +++ b/repo/content/content_manager_indexes.go @@ -70,15 +70,15 @@ func (sm *SharedManager) CompactIndexes(ctx context.Context, opt CompactOptions) } // ParseIndexBlob loads entries in a given index blob and returns them. -func ParseIndexBlob(ctx context.Context, blobID blob.ID, encrypted gather.Bytes, crypter *Crypter) ([]Info, error) { +func ParseIndexBlob(ctx context.Context, blobID blob.ID, encrypted gather.Bytes, crypter crypter) ([]Info, error) { var data gather.WriteBuffer defer data.Close() - if err := crypter.DecryptBLOB(encrypted, blobID, &data); err != nil { + if err := DecryptBLOB(crypter, encrypted, blobID, &data); err != nil { return nil, errors.Wrap(err, "unable to decrypt index blob") } - ndx, err := index.Open(data.Bytes().ToByteSlice(), nil, uint32(crypter.Encryptor.Overhead())) + ndx, err := index.Open(data.Bytes().ToByteSlice(), nil, uint32(crypter.Encryptor().Overhead())) if err != nil { return nil, errors.Wrapf(err, "unable to open index blob") } diff --git a/repo/content/content_manager_lock_free.go b/repo/content/content_manager_lock_free.go index 52a194b06..6ed6d4772 100644 --- a/repo/content/content_manager_lock_free.go +++ b/repo/content/content_manager_lock_free.go @@ -16,7 +16,6 @@ "github.com/kopia/kopia/repo/blob" "github.com/kopia/kopia/repo/compression" "github.com/kopia/kopia/repo/content/index" - "github.com/kopia/kopia/repo/encryption" "github.com/kopia/kopia/repo/hashing" "github.com/kopia/kopia/repo/logging" ) @@ -62,7 +61,7 @@ func (sm *SharedManager) maybeCompressAndEncryptDataForPacking(data gather.Bytes } } - if err := sm.Crypter().Encryptor.Encrypt(data, iv, output); err != nil { + if err := sm.format.Encryptor().Encrypt(data, iv, output); err != nil { return NoCompression, errors.Wrap(err, "unable to encrypt") } @@ -181,33 +180,8 @@ func (sm *SharedManager) writePackFileNotLocked(ctx context.Context, packFile bl func (sm *SharedManager) hashData(output []byte, data gather.Bytes) []byte { // Hash the content and compute encryption key. - contentID := sm.format.Crypter().HashFunction(output, data) + contentID := sm.format.HashFunc()(output, data) sm.Stats.hashedContent(data.Length()) return contentID } - -// CreateCrypter returns a Crypter based on the specified formatting options. -func CreateCrypter(f *FormattingOptions) (*Crypter, error) { - h, err := hashing.CreateHashFunc(f) - if err != nil { - return nil, errors.Wrap(err, "unable to create hash") - } - - e, err := encryption.CreateEncryptor(f) - if err != nil { - return nil, errors.Wrap(err, "unable to create encryptor") - } - - contentID := h(nil, gather.FromSlice(nil)) - - var tmp gather.WriteBuffer - defer tmp.Close() - - err = e.Encrypt(gather.FromSlice(nil), contentID, &tmp) - if err != nil { - return nil, errors.Wrap(err, "invalid encryptor") - } - - return &Crypter{h, e}, nil -} diff --git a/repo/content/encrypted_blob_mgr.go b/repo/content/encrypted_blob_mgr.go index e9686555f..5e7a5d3a7 100644 --- a/repo/content/encrypted_blob_mgr.go +++ b/repo/content/encrypted_blob_mgr.go @@ -12,10 +12,10 @@ ) type encryptedBlobMgr struct { - st blob.Storage - crypterProvider CrypterProvider - indexBlobCache *cache.PersistentCache - log logging.Logger + st blob.Storage + crypter crypter + indexBlobCache *cache.PersistentCache + log logging.Logger } func (m *encryptedBlobMgr) getEncryptedBlob(ctx context.Context, blobID blob.ID, output *gather.WriteBuffer) error { @@ -29,14 +29,14 @@ func (m *encryptedBlobMgr) getEncryptedBlob(ctx context.Context, blobID blob.ID, return errors.Wrap(err, "getContent") } - return m.crypterProvider.Crypter().DecryptBLOB(payload.Bytes(), blobID, output) + return DecryptBLOB(m.crypter, payload.Bytes(), blobID, output) } func (m *encryptedBlobMgr) encryptAndWriteBlob(ctx context.Context, data gather.Bytes, prefix blob.ID, sessionID SessionID) (blob.Metadata, error) { var data2 gather.WriteBuffer defer data2.Close() - blobID, err := m.crypterProvider.Crypter().EncryptBLOB(data, prefix, sessionID, &data2) + blobID, err := EncryptBLOB(m.crypter, data, prefix, sessionID, &data2) if err != nil { return blob.Metadata{}, errors.Wrap(err, "error encrypting") } diff --git a/repo/content/encrypted_blob_mgr_test.go b/repo/content/encrypted_blob_mgr_test.go index ed589e3c1..3381be2a0 100644 --- a/repo/content/encrypted_blob_mgr_test.go +++ b/repo/content/encrypted_blob_mgr_test.go @@ -37,16 +37,11 @@ func TestEncryptedBlobManager(t *testing.T) { enc, err := encryption.CreateEncryptor(f) require.NoError(t, err) - cr := &Crypter{ - HashFunction: hf, - Encryptor: enc, - } - ebm := encryptedBlobMgr{ - st: fs, - crypterProvider: staticCrypterProvider{cr}, - indexBlobCache: nil, - log: logging.NullLogger, + st: fs, + crypter: staticCrypter{hf, enc}, + indexBlobCache: nil, + log: logging.NullLogger, } ctx := testlogging.Context(t) @@ -80,16 +75,21 @@ func TestEncryptedBlobManager(t *testing.T) { someError2 := errors.Errorf("some error 2") - cr.Encryptor = failingEncryptor{nil, someError2} + ebm.crypter = staticCrypter{hf, failingEncryptor{nil, someError2}} _, err = ebm.encryptAndWriteBlob(ctx, gather.FromSlice([]byte{1, 2, 3, 4}), "x", "session1") require.ErrorIs(t, err, someError2) } -type staticCrypterProvider struct { - theCrypter *Crypter +type staticCrypter struct { + h hashing.HashFunc + e encryption.Encryptor } -func (p staticCrypterProvider) Crypter() *Crypter { - return p.theCrypter +func (p staticCrypter) Encryptor() encryption.Encryptor { + return p.e +} + +func (p staticCrypter) HashFunc() hashing.HashFunc { + return p.h } diff --git a/repo/content/index_blob_manager_v0.go b/repo/content/index_blob_manager_v0.go index a73d52bbe..ca5067d5e 100644 --- a/repo/content/index_blob_manager_v0.go +++ b/repo/content/index_blob_manager_v0.go @@ -543,7 +543,7 @@ func addIndexBlobsToBuilder(ctx context.Context, enc *encryptedBlobMgr, bld inde return errors.Wrapf(err, "error getting index %q", indexBlobID) } - ndx, err := index.Open(data.ToByteSlice(), nil, uint32(enc.crypterProvider.Crypter().Encryptor.Overhead())) + ndx, err := index.Open(data.ToByteSlice(), nil, uint32(enc.crypter.Encryptor().Overhead())) if err != nil { return errors.Wrapf(err, "unable to open index blob %q", indexBlobID) } diff --git a/repo/content/index_blob_manager_v0_test.go b/repo/content/index_blob_manager_v0_test.go index 131e8fece..d3ca1625e 100644 --- a/repo/content/index_blob_manager_v0_test.go +++ b/repo/content/index_blob_manager_v0_test.go @@ -794,11 +794,8 @@ func newIndexBlobManagerForTesting(t *testing.T, st blob.Storage, localTimeNow f enc: &encryptedBlobMgr{ st: st, indexBlobCache: nil, - crypterProvider: staticCrypterProvider{&Crypter{ - HashFunction: hf, - Encryptor: enc, - }}, - log: log, + crypter: staticCrypter{hf, enc}, + log: log, }, timeNow: localTimeNow, log: log, diff --git a/repo/content/index_blob_manager_v1.go b/repo/content/index_blob_manager_v1.go index 74f62929e..885fdbc76 100644 --- a/repo/content/index_blob_manager_v1.go +++ b/repo/content/index_blob_manager_v1.go @@ -89,7 +89,7 @@ func (m *indexBlobManagerV1) compactEpoch(ctx context.Context, blobIDs []blob.ID for _, data := range dataShards { data2.Reset() - blobID, err := m.enc.crypterProvider.Crypter().EncryptBLOB(data, outputPrefix, SessionID(sessionID), &data2) + blobID, err := EncryptBLOB(m.enc.crypter, data, outputPrefix, SessionID(sessionID), &data2) if err != nil { return errors.Wrap(err, "error encrypting") } @@ -113,7 +113,7 @@ func (m *indexBlobManagerV1) writeIndexBlobs(ctx context.Context, dataShards []g data2 := gather.NewWriteBuffer() defer data2.Close() //nolint:gocritic - unprefixedBlobID, err := m.enc.crypterProvider.Crypter().EncryptBLOB(data, "", sessionID, data2) + unprefixedBlobID, err := EncryptBLOB(m.enc.crypter, data, "", sessionID, data2) if err != nil { return nil, errors.Wrap(err, "error encrypting") } diff --git a/repo/content/internal_logger.go b/repo/content/internal_logger.go index 7931ceade..7a09cdaaf 100644 --- a/repo/content/internal_logger.go +++ b/repo/content/internal_logger.go @@ -33,7 +33,7 @@ type internalLogManager struct { ctx context.Context // nolint:containedctx st blob.Storage - bc CrypterProvider + bc crypter wg sync.WaitGroup timeFunc func() time.Time flushThreshold int @@ -48,7 +48,7 @@ func (m *internalLogManager) encryptAndWriteLogBlob(prefix blob.ID, data gather. encrypted := gather.NewWriteBuffer() // Close happens in a goroutine - blobID, err := m.bc.Crypter().EncryptBLOB(data, prefix, "", encrypted) + blobID, err := EncryptBLOB(m.bc, data, prefix, "", encrypted) if err != nil { encrypted.Close() @@ -208,7 +208,7 @@ func (l *internalLogger) Sync() error { } // newInternalLogManager creates a new blobLogManager that will emit logs as repository blobs with a given prefix. -func newInternalLogManager(ctx context.Context, st blob.Storage, bc CrypterProvider) *internalLogManager { +func newInternalLogManager(ctx context.Context, st blob.Storage, bc crypter) *internalLogManager { return &internalLogManager{ ctx: ctx, st: st, diff --git a/repo/content/sessions.go b/repo/content/sessions.go index 7c522f2f6..b7720dbfb 100644 --- a/repo/content/sessions.go +++ b/repo/content/sessions.go @@ -111,7 +111,7 @@ func (bm *WriteManager) writeSessionMarkerLocked(ctx context.Context) error { var encrypted gather.WriteBuffer defer encrypted.Close() - sessionBlobID, err := bm.format.Crypter().EncryptBLOB(gather.FromSlice(js), BlobIDPrefixSession, bm.currentSessionInfo.ID, &encrypted) + sessionBlobID, err := EncryptBLOB(bm.format, gather.FromSlice(js), BlobIDPrefixSession, bm.currentSessionInfo.ID, &encrypted) if err != nil { return errors.Wrap(err, "unable to encrypt session marker") } @@ -178,7 +178,7 @@ func (bm *WriteManager) ListActiveSessions(ctx context.Context) (map[SessionID]* return nil, errors.Wrapf(err, "error loading session: %v", b.BlobID) } - err = bm.format.Crypter().DecryptBLOB(payload.Bytes(), b.BlobID, &decrypted) + err = DecryptBLOB(bm.format, payload.Bytes(), b.BlobID, &decrypted) if err != nil { return nil, errors.Wrapf(err, "error decrypting session: %v", b.BlobID) } diff --git a/repo/repository.go b/repo/repository.go index 93a6601f4..ffedb67c4 100644 --- a/repo/repository.go +++ b/repo/repository.go @@ -58,7 +58,6 @@ type DirectRepository interface { BlobVolume() blob.Volume ContentReader() content.Reader IndexBlobs(ctx context.Context, includeInactive bool) ([]content.IndexBlobInfo, error) - Crypter() *content.Crypter NewDirectWriter(ctx context.Context, opt WriteSessionOptions) (context.Context, DirectRepositoryWriter, error) AlsoLogToContentLog(ctx context.Context) context.Context UniqueID() []byte @@ -147,11 +146,6 @@ func (r *directRepository) ConfigFilename() string { return r.configFile } -// Crypter returns a Crypter object. -func (r *directRepository) Crypter() *content.Crypter { - return r.sm.Crypter() -} - // NewObjectWriter creates an object writer. func (r *directRepository) NewObjectWriter(ctx context.Context, opt object.WriterOptions) object.Writer { return r.omgr.NewWriter(ctx, opt)