mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2025-06-21 00:20:50 +00:00
feat: migrate TOTP secrets to keying
- Currently the TOTP secrets are stored using the `secrets` module with as key the MD5 hash of the Secretkey, the `secrets` module uses general bad practices. This patch migrates the secrets to use the `keying` module (#5041) which is easier to use and use better practices to store secrets in databases. - Migration test added. - Remove the Forgejo migration databases, and let the gitea migration databases also run forgejo migration databases. This is required as the Forgejo migration is now also touching tables that the forgejo migration didn't create itself.
This commit is contained in:
parent
ad70e7dfb3
commit
a8c61532d2
18 changed files with 149 additions and 47 deletions
|
@ -5,17 +5,14 @@ package auth
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/md5"
|
||||
"crypto/sha256"
|
||||
"crypto/subtle"
|
||||
"encoding/base32"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/modules/secret"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/keying"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
|
@ -49,9 +46,9 @@ func (err ErrTwoFactorNotEnrolled) Unwrap() error {
|
|||
|
||||
// TwoFactor represents a two-factor authentication token.
|
||||
type TwoFactor struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
UID int64 `xorm:"UNIQUE"`
|
||||
Secret string
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
UID int64 `xorm:"UNIQUE"`
|
||||
Secret []byte `xorm:"BLOB"`
|
||||
ScratchSalt string
|
||||
ScratchHash string
|
||||
LastUsedPasscode string `xorm:"VARCHAR(10)"`
|
||||
|
@ -92,39 +89,35 @@ func (t *TwoFactor) VerifyScratchToken(token string) bool {
|
|||
return subtle.ConstantTimeCompare([]byte(t.ScratchHash), []byte(tempHash)) == 1
|
||||
}
|
||||
|
||||
func (t *TwoFactor) getEncryptionKey() []byte {
|
||||
k := md5.Sum([]byte(setting.SecretKey))
|
||||
return k[:]
|
||||
}
|
||||
|
||||
// SetSecret sets the 2FA secret.
|
||||
func (t *TwoFactor) SetSecret(secretString string) error {
|
||||
secretBytes, err := secret.AesEncrypt(t.getEncryptionKey(), []byte(secretString))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t.Secret = base64.StdEncoding.EncodeToString(secretBytes)
|
||||
return nil
|
||||
func (t *TwoFactor) SetSecret(secretString string) {
|
||||
key := keying.DeriveKey(keying.ContextTOTP)
|
||||
t.Secret = key.Encrypt([]byte(secretString), keying.ColumnAndID("secret", t.ID))
|
||||
}
|
||||
|
||||
// ValidateTOTP validates the provided passcode.
|
||||
func (t *TwoFactor) ValidateTOTP(passcode string) (bool, error) {
|
||||
decodedStoredSecret, err := base64.StdEncoding.DecodeString(t.Secret)
|
||||
key := keying.DeriveKey(keying.ContextTOTP)
|
||||
secret, err := key.Decrypt(t.Secret, keying.ColumnAndID("secret", t.ID))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
secretBytes, err := secret.AesDecrypt(t.getEncryptionKey(), decodedStoredSecret)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
secretStr := string(secretBytes)
|
||||
return totp.Validate(passcode, secretStr), nil
|
||||
return totp.Validate(passcode, string(secret)), nil
|
||||
}
|
||||
|
||||
// NewTwoFactor creates a new two-factor authentication token.
|
||||
func NewTwoFactor(ctx context.Context, t *TwoFactor) error {
|
||||
_, err := db.GetEngine(ctx).Insert(t)
|
||||
return err
|
||||
func NewTwoFactor(ctx context.Context, t *TwoFactor, secret string) error {
|
||||
return db.WithTx(ctx, func(ctx context.Context) error {
|
||||
sess := db.GetEngine(ctx)
|
||||
_, err := sess.Insert(t)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
t.SetSecret(secret)
|
||||
_, err = sess.Cols("secret").ID(t.ID).Update(t)
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateTwoFactor updates a two-factor authentication token.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue