diff options
author | EpicCoder <EpicCoder@users.noreply.github.com> | 2019-07-07 08:01:01 +0200 |
---|---|---|
committer | techknowlogick <techknowlogick@gitea.io> | 2019-07-07 02:01:01 -0400 |
commit | 8d9d6aa903baf3662fa31bceb489291564a873d1 (patch) | |
tree | 7a952e1c0fce29af1de19df54fa570e24ff09fcc /models | |
parent | 1b85b248e42a9d45d4dc278d399ddab3edcedfdb (diff) | |
download | gitea-8d9d6aa903baf3662fa31bceb489291564a873d1.tar.gz gitea-8d9d6aa903baf3662fa31bceb489291564a873d1.zip |
Add additional password hash algorithms (closes #5859) (#6023)
Diffstat (limited to 'models')
-rw-r--r-- | models/login_source.go | 10 | ||||
-rw-r--r-- | models/user.go | 48 | ||||
-rw-r--r-- | models/user_test.go | 38 |
3 files changed, 74 insertions, 22 deletions
diff --git a/models/login_source.go b/models/login_source.go index 626c232772..26544588c1 100644 --- a/models/login_source.go +++ b/models/login_source.go @@ -22,6 +22,7 @@ import ( "code.gitea.io/gitea/modules/auth/oauth2" "code.gitea.io/gitea/modules/auth/pam" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" ) @@ -665,6 +666,15 @@ func UserSignIn(username, password string) (*User, error) { switch user.LoginType { case LoginNoType, LoginPlain, LoginOAuth2: if user.IsPasswordSet() && user.ValidatePassword(password) { + + // Update password hash if server password hash algorithm have changed + if user.PasswdHashAlgo != setting.PasswordHashAlgo { + user.HashPassword(password) + if err := UpdateUserCols(user, "passwd", "passwd_hash_algo"); err != nil { + return nil, err + } + } + // WARN: DON'T check user.IsActive, that will be checked on reqSign so that // user could be hint to resend confirm email. if user.ProhibitLogin { diff --git a/models/user.go b/models/user.go index aa392b1ea9..fc0dfee187 100644 --- a/models/user.go +++ b/models/user.go @@ -33,7 +33,10 @@ import ( "github.com/Unknwon/com" "github.com/go-xorm/xorm" + "golang.org/x/crypto/argon2" + "golang.org/x/crypto/bcrypt" "golang.org/x/crypto/pbkdf2" + "golang.org/x/crypto/scrypt" "golang.org/x/crypto/ssh" "xorm.io/builder" "xorm.io/core" @@ -50,6 +53,13 @@ const ( UserTypeOrganization ) +const ( + algoBcrypt = "bcrypt" + algoScrypt = "scrypt" + algoArgon2 = "argon2" + algoPbkdf2 = "pbkdf2" +) + const syncExternalUsers = "sync_external_users" var ( @@ -82,6 +92,7 @@ type User struct { Email string `xorm:"NOT NULL"` KeepEmailPrivate bool Passwd string `xorm:"NOT NULL"` + PasswdHashAlgo string `xorm:"NOT NULL DEFAULT 'pbkdf2'"` // MustChangePassword is an attribute that determines if a user // is to change his/her password after registration. @@ -430,25 +441,48 @@ func (u *User) NewGitSig() *git.Signature { } } -func hashPassword(passwd, salt string) string { - tempPasswd := pbkdf2.Key([]byte(passwd), []byte(salt), 10000, 50, sha256.New) +func hashPassword(passwd, salt, algo string) string { + var tempPasswd []byte + + switch algo { + case algoBcrypt: + tempPasswd, _ = bcrypt.GenerateFromPassword([]byte(passwd), bcrypt.DefaultCost) + return string(tempPasswd) + case algoScrypt: + tempPasswd, _ = scrypt.Key([]byte(passwd), []byte(salt), 65536, 16, 2, 50) + case algoArgon2: + tempPasswd = argon2.IDKey([]byte(passwd), []byte(salt), 2, 65536, 8, 50) + case algoPbkdf2: + fallthrough + default: + tempPasswd = pbkdf2.Key([]byte(passwd), []byte(salt), 10000, 50, sha256.New) + } + return fmt.Sprintf("%x", tempPasswd) } -// HashPassword hashes a password using PBKDF. +// HashPassword hashes a password using the algorithm defined in the config value of PASSWORD_HASH_ALGO. func (u *User) HashPassword(passwd string) { - u.Passwd = hashPassword(passwd, u.Salt) + u.PasswdHashAlgo = setting.PasswordHashAlgo + u.Passwd = hashPassword(passwd, u.Salt, setting.PasswordHashAlgo) } // ValidatePassword checks if given password matches the one belongs to the user. func (u *User) ValidatePassword(passwd string) bool { - tempHash := hashPassword(passwd, u.Salt) - return subtle.ConstantTimeCompare([]byte(u.Passwd), []byte(tempHash)) == 1 + tempHash := hashPassword(passwd, u.Salt, u.PasswdHashAlgo) + + if u.PasswdHashAlgo != algoBcrypt && subtle.ConstantTimeCompare([]byte(u.Passwd), []byte(tempHash)) == 1 { + return true + } + if u.PasswdHashAlgo == algoBcrypt && bcrypt.CompareHashAndPassword([]byte(u.Passwd), []byte(passwd)) == nil { + return true + } + return false } // IsPasswordSet checks if the password is set or left empty func (u *User) IsPasswordSet() bool { - return !u.ValidatePassword("") + return len(u.Passwd) > 0 } // UploadAvatar saves custom avatar for user. diff --git a/models/user_test.go b/models/user_test.go index 6af9752c9b..10420a143f 100644 --- a/models/user_test.go +++ b/models/user_test.go @@ -147,21 +147,29 @@ func TestHashPasswordDeterministic(t *testing.T) { b := make([]byte, 16) rand.Read(b) u := &User{Salt: string(b)} - for i := 0; i < 50; i++ { - // generate a random password - rand.Read(b) - pass := string(b) - - // save the current password in the user - hash it and store the result - u.HashPassword(pass) - r1 := u.Passwd - - // run again - u.HashPassword(pass) - r2 := u.Passwd - - // assert equal (given the same salt+pass, the same result is produced) - assert.Equal(t, r1, r2) + algos := []string{"pbkdf2", "argon2", "scrypt", "bcrypt"} + for j := 0; j < len(algos); j++ { + u.PasswdHashAlgo = algos[j] + for i := 0; i < 50; i++ { + // generate a random password + rand.Read(b) + pass := string(b) + + // save the current password in the user - hash it and store the result + u.HashPassword(pass) + r1 := u.Passwd + + // run again + u.HashPassword(pass) + r2 := u.Passwd + + // assert equal (given the same salt+pass, the same result is produced) except bcrypt + if u.PasswdHashAlgo == "bcrypt" { + assert.NotEqual(t, r1, r2) + } else { + assert.Equal(t, r1, r2) + } + } } } |