mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-26 12:27:06 +00:00 
			
		
		
		
	Switch plaintext scratch tokens to use hash instead (#4331)
This commit is contained in:
		| @@ -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 | ||||||
|   | |||||||
							
								
								
									
										88
									
								
								models/migrations/v71.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								models/migrations/v71.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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") | ||||||
| } | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 techknowlogick
					techknowlogick