diff --git a/server-spi-private/src/main/java/org/keycloak/credential/hash/Pbkdf2PasswordHashProvider.java b/server-spi-private/src/main/java/org/keycloak/credential/hash/Pbkdf2PasswordHashProvider.java index b3f845990b..6c170e1ed7 100644 --- a/server-spi-private/src/main/java/org/keycloak/credential/hash/Pbkdf2PasswordHashProvider.java +++ b/server-spi-private/src/main/java/org/keycloak/credential/hash/Pbkdf2PasswordHashProvider.java @@ -17,11 +17,8 @@ package org.keycloak.credential.hash; -import org.keycloak.Config; import org.keycloak.common.util.Base64; import org.keycloak.credential.CredentialModel; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.PasswordPolicy; import org.keycloak.models.UserCredentialModel; @@ -35,42 +32,34 @@ import java.security.spec.KeySpec; /** * @author Kunal Kerkar */ -public class Pbkdf2PasswordHashProvider implements PasswordHashProviderFactory, PasswordHashProvider { +public class Pbkdf2PasswordHashProvider implements PasswordHashProvider { - public static final String ID = "pbkdf2"; + private final String providerId; - private static final String PBKDF2_ALGORITHM = "PBKDF2WithHmacSHA1"; - private static final int DERIVED_KEY_SIZE = 512; + private final String pbkdf2Algorithm; - public CredentialModel encode(String rawPassword, int iterations) { - byte[] salt = getSalt(); - String encodedPassword = encode(rawPassword, iterations, salt); + public static final int DERIVED_KEY_SIZE = 512; - CredentialModel credentials = new CredentialModel(); - credentials.setAlgorithm(ID); - credentials.setType(UserCredentialModel.PASSWORD); - credentials.setSalt(salt); - credentials.setHashIterations(iterations); - credentials.setValue(encodedPassword); - return credentials; + public Pbkdf2PasswordHashProvider(String providerId, String pbkdf2Algorithm) { + this.providerId = providerId; + this.pbkdf2Algorithm = pbkdf2Algorithm; } @Override public boolean policyCheck(PasswordPolicy policy, CredentialModel credential) { - return credential.getHashIterations() == policy.getHashIterations() && ID.equals(credential.getAlgorithm()); + return credential.getHashIterations() == policy.getHashIterations() && providerId.equals(credential.getAlgorithm()); } @Override - public void encode(String rawPassword, PasswordPolicy policy, CredentialModel credential) { + public void encode(String rawPassword, int iterations, CredentialModel credential) { byte[] salt = getSalt(); - String encodedPassword = encode(rawPassword, policy.getHashIterations(), salt); + String encodedPassword = encode(rawPassword, iterations, salt); - credential.setAlgorithm(ID); + credential.setAlgorithm(providerId); credential.setType(UserCredentialModel.PASSWORD); credential.setSalt(salt); - credential.setHashIterations(policy.getHashIterations()); + credential.setHashIterations(iterations); credential.setValue(encodedPassword); - } @Override @@ -78,27 +67,9 @@ public class Pbkdf2PasswordHashProvider implements PasswordHashProviderFactory, return encode(rawPassword, credential.getHashIterations(), credential.getSalt()).equals(credential.getValue()); } - @Override - public PasswordHashProvider create(KeycloakSession session) { - return this; - } - - @Override - public void init(Config.Scope config) { - } - - @Override - public void postInit(KeycloakSessionFactory factory) { - } - public void close() { } - @Override - public String getId() { - return ID; - } - private String encode(String rawPassword, int iterations, byte[] salt) { KeySpec spec = new PBEKeySpec(rawPassword.toCharArray(), salt, iterations, DERIVED_KEY_SIZE); @@ -122,10 +93,9 @@ public class Pbkdf2PasswordHashProvider implements PasswordHashProviderFactory, private SecretKeyFactory getSecretKeyFactory() { try { - return SecretKeyFactory.getInstance(PBKDF2_ALGORITHM); + return SecretKeyFactory.getInstance(pbkdf2Algorithm); } catch (NoSuchAlgorithmException e) { throw new RuntimeException("PBKDF2 algorithm not found", e); } } - } diff --git a/server-spi-private/src/main/java/org/keycloak/credential/hash/Pbkdf2PasswordHashProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/credential/hash/Pbkdf2PasswordHashProviderFactory.java new file mode 100644 index 0000000000..ecd917d25d --- /dev/null +++ b/server-spi-private/src/main/java/org/keycloak/credential/hash/Pbkdf2PasswordHashProviderFactory.java @@ -0,0 +1,54 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.credential.hash; + +import org.keycloak.Config; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionFactory; + +/** + * @author Kunal Kerkar + */ +public class Pbkdf2PasswordHashProviderFactory implements PasswordHashProviderFactory { + + public static final String ID = "pbkdf2"; + + public static final String PBKDF2_ALGORITHM = "PBKDF2WithHmacSHA1"; + + @Override + public PasswordHashProvider create(KeycloakSession session) { + return new Pbkdf2PasswordHashProvider(ID, PBKDF2_ALGORITHM); + } + + @Override + public void init(Config.Scope config) { + } + + @Override + public void postInit(KeycloakSessionFactory factory) { + } + + @Override + public String getId() { + return ID; + } + + @Override + public void close() { + } +} diff --git a/server-spi-private/src/main/java/org/keycloak/credential/hash/Pbkdf2Sha256PasswordHashProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/credential/hash/Pbkdf2Sha256PasswordHashProviderFactory.java new file mode 100644 index 0000000000..c6453d1cc3 --- /dev/null +++ b/server-spi-private/src/main/java/org/keycloak/credential/hash/Pbkdf2Sha256PasswordHashProviderFactory.java @@ -0,0 +1,39 @@ +package org.keycloak.credential.hash; + +import org.keycloak.Config; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionFactory; + +/** + * PBKDF2 Password Hash provider with HMAC using SHA256 + * + * @author Adam Kaplan + */ +public class Pbkdf2Sha256PasswordHashProviderFactory implements PasswordHashProviderFactory { + + public static final String ID = "pbkdf2-sha256"; + + public static final String PBKDF2_ALGORITHM = "PBKDF2WithHmacSHA256"; + + @Override + public PasswordHashProvider create(KeycloakSession session) { + return new Pbkdf2PasswordHashProvider(ID, PBKDF2_ALGORITHM); + } + + @Override + public void init(Config.Scope config) { + } + + @Override + public void postInit(KeycloakSessionFactory factory) { + } + + @Override + public String getId() { + return ID; + } + + @Override + public void close() { + } +} diff --git a/server-spi-private/src/main/java/org/keycloak/credential/hash/Pbkdf2Sha512PasswordHashProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/credential/hash/Pbkdf2Sha512PasswordHashProviderFactory.java new file mode 100644 index 0000000000..5f838a1923 --- /dev/null +++ b/server-spi-private/src/main/java/org/keycloak/credential/hash/Pbkdf2Sha512PasswordHashProviderFactory.java @@ -0,0 +1,39 @@ +package org.keycloak.credential.hash; + +import org.keycloak.Config; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionFactory; + +/** + * Provider factory for SHA512 variant of the PBKDF2 password hash algorithm. + * + * @author @author Adam Kaplan + */ +public class Pbkdf2Sha512PasswordHashProviderFactory implements PasswordHashProviderFactory { + + public static final String ID = "pbkdf2-sha512"; + + public static final String PBKDF2_ALGORITHM = "PBKDF2WithHmacSHA512"; + + @Override + public PasswordHashProvider create(KeycloakSession session) { + return new Pbkdf2PasswordHashProvider(ID, PBKDF2_ALGORITHM); + } + + @Override + public void init(Config.Scope config) { + } + + @Override + public void postInit(KeycloakSessionFactory factory) { + } + + @Override + public String getId() { + return ID; + } + + @Override + public void close() { + } +} diff --git a/server-spi-private/src/main/java/org/keycloak/policy/HashAlgorithmPasswordPolicyProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/policy/HashAlgorithmPasswordPolicyProviderFactory.java index 303ba7993f..c1c621897a 100644 --- a/server-spi-private/src/main/java/org/keycloak/policy/HashAlgorithmPasswordPolicyProviderFactory.java +++ b/server-spi-private/src/main/java/org/keycloak/policy/HashAlgorithmPasswordPolicyProviderFactory.java @@ -18,8 +18,10 @@ package org.keycloak.policy; import org.keycloak.Config; +import org.keycloak.credential.hash.PasswordHashProvider; import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.models.ModelException; import org.keycloak.models.PasswordPolicy; import org.keycloak.models.RealmModel; import org.keycloak.models.UserModel; @@ -29,8 +31,11 @@ import org.keycloak.models.UserModel; */ public class HashAlgorithmPasswordPolicyProviderFactory implements PasswordPolicyProviderFactory, PasswordPolicyProvider { + private KeycloakSession session; + @Override public PasswordPolicyProvider create(KeycloakSession session) { + this.session = session; return this; } @@ -83,7 +88,12 @@ public class HashAlgorithmPasswordPolicyProviderFactory implements PasswordPolic @Override public Object parseConfig(String value) { - return value != null ? value : PasswordPolicy.HASH_ALGORITHM_DEFAULT; + String providerId = value != null && value.length() > 0 ? value : PasswordPolicy.HASH_ALGORITHM_DEFAULT; + PasswordHashProvider provider = session.getProvider(PasswordHashProvider.class, providerId); + if (provider == null) { + throw new ModelException("Password hashing provider not found"); + } + return providerId; } } diff --git a/server-spi/src/main/java/org/keycloak/credential/hash/PasswordHashProvider.java b/server-spi/src/main/java/org/keycloak/credential/hash/PasswordHashProvider.java index 0a4013efab..ee555c2530 100644 --- a/server-spi/src/main/java/org/keycloak/credential/hash/PasswordHashProvider.java +++ b/server-spi/src/main/java/org/keycloak/credential/hash/PasswordHashProvider.java @@ -27,8 +27,7 @@ import org.keycloak.provider.Provider; public interface PasswordHashProvider extends Provider { boolean policyCheck(PasswordPolicy policy, CredentialModel credentia); - void encode(String rawPassword, PasswordPolicy policy, CredentialModel credential); + void encode(String rawPassword, int iterations, CredentialModel credential); boolean verify(String rawPassword, CredentialModel credential); - } diff --git a/services/src/main/java/org/keycloak/credential/PasswordCredentialProvider.java b/services/src/main/java/org/keycloak/credential/PasswordCredentialProvider.java index dc6827d4e3..a3f468ed74 100644 --- a/services/src/main/java/org/keycloak/credential/PasswordCredentialProvider.java +++ b/services/src/main/java/org/keycloak/credential/PasswordCredentialProvider.java @@ -95,7 +95,7 @@ public class PasswordCredentialProvider implements CredentialProvider, Credentia newPassword.setType(CredentialModel.PASSWORD); long createdDate = Time.currentTimeMillis(); newPassword.setCreatedDate(createdDate); - hash.encode(cred.getValue(), policy, newPassword); + hash.encode(cred.getValue(), policy.getHashIterations(), newPassword); getCredentialStore().createCredential(realm, user, newPassword); UserCache userCache = session.userCache(); if (userCache != null) { @@ -207,7 +207,7 @@ public class PasswordCredentialProvider implements CredentialProvider, Credentia return true; } - hash.encode(cred.getValue(), policy, password); + hash.encode(cred.getValue(), policy.getHashIterations(), password); getCredentialStore().updateCredential(realm, user, password); UserCache userCache = session.userCache(); if (userCache != null) { diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java index 6797344c8d..db615aa78a 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java @@ -43,6 +43,7 @@ import org.keycloak.models.GroupModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.LDAPConstants; import org.keycloak.models.ModelDuplicateException; +import org.keycloak.models.ModelException; import org.keycloak.models.RealmModel; import org.keycloak.models.UserSessionModel; import org.keycloak.models.cache.CacheRealmProvider; @@ -328,6 +329,8 @@ public class RealmAdminResource { return ErrorResponse.error("Specified regex pattern(s) is invalid.", Response.Status.BAD_REQUEST); } catch (ModelDuplicateException e) { return ErrorResponse.exists("Realm with same name exists"); + } catch (ModelException e) { + return ErrorResponse.error(e.getMessage(), Status.BAD_REQUEST); } catch (Exception e) { logger.error(e.getMessage(), e); return ErrorResponse.error("Failed to update realm", Response.Status.INTERNAL_SERVER_ERROR); diff --git a/services/src/main/resources/META-INF/services/org.keycloak.credential.hash.PasswordHashProviderFactory b/services/src/main/resources/META-INF/services/org.keycloak.credential.hash.PasswordHashProviderFactory index e72e56d77d..48f56fc836 100644 --- a/services/src/main/resources/META-INF/services/org.keycloak.credential.hash.PasswordHashProviderFactory +++ b/services/src/main/resources/META-INF/services/org.keycloak.credential.hash.PasswordHashProviderFactory @@ -1 +1,3 @@ -org.keycloak.credential.hash.Pbkdf2PasswordHashProvider \ No newline at end of file +org.keycloak.credential.hash.Pbkdf2PasswordHashProviderFactory +org.keycloak.credential.hash.Pbkdf2Sha256PasswordHashProviderFactory +org.keycloak.credential.hash.Pbkdf2Sha512PasswordHashProviderFactory \ No newline at end of file diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/PasswordHashingTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/PasswordHashingTest.java new file mode 100644 index 0000000000..caaadb9cdb --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/PasswordHashingTest.java @@ -0,0 +1,201 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.testsuite.forms; + +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.arquillian.graphene.page.Page; +import org.jboss.shrinkwrap.api.spec.WebArchive; +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; +import org.keycloak.OAuth2Constants; +import org.keycloak.admin.client.resource.UserResource; +import org.keycloak.common.util.Base64; +import org.keycloak.credential.CredentialModel; +import org.keycloak.credential.hash.Pbkdf2PasswordHashProviderFactory; +import org.keycloak.credential.hash.Pbkdf2Sha256PasswordHashProviderFactory; +import org.keycloak.credential.hash.Pbkdf2Sha512PasswordHashProviderFactory; +import org.keycloak.events.Details; +import org.keycloak.events.EventType; +import org.keycloak.models.BrowserSecurityHeaders; +import org.keycloak.models.RealmModel; +import org.keycloak.models.UserModel; +import org.keycloak.models.utils.ModelToRepresentation; +import org.keycloak.models.utils.RepresentationToModel; +import org.keycloak.representations.idm.CredentialRepresentation; +import org.keycloak.representations.idm.ErrorRepresentation; +import org.keycloak.representations.idm.EventRepresentation; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.representations.idm.UserRepresentation; +import org.keycloak.testsuite.AbstractTestRealmKeycloakTest; +import org.keycloak.testsuite.AssertEvents; +import org.keycloak.testsuite.admin.ApiUtil; +import org.keycloak.testsuite.client.KeycloakTestingClient; +import org.keycloak.testsuite.pages.AppPage; +import org.keycloak.testsuite.pages.AppPage.RequestType; +import org.keycloak.testsuite.pages.ErrorPage; +import org.keycloak.testsuite.pages.LoginPage; +import org.keycloak.testsuite.pages.LoginPasswordUpdatePage; +import org.keycloak.testsuite.runonserver.RunOnServerDeployment; +import org.keycloak.testsuite.util.RealmBuilder; +import org.keycloak.testsuite.util.UserBuilder; +import org.keycloak.util.JsonSerialization; + +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.PBEKeySpec; +import javax.ws.rs.BadRequestException; +import javax.ws.rs.InternalServerErrorException; +import javax.ws.rs.client.Client; +import javax.ws.rs.client.ClientBuilder; +import javax.ws.rs.core.Response; +import java.io.IOException; +import java.security.NoSuchAlgorithmException; +import java.security.spec.KeySpec; +import java.util.Map; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +/** + * @author Stian Thorgersen + */ +public class PasswordHashingTest extends AbstractTestRealmKeycloakTest { + + @Deployment + public static WebArchive deploy() { + return RunOnServerDeployment.create(PasswordHashingTest.class, AbstractTestRealmKeycloakTest.class); + } + + @Override + public void configureTestRealm(RealmRepresentation testRealm) { + } + + @Page + protected LoginPage loginPage; + + @Test + public void testSetInvalidProvider() throws Exception { + try { + setPasswordPolicy("hashAlgorithm(nosuch)"); + fail("Expected error"); + } catch (BadRequestException e) { + ErrorRepresentation error = e.getResponse().readEntity(ErrorRepresentation.class); + assertEquals("Password hashing provider not found", error.getErrorMessage()); + } + } + + @Test + public void testPasswordRehashedOnAlgorithmChanged() throws Exception { + String username = "testPasswordRehashedOnAlgorithmChanged"; + createUser(username); + + CredentialModel credential = fetchCredentials(username); + + assertEquals(Pbkdf2PasswordHashProviderFactory.ID, credential.getAlgorithm()); + + assertEncoded(credential, "password", credential.getSalt(), "PBKDF2WithHmacSHA1", 20000); + + setPasswordPolicy("hashAlgorithm(" + Pbkdf2Sha256PasswordHashProviderFactory.ID + ")"); + + loginPage.open(); + loginPage.login(username, "password"); + + credential = fetchCredentials(username); + + assertEquals(Pbkdf2Sha256PasswordHashProviderFactory.ID, credential.getAlgorithm()); + assertEncoded(credential, "password", credential.getSalt(), "PBKDF2WithHmacSHA256", 20000); + } + + @Test + public void testPasswordRehashedOnIterationsChanged() throws Exception { + String username = "testPasswordRehashedOnIterationsChanged"; + createUser(username); + + CredentialModel credential = fetchCredentials(username); + + assertEquals(20000, credential.getHashIterations()); + + setPasswordPolicy("hashIterations(1)"); + + loginPage.open(); + loginPage.login(username, "password"); + + credential = fetchCredentials(username); + + assertEquals(1, credential.getHashIterations()); + assertEncoded(credential, "password", credential.getSalt(), "PBKDF2WithHmacSHA1", 1); + } + + @Test + public void testPbkdf2Sha1() throws Exception { + setPasswordPolicy("hashAlgorithm(" + Pbkdf2PasswordHashProviderFactory.ID + ")"); + String username = "testPbkdf2Sha1"; + createUser(username); + + CredentialModel credential = fetchCredentials(username); + assertEncoded(credential, "password", credential.getSalt(), "PBKDF2WithHmacSHA1", 20000); + } + + @Test + public void testPbkdf2Sha256() throws Exception { + setPasswordPolicy("hashAlgorithm(" + Pbkdf2Sha256PasswordHashProviderFactory.ID + ")"); + String username = "testPbkdf2Sha256"; + createUser(username); + + CredentialModel credential = fetchCredentials(username); + assertEncoded(credential, "password", credential.getSalt(), "PBKDF2WithHmacSHA256", 20000); + } + + @Test + public void testPbkdf2Sha512() throws Exception { + setPasswordPolicy("hashAlgorithm(" + Pbkdf2Sha512PasswordHashProviderFactory.ID + ")"); + String username = "testPbkdf2Sha512"; + createUser(username); + + CredentialModel credential = fetchCredentials(username); + assertEncoded(credential, "password", credential.getSalt(), "PBKDF2WithHmacSHA512", 20000); + } + + + private void createUser(String username) { + ApiUtil.createUserAndResetPasswordWithAdminClient(adminClient.realm("test"), UserBuilder.create().username(username).build(), "password"); + } + + private void setPasswordPolicy(String policy) { + RealmRepresentation realmRep = testRealm().toRepresentation(); + realmRep.setPasswordPolicy(policy); + testRealm().update(realmRep); + } + + private CredentialModel fetchCredentials(String username) { + return testingClient.server("test").fetch(session -> { + RealmModel realm = session.getContext().getRealm(); + UserModel user = session.users().getUserByUsername(username, realm); + return session.userCredentialManager().getStoredCredentialsByType(realm, user, CredentialRepresentation.PASSWORD).get(0); + }, CredentialModel.class); + } + + private void assertEncoded(CredentialModel credential, String password, byte[] salt, String algorithm, int iterations) throws Exception { + KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, iterations, 512); + byte[] key = SecretKeyFactory.getInstance(algorithm).generateSecret(spec).getEncoded(); + assertEquals(Base64.encodeBytes(key), credential.getValue()); + } + +} diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/adduser/AddUserTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/adduser/AddUserTest.java index 651afc54d7..fb23bcd5c5 100644 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/adduser/AddUserTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/adduser/AddUserTest.java @@ -26,7 +26,7 @@ import org.junit.rules.TemporaryFolder; import org.keycloak.admin.client.Keycloak; import org.keycloak.admin.client.resource.RealmResource; import org.keycloak.admin.client.resource.UserResource; -import org.keycloak.credential.hash.Pbkdf2PasswordHashProvider; +import org.keycloak.credential.hash.Pbkdf2PasswordHashProviderFactory; import org.keycloak.models.Constants; import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.CredentialRepresentation; @@ -85,7 +85,7 @@ public class AddUserTest { CredentialRepresentation credentials = user.getCredentials().get(0); - assertEquals(Pbkdf2PasswordHashProvider.ID, credentials.getAlgorithm()); + assertEquals(Pbkdf2PasswordHashProviderFactory.ID, credentials.getAlgorithm()); assertEquals(new Integer(100000), credentials.getHashIterations()); KeycloakServer server = new KeycloakServer(); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/FederatedStorageExportImportTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/FederatedStorageExportImportTest.java index 6eccb21d23..8a1bf5dbeb 100644 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/FederatedStorageExportImportTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/FederatedStorageExportImportTest.java @@ -103,7 +103,7 @@ public class FederatedStorageExportImportTest { session.userFederatedStorage().addRequiredAction(realm, userId, "UPDATE_PASSWORD"); CredentialModel credential = new CredentialModel(); getHashProvider(session, realm.getPasswordPolicy()).encode("password", realm. - getPasswordPolicy(), credential); + getPasswordPolicy().getHashIterations(), credential); session.userFederatedStorage().createCredential(realm, userId, credential); session.userFederatedStorage().grantRole(realm, userId, role); session.userFederatedStorage().joinGroup(realm, userId, group); @@ -170,7 +170,7 @@ public class FederatedStorageExportImportTest { session.userFederatedStorage().addRequiredAction(realm, userId, "UPDATE_PASSWORD"); CredentialModel credential = new CredentialModel(); getHashProvider(session, realm.getPasswordPolicy()).encode("password", realm. - getPasswordPolicy(), credential); + getPasswordPolicy().getHashIterations(), credential); session.userFederatedStorage().createCredential(realm, userId, credential); session.userFederatedStorage().grantRole(realm, userId, role); session.userFederatedStorage().joinGroup(realm, userId, group); diff --git a/wildfly/adduser/src/main/java/org/keycloak/wildfly/adduser/AddUser.java b/wildfly/adduser/src/main/java/org/keycloak/wildfly/adduser/AddUser.java index 79fcaafcdf..048b3847e8 100644 --- a/wildfly/adduser/src/main/java/org/keycloak/wildfly/adduser/AddUser.java +++ b/wildfly/adduser/src/main/java/org/keycloak/wildfly/adduser/AddUser.java @@ -30,7 +30,10 @@ import org.jboss.aesh.console.command.registry.AeshCommandRegistryBuilder; import org.jboss.aesh.console.command.registry.CommandRegistry; import org.keycloak.common.util.Base64; import org.keycloak.credential.CredentialModel; -import org.keycloak.credential.hash.Pbkdf2PasswordHashProvider; +import org.keycloak.credential.hash.PasswordHashProvider; +import org.keycloak.credential.hash.PasswordHashProviderFactory; +import org.keycloak.credential.hash.Pbkdf2PasswordHashProviderFactory; +import org.keycloak.models.PasswordPolicy; import org.keycloak.representations.idm.CredentialRepresentation; import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.UserRepresentation; @@ -44,6 +47,8 @@ import java.lang.reflect.Method; import java.util.HashMap; import java.util.LinkedList; import java.util.List; +import java.util.Map; +import java.util.ServiceLoader; /** * @author Stian Thorgersen @@ -52,6 +57,7 @@ public class AddUser { private static final String COMMAND_NAME = "add-user"; private static final int DEFAULT_HASH_ITERATIONS = 100000; + private static final String DEFAULT_HASH_ALGORITH = PasswordPolicy.HASH_ALGORITHM_DEFAULT; public static void main(String[] args) throws Exception { AddUserCommand command = new AddUserCommand(); @@ -152,14 +158,23 @@ public class AddUser { user.setUsername(userName); user.setCredentials(new LinkedList()); - CredentialModel credentialValueModel = new Pbkdf2PasswordHashProvider().encode(password, iterations > 0 ? iterations : DEFAULT_HASH_ITERATIONS); + Map config = new HashMap<>(); + if (iterations > 0) { + config.put("hashIterations", iterations); + } + + PasswordHashProviderFactory hashProviderFactory = getHashProviderFactory(DEFAULT_HASH_ALGORITH); + PasswordHashProvider hashProvider = hashProviderFactory.create(null); + + CredentialModel credentialModel = new CredentialModel(); + hashProvider.encode(password, iterations > 0 ? iterations : DEFAULT_HASH_ITERATIONS, credentialModel); CredentialRepresentation credentials = new CredentialRepresentation(); - credentials.setType(credentialValueModel.getType()); - credentials.setAlgorithm(credentialValueModel.getAlgorithm()); - credentials.setHashIterations(credentialValueModel.getHashIterations()); - credentials.setSalt(Base64.encodeBytes(credentialValueModel.getSalt())); - credentials.setHashedSaltedValue(credentialValueModel.getValue()); + credentials.setType(credentialModel.getType()); + credentials.setAlgorithm(credentialModel.getAlgorithm()); + credentials.setHashIterations(credentialModel.getHashIterations()); + credentials.setSalt(Base64.encodeBytes(credentialModel.getSalt())); + credentials.setHashedSaltedValue(credentialModel.getValue()); user.getCredentials().add(credentials); @@ -203,6 +218,16 @@ public class AddUser { System.out.println("Added '" + userName + "' to '" + addUserFile + "', restart server to load user"); } + private static PasswordHashProviderFactory getHashProviderFactory(String providerId) { + ServiceLoader providerFactories = ServiceLoader.load(PasswordHashProviderFactory.class); + for (PasswordHashProviderFactory f : providerFactories) { + if (f.getId().equals(providerId)) { + return f; + } + } + return null; + } + private static void checkRequired(Command command, String field) throws Exception { if (isEmpty(command, field)) { Option option = command.getClass().getDeclaredField(field).getAnnotation(Option.class);