From f7a1fb37b086ebec3af6e40b2babd4de8545d0f7 Mon Sep 17 00:00:00 2001 From: Jarek Kowalski Date: Sun, 15 Oct 2017 09:00:53 -0700 Subject: [PATCH] added auth.KeyManager --- auth/key_manager.go | 32 +++++++++++++++++++++++++++++++ repo/connection.go | 23 ++++++++++++++++------ repo/initialize.go | 2 +- repo/metadata_manager.go | 36 ++++------------------------------- repo/metadata_manager_test.go | 9 --------- repo/repository.go | 10 ++++++---- snapshot/manager.go | 4 ++-- 7 files changed, 62 insertions(+), 54 deletions(-) create mode 100644 auth/key_manager.go diff --git a/auth/key_manager.go b/auth/key_manager.go new file mode 100644 index 000000000..265929fce --- /dev/null +++ b/auth/key_manager.go @@ -0,0 +1,32 @@ +package auth + +import ( + "crypto/sha256" + "io" + + "golang.org/x/crypto/hkdf" +) + +// KeyManager manages key derivation from a master key. +type KeyManager struct { + masterKey []byte + uniqueID []byte +} + +// DeriveKey computes a key for a specific purpose and length using HKDF based on the master key. +func (km *KeyManager) DeriveKey(purpose []byte, length int) []byte { + key := make([]byte, length) + k := hkdf.New(sha256.New, km.masterKey, km.uniqueID, purpose) + io.ReadFull(k, key) + return key +} + +// NewKeyManager constructs a KeyManager with given credentials and options. +func NewKeyManager(creds Credentials, so SecurityOptions) (*KeyManager, error) { + mk, err := creds.GetMasterKey(so) + if err != nil { + return nil, err + } + + return &KeyManager{mk, so.UniqueID}, nil +} diff --git a/repo/connection.go b/repo/connection.go index f1a327779..f483b7905 100644 --- a/repo/connection.go +++ b/repo/connection.go @@ -90,9 +90,19 @@ func Connect(ctx context.Context, configFile string, st storage.Storage, creds a return err } - cfg, err := r.Metadata.connectionConfiguration() + cip, ok := st.(storage.ConnectionInfoProvider) + if !ok { + return errors.New("repository does not support persisting configuration") + } + + masterKey, err := creds.GetMasterKey(r.Metadata.format.SecurityOptions) if err != nil { - return err + return fmt.Errorf("can't get master key: %v", err) + } + + cfg := &config.RepositoryConnectionInfo{ + ConnectionInfo: cip.ConnectionInfo(), + Key: masterKey, } var lc config.LocalConfig @@ -145,10 +155,11 @@ func connect(ctx context.Context, st storage.Storage, creds auth.Credentials, op } return &Repository{ - Blocks: bm, - Objects: om, - Metadata: mm, - Storage: st, + Blocks: bm, + Objects: om, + Metadata: mm, + Storage: st, + KeyManager: mm.keyManager, }, nil } diff --git a/repo/initialize.go b/repo/initialize.go index a29a86a7f..10db38c88 100644 --- a/repo/initialize.go +++ b/repo/initialize.go @@ -51,7 +51,7 @@ func Initialize(st storage.Storage, opt *NewRepositoryOptions, creds auth.Creden format: metadataFormatFromOptions(opt), } - mm.masterKey, err = creds.GetMasterKey(mm.format.SecurityOptions) + mm.keyManager, err = auth.NewKeyManager(creds, mm.format.SecurityOptions) if err != nil { return err } diff --git a/repo/metadata_manager.go b/repo/metadata_manager.go index 02a9a179f..79f35c882 100644 --- a/repo/metadata_manager.go +++ b/repo/metadata_manager.go @@ -4,7 +4,6 @@ import ( "crypto/aes" "crypto/cipher" "crypto/rand" - "crypto/sha256" "encoding/json" "errors" "fmt" @@ -14,8 +13,6 @@ import ( "github.com/kopia/kopia/auth" "github.com/kopia/kopia/internal/config" "github.com/kopia/kopia/storage" - - "golang.org/x/crypto/hkdf" ) const ( @@ -58,8 +55,7 @@ type MetadataManager struct { cache *metadataCache format config.MetadataFormat repoConfig config.EncryptedRepositoryConfig - - masterKey []byte + keyManager *auth.KeyManager aead cipher.AEAD // authenticated encryption to use authData []byte // additional data to authenticate @@ -121,14 +117,6 @@ func (mm *MetadataManager) decryptBlock(content []byte) ([]byte, error) { return content, nil } -// DeriveKey computes a key for a specific purpose and length using HKDF based on the master key. -func (mm *MetadataManager) DeriveKey(purpose []byte, length int) []byte { - key := make([]byte, length) - k := hkdf.New(sha256.New, mm.masterKey, mm.format.UniqueID, purpose) - io.ReadFull(k, key) - return key -} - // GetMetadata returns the contents of a specified metadata item. func (mm *MetadataManager) GetMetadata(itemID string) ([]byte, error) { if err := checkReservedName(itemID); err != nil { @@ -219,22 +207,6 @@ func (mm *MetadataManager) ListContents(prefix string) (map[string][]byte, error return mm.MultiGet(itemIDs) } -// Config returns a configuration of storage its credentials that's suitable -// for storing in configuration file. -func (mm *MetadataManager) connectionConfiguration() (*config.RepositoryConnectionInfo, error) { - cip, ok := mm.storage.(storage.ConnectionInfoProvider) - if !ok { - return nil, errors.New("repository does not support persisting configuration") - } - - ci := cip.ConnectionInfo() - - return &config.RepositoryConnectionInfo{ - ConnectionInfo: ci, - Key: mm.masterKey, - }, nil -} - // Remove removes the specified metadata item. func (mm *MetadataManager) Remove(itemID string) error { if err := checkReservedName(itemID); err != nil { @@ -310,7 +282,7 @@ func newMetadataManager(st storage.Storage, creds auth.Credentials) (*MetadataMa return nil, err } - mm.masterKey, err = creds.GetMasterKey(mm.format.SecurityOptions) + mm.keyManager, err = auth.NewKeyManager(creds, mm.format.SecurityOptions) if err != nil { return nil, err } @@ -340,8 +312,8 @@ func (mm *MetadataManager) initCrypto() error { case "NONE": // do nothing return nil case "AES256_GCM": - aesKey := mm.DeriveKey(purposeAESKey, 32) - mm.authData = mm.DeriveKey(purposeAuthData, 32) + aesKey := mm.keyManager.DeriveKey(purposeAESKey, 32) + mm.authData = mm.keyManager.DeriveKey(purposeAuthData, 32) blk, err := aes.NewCipher(aesKey) if err != nil { diff --git a/repo/metadata_manager_test.go b/repo/metadata_manager_test.go index c6e4d96b7..3b69274c7 100644 --- a/repo/metadata_manager_test.go +++ b/repo/metadata_manager_test.go @@ -47,15 +47,6 @@ func TestMetadataManager(t *testing.T) { return } - cfg, err := v.connectionConfiguration() - if err != nil { - t.Errorf("error getting token1 %v", err) - } - - if got, want := cfg.ConnectionInfo.Type, "filesystem"; got != want { - t.Errorf("invalid connection type: %q, want %q", got, want) - } - _, err = newMetadataManager(st, otherCreds) if err == nil { t.Errorf("unexpectedly opened repository with invalid credentials") diff --git a/repo/repository.go b/repo/repository.go index 3d5c906b9..36b5ad447 100644 --- a/repo/repository.go +++ b/repo/repository.go @@ -4,6 +4,7 @@ import ( "encoding/hex" "fmt" + "github.com/kopia/kopia/auth" "github.com/kopia/kopia/block" "github.com/kopia/kopia/object" "github.com/kopia/kopia/storage" @@ -11,10 +12,11 @@ import ( // Repository represents storage where both content-addressable and user-addressable data is kept. type Repository struct { - Blocks *block.Manager - Objects *object.Manager - Metadata *MetadataManager - Storage storage.Storage + Blocks *block.Manager + Objects *object.Manager + Metadata *MetadataManager + Storage storage.Storage + KeyManager *auth.KeyManager ConfigFile string CacheDirectory string diff --git a/snapshot/manager.go b/snapshot/manager.go index cb59f6bbc..fbbcb94e0 100644 --- a/snapshot/manager.go +++ b/snapshot/manager.go @@ -311,7 +311,7 @@ func (m *Manager) ListPolicies() ([]*Policy, error) { func NewManager(r *repo.Repository) *Manager { return &Manager{ r, - r.Metadata.DeriveKey([]byte("snapshot-id"), 32), - r.Metadata.DeriveKey([]byte("policyID-id"), 32), + r.KeyManager.DeriveKey([]byte("snapshot-id"), 32), + r.KeyManager.DeriveKey([]byte("policyID-id"), 32), } }