diff --git a/models/admin/task.go b/models/admin/task.go index 5ba9e503f7..1431e5ed70 100644 --- a/models/admin/task.go +++ b/models/admin/task.go @@ -120,7 +120,7 @@ func (task *Task) MigrateConfig() (*migration.MigrateOptions, error) { return nil, err } - key := keying.DeriveKey(keying.ContextMigrateTask) + key := keying.MigrateTask // decrypt credentials if opts.CloneAddrEncrypted != "" { diff --git a/models/auth/twofactor.go b/models/auth/twofactor.go index 5c9cf45f97..652e1db924 100644 --- a/models/auth/twofactor.go +++ b/models/auth/twofactor.go @@ -87,14 +87,12 @@ func (t *TwoFactor) VerifyScratchToken(token string) bool { // SetSecret sets the 2FA secret. func (t *TwoFactor) SetSecret(secretString string) { - key := keying.DeriveKey(keying.ContextTOTP) - t.Secret = key.Encrypt([]byte(secretString), keying.ColumnAndID("secret", t.ID)) + t.Secret = keying.TOTP.Encrypt([]byte(secretString), keying.ColumnAndID("secret", t.ID)) } // ValidateTOTP validates the provided passcode. func (t *TwoFactor) ValidateTOTP(passcode string) (bool, error) { - key := keying.DeriveKey(keying.ContextTOTP) - secret, err := key.Decrypt(t.Secret, keying.ColumnAndID("secret", t.ID)) + secret, err := keying.TOTP.Decrypt(t.Secret, keying.ColumnAndID("secret", t.ID)) if err != nil { return false, err } diff --git a/models/forgejo_migrations/v14a_migrate_task_secrets.go b/models/forgejo_migrations/v14a_migrate_task_secrets.go index 98015d53b9..a177dff92a 100644 --- a/models/forgejo_migrations/v14a_migrate_task_secrets.go +++ b/models/forgejo_migrations/v14a_migrate_task_secrets.go @@ -33,7 +33,7 @@ func migrateTaskSecrets(x *xorm.Engine) error { return db.WithTx(db.DefaultContext, func(ctx context.Context) error { sess := db.GetEngine(ctx) - key := keying.DeriveKey(keying.ContextMigrateTask) + key := keying.MigrateTask oldEncryptionKey := setting.SecretKey messages := make([]string, 0, 100) diff --git a/models/forgejo_migrations/v14a_migrate_task_secrets_test.go b/models/forgejo_migrations/v14a_migrate_task_secrets_test.go index 58bc08502a..c349c80f21 100644 --- a/models/forgejo_migrations/v14a_migrate_task_secrets_test.go +++ b/models/forgejo_migrations/v14a_migrate_task_secrets_test.go @@ -56,7 +56,7 @@ func Test_MigrateTaskSecretsToKeying(t *testing.T) { var opts migration.MigrateOptions require.NoError(t, json.Unmarshal([]byte(task.PayloadContent), &opts)) - key := keying.DeriveKey(keying.ContextMigrateTask) + key := keying.MigrateTask encryptedCloneAddr, err := base64.RawStdEncoding.DecodeString(opts.CloneAddrEncrypted) require.NoError(t, err) diff --git a/models/forgejo_migrations_legacy/v25_test.go b/models/forgejo_migrations_legacy/v25_test.go index 86286b2ab1..bd796b1241 100644 --- a/models/forgejo_migrations_legacy/v25_test.go +++ b/models/forgejo_migrations_legacy/v25_test.go @@ -48,7 +48,7 @@ func Test_MigrateTwoFactorToKeying(t *testing.T) { _, err = x.Table("two_factor").ID(1).Get(&twofactor) require.NoError(t, err) - secretBytes, err := keying.DeriveKey(keying.ContextTOTP).Decrypt(twofactor.Secret, keying.ColumnAndID("secret", twofactor.ID)) + secretBytes, err := keying.TOTP.Decrypt(twofactor.Secret, keying.ColumnAndID("secret", twofactor.ID)) require.NoError(t, err) assert.Equal(t, []byte("AVDYS32OPIAYSNBG2NKYV4AHBVEMKKKIGBQ46OXTLMJO664G4TIECOGEANMSNBLS"), secretBytes) } diff --git a/models/forgejo_migrations_legacy/v39_test.go b/models/forgejo_migrations_legacy/v39_test.go index 6c82f7180f..d2aa9c7364 100644 --- a/models/forgejo_migrations_legacy/v39_test.go +++ b/models/forgejo_migrations_legacy/v39_test.go @@ -46,7 +46,7 @@ func Test_MigrateActionSecretToKeying(t *testing.T) { _, err = x.Table("secret").ID(1).Get(&secret) require.NoError(t, err) - secretBytes, err := keying.DeriveKey(keying.ContextActionSecret).Decrypt(secret.Data, keying.ColumnAndID("data", secret.ID)) + secretBytes, err := keying.ActionSecret.Decrypt(secret.Data, keying.ColumnAndID("data", secret.ID)) require.NoError(t, err) assert.Equal(t, []byte("A deep dark secret"), secretBytes) } diff --git a/models/repo/pushmirror.go b/models/repo/pushmirror.go index e57897fb7e..909fa22d6c 100644 --- a/models/repo/pushmirror.go +++ b/models/repo/pushmirror.go @@ -98,8 +98,7 @@ func (m *PushMirror) GetPublicKey() string { // The ID of the push mirror must be known, so this should be done after the // push mirror is inserted. func (m *PushMirror) SetPrivatekey(ctx context.Context, privateKey []byte) error { - key := keying.DeriveKey(keying.ContextPushMirror) - m.PrivateKey = key.Encrypt(privateKey, keying.ColumnAndID("private_key", m.ID)) + m.PrivateKey = keying.PushMirror.Encrypt(privateKey, keying.ColumnAndID("private_key", m.ID)) _, err := db.GetEngine(ctx).ID(m.ID).Cols("private_key").Update(m) return err @@ -107,8 +106,7 @@ func (m *PushMirror) SetPrivatekey(ctx context.Context, privateKey []byte) error // Privatekey retrieves the encrypted private key and decrypts it. func (m *PushMirror) Privatekey() ([]byte, error) { - key := keying.DeriveKey(keying.ContextPushMirror) - return key.Decrypt(m.PrivateKey, keying.ColumnAndID("private_key", m.ID)) + return keying.PushMirror.Decrypt(m.PrivateKey, keying.ColumnAndID("private_key", m.ID)) } // UpdatePushMirror updates the push-mirror diff --git a/models/secret/secret.go b/models/secret/secret.go index 6f6867db52..0a5db17690 100644 --- a/models/secret/secret.go +++ b/models/secret/secret.go @@ -117,8 +117,7 @@ func (opts FindSecretsOptions) ToConds() builder.Cond { } func (s *Secret) SetSecret(data string) { - key := keying.DeriveKey(keying.ContextActionSecret) - s.Data = key.Encrypt([]byte(data), keying.ColumnAndID("data", s.ID)) + s.Data = keying.ActionSecret.Encrypt([]byte(data), keying.ColumnAndID("data", s.ID)) } func GetSecretsOfTask(ctx context.Context, task *actions_model.ActionTask) (map[string]string, error) { @@ -146,7 +145,7 @@ func GetSecretsOfTask(ctx context.Context, task *actions_model.ActionTask) (map[ return nil, err } - key := keying.DeriveKey(keying.ContextActionSecret) + key := keying.ActionSecret for _, secret := range append(ownerSecrets, repoSecrets...) { v, err := key.Decrypt(secret.Data, keying.ColumnAndID("data", secret.ID)) if err != nil { diff --git a/models/secret/secret_test.go b/models/secret/secret_test.go index 15142d207b..76b673f2fe 100644 --- a/models/secret/secret_test.go +++ b/models/secret/secret_test.go @@ -25,7 +25,7 @@ func TestInsertEncryptedSecret(t *testing.T) { assert.Nil(t, secret) }) - key := keying.DeriveKey(keying.ContextActionSecret) + key := keying.ActionSecret t.Run("Insert repository secret", func(t *testing.T) { secret, err := InsertEncryptedSecret(t.Context(), 0, 1, "REPO_SECRET", "some repository secret") diff --git a/modules/keying/keying.go b/modules/keying/keying.go index ff73d1995a..0d7eecb904 100644 --- a/modules/keying/keying.go +++ b/modules/keying/keying.go @@ -16,71 +16,90 @@ package keying import ( + "bytes" + "crypto/cipher" "crypto/hkdf" + crand "crypto/rand" "crypto/sha256" "encoding/binary" - - "forgejo.org/modules/util" + "errors" + "sync" + "sync/atomic" "golang.org/x/crypto/chacha20poly1305" ) +// Specifies the context for which a subkey should be derived for. +var ( + // Used for the `push_mirror` table. + PushMirror = deriveKey("pushmirror") + // Used for the `two_factor` table. + TOTP = deriveKey("totp") + // Used for the `secret` table. + ActionSecret = deriveKey("action_secret") + // Used for the `task` table where type == TaskTypeMigrateRepo. + MigrateTask = deriveKey("migrate_repo_task") +) + var ( // The hash used for HKDF. hash = sha256.New // The AEAD used for encryption/decryption. aead = chacha20poly1305.NewX // The pseudorandom key generated by HKDF-Extract. - prk []byte + prk atomic.Value ) +// Set the main IKM for this module. +func Init(ikm []byte) { + // Salt is intentionally left empty, it's not useful to Forgejo's use case. + buf, err := hkdf.Extract(hash, ikm, nil) + if err != nil { + panic(err) + } + if ok := prk.CompareAndSwap(nil, buf); ok { + return + } + // prk was already set + old := prk.Load().([]byte) + if bytes.Equal(old, buf) { + return + } + panic("main IKM cannot be updated at runtime") +} + const ( aeadKeySize = chacha20poly1305.KeySize aeadNonceSize = chacha20poly1305.NonceSizeX ) -// Set the main IKM for this module. -func Init(ikm []byte) { - // Salt is intentionally left empty, it's not useful to Forgejo's use case. - var err error - prk, err = hkdf.Extract(hash, ikm, nil) - if err != nil { - panic(err) - } -} - -// Specifies the context for which a subkey should be derived for. -// This must be a hardcoded string and must not be arbitrarily constructed. -type Context string - -var ( - // Used for the `push_mirror` table. - ContextPushMirror Context = "pushmirror" - // Used for the `two_factor` table. - ContextTOTP Context = "totp" - // Used for the `secret` table. - ContextActionSecret Context = "action_secret" - // Used for the `task` table where type == TaskTypeMigrateRepo. - ContextMigrateTask Context = "migrate_repo_task" -) - // Derive *the* key for a given context, this is a deterministic function. // The same key will be provided for the same context. -func DeriveKey(context Context) *Key { +func deriveKey(context string) Context { + // wrap another sync.Once to prevent panic on initialization (prk would be nil) + return Context{sync.OnceValue(func() cipher.AEAD { + return expandPRK(prk.Load().([]byte), context) + })} +} + +func expandPRK(prk []byte, context string) cipher.AEAD { if len(prk) != sha256.Size { panic("keying: not initialized") } - key, err := hkdf.Expand(hash, prk, string(context), aeadKeySize) + key, err := hkdf.Expand(hash, prk, context, aeadKeySize) if err != nil { panic(err) } - - return &Key{key} + e, err := aead(key) + if err != nil { + panic(err) + } + return e } -type Key struct { - key []byte +type Context struct { + aead func() cipher.AEAD } // Encrypts the specified plaintext with some additional data that is tied to @@ -92,36 +111,25 @@ type Key struct { // the ID and database column name as additional data. The additional data isn't // appended to the ciphertext and may be publicly known, it must be available // when decryping the ciphertext. -func (k *Key) Encrypt(plaintext, additionalData []byte) []byte { - // Construct a new AEAD with the key. - e, err := aead(k.key) - if err != nil { - panic(err) - } - - // Generate a random nonce. - nonce := util.CryptoRandomBytes(aeadNonceSize) +func (k Context) Encrypt(plaintext, additionalData []byte) []byte { + nonce := make([]byte, aeadNonceSize) + _, _ = crand.Read(nonce) // never returns an error // Returns the ciphertext of this plaintext. - return e.Seal(nonce, nonce, plaintext, additionalData) + return k.aead().Seal(nonce, nonce, plaintext, additionalData) } // Decrypts the ciphertext and authenticates it against the given additional // data that was given when it was encrypted. It returns an error if the // authentication failed. -func (k *Key) Decrypt(ciphertext, additionalData []byte) ([]byte, error) { +func (k Context) Decrypt(ciphertext, additionalData []byte) ([]byte, error) { if len(ciphertext) <= aeadNonceSize { - panic("keying: ciphertext is too short") - } - - e, err := aead(k.key) - if err != nil { - panic(err) + return nil, errors.New("keying: ciphertext is too short") } nonce, ciphertext := ciphertext[:aeadNonceSize], ciphertext[aeadNonceSize:] - return e.Open(nil, nonce, ciphertext, additionalData) + return k.aead().Open(nil, nonce, ciphertext, additionalData) } // ColumnAndID generates a context that can be used as additional context for diff --git a/modules/keying/keying_test.go b/modules/keying/keying_test.go index 2c05abb186..cd812b5647 100644 --- a/modules/keying/keying_test.go +++ b/modules/keying/keying_test.go @@ -1,33 +1,39 @@ // Copyright 2024 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: MIT -package keying_test +package keying import ( + "crypto/cipher" + "crypto/hkdf" + "encoding/base64" "math" + "sync" "testing" - "forgejo.org/modules/keying" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "golang.org/x/crypto/chacha20poly1305" ) func TestKeying(t *testing.T) { - t.Run("Not initialized", func(t *testing.T) { - assert.Panics(t, func() { - keying.DeriveKey(keying.Context("TESTING")) + t.Run("Initialization", func(t *testing.T) { + Init([]byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07}) + }) + t.Run("Double initialization", func(t *testing.T) { + t.Run("Same key allowed", func(t *testing.T) { + Init([]byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07}) + }) + t.Run("Different key panics", func(t *testing.T) { + assert.Panics(t, func() { + Init([]byte{0x02, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07}) + }) }) }) - t.Run("Initialization", func(t *testing.T) { - keying.Init([]byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07}) - }) - t.Run("Context separation", func(t *testing.T) { - key1 := keying.DeriveKey(keying.Context("TESTING")) - key2 := keying.DeriveKey(keying.Context("TESTING2")) + key1 := deriveKey("TESTING") + key2 := deriveKey("TESTING2") ciphertext := key1.Encrypt([]byte("This is for context TESTING"), nil) @@ -40,12 +46,10 @@ func TestKeying(t *testing.T) { assert.EqualValues(t, "This is for context TESTING", plaintext) }) - context := keying.Context("TESTING PURPOSES") + key := deriveKey("TESTING PURPOSES") plainText := []byte("Forgejo is run by [Redacted]") var cipherText []byte t.Run("Encrypt", func(t *testing.T) { - key := keying.DeriveKey(context) - cipherText = key.Encrypt(plainText, []byte{0x05, 0x06}) cipherText2 := key.Encrypt(plainText, []byte{0x05, 0x06}) @@ -54,14 +58,21 @@ func TestKeying(t *testing.T) { }) t.Run("Decrypt", func(t *testing.T) { - key := keying.DeriveKey(context) - t.Run("Successful", func(t *testing.T) { convertedPlainText, err := key.Decrypt(cipherText, []byte{0x05, 0x06}) require.NoError(t, err) assert.Equal(t, plainText, convertedPlainText) }) + t.Run("Old secret", func(t *testing.T) { + // ensure that new code can still decode old secrets + known, err := base64.RawStdEncoding.DecodeString("LABcdFTke+FAESOAUkaQvdFO/tLFdugvXHqUYQaESy9eCedUsorjpe1N350NN+AU7gv6xyK3DHuugD+wjnVcNvt+9hA") + require.NoError(t, err) + convertedPlainText, err := key.Decrypt(known, []byte{0x05, 0x06}) + require.NoError(t, err) + assert.Equal(t, plainText, convertedPlainText) + }) + t.Run("Not enough additional data", func(t *testing.T) { plainText, err := key.Decrypt(cipherText, []byte{0x05}) require.Error(t, err) @@ -84,48 +95,67 @@ func TestKeying(t *testing.T) { }) t.Run("Incorrect ciphertext", func(t *testing.T) { - assert.Panics(t, func() { - key.Decrypt(nil, nil) - }) + _, err := key.Decrypt(nil, nil) + require.Error(t, err) - assert.Panics(t, func() { - cipherText := make([]byte, chacha20poly1305.NonceSizeX) - key.Decrypt(cipherText, nil) - }) + cipherText := make([]byte, chacha20poly1305.NonceSizeX) + _, err = key.Decrypt(cipherText, nil) + require.Error(t, err) }) }) } func TestKeyingColumnAndID(t *testing.T) { - assert.Equal(t, []byte{0x74, 0x61, 0x62, 0x6c, 0x65, 0x3a, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, keying.ColumnAndID("table", math.MinInt64)) - assert.Equal(t, []byte{0x74, 0x61, 0x62, 0x6c, 0x65, 0x3a, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, keying.ColumnAndID("table", -1)) - assert.Equal(t, []byte{0x74, 0x61, 0x62, 0x6c, 0x65, 0x3a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, keying.ColumnAndID("table", 0)) - assert.Equal(t, []byte{0x74, 0x61, 0x62, 0x6c, 0x65, 0x3a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}, keying.ColumnAndID("table", 1)) - assert.Equal(t, []byte{0x74, 0x61, 0x62, 0x6c, 0x65, 0x3a, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, keying.ColumnAndID("table", math.MaxInt64)) + assert.Equal(t, []byte{0x74, 0x61, 0x62, 0x6c, 0x65, 0x3a, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ColumnAndID("table", math.MinInt64)) + assert.Equal(t, []byte{0x74, 0x61, 0x62, 0x6c, 0x65, 0x3a, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, ColumnAndID("table", -1)) + assert.Equal(t, []byte{0x74, 0x61, 0x62, 0x6c, 0x65, 0x3a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ColumnAndID("table", 0)) + assert.Equal(t, []byte{0x74, 0x61, 0x62, 0x6c, 0x65, 0x3a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}, ColumnAndID("table", 1)) + assert.Equal(t, []byte{0x74, 0x61, 0x62, 0x6c, 0x65, 0x3a, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, ColumnAndID("table", math.MaxInt64)) - assert.Equal(t, []byte{0x74, 0x61, 0x62, 0x6c, 0x65, 0x32, 0x3a, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, keying.ColumnAndID("table2", math.MinInt64)) - assert.Equal(t, []byte{0x74, 0x61, 0x62, 0x6c, 0x65, 0x32, 0x3a, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, keying.ColumnAndID("table2", -1)) - assert.Equal(t, []byte{0x74, 0x61, 0x62, 0x6c, 0x65, 0x32, 0x3a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, keying.ColumnAndID("table2", 0)) - assert.Equal(t, []byte{0x74, 0x61, 0x62, 0x6c, 0x65, 0x32, 0x3a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}, keying.ColumnAndID("table2", 1)) - assert.Equal(t, []byte{0x74, 0x61, 0x62, 0x6c, 0x65, 0x32, 0x3a, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, keying.ColumnAndID("table2", math.MaxInt64)) + assert.Equal(t, []byte{0x74, 0x61, 0x62, 0x6c, 0x65, 0x32, 0x3a, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ColumnAndID("table2", math.MinInt64)) + assert.Equal(t, []byte{0x74, 0x61, 0x62, 0x6c, 0x65, 0x32, 0x3a, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, ColumnAndID("table2", -1)) + assert.Equal(t, []byte{0x74, 0x61, 0x62, 0x6c, 0x65, 0x32, 0x3a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ColumnAndID("table2", 0)) + assert.Equal(t, []byte{0x74, 0x61, 0x62, 0x6c, 0x65, 0x32, 0x3a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}, ColumnAndID("table2", 1)) + assert.Equal(t, []byte{0x74, 0x61, 0x62, 0x6c, 0x65, 0x32, 0x3a, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, ColumnAndID("table2", math.MaxInt64)) } func TestColumnAndJSONSelectorAndID(t *testing.T) { - assert.Equal(t, []byte{0x74, 0x61, 0x62, 0x6c, 0x65, 0x3a, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x31, 0x3a, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, keying.ColumnAndJSONSelectorAndID("table", "field1", math.MinInt64)) - assert.Equal(t, []byte{0x74, 0x61, 0x62, 0x6c, 0x65, 0x3a, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x31, 0x3a, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, keying.ColumnAndJSONSelectorAndID("table", "field1", -1)) - assert.Equal(t, []byte{0x74, 0x61, 0x62, 0x6c, 0x65, 0x3a, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x31, 0x3a, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, keying.ColumnAndJSONSelectorAndID("table", "field1", 0)) - assert.Equal(t, []byte{0x74, 0x61, 0x62, 0x6c, 0x65, 0x3a, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x31, 0x3a, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1}, keying.ColumnAndJSONSelectorAndID("table", "field1", 1)) - assert.Equal(t, []byte{0x74, 0x61, 0x62, 0x6c, 0x65, 0x3a, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x31, 0x3a, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, keying.ColumnAndJSONSelectorAndID("table", "field1", math.MaxInt64)) + assert.Equal(t, []byte{0x74, 0x61, 0x62, 0x6c, 0x65, 0x3a, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x31, 0x3a, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, ColumnAndJSONSelectorAndID("table", "field1", math.MinInt64)) + assert.Equal(t, []byte{0x74, 0x61, 0x62, 0x6c, 0x65, 0x3a, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x31, 0x3a, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, ColumnAndJSONSelectorAndID("table", "field1", -1)) + assert.Equal(t, []byte{0x74, 0x61, 0x62, 0x6c, 0x65, 0x3a, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x31, 0x3a, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, ColumnAndJSONSelectorAndID("table", "field1", 0)) + assert.Equal(t, []byte{0x74, 0x61, 0x62, 0x6c, 0x65, 0x3a, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x31, 0x3a, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1}, ColumnAndJSONSelectorAndID("table", "field1", 1)) + assert.Equal(t, []byte{0x74, 0x61, 0x62, 0x6c, 0x65, 0x3a, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x31, 0x3a, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, ColumnAndJSONSelectorAndID("table", "field1", math.MaxInt64)) - assert.Equal(t, []byte{0x74, 0x61, 0x62, 0x6c, 0x65, 0x3a, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x32, 0x3a, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, keying.ColumnAndJSONSelectorAndID("table", "field2", math.MinInt64)) - assert.Equal(t, []byte{0x74, 0x61, 0x62, 0x6c, 0x65, 0x3a, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x32, 0x3a, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, keying.ColumnAndJSONSelectorAndID("table", "field2", -1)) - assert.Equal(t, []byte{0x74, 0x61, 0x62, 0x6c, 0x65, 0x3a, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x32, 0x3a, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, keying.ColumnAndJSONSelectorAndID("table", "field2", 0)) - assert.Equal(t, []byte{0x74, 0x61, 0x62, 0x6c, 0x65, 0x3a, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x32, 0x3a, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1}, keying.ColumnAndJSONSelectorAndID("table", "field2", 1)) - assert.Equal(t, []byte{0x74, 0x61, 0x62, 0x6c, 0x65, 0x3a, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x32, 0x3a, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, keying.ColumnAndJSONSelectorAndID("table", "field2", math.MaxInt64)) + assert.Equal(t, []byte{0x74, 0x61, 0x62, 0x6c, 0x65, 0x3a, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x32, 0x3a, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, ColumnAndJSONSelectorAndID("table", "field2", math.MinInt64)) + assert.Equal(t, []byte{0x74, 0x61, 0x62, 0x6c, 0x65, 0x3a, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x32, 0x3a, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, ColumnAndJSONSelectorAndID("table", "field2", -1)) + assert.Equal(t, []byte{0x74, 0x61, 0x62, 0x6c, 0x65, 0x3a, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x32, 0x3a, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, ColumnAndJSONSelectorAndID("table", "field2", 0)) + assert.Equal(t, []byte{0x74, 0x61, 0x62, 0x6c, 0x65, 0x3a, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x32, 0x3a, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1}, ColumnAndJSONSelectorAndID("table", "field2", 1)) + assert.Equal(t, []byte{0x74, 0x61, 0x62, 0x6c, 0x65, 0x3a, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x32, 0x3a, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, ColumnAndJSONSelectorAndID("table", "field2", math.MaxInt64)) - assert.Equal(t, []byte{0x74, 0x61, 0x62, 0x6c, 0x65, 0x32, 0x3a, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x31, 0x3a, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, keying.ColumnAndJSONSelectorAndID("table2", "field1", math.MinInt64)) - assert.Equal(t, []byte{0x74, 0x61, 0x62, 0x6c, 0x65, 0x32, 0x3a, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x31, 0x3a, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, keying.ColumnAndJSONSelectorAndID("table2", "field1", -1)) - assert.Equal(t, []byte{0x74, 0x61, 0x62, 0x6c, 0x65, 0x32, 0x3a, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x31, 0x3a, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, keying.ColumnAndJSONSelectorAndID("table2", "field1", 0)) - assert.Equal(t, []byte{0x74, 0x61, 0x62, 0x6c, 0x65, 0x32, 0x3a, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x31, 0x3a, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1}, keying.ColumnAndJSONSelectorAndID("table2", "field1", 1)) - assert.Equal(t, []byte{0x74, 0x61, 0x62, 0x6c, 0x65, 0x32, 0x3a, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x31, 0x3a, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, keying.ColumnAndJSONSelectorAndID("table2", "field1", math.MaxInt64)) + assert.Equal(t, []byte{0x74, 0x61, 0x62, 0x6c, 0x65, 0x32, 0x3a, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x31, 0x3a, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, ColumnAndJSONSelectorAndID("table2", "field1", math.MinInt64)) + assert.Equal(t, []byte{0x74, 0x61, 0x62, 0x6c, 0x65, 0x32, 0x3a, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x31, 0x3a, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, ColumnAndJSONSelectorAndID("table2", "field1", -1)) + assert.Equal(t, []byte{0x74, 0x61, 0x62, 0x6c, 0x65, 0x32, 0x3a, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x31, 0x3a, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, ColumnAndJSONSelectorAndID("table2", "field1", 0)) + assert.Equal(t, []byte{0x74, 0x61, 0x62, 0x6c, 0x65, 0x32, 0x3a, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x31, 0x3a, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1}, ColumnAndJSONSelectorAndID("table2", "field1", 1)) + assert.Equal(t, []byte{0x74, 0x61, 0x62, 0x6c, 0x65, 0x32, 0x3a, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x31, 0x3a, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, ColumnAndJSONSelectorAndID("table2", "field1", math.MaxInt64)) +} + +// 500 ns/op +func BenchmarkExpandPRK(b *testing.B) { + prk, err := hkdf.Extract(hash, []byte("secret"), nil) + require.NoError(b, err) + for b.Loop() { + expandPRK(prk, "testing") + } +} + +// 2 ns/op +func BenchmarkExpandPRKOnce(b *testing.B) { + prk, err := hkdf.Extract(hash, []byte("secret"), nil) + require.NoError(b, err) + once := sync.OnceValue(func() cipher.AEAD { + return expandPRK(prk, "testing") + }) + for b.Loop() { + once() + } } diff --git a/services/task/task.go b/services/task/task.go index f011feaf8b..0012b12cb0 100644 --- a/services/task/task.go +++ b/services/task/task.go @@ -84,7 +84,7 @@ func CreateMigrateTask(ctx context.Context, doer, u *user_model.User, opts base. return err } - key := keying.DeriveKey(keying.ContextMigrateTask) + key := keying.MigrateTask opts.CloneAddrEncrypted = base64.RawStdEncoding.EncodeToString(key.Encrypt([]byte(opts.CloneAddr), keying.ColumnAndJSONSelectorAndID("payload_content", "clone_addr_encrypted", task.ID))) opts.CloneAddr = util.SanitizeCredentialURLs(opts.CloneAddr) diff --git a/tests/integration/integration_test.go b/tests/integration/integration_test.go index c2bf728b88..ad0b09305f 100644 --- a/tests/integration/integration_test.go +++ b/tests/integration/integration_test.go @@ -444,7 +444,7 @@ func loginUserWithTOTP(t testing.TB, user *user_model.User) *TestSession { twoFactor, err := auth.GetTwoFactorByUID(db.DefaultContext, user.ID) require.NoError(t, err) - key := keying.DeriveKey(keying.ContextTOTP) + key := keying.TOTP code, err := key.Decrypt(twoFactor.Secret, keying.ColumnAndID("secret", twoFactor.ID)) require.NoError(t, err)