Switch plaintext scratch tokens to use hash instead (#4331)

tokarchuk/v1.17
techknowlogick 6 years ago committed by GitHub
parent ac968c3c6f
commit adf3f004b6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      models/migrations/migrations.go
  2. 88
      models/migrations/v71.go
  3. 25
      models/twofactor.go
  4. 6
      routers/user/auth.go
  5. 9
      routers/user/setting/security_twofa.go

@ -194,6 +194,8 @@ var migrations = []Migration{
NewMigration("move team units to team_unit table", moveTeamUnitsToTeamUnitTable), NewMigration("move team units to team_unit table", moveTeamUnitsToTeamUnitTable),
// v70 -> v71 // v70 -> v71
NewMigration("add issue_dependencies", addIssueDependencies), NewMigration("add issue_dependencies", addIssueDependencies),
// v70 -> v71
NewMigration("protect each scratch token", addScratchHash),
} }
// Migrate database to current version // Migrate database to current version

@ -0,0 +1,88 @@
// Copyright 2018 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package migrations
import (
"crypto/sha256"
"fmt"
"github.com/go-xorm/xorm"
"golang.org/x/crypto/pbkdf2"
"code.gitea.io/gitea/modules/generate"
"code.gitea.io/gitea/modules/util"
)
func addScratchHash(x *xorm.Engine) error {
// TwoFactor see models/twofactor.go
type TwoFactor struct {
ID int64 `xorm:"pk autoincr"`
UID int64 `xorm:"UNIQUE"`
Secret string
ScratchToken string
ScratchSalt string
ScratchHash string
LastUsedPasscode string `xorm:"VARCHAR(10)"`
CreatedUnix util.TimeStamp `xorm:"INDEX created"`
UpdatedUnix util.TimeStamp `xorm:"INDEX updated"`
}
if err := x.Sync2(new(TwoFactor)); err != nil {
return fmt.Errorf("Sync2: %v", err)
}
sess := x.NewSession()
defer sess.Close()
if err := sess.Begin(); err != nil {
return err
}
// transform all tokens to hashes
const batchSize = 100
for start := 0; ; start += batchSize {
tfas := make([]*TwoFactor, 0, batchSize)
if err := x.Limit(batchSize, start).Find(&tfas); err != nil {
return err
}
if len(tfas) == 0 {
break
}
for _, tfa := range tfas {
// generate salt
salt, err := generate.GetRandomString(10)
if err != nil {
return err
}
tfa.ScratchSalt = salt
tfa.ScratchHash = hashToken(tfa.ScratchToken, salt)
if _, err := sess.ID(tfa.ID).Cols("scratch_salt, scratch_hash").Update(tfa); err != nil {
return fmt.Errorf("couldn't add in scratch_hash and scratch_salt: %v", err)
}
}
}
// Commit and begin new transaction for dropping columns
if err := sess.Commit(); err != nil {
return err
}
if err := sess.Begin(); err != nil {
return err
}
if err := dropTableColumns(sess, "two_factor", "scratch_token"); err != nil {
return err
}
return sess.Commit()
}
func hashToken(token, salt string) string {
tempHash := pbkdf2.Key([]byte(token), []byte(salt), 10000, 50, sha256.New)
return fmt.Sprintf("%x", tempHash)
}

@ -9,12 +9,15 @@ import (
"crypto/cipher" "crypto/cipher"
"crypto/md5" "crypto/md5"
"crypto/rand" "crypto/rand"
"crypto/sha256"
"crypto/subtle" "crypto/subtle"
"encoding/base64" "encoding/base64"
"errors" "errors"
"fmt"
"io" "io"
"github.com/pquerna/otp/totp" "github.com/pquerna/otp/totp"
"golang.org/x/crypto/pbkdf2"
"code.gitea.io/gitea/modules/generate" "code.gitea.io/gitea/modules/generate"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
@ -26,20 +29,27 @@ type TwoFactor struct {
ID int64 `xorm:"pk autoincr"` ID int64 `xorm:"pk autoincr"`
UID int64 `xorm:"UNIQUE"` UID int64 `xorm:"UNIQUE"`
Secret string Secret string
ScratchToken string ScratchSalt string
ScratchHash string
LastUsedPasscode string `xorm:"VARCHAR(10)"` LastUsedPasscode string `xorm:"VARCHAR(10)"`
CreatedUnix util.TimeStamp `xorm:"INDEX created"` CreatedUnix util.TimeStamp `xorm:"INDEX created"`
UpdatedUnix util.TimeStamp `xorm:"INDEX updated"` UpdatedUnix util.TimeStamp `xorm:"INDEX updated"`
} }
// GenerateScratchToken recreates the scratch token the user is using. // GenerateScratchToken recreates the scratch token the user is using.
func (t *TwoFactor) GenerateScratchToken() error { func (t *TwoFactor) GenerateScratchToken() (string, error) {
token, err := generate.GetRandomString(8) token, err := generate.GetRandomString(8)
if err != nil { if err != nil {
return err return "", err
} }
t.ScratchToken = token t.ScratchSalt, _ = generate.GetRandomString(10)
return nil t.ScratchHash = hashToken(token, t.ScratchSalt)
return token, nil
}
func hashToken(token, salt string) string {
tempHash := pbkdf2.Key([]byte(token), []byte(salt), 10000, 50, sha256.New)
return fmt.Sprintf("%x", tempHash)
} }
// VerifyScratchToken verifies if the specified scratch token is valid. // VerifyScratchToken verifies if the specified scratch token is valid.
@ -47,7 +57,8 @@ func (t *TwoFactor) VerifyScratchToken(token string) bool {
if len(token) == 0 { if len(token) == 0 {
return false return false
} }
return subtle.ConstantTimeCompare([]byte(token), []byte(t.ScratchToken)) == 1 tempHash := hashToken(token, t.ScratchSalt)
return subtle.ConstantTimeCompare([]byte(t.ScratchHash), []byte(tempHash)) == 1
} }
func (t *TwoFactor) getEncryptionKey() []byte { func (t *TwoFactor) getEncryptionKey() []byte {
@ -118,7 +129,7 @@ func aesDecrypt(key, text []byte) ([]byte, error) {
// NewTwoFactor creates a new two-factor authentication token. // NewTwoFactor creates a new two-factor authentication token.
func NewTwoFactor(t *TwoFactor) error { func NewTwoFactor(t *TwoFactor) error {
err := t.GenerateScratchToken() _, err := t.GenerateScratchToken()
if err != nil { if err != nil {
return err return err
} }

@ -306,7 +306,11 @@ func TwoFactorScratchPost(ctx *context.Context, form auth.TwoFactorScratchAuthFo
// Validate the passcode with the stored TOTP secret. // Validate the passcode with the stored TOTP secret.
if twofa.VerifyScratchToken(form.Token) { if twofa.VerifyScratchToken(form.Token) {
// Invalidate the scratch token. // Invalidate the scratch token.
twofa.ScratchToken = "" _, err = twofa.GenerateScratchToken()
if err != nil {
ctx.ServerError("UserSignIn", err)
return
}
if err = models.UpdateTwoFactor(twofa); err != nil { if err = models.UpdateTwoFactor(twofa); err != nil {
ctx.ServerError("UserSignIn", err) ctx.ServerError("UserSignIn", err)
return return

@ -32,7 +32,8 @@ func RegenerateScratchTwoFactor(ctx *context.Context) {
return return
} }
if err = t.GenerateScratchToken(); err != nil { token, err := t.GenerateScratchToken()
if err != nil {
ctx.ServerError("SettingsTwoFactor", err) ctx.ServerError("SettingsTwoFactor", err)
return return
} }
@ -42,7 +43,7 @@ func RegenerateScratchTwoFactor(ctx *context.Context) {
return return
} }
ctx.Flash.Success(ctx.Tr("settings.twofa_scratch_token_regenerated", t.ScratchToken)) ctx.Flash.Success(ctx.Tr("settings.twofa_scratch_token_regenerated", token))
ctx.Redirect(setting.AppSubURL + "/user/settings/security") ctx.Redirect(setting.AppSubURL + "/user/settings/security")
} }
@ -170,7 +171,7 @@ func EnrollTwoFactorPost(ctx *context.Context, form auth.TwoFactorAuthForm) {
ctx.ServerError("SettingsTwoFactor", err) ctx.ServerError("SettingsTwoFactor", err)
return return
} }
err = t.GenerateScratchToken() token, err := t.GenerateScratchToken()
if err != nil { if err != nil {
ctx.ServerError("SettingsTwoFactor", err) ctx.ServerError("SettingsTwoFactor", err)
return return
@ -183,6 +184,6 @@ func EnrollTwoFactorPost(ctx *context.Context, form auth.TwoFactorAuthForm) {
ctx.Session.Delete("twofaSecret") ctx.Session.Delete("twofaSecret")
ctx.Session.Delete("twofaUri") ctx.Session.Delete("twofaUri")
ctx.Flash.Success(ctx.Tr("settings.twofa_enrolled", t.ScratchToken)) ctx.Flash.Success(ctx.Tr("settings.twofa_enrolled", token))
ctx.Redirect(setting.AppSubURL + "/user/settings/security") ctx.Redirect(setting.AppSubURL + "/user/settings/security")
} }

Loading…
Cancel
Save