forked from gitea/gitea
		
	Switch plaintext scratch tokens to use hash instead (#4331)
This commit is contained in:
		
							parent
							
								
									ac968c3c6f
								
							
						
					
					
						commit
						adf3f004b6
					
				| @ -194,6 +194,8 @@ var migrations = []Migration{ | ||||
| 	NewMigration("move team units to team_unit table", moveTeamUnitsToTeamUnitTable), | ||||
| 	// v70 -> v71 | ||||
| 	NewMigration("add issue_dependencies", addIssueDependencies), | ||||
| 	// v70 -> v71 | ||||
| 	NewMigration("protect each scratch token", addScratchHash), | ||||
| } | ||||
| 
 | ||||
| // 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/md5" | ||||
| 	"crypto/rand" | ||||
| 	"crypto/sha256" | ||||
| 	"crypto/subtle" | ||||
| 	"encoding/base64" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 
 | ||||
| 	"github.com/pquerna/otp/totp" | ||||
| 	"golang.org/x/crypto/pbkdf2" | ||||
| 
 | ||||
| 	"code.gitea.io/gitea/modules/generate" | ||||
| 	"code.gitea.io/gitea/modules/setting" | ||||
| @ -26,20 +29,27 @@ 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"` | ||||
| } | ||||
| 
 | ||||
| // 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) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 		return "", err | ||||
| 	} | ||||
| 	t.ScratchToken = token | ||||
| 	return nil | ||||
| 	t.ScratchSalt, _ = generate.GetRandomString(10) | ||||
| 	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. | ||||
| @ -47,7 +57,8 @@ func (t *TwoFactor) VerifyScratchToken(token string) bool { | ||||
| 	if len(token) == 0 { | ||||
| 		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 { | ||||
| @ -118,7 +129,7 @@ func aesDecrypt(key, text []byte) ([]byte, error) { | ||||
| 
 | ||||
| // NewTwoFactor creates a new two-factor authentication token. | ||||
| func NewTwoFactor(t *TwoFactor) error { | ||||
| 	err := t.GenerateScratchToken() | ||||
| 	_, err := t.GenerateScratchToken() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| @ -306,7 +306,11 @@ func TwoFactorScratchPost(ctx *context.Context, form auth.TwoFactorScratchAuthFo | ||||
| 	// Validate the passcode with the stored TOTP secret. | ||||
| 	if twofa.VerifyScratchToken(form.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 { | ||||
| 			ctx.ServerError("UserSignIn", err) | ||||
| 			return | ||||
|  | ||||
| @ -32,7 +32,8 @@ func RegenerateScratchTwoFactor(ctx *context.Context) { | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	if err = t.GenerateScratchToken(); err != nil { | ||||
| 	token, err := t.GenerateScratchToken() | ||||
| 	if err != nil { | ||||
| 		ctx.ServerError("SettingsTwoFactor", err) | ||||
| 		return | ||||
| 	} | ||||
| @ -42,7 +43,7 @@ func RegenerateScratchTwoFactor(ctx *context.Context) { | ||||
| 		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") | ||||
| } | ||||
| 
 | ||||
| @ -170,7 +171,7 @@ func EnrollTwoFactorPost(ctx *context.Context, form auth.TwoFactorAuthForm) { | ||||
| 		ctx.ServerError("SettingsTwoFactor", err) | ||||
| 		return | ||||
| 	} | ||||
| 	err = t.GenerateScratchToken() | ||||
| 	token, err := t.GenerateScratchToken() | ||||
| 	if err != nil { | ||||
| 		ctx.ServerError("SettingsTwoFactor", err) | ||||
| 		return | ||||
| @ -183,6 +184,6 @@ func EnrollTwoFactorPost(ctx *context.Context, form auth.TwoFactorAuthForm) { | ||||
| 
 | ||||
| 	ctx.Session.Delete("twofaSecret") | ||||
| 	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") | ||||
| } | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 techknowlogick
						techknowlogick