Improve logs of password hashing to help troubleshoot issues.
parent
d037fb2728
commit
1ee442e86f
|
@ -3,6 +3,7 @@ package authentication
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/asaskevich/govalidator"
|
"github.com/asaskevich/govalidator"
|
||||||
|
@ -68,7 +69,7 @@ func readDatabase(path string) (*DatabaseModel, error) {
|
||||||
// CheckUserPassword checks if provided password matches for the given user.
|
// CheckUserPassword checks if provided password matches for the given user.
|
||||||
func (p *FileUserProvider) CheckUserPassword(username string, password string) (bool, error) {
|
func (p *FileUserProvider) CheckUserPassword(username string, password string) (bool, error) {
|
||||||
if details, ok := p.database.Users[username]; ok {
|
if details, ok := p.database.Users[username]; ok {
|
||||||
hashedPassword := details.HashedPassword[7:] // Remove {CRYPT}
|
hashedPassword := strings.ReplaceAll(details.HashedPassword, "{CRYPT}", "")
|
||||||
ok, err := CheckPassword(password, hashedPassword)
|
ok, err := CheckPassword(password, hashedPassword)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
|
|
|
@ -98,6 +98,16 @@ func TestShouldRaiseWhenLoadingDatabaseWithBadSchemaForFirstTime(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestShouldSupportHashPasswordWithoutCRYPT(t *testing.T) {
|
||||||
|
WithDatabase(UserDatabaseWithouCryptContent, func(path string) {
|
||||||
|
provider := NewFileUserProvider(path)
|
||||||
|
ok, err := provider.CheckUserPassword("john", "password")
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.True(t, ok)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
var UserDatabaseContent = []byte(`
|
var UserDatabaseContent = []byte(`
|
||||||
users:
|
users:
|
||||||
john:
|
john:
|
||||||
|
@ -142,3 +152,16 @@ user:
|
||||||
- admins
|
- admins
|
||||||
- dev
|
- dev
|
||||||
`)
|
`)
|
||||||
|
|
||||||
|
var UserDatabaseWithouCryptContent = []byte(`
|
||||||
|
users:
|
||||||
|
john:
|
||||||
|
password: "$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/"
|
||||||
|
email: john.doe@authelia.com
|
||||||
|
groups:
|
||||||
|
- admins
|
||||||
|
- dev
|
||||||
|
james:
|
||||||
|
password: "$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/"
|
||||||
|
email: james.dean@authelia.com
|
||||||
|
`)
|
||||||
|
|
|
@ -153,15 +153,15 @@ func (p *LDAPUserProvider) getUserUID(conn LDAPConnection, username string) (str
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *LDAPUserProvider) createGroupsFilter(conn LDAPConnection, username string) (string, error) {
|
func (p *LDAPUserProvider) createGroupsFilter(conn LDAPConnection, username string) (string, error) {
|
||||||
if strings.Index(p.configuration.GroupsFilter, "{0}") >= 0 {
|
if strings.Contains(p.configuration.GroupsFilter, "{0}") {
|
||||||
return strings.Replace(p.configuration.GroupsFilter, "{0}", username, -1), nil
|
return strings.Replace(p.configuration.GroupsFilter, "{0}", username, -1), nil
|
||||||
} else if strings.Index(p.configuration.GroupsFilter, "{dn}") >= 0 {
|
} else if strings.Contains(p.configuration.GroupsFilter, "{dn}") {
|
||||||
userDN, err := p.getUserDN(conn, username)
|
userDN, err := p.getUserDN(conn, username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return strings.Replace(p.configuration.GroupsFilter, "{dn}", userDN, -1), nil
|
return strings.Replace(p.configuration.GroupsFilter, "{dn}", userDN, -1), nil
|
||||||
} else if strings.Index(p.configuration.GroupsFilter, "{uid}") >= 0 {
|
} else if strings.Contains(p.configuration.GroupsFilter, "{uid}") {
|
||||||
userUID, err := p.getUserUID(conn, username)
|
userUID, err := p.getUserUID(conn, username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
|
|
@ -22,26 +22,27 @@ type PasswordHash struct {
|
||||||
Hash string
|
Hash string
|
||||||
}
|
}
|
||||||
|
|
||||||
// passwordHashFromString extracts all characteristics of a hash given its string representation.
|
// ParseHash extracts all characteristics of a hash given its string representation.
|
||||||
func passwordHashFromString(hash string) (*PasswordHash, error) {
|
func ParseHash(hash string) (*PasswordHash, error) {
|
||||||
// Only supports salted sha 512.
|
|
||||||
if hash[:3] != "$6$" {
|
|
||||||
return nil, errors.New("Authelia only supports salted SHA512 hashing")
|
|
||||||
}
|
|
||||||
parts := strings.Split(hash, "$")
|
parts := strings.Split(hash, "$")
|
||||||
|
|
||||||
if len(parts) != 5 {
|
if len(parts) != 5 {
|
||||||
return nil, errors.New("Cannot parse the hash")
|
return nil, fmt.Errorf("Cannot parse the hash %s", hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only supports salted sha 512.
|
||||||
|
if parts[1] != "6" {
|
||||||
|
return nil, fmt.Errorf("Authelia only supports salted SHA512 hashing ($6$), not $%s$", parts[1])
|
||||||
}
|
}
|
||||||
|
|
||||||
roundsKV := strings.Split(parts[2], "=")
|
roundsKV := strings.Split(parts[2], "=")
|
||||||
if len(roundsKV) != 2 {
|
if len(roundsKV) != 2 {
|
||||||
return nil, errors.New("Cannot find the number of rounds")
|
return nil, errors.New("Cannot match pattern 'rounds=<int>' to find the number of rounds")
|
||||||
}
|
}
|
||||||
|
|
||||||
rounds, err := strconv.ParseInt(roundsKV[1], 10, 0)
|
rounds, err := strconv.ParseInt(roundsKV[1], 10, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Cannot find the number of rounds in the hash: %s", err.Error())
|
return nil, fmt.Errorf("Cannot find the number of rounds from %s using pattern 'rounds=<int>'. Cause: %s", roundsKV[1], err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
return &PasswordHash{
|
return &PasswordHash{
|
||||||
|
@ -78,7 +79,7 @@ func HashPassword(password string, salt string) string {
|
||||||
|
|
||||||
// CheckPassword check a password against a hash.
|
// CheckPassword check a password against a hash.
|
||||||
func CheckPassword(password string, hash string) (bool, error) {
|
func CheckPassword(password string, hash string) (bool, error) {
|
||||||
passwordHash, err := passwordHashFromString(hash)
|
passwordHash, err := ParseHash(hash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestShouldHashPassword(t *testing.T) {
|
func TestShouldHashPassword(t *testing.T) {
|
||||||
|
@ -17,3 +18,40 @@ func TestShouldCheckPassword(t *testing.T) {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.True(t, ok)
|
assert.True(t, ok)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCannotParseHash(t *testing.T) {
|
||||||
|
ok, err := CheckPassword("password", "$6$roSnSL3fEVkK0yHFQ.oFFAd8D4OhPAy18K5U61Z2eBhxQXExGU/eknXlY1")
|
||||||
|
|
||||||
|
assert.EqualError(t, err, "Cannot parse the hash $6$roSnSL3fEVkK0yHFQ.oFFAd8D4OhPAy18K5U61Z2eBhxQXExGU/eknXlY1")
|
||||||
|
assert.False(t, ok)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOnlySupportSHA512(t *testing.T) {
|
||||||
|
ok, err := CheckPassword("password", "$8$rounds=50000$aFr56HjK3DrB8t3S$zhPQiS85cgBlNhUKKE6n/AHMlpqrvYSnSL3fEVkK0yHFQ.oFFAd8D4OhPAy18K5U61Z2eBhxQXExGU/eknXlY1")
|
||||||
|
|
||||||
|
assert.EqualError(t, err, "Authelia only supports salted SHA512 hashing ($6$), not $8$")
|
||||||
|
assert.False(t, ok)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCannotFindNumberOfRounds(t *testing.T) {
|
||||||
|
ok, err := CheckPassword("password", "$6$rounds50000$aFr56HjK3DrB8t3S$zhPQiS85cgBlNhUKKE6n/AHMlpqrvYSnSL3fEVkK0yHFQ.oFFAd8D4OhPAy18K5U61Z2eBhxQXExGU/eknXlY1")
|
||||||
|
|
||||||
|
assert.EqualError(t, err, "Cannot match pattern 'rounds=<int>' to find the number of rounds")
|
||||||
|
assert.False(t, ok)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNumberOfRoundsNotInt(t *testing.T) {
|
||||||
|
ok, err := CheckPassword("password", "$6$rounds=abc$aFr56HjK3DrB8t3S$zhPQiS85cgBlNhUKKE6n/AHMlpqrvYSnSL3fEVkK0yHFQ.oFFAd8D4OhPAy18K5U61Z2eBhxQXExGU/eknXlY1")
|
||||||
|
|
||||||
|
assert.EqualError(t, err, "Cannot find the number of rounds from abc using pattern 'rounds=<int>'. Cause: strconv.ParseInt: parsing \"abc\": invalid syntax")
|
||||||
|
assert.False(t, ok)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShouldCheckPasswordHashedWithAuthelia(t *testing.T) {
|
||||||
|
password := "my;secure*password"
|
||||||
|
hash := HashPassword(password, "")
|
||||||
|
equal, err := CheckPassword(password, hash)
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.True(t, equal)
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue