forked from forgejo/forgejo
parent
1b85b248e4
commit
8d9d6aa903
23 changed files with 2895 additions and 20 deletions
|
@ -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 {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
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
|
||||
// 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
|
||||
// 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)
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue