Improve logs of password hashing to help troubleshoot issues.

pull/525/head
Clement Michaud 2019-12-27 17:55:00 +01:00 committed by Clément Michaud
parent d037fb2728
commit 1ee442e86f
5 changed files with 77 additions and 14 deletions

View File

@ -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

View File

@ -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
`)

View File

@ -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

View File

@ -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
} }

View File

@ -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)
}