forked from mirrors/forgejo
feat: cache derived keys for faster keying (#10114)
Currently `DeriveKey` is called every time that a secret must be encoded/decoded. Since this function is deterministic, its result can be cached to allow a 250x speedup (the original took less than half a microsecond, so this more of a micro-optimization...). ``` go test -bench=. goos: linux goarch: amd64 pkg: forgejo.org/modules/keying cpu: Intel(R) Core(TM) Ultra 5 125H BenchmarkExpandPRK-18 2071627 564.2 ns/op BenchmarkExpandPRKOnce-18 541438192 2.206 ns/op PASS ok forgejo.org/modules/keying 2.369s ``` ## Other changes - Since the keys can be constructed once, it simplifies a bit the callsites (`keying.TOTP.Encrypt(...)` instead of `keying.DeriveKey(keying.ContextTOTP).Encrypt(...)`) - All `Encrypt`/`Decrypt` calls will panic forever if called before `Init` has been called (current it panics as long as `Init` has not been called) - Calling `Init` twice with different keys will trigger a panic (currently racy) - Calling `Decrypt` with a short ciphertext does not panic anymore (like when calling with long-enough garbage) Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/10114 Reviewed-by: Gusted <gusted@noreply.codeberg.org> Co-authored-by: oliverpool <git@olivier.pfad.fr> Co-committed-by: oliverpool <git@olivier.pfad.fr>
This commit is contained in:
parent
2900a4c43a
commit
67df538958
13 changed files with 151 additions and 118 deletions
|
|
@ -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 != "" {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue