summaryrefslogtreecommitdiffstats
path: root/models
diff options
context:
space:
mode:
Diffstat (limited to 'models')
-rw-r--r--models/migrations/migrations.go2
-rw-r--r--models/migrations/v71.go88
-rw-r--r--models/twofactor.go25
3 files changed, 108 insertions, 7 deletions
diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go
index 48c4228fe1..b4e1a67187 100644
--- a/models/migrations/migrations.go
+++ b/models/migrations/migrations.go
@@ -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
diff --git a/models/migrations/v71.go b/models/migrations/v71.go
new file mode 100644
index 0000000000..c725908bd5
--- /dev/null
+++ b/models/migrations/v71.go
@@ -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)
+}
diff --git a/models/twofactor.go b/models/twofactor.go
index 5f3c6efc21..be37c50b46 100644
--- a/models/twofactor.go
+++ b/models/twofactor.go
@@ -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
}