diff --git a/cli/command_repository_connect_server.go b/cli/command_repository_connect_server.go index f8d3ac09a..65a047d00 100644 --- a/cli/command_repository_connect_server.go +++ b/cli/command_repository_connect_server.go @@ -6,6 +6,7 @@ "github.com/pkg/errors" + "github.com/kopia/kopia/internal/crypto" "github.com/kopia/kopia/internal/passwordpersist" "github.com/kopia/kopia/repo" ) @@ -13,8 +14,9 @@ type commandRepositoryConnectServer struct { co *connectOptions - connectAPIServerURL string - connectAPIServerCertFingerprint string + connectAPIServerURL string + connectAPIServerCertFingerprint string + connectAPIServerLocalCacheKeyDerivationAlgorithm string svc advancedAppServices out textOutput @@ -28,13 +30,21 @@ func (c *commandRepositoryConnectServer) setup(svc advancedAppServices, parent c cmd := parent.Command("server", "Connect to a repository API Server.") cmd.Flag("url", "Server URL").Required().StringVar(&c.connectAPIServerURL) cmd.Flag("server-cert-fingerprint", "Server certificate fingerprint").StringVar(&c.connectAPIServerCertFingerprint) + //nolint:lll + cmd.Flag("local-cache-key-derivation-algorithm", "Key derivation algorithm used to derive the local cache encryption key").Hidden().Default(repo.DefaultKeyDerivationAlgorithm).EnumVar(&c.connectAPIServerLocalCacheKeyDerivationAlgorithm, crypto.AllowedKeyDerivationAlgorithms()...) cmd.Action(svc.noRepositoryAction(c.run)) } func (c *commandRepositoryConnectServer) run(ctx context.Context) error { + localCacheKeyDerivationAlgorithm := c.connectAPIServerLocalCacheKeyDerivationAlgorithm + if localCacheKeyDerivationAlgorithm == "" { + localCacheKeyDerivationAlgorithm = repo.DefaultKeyDerivationAlgorithm + } + as := &repo.APIServerInfo{ BaseURL: strings.TrimSuffix(c.connectAPIServerURL, "/"), TrustedServerCertificateFingerprint: strings.ToLower(c.connectAPIServerCertFingerprint), + LocalCacheKeyDerivationAlgorithm: localCacheKeyDerivationAlgorithm, } configFile := c.svc.repositoryConfigFileName() diff --git a/internal/crypto/key_derivation_nontest.go b/internal/crypto/key_derivation_nontest.go index e8b8c7659..ce546eacd 100644 --- a/internal/crypto/key_derivation_nontest.go +++ b/internal/crypto/key_derivation_nontest.go @@ -39,7 +39,7 @@ func RegisterKeyDerivers(name string, keyDeriver KeyDeriver) { func DeriveKeyFromPassword(password string, salt []byte, algorithm string) ([]byte, error) { kd, ok := keyDerivers[algorithm] if !ok { - return nil, errors.Errorf("unsupported key algorithm: %v, supported algorithms %v", algorithm, AllowedKeyDerivationAlgorithms()) + return nil, errors.Errorf("unsupported key derivation algorithm: %v, supported algorithms %v", algorithm, AllowedKeyDerivationAlgorithms()) } //nolint:wrapcheck @@ -50,7 +50,7 @@ func DeriveKeyFromPassword(password string, salt []byte, algorithm string) ([]by func RecommendedSaltLength(algorithm string) (int, error) { kd, ok := keyDerivers[algorithm] if !ok { - return 0, errors.Errorf("unsupported key algorithm: %v, supported algorithms %v", algorithm, AllowedKeyDerivationAlgorithms()) + return 0, errors.Errorf("failed to get salt length for unsupported key derivation algorithm: %v, supported algorithms %v", algorithm, AllowedKeyDerivationAlgorithms()) } return kd.RecommendedSaltLength(), nil diff --git a/internal/servertesting/servertesting.go b/internal/servertesting/servertesting.go index 875118e0f..0fa211835 100644 --- a/internal/servertesting/servertesting.go +++ b/internal/servertesting/servertesting.go @@ -79,6 +79,8 @@ func StartServer(t *testing.T, env *repotesting.Environment, tls bool) *repo.API asi.BaseURL = hs.URL } + asi.LocalCacheKeyDerivationAlgorithm = repo.DefaultKeyDerivationAlgorithm + t.Cleanup(hs.Close) return asi diff --git a/repo/api_server_repository.go b/repo/api_server_repository.go index 0ffa5313b..13207e613 100644 --- a/repo/api_server_repository.go +++ b/repo/api_server_repository.go @@ -10,6 +10,7 @@ type APIServerInfo struct { BaseURL string `json:"url"` TrustedServerCertificateFingerprint string `json:"serverCertFingerprint"` + LocalCacheKeyDerivationAlgorithm string `json:"localCacheKeyDerivationAlgorithm,omitempty"` } // ConnectAPIServer sets up repository connection to a particular API server. diff --git a/repo/open.go b/repo/open.go index b10a50f5e..577250ffd 100644 --- a/repo/open.go +++ b/repo/open.go @@ -58,6 +58,9 @@ // localCacheIntegrityHMACSecretLength length of HMAC secret protecting local cache items. const localCacheIntegrityHMACSecretLength = 16 +// DefaultKeyDerivationAlgorithm is the default key derivation algorithm used to derive a cache encryption key. +const DefaultKeyDerivationAlgorithm = crypto.DefaultKeyDerivationAlgorithm + //nolint:gochecknoglobals var localCacheIntegrityPurpose = []byte("local-cache-integrity") @@ -131,7 +134,7 @@ func Open(ctx context.Context, configFile, password string, options *Options) (r return openDirect(ctx, configFile, lc, password, options) } -func getContentCacheOrNil(ctx context.Context, opt *content.CachingOptions, password string, mr *metrics.Registry, timeNow func() time.Time) (*cache.PersistentCache, error) { +func getContentCacheOrNil(ctx context.Context, si *APIServerInfo, opt *content.CachingOptions, password string, mr *metrics.Registry, timeNow func() time.Time) (*cache.PersistentCache, error) { opt = opt.CloneOrDefault() cs, err := cache.NewStorageOrNil(ctx, opt.CacheDirectory, opt.ContentCacheSizeBytes, "server-contents") @@ -143,7 +146,7 @@ func getContentCacheOrNil(ctx context.Context, opt *content.CachingOptions, pass // derive content cache key from the password & HMAC secret saltWithPurpose := append([]byte("content-cache-protection"), opt.HMACSecret...) - cacheEncryptionKey, err := crypto.DeriveKeyFromPassword(password, saltWithPurpose, crypto.DefaultKeyDerivationAlgorithm) + cacheEncryptionKey, err := crypto.DeriveKeyFromPassword(password, saltWithPurpose, si.LocalCacheKeyDerivationAlgorithm) if err != nil { return nil, errors.Wrap(err, "unable to derive cache encryption key from password") } @@ -171,7 +174,7 @@ func openAPIServer(ctx context.Context, si *APIServerInfo, cliOpts ClientOptions mr := metrics.NewRegistry() - contentCache, err := getContentCacheOrNil(ctx, cachingOptions, password, mr, options.TimeNowFunc) + contentCache, err := getContentCacheOrNil(ctx, si, cachingOptions, password, mr, options.TimeNowFunc) if err != nil { return nil, errors.Wrap(err, "error opening content cache") } diff --git a/tests/end_to_end_test/api_server_repository_test.go b/tests/end_to_end_test/api_server_repository_test.go index 1b20a5539..0d2ac1e83 100644 --- a/tests/end_to_end_test/api_server_repository_test.go +++ b/tests/end_to_end_test/api_server_repository_test.go @@ -136,6 +136,7 @@ func testAPIServerRepository(t *testing.T, allowRepositoryUsers bool) { rep, err := servertesting.ConnectAndOpenAPIServer(t, ctx2, &repo.APIServerInfo{ BaseURL: sp.BaseURL, TrustedServerCertificateFingerprint: sp.SHA256Fingerprint, + LocalCacheKeyDerivationAlgorithm: repo.DefaultKeyDerivationAlgorithm, }, repo.ClientOptions{ Username: "foo", Hostname: "bar", @@ -258,6 +259,7 @@ func testAPIServerRepository(t *testing.T, allowRepositoryUsers bool) { servertesting.ConnectAndOpenAPIServer(t, ctx, &repo.APIServerInfo{ BaseURL: sp.BaseURL, TrustedServerCertificateFingerprint: sp.SHA256Fingerprint, + LocalCacheKeyDerivationAlgorithm: repo.DefaultKeyDerivationAlgorithm, }, repo.ClientOptions{ Username: "foo", Hostname: "bar", @@ -328,6 +330,7 @@ func TestFindManifestsPaginationOverGRPC(t *testing.T) { rep, err := servertesting.ConnectAndOpenAPIServer(t, ctx, &repo.APIServerInfo{ BaseURL: sp.BaseURL, TrustedServerCertificateFingerprint: sp.SHA256Fingerprint, + LocalCacheKeyDerivationAlgorithm: repo.DefaultKeyDerivationAlgorithm, }, repo.ClientOptions{ Username: "foo", Hostname: "bar",