fix(repository): fixed V1 key derivation bug from previous refactoring (#2286)

See 23299c3451
This commit is contained in:
Jarek Kowalski
2022-08-08 21:45:08 -07:00
committed by GitHub
parent 5fce956853
commit 419c7acb11
4 changed files with 86 additions and 18 deletions

View File

@@ -20,7 +20,8 @@
"github.com/kopia/kopia/snapshot"
)
const defaultPassword = "foobarbazfoobarbaz"
// DefaultPasswordForTesting is the default password to use for all testing repositories.
const DefaultPasswordForTesting = "foobarbazfoobarbaz"
// Environment encapsulates details of a test environment.
type Environment struct {
@@ -91,7 +92,7 @@ func (e *Environment) setup(tb testing.TB, version format.Version, opts ...Optio
e.st = st
if e.Password == "" {
e.Password = defaultPassword
e.Password = DefaultPasswordForTesting
}
if err := repo.Initialize(ctx, st, opt, e.Password); err != nil {

View File

@@ -62,12 +62,7 @@ func (s *Schedule) ReportRun(taskType TaskType, info RunInfo) {
}
func getAES256GCM(rep repo.DirectRepository) (cipher.AEAD, error) {
key, err := rep.DeriveKey(maintenanceScheduleKeyPurpose, maintenanceScheduleKeySize)
if err != nil {
return nil, errors.Wrap(err, "derive key")
}
c, err := aes.NewCipher(key)
c, err := aes.NewCipher(rep.DeriveKey(maintenanceScheduleKeyPurpose, maintenanceScheduleKeySize))
if err != nil {
return nil, errors.Wrap(err, "unable to create AES-256 cipher")
}

View File

@@ -63,7 +63,7 @@ type DirectRepository interface {
AlsoLogToContentLog(ctx context.Context) context.Context
UniqueID() []byte
ConfigFilename() string
DeriveKey(purpose []byte, keyLength int) ([]byte, error)
DeriveKey(purpose []byte, keyLength int) []byte
Token(password string) (string, error)
Throttler() throttling.SettableThrottler
RequiredFeatures() ([]feature.Required, error)
@@ -111,20 +111,15 @@ type directRepository struct {
}
// DeriveKey derives encryption key of the provided length from the master key.
func (r *directRepository) DeriveKey(purpose []byte, keyLength int) ([]byte, error) {
mp, mperr := r.cmgr.ContentFormat().GetMutableParameters()
if mperr != nil {
return nil, errors.Wrap(mperr, "mutable parameters")
}
if mp.Version >= format.FormatVersion2 {
return format.DeriveKeyFromMasterKey(r.cmgr.ContentFormat().GetMasterKey(), r.uniqueID, purpose, keyLength), nil
func (r *directRepository) DeriveKey(purpose []byte, keyLength int) []byte {
if r.cmgr.ContentFormat().SupportsPasswordChange() {
return format.DeriveKeyFromMasterKey(r.cmgr.ContentFormat().GetMasterKey(), r.uniqueID, purpose, keyLength)
}
// version of kopia <v0.9 had a bug where certain keys were derived directly from
// the password and not from the random master key. This made it impossible to change
// password.
return format.DeriveKeyFromMasterKey(r.formatEncryptionKey, r.uniqueID, purpose, keyLength), nil
return format.DeriveKeyFromMasterKey(r.formatEncryptionKey, r.uniqueID, purpose, keyLength)
}
// ClientOptions returns client options.

View File

@@ -647,6 +647,83 @@ func (s *formatSpecificTestSuite) TestChangePassword(t *testing.T) {
}
}
func TestDeriveKey(t *testing.T) {
testPurpose := []byte{0, 0, 0, 0}
testKeyLength := 8
masterKey := []byte("01234567890123456789012345678901")
uniqueID := []byte("a5ba5d2da4b14b518b9501b64b5d87ca")
j := format.KopiaRepositoryJSON{
UniqueID: uniqueID,
KeyDerivationAlgorithm: format.DefaultKeyDerivationAlgorithm,
}
formatEncryptionKeyFromPassword, err := j.DeriveFormatEncryptionKeyFromPassword(repotesting.DefaultPasswordForTesting)
require.NoError(t, err)
validV1KeyDerivedFromPassword := format.DeriveKeyFromMasterKey(formatEncryptionKeyFromPassword, uniqueID, testPurpose, testKeyLength)
validV2KeyDerivedFromMasterKey := format.DeriveKeyFromMasterKey(masterKey, uniqueID, testPurpose, testKeyLength)
setup := func(v format.Version) repo.DirectRepositoryWriter {
_, env := repotesting.NewEnvironment(t, v, repotesting.Options{
NewRepositoryOptions: func(nro *repo.NewRepositoryOptions) {
nro.BlockFormat.MasterKey = masterKey
nro.UniqueID = uniqueID
},
})
return env.RepositoryWriter
}
setupUpgraded := func(v1, v2 format.Version) repo.DirectRepositoryWriter {
ctx, env := repotesting.NewEnvironment(t, v1, repotesting.Options{
NewRepositoryOptions: func(nro *repo.NewRepositoryOptions) {
// do not set nro.BlockFormat.MasterKey
nro.UniqueID = uniqueID
},
})
// prepare upgrade
dw1Upgraded := env.Repository.(repo.DirectRepositoryWriter)
cf := dw1Upgraded.ContentReader().ContentFormat()
mp, mperr := cf.GetMutableParameters()
require.NoError(t, mperr)
feat, err := dw1Upgraded.RequiredFeatures()
require.NoError(t, err)
// perform upgrade
mp.Version = v2
require.NoError(t, dw1Upgraded.SetParameters(ctx, mp, dw1Upgraded.BlobCfg(), feat))
return env.MustConnectOpenAnother(t).(repo.DirectRepositoryWriter)
}
// we verify that repositories started on V1 will continue to derive keys from
// password (which can't be changed) and not from the master key.
cases := []struct {
desc string
dw repo.DirectRepositoryWriter
wantFormat format.Version
wantKey []byte
}{
{"v1", setup(format.FormatVersion1), format.FormatVersion1, validV1KeyDerivedFromPassword},
{"v1-v2", setupUpgraded(format.FormatVersion1, format.FormatVersion2), format.FormatVersion2, validV1KeyDerivedFromPassword},
{"v1-v3", setupUpgraded(format.FormatVersion1, format.FormatVersion3), format.FormatVersion3, validV1KeyDerivedFromPassword},
{"v2", setup(format.FormatVersion2), format.FormatVersion2, validV2KeyDerivedFromMasterKey},
{"v3", setup(format.FormatVersion3), format.FormatVersion3, validV2KeyDerivedFromMasterKey},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
require.Equal(t, tc.wantFormat, tc.dw.ContentReader().ContentFormat().Struct().Version)
require.Equal(t, tc.wantKey, tc.dw.DeriveKey(testPurpose, testKeyLength))
})
}
}
func verifyNotFound(ctx context.Context, t *testing.T, rep repo.Repository, objectID object.ID, testCaseID string) {
t.Helper()