summaryrefslogtreecommitdiff
path: root/internal/authentication/file_user_provider_test.go
diff options
context:
space:
mode:
authorJames Elliott <james-d-elliott@users.noreply.github.com>2020-03-06 12:38:02 +1100
committerGitHub <noreply@github.com>2020-03-06 12:38:02 +1100
commit26369fff3d8d397018a08ae044f36b723fc7d880 (patch)
treeb91dace87f3bb683491975cb5eb74e8babd6b7f4 /internal/authentication/file_user_provider_test.go
parent72a3f1e0d7524a1d86e87781d264d5db195ea3e6 (diff)
[FEATURE] Support Argon2id password hasing and improved entropy (#679)
* [FEATURE] Support Argon2id Passwords - Updated go module github.com/simia-tech/crypt - Added Argon2id support for file based authentication backend - Made it the default method - Made it so backwards compatibility with SHA512 exists - Force seeding of the random string generator used for salts to ensure they are all different - Added command params to the authelia hash-password command - Automatically remove {CRYPT} from hashes as they are updated - Automatically change hashes when they are updated to the configured algorithm - Made the hashing algorithm parameters completely configurable - Added reasonably comprehensive test suites - Updated docs - Updated config template * Adjust error output * Fix unit test * Add unit tests and argon2 version check * Fix new unit tests * Update docs, added tests * Implement configurable values and more comprehensive testing * Added cmd params to hash_password, updated docs, misc fixes * More detailed error for cmd, fixed a typo * Fixed cmd flag error, minor refactoring * Requested Changes and Minor refactoring * Increase entropy * Update docs for entropy changes * Refactor to reduce nesting and easier code maintenance * Cleanup Errors (uniformity for the function call) * Check salt length, fix docs * Add Base64 string validation for argon2id * Cleanup and Finalization - Moved RandomString function from ./internal/authentication/password_hash.go to ./internal/utils/strings.go - Added SplitStringToArrayOfStrings func that splits strings into an array with a fixed max string len - Fixed an error in validator that would allow a zero salt length - Added a test to verify the upstream crypt module supports our defined random salt chars - Updated docs - Removed unused "HashingAlgorithm" string type * Update crypt go mod, support argon2id key length and major refactor * Config Template Update, Final Tests * Use schema defaults for hash-password cmd * Iterations check * Docs requested changes * Test Coverage, suggested edits * Wording edit * Doc changes * Default sanity changes * Default sanity changes - docs * CI Sanity changes * Memory in MB
Diffstat (limited to 'internal/authentication/file_user_provider_test.go')
-rw-r--r--internal/authentication/file_user_provider_test.go184
1 files changed, 166 insertions, 18 deletions
diff --git a/internal/authentication/file_user_provider_test.go b/internal/authentication/file_user_provider_test.go
index 2e3d03a25..09fef413f 100644
--- a/internal/authentication/file_user_provider_test.go
+++ b/internal/authentication/file_user_provider_test.go
@@ -4,8 +4,10 @@ import (
"io/ioutil"
"log"
"os"
+ "strings"
"testing"
+ "github.com/authelia/authelia/internal/configuration/schema"
"github.com/stretchr/testify/assert"
)
@@ -29,9 +31,11 @@ func WithDatabase(content []byte, f func(path string)) {
}
}
-func TestShouldCheckUserPasswordIsCorrect(t *testing.T) {
+func TestShouldCheckUserArgon2idPasswordIsCorrect(t *testing.T) {
WithDatabase(UserDatabaseContent, func(path string) {
- provider := NewFileUserProvider(path)
+ config := DefaultFileAuthenticationBackendConfiguration
+ config.Path = path
+ provider := NewFileUserProvider(&config)
ok, err := provider.CheckUserPassword("john", "password")
assert.NoError(t, err)
@@ -39,9 +43,23 @@ func TestShouldCheckUserPasswordIsCorrect(t *testing.T) {
})
}
+func TestShouldCheckUserSHA512PasswordIsCorrect(t *testing.T) {
+ WithDatabase(UserDatabaseContent, func(path string) {
+ config := DefaultFileAuthenticationBackendConfiguration
+ config.Path = path
+ provider := NewFileUserProvider(&config)
+ ok, err := provider.CheckUserPassword("harry", "password")
+
+ assert.NoError(t, err)
+ assert.True(t, ok)
+ })
+}
+
func TestShouldCheckUserPasswordIsWrong(t *testing.T) {
WithDatabase(UserDatabaseContent, func(path string) {
- provider := NewFileUserProvider(path)
+ config := DefaultFileAuthenticationBackendConfiguration
+ config.Path = path
+ provider := NewFileUserProvider(&config)
ok, err := provider.CheckUserPassword("john", "wrong_password")
assert.NoError(t, err)
@@ -51,7 +69,9 @@ func TestShouldCheckUserPasswordIsWrong(t *testing.T) {
func TestShouldCheckUserPasswordOfUnexistingUser(t *testing.T) {
WithDatabase(UserDatabaseContent, func(path string) {
- provider := NewFileUserProvider(path)
+ config := DefaultFileAuthenticationBackendConfiguration
+ config.Path = path
+ provider := NewFileUserProvider(&config)
_, err := provider.CheckUserPassword("fake", "password")
assert.Error(t, err)
assert.Equal(t, "User 'fake' does not exist in database", err.Error())
@@ -60,7 +80,9 @@ func TestShouldCheckUserPasswordOfUnexistingUser(t *testing.T) {
func TestShouldRetrieveUserDetails(t *testing.T) {
WithDatabase(UserDatabaseContent, func(path string) {
- provider := NewFileUserProvider(path)
+ config := DefaultFileAuthenticationBackendConfiguration
+ config.Path = path
+ provider := NewFileUserProvider(&config)
details, err := provider.GetDetails("john")
assert.NoError(t, err)
assert.Equal(t, details.Emails, []string{"john.doe@authelia.com"})
@@ -70,45 +92,125 @@ func TestShouldRetrieveUserDetails(t *testing.T) {
func TestShouldUpdatePassword(t *testing.T) {
WithDatabase(UserDatabaseContent, func(path string) {
- provider := NewFileUserProvider(path)
+ config := DefaultFileAuthenticationBackendConfiguration
+ config.Path = path
+ provider := NewFileUserProvider(&config)
+ err := provider.UpdatePassword("harry", "newpassword")
+ assert.NoError(t, err)
+
+ // Reset the provider to force a read from disk.
+ provider = NewFileUserProvider(&config)
+ ok, err := provider.CheckUserPassword("harry", "newpassword")
+ assert.NoError(t, err)
+ assert.True(t, ok)
+ })
+}
+
+// Checks both that the hashing algo changes and that it removes {CRYPT} from the start.
+func TestShouldUpdatePasswordHashingAlgorithmToArgon2id(t *testing.T) {
+ WithDatabase(UserDatabaseContent, func(path string) {
+ config := DefaultFileAuthenticationBackendConfiguration
+ config.Path = path
+ provider := NewFileUserProvider(&config)
+ assert.True(t, strings.HasPrefix(provider.database.Users["harry"].HashedPassword, "{CRYPT}$6$"))
+ err := provider.UpdatePassword("harry", "newpassword")
+ assert.NoError(t, err)
+
+ // Reset the provider to force a read from disk.
+ provider = NewFileUserProvider(&config)
+ ok, err := provider.CheckUserPassword("harry", "newpassword")
+ assert.NoError(t, err)
+ assert.True(t, ok)
+ assert.True(t, strings.HasPrefix(provider.database.Users["harry"].HashedPassword, "$argon2id$"))
+ })
+}
+
+func TestShouldUpdatePasswordHashingAlgorithmToSHA512(t *testing.T) {
+ WithDatabase(UserDatabaseContent, func(path string) {
+ config := DefaultFileAuthenticationBackendConfiguration
+ config.Path = path
+ config.PasswordHashing.Algorithm = "sha512"
+ config.PasswordHashing.Iterations = 50000
+
+ provider := NewFileUserProvider(&config)
+ assert.True(t, strings.HasPrefix(provider.database.Users["john"].HashedPassword, "{CRYPT}$argon2id$"))
err := provider.UpdatePassword("john", "newpassword")
assert.NoError(t, err)
// Reset the provider to force a read from disk.
- provider = NewFileUserProvider(path)
+ provider = NewFileUserProvider(&config)
ok, err := provider.CheckUserPassword("john", "newpassword")
assert.NoError(t, err)
assert.True(t, ok)
+ assert.True(t, strings.HasPrefix(provider.database.Users["john"].HashedPassword, "$6$"))
})
}
func TestShouldRaiseWhenLoadingMalformedDatabaseForFirstTime(t *testing.T) {
WithDatabase(MalformedUserDatabaseContent, func(path string) {
+ config := DefaultFileAuthenticationBackendConfiguration
+ config.Path = path
assert.PanicsWithValue(t, "Unable to parse database: yaml: line 4: mapping values are not allowed in this context", func() {
- NewFileUserProvider(path)
+ NewFileUserProvider(&config)
})
})
}
func TestShouldRaiseWhenLoadingDatabaseWithBadSchemaForFirstTime(t *testing.T) {
WithDatabase(BadSchemaUserDatabaseContent, func(path string) {
+ config := DefaultFileAuthenticationBackendConfiguration
+ config.Path = path
assert.PanicsWithValue(t, "Invalid schema of database: Users: non zero value required", func() {
- NewFileUserProvider(path)
+ NewFileUserProvider(&config)
+ })
+ })
+}
+
+func TestShouldRaiseWhenLoadingDatabaseWithBadSHA512HashesForTheFirstTime(t *testing.T) {
+ WithDatabase(BadSHA512HashContent, func(path string) {
+ config := DefaultFileAuthenticationBackendConfiguration
+ config.Path = path
+ assert.PanicsWithValue(t, "Unable to parse hash of user john: Hash key is not the last parameter, the hash is likely malformed ($6$rounds00000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/).", func() {
+ NewFileUserProvider(&config)
+ })
+ })
+}
+
+func TestShouldRaiseWhenLoadingDatabaseWithBadArgon2idHashSettingsForTheFirstTime(t *testing.T) {
+ WithDatabase(BadArgon2idHashSettingsContent, func(path string) {
+ config := DefaultFileAuthenticationBackendConfiguration
+ config.Path = path
+ assert.PanicsWithValue(t, "Unable to parse hash of user john: Hash key is not the last parameter, the hash is likely malformed ($argon2id$v=19$m65536,t3,p2$BpLnfgDsc2WD8F2q$o/vzA4myCqZZ36bUGsDY//8mKUYNZZaR0t4MFFSs+iM).", func() {
+ NewFileUserProvider(&config)
})
})
}
-func TestShouldRaiseWhenLoadingDatabaseWithBadHashesForTheFirstTime(t *testing.T) {
- WithDatabase(BadHashContent, func(path string) {
- assert.PanicsWithValue(t, "Unable to parse hash of user john: Cannot match pattern 'rounds=<int>' to find the number of rounds", func() {
- NewFileUserProvider(path)
+func TestShouldRaiseWhenLoadingDatabaseWithBadArgon2idHashKeyForTheFirstTime(t *testing.T) {
+ WithDatabase(BadArgon2idHashKeyContent, func(path string) {
+ config := DefaultFileAuthenticationBackendConfiguration
+ config.Path = path
+ assert.PanicsWithValue(t, "Unable to parse hash of user john: Hash key contains invalid base64 characters.", func() {
+ NewFileUserProvider(&config)
+ })
+ })
+}
+
+func TestShouldRaiseWhenLoadingDatabaseWithBadArgon2idHashSaltForTheFirstTime(t *testing.T) {
+ WithDatabase(BadArgon2idHashSaltContent, func(path string) {
+ config := DefaultFileAuthenticationBackendConfiguration
+ config.Path = path
+ assert.PanicsWithValue(t, "Unable to parse hash of user john: Salt contains invalid base64 characters.", func() {
+ NewFileUserProvider(&config)
})
})
}
func TestShouldSupportHashPasswordWithoutCRYPT(t *testing.T) {
- WithDatabase(UserDatabaseWithouCryptContent, func(path string) {
- provider := NewFileUserProvider(path)
+ WithDatabase(UserDatabaseWithoutCryptContent, func(path string) {
+ config := DefaultFileAuthenticationBackendConfiguration
+ config.Path = path
+ provider := NewFileUserProvider(&config)
ok, err := provider.CheckUserPassword("john", "password")
assert.NoError(t, err)
@@ -116,10 +218,24 @@ func TestShouldSupportHashPasswordWithoutCRYPT(t *testing.T) {
})
}
+var (
+ DefaultFileAuthenticationBackendConfiguration = schema.FileAuthenticationBackendConfiguration{
+ Path: "",
+ PasswordHashing: &schema.PasswordHashingConfiguration{
+ Iterations: schema.DefaultCIPasswordOptionsConfiguration.Iterations,
+ KeyLength: schema.DefaultCIPasswordOptionsConfiguration.KeyLength,
+ SaltLength: schema.DefaultCIPasswordOptionsConfiguration.SaltLength,
+ Algorithm: schema.DefaultCIPasswordOptionsConfiguration.Algorithm,
+ Memory: schema.DefaultCIPasswordOptionsConfiguration.Memory,
+ Parallelism: schema.DefaultCIPasswordOptionsConfiguration.Parallelism,
+ },
+ }
+)
+
var UserDatabaseContent = []byte(`
users:
john:
- password: "{CRYPT}$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/"
+ password: "{CRYPT}$argon2id$v=19$m=65536,t=3,p=2$BpLnfgDsc2WD8F2q$o/vzA4myCqZZ36bUGsDY//8mKUYNZZaR0t4MFFSs+iM"
email: john.doe@authelia.com
groups:
- admins
@@ -161,7 +277,7 @@ user:
- dev
`)
-var UserDatabaseWithouCryptContent = []byte(`
+var UserDatabaseWithoutCryptContent = []byte(`
users:
john:
password: "$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/"
@@ -174,7 +290,7 @@ users:
email: james.dean@authelia.com
`)
-var BadHashContent = []byte(`
+var BadSHA512HashContent = []byte(`
users:
john:
password: "$6$rounds00000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/"
@@ -186,3 +302,35 @@ users:
password: "$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/"
email: james.dean@authelia.com
`)
+
+var BadArgon2idHashSettingsContent = []byte(`
+users:
+ john:
+ password: "$argon2id$v=19$m65536,t3,p2$BpLnfgDsc2WD8F2q$o/vzA4myCqZZ36bUGsDY//8mKUYNZZaR0t4MFFSs+iM"
+ email: john.doe@authelia.com
+ groups:
+ - admins
+ - dev
+ james:
+ password: "$argon2id$v=19$m=65536,t=3,p=2$BpLnfgDsc2WD8F2q$o/vzA4myCqZZ36bUGsDY//8mKUYNZZaR0t4MFFSs+iM"
+ email: james.dean@authelia.com
+`)
+
+var BadArgon2idHashKeyContent = []byte(`
+users:
+ john:
+ password: "$argon2id$v=19$m=65536,t=3,p=2$BpLnfgDsc2WD8F2q$^^vzA4myCqZZ36bUGsDY//8mKUYNZZaR0t4MFFSs+iM"
+ email: john.doe@authelia.com
+ groups:
+ - admins
+ - dev
+`)
+var BadArgon2idHashSaltContent = []byte(`
+users:
+ john:
+ password: "$argon2id$v=19$m=65536,t=3,p=2$^^LnfgDsc2WD8F2q$o/vzA4myCqZZ36bUGsDY//8mKUYNZZaR0t4MFFSs+iM"
+ email: john.doe@authelia.com
+ groups:
+ - admins
+ - dev
+`)