From 162dd4051d958cce418066622de0fa4f17c011c3 Mon Sep 17 00:00:00 2001 From: tsudot Date: Wed, 18 Nov 2015 19:36:23 +0530 Subject: [PATCH 1/2] KEYCLOAK-1900 Add password hash SPI and provider - Default encoder set to Pbkdf2PasswordEncoder --- .../hash/DefaultPasswordHashProvider.java | 41 +++++++++++++++++++ .../DefaultPasswordHashProviderFactory.java | 35 ++++++++++++++++ .../keycloak/hash/PasswordHashProvider.java | 16 ++++++++ .../hash/PasswordHashProviderFactory.java | 10 +++++ .../org/keycloak/hash/PasswordHashSpi.java | 31 ++++++++++++++ .../java/org/keycloak/models/Constants.java | 2 + .../keycloak/models/UserCredentialModel.java | 9 ++++ .../models/UserFederationManager.java | 12 +++--- .../org/keycloak/models/UserProvider.java | 6 +-- .../models/utils/CredentialValidation.java | 41 ++++++++++++++----- ....keycloak.hash.PasswordHashProviderFactory | 1 + .../services/org.keycloak.provider.Spi | 3 +- .../infinispan/DefaultCacheUserProvider.java | 12 +++--- .../org/keycloak/models/jpa/GroupAdapter.java | 4 +- .../keycloak/models/jpa/JpaUserProvider.java | 34 +++++++-------- .../org/keycloak/models/jpa/UserAdapter.java | 10 +++-- .../keycloak/adapters/MongoUserProvider.java | 10 ++--- .../mongo/keycloak/adapters/UserAdapter.java | 5 ++- .../AbstractUsernameFormAuthenticator.java | 2 +- .../browser/OTPFormAuthenticator.java | 2 +- .../browser/SpnegoAuthenticator.java | 2 +- .../directgrant/ValidateOTP.java | 2 +- .../directgrant/ValidatePassword.java | 2 +- .../requiredactions/UpdateTotp.java | 2 +- .../services/resources/AccountService.java | 4 +- .../services/org.keycloak.provider.Spi | 2 +- .../exportimport/ExportImportTest.java | 14 ++++++- .../FederationProvidersIntegrationTest.java | 2 +- .../keycloak/testsuite/model/AdapterTest.java | 4 +- .../testsuite/model/MultipleRealmsTest.java | 8 ++-- .../performance/ReadUsersWorker.java | 2 +- .../org/keycloak/wildfly/adduser/AddUser.java | 5 ++- 32 files changed, 257 insertions(+), 78 deletions(-) create mode 100644 model/api/src/main/java/org/keycloak/hash/DefaultPasswordHashProvider.java create mode 100644 model/api/src/main/java/org/keycloak/hash/DefaultPasswordHashProviderFactory.java create mode 100644 model/api/src/main/java/org/keycloak/hash/PasswordHashProvider.java create mode 100644 model/api/src/main/java/org/keycloak/hash/PasswordHashProviderFactory.java create mode 100644 model/api/src/main/java/org/keycloak/hash/PasswordHashSpi.java create mode 100644 model/api/src/main/resources/META-INF/services/org.keycloak.hash.PasswordHashProviderFactory diff --git a/model/api/src/main/java/org/keycloak/hash/DefaultPasswordHashProvider.java b/model/api/src/main/java/org/keycloak/hash/DefaultPasswordHashProvider.java new file mode 100644 index 0000000000..c8a5b38ba3 --- /dev/null +++ b/model/api/src/main/java/org/keycloak/hash/DefaultPasswordHashProvider.java @@ -0,0 +1,41 @@ +package org.keycloak.hash; + +import static org.keycloak.models.utils.Pbkdf2PasswordEncoder.getSalt; + +import org.keycloak.models.utils.Pbkdf2PasswordEncoder; + +/** + * @author Kunal Kerkar + */ +public class DefaultPasswordHashProvider implements PasswordHashProvider { + + private final String algorithm; + private final int iterations; + + public DefaultPasswordHashProvider() { + this.algorithm = "pbkdf2"; + this.iterations = 1; + } + + public String encode(String rawPassword, byte[] salt) { + return this.encode(rawPassword, salt, this.iterations); + } + + public String encode(String rawPassword, byte[] salt, int iterations) { + Pbkdf2PasswordEncoder encoder = new Pbkdf2PasswordEncoder(salt, iterations); + return encoder.encode(rawPassword); + } + + public boolean verify(String rawPassword, String encodedPassword, byte[] salt) { + Pbkdf2PasswordEncoder encoder = new Pbkdf2PasswordEncoder(salt); + return encoder.verify(rawPassword, encodedPassword); + } + + public String getAlgorithm() { + return this.algorithm; + } + + public void close() { + } + +} diff --git a/model/api/src/main/java/org/keycloak/hash/DefaultPasswordHashProviderFactory.java b/model/api/src/main/java/org/keycloak/hash/DefaultPasswordHashProviderFactory.java new file mode 100644 index 0000000000..8d3da0e8ea --- /dev/null +++ b/model/api/src/main/java/org/keycloak/hash/DefaultPasswordHashProviderFactory.java @@ -0,0 +1,35 @@ +package org.keycloak.hash; + +import org.keycloak.Config; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionFactory; + +/** + * @author Kunal Kerkar + */ +public class DefaultPasswordHashProviderFactory implements PasswordHashProviderFactory { + + @Override + public PasswordHashProvider create(KeycloakSession session) { + return new DefaultPasswordHashProvider(); + } + + @Override + public void init(Config.Scope config) { + } + + @Override + public void postInit(KeycloakSessionFactory factory) { + } + + @Override + public String getId() { + return "pbkdf2"; + } + + @Override + public void close() { + } + +} + diff --git a/model/api/src/main/java/org/keycloak/hash/PasswordHashProvider.java b/model/api/src/main/java/org/keycloak/hash/PasswordHashProvider.java new file mode 100644 index 0000000000..34dc14af7a --- /dev/null +++ b/model/api/src/main/java/org/keycloak/hash/PasswordHashProvider.java @@ -0,0 +1,16 @@ +package org.keycloak.hash; + +import org.keycloak.provider.Provider; + +/** + * @author Kunal Kerkar + */ +public interface PasswordHashProvider extends Provider { + + String encode(String rawPassword, byte[] salt); + + String encode(String rawPassword, byte[] salt, int iterations); + + boolean verify(String rawPassword, String encodedPassword, byte[] salt); + +} diff --git a/model/api/src/main/java/org/keycloak/hash/PasswordHashProviderFactory.java b/model/api/src/main/java/org/keycloak/hash/PasswordHashProviderFactory.java new file mode 100644 index 0000000000..5c16baa870 --- /dev/null +++ b/model/api/src/main/java/org/keycloak/hash/PasswordHashProviderFactory.java @@ -0,0 +1,10 @@ +package org.keycloak.hash; + +import org.keycloak.provider.ProviderFactory; + +/** + * @author Kunal Kerkar + */ +public interface PasswordHashProviderFactory extends ProviderFactory { + +} diff --git a/model/api/src/main/java/org/keycloak/hash/PasswordHashSpi.java b/model/api/src/main/java/org/keycloak/hash/PasswordHashSpi.java new file mode 100644 index 0000000000..3f1d9afb58 --- /dev/null +++ b/model/api/src/main/java/org/keycloak/hash/PasswordHashSpi.java @@ -0,0 +1,31 @@ +package org.keycloak.hash; + +import org.keycloak.provider.Provider; +import org.keycloak.provider.ProviderFactory; +import org.keycloak.provider.Spi; + +/** + * @author Kunal Kerkar + */ +public class PasswordHashSpi implements Spi { + + @Override + public boolean isInternal() { + return false; + } + + @Override + public String getName() { + return "password-hash"; + } + + @Override + public Class getProviderClass() { + return PasswordHashProvider.class; + } + + @Override + public Class getProviderFactoryClass() { + return PasswordHashProviderFactory.class; + } +} diff --git a/model/api/src/main/java/org/keycloak/models/Constants.java b/model/api/src/main/java/org/keycloak/models/Constants.java index 0c529293f2..159d94392e 100755 --- a/model/api/src/main/java/org/keycloak/models/Constants.java +++ b/model/api/src/main/java/org/keycloak/models/Constants.java @@ -21,6 +21,8 @@ public interface Constants { String[] BROKER_SERVICE_ROLES = {READ_TOKEN_ROLE}; String OFFLINE_ACCESS_ROLE = OAuth2Constants.OFFLINE_ACCESS; + String DEFAULT_HASH_ALGORITHM = "pbkdf2"; + // 15 minutes int DEFAULT_ACCESS_TOKEN_LIFESPAN_FOR_IMPLICIT_FLOW_TIMEOUT = 900; // 30 days diff --git a/model/api/src/main/java/org/keycloak/models/UserCredentialModel.java b/model/api/src/main/java/org/keycloak/models/UserCredentialModel.java index 7e2bfb4658..1929c9f700 100755 --- a/model/api/src/main/java/org/keycloak/models/UserCredentialModel.java +++ b/model/api/src/main/java/org/keycloak/models/UserCredentialModel.java @@ -21,6 +21,7 @@ public class UserCredentialModel { protected String type; protected String value; protected String device; + protected String algorithm; public UserCredentialModel() { } @@ -107,4 +108,12 @@ public class UserCredentialModel { public void setDevice(String device) { this.device = device; } + + public String getAlgorithm() { + return algorithm; + } + + public void setAlgorithm(String algorithm) { + this.algorithm = algorithm; + } } diff --git a/model/api/src/main/java/org/keycloak/models/UserFederationManager.java b/model/api/src/main/java/org/keycloak/models/UserFederationManager.java index 1a1709b8dd..8c64dafb2a 100755 --- a/model/api/src/main/java/org/keycloak/models/UserFederationManager.java +++ b/model/api/src/main/java/org/keycloak/models/UserFederationManager.java @@ -403,7 +403,7 @@ public class UserFederationManager implements UserProvider { } @Override - public boolean validCredentials(RealmModel realm, UserModel user, List input) { + public boolean validCredentials(KeycloakSession session, RealmModel realm, UserModel user, List input) { UserFederationProvider link = getFederationLink(realm, user); if (link != null) { validateUser(realm, user); @@ -421,10 +421,10 @@ public class UserFederationManager implements UserProvider { if (!link.validCredentials(realm, user, fedCreds)) { return false; } - return session.userStorage().validCredentials(realm, user, localCreds); + return session.userStorage().validCredentials(session, realm, user, localCreds); } } - return session.userStorage().validCredentials(realm, user, input); + return session.userStorage().validCredentials(session, realm, user, input); } /** @@ -466,12 +466,12 @@ public class UserFederationManager implements UserProvider { @Override - public boolean validCredentials(RealmModel realm, UserModel user, UserCredentialModel... input) { - return validCredentials(realm, user, Arrays.asList(input)); + public boolean validCredentials(KeycloakSession session, RealmModel realm, UserModel user, UserCredentialModel... input) { + return validCredentials(session, realm, user, Arrays.asList(input)); } @Override - public CredentialValidationOutput validCredentials(RealmModel realm, UserCredentialModel... input) { + public CredentialValidationOutput validCredentials(KeycloakSession session, RealmModel realm, UserCredentialModel... input) { List fedProviderModels = realm.getUserFederationProviders(); List fedProviders = new ArrayList(); for (UserFederationProviderModel fedProviderModel : fedProviderModels) { diff --git a/model/api/src/main/java/org/keycloak/models/UserProvider.java b/model/api/src/main/java/org/keycloak/models/UserProvider.java index 962bc772c0..1c2b5c7158 100755 --- a/model/api/src/main/java/org/keycloak/models/UserProvider.java +++ b/model/api/src/main/java/org/keycloak/models/UserProvider.java @@ -59,9 +59,9 @@ public interface UserProvider extends Provider { void preRemove(RealmModel realm, ClientModel client); void preRemove(ClientModel realm, ProtocolMapperModel protocolMapper); - boolean validCredentials(RealmModel realm, UserModel user, List input); - boolean validCredentials(RealmModel realm, UserModel user, UserCredentialModel... input); - CredentialValidationOutput validCredentials(RealmModel realm, UserCredentialModel... input); + boolean validCredentials(KeycloakSession session, RealmModel realm, UserModel user, List input); + boolean validCredentials(KeycloakSession session, RealmModel realm, UserModel user, UserCredentialModel... input); + CredentialValidationOutput validCredentials(KeycloakSession session, RealmModel realm, UserCredentialModel... input); void close(); } diff --git a/model/api/src/main/java/org/keycloak/models/utils/CredentialValidation.java b/model/api/src/main/java/org/keycloak/models/utils/CredentialValidation.java index d3e4d4ab25..49623e5331 100755 --- a/model/api/src/main/java/org/keycloak/models/utils/CredentialValidation.java +++ b/model/api/src/main/java/org/keycloak/models/utils/CredentialValidation.java @@ -1,8 +1,12 @@ package org.keycloak.models.utils; +import static org.keycloak.models.utils.Pbkdf2PasswordEncoder.getSalt; + import org.keycloak.jose.jws.JWSInput; import org.keycloak.jose.jws.JWSInputException; import org.keycloak.jose.jws.crypto.RSAProvider; +import org.keycloak.models.Constants; +import org.keycloak.models.KeycloakSession; import org.keycloak.models.OTPPolicy; import org.keycloak.models.PasswordPolicy; import org.keycloak.models.RealmModel; @@ -11,6 +15,7 @@ import org.keycloak.models.UserCredentialValueModel; import org.keycloak.models.UserModel; import org.keycloak.representations.PasswordToken; import org.keycloak.common.util.Time; +import org.keycloak.hash.PasswordHashProvider; import java.io.IOException; import java.util.List; @@ -38,7 +43,7 @@ public class CredentialValidation { * @param password * @return */ - public static boolean validPassword(RealmModel realm, UserModel user, String password) { + public static boolean validPassword(KeycloakSession session, RealmModel realm, UserModel user, String password) { UserCredentialValueModel passwordCred = null; for (UserCredentialValueModel cred : user.getCredentialsDirectly()) { if (cred.getType().equals(UserCredentialModel.PASSWORD)) { @@ -47,16 +52,30 @@ public class CredentialValidation { } if (passwordCred == null) return false; - return validateHashedCredential(realm, user, password, passwordCred); + return validateHashedCredential(session, realm, user, password, passwordCred); } - public static boolean validateHashedCredential(RealmModel realm, UserModel user, String unhashedCredValue, UserCredentialValueModel credential) { + + public static boolean validateHashedCredential(KeycloakSession session, RealmModel realm, UserModel user, String unhashedCredValue, UserCredentialValueModel credential) { if(unhashedCredValue == null){ return false; } + String algorithm; + if (credential.getAlgorithm() == null || credential.getAlgorithm().trim().equals("")) { + algorithm = Constants.DEFAULT_HASH_ALGORITHM; + } else { + algorithm = credential.getAlgorithm(); + } + + byte[] salt = getSalt(); + + PasswordHashProvider provider = session.getProvider(PasswordHashProvider.class, algorithm); + + byte[] credSalt = credential.getSalt(); + + boolean validated = provider.verify(unhashedCredValue, credential.getValue(), credSalt); - boolean validated = new Pbkdf2PasswordEncoder(credential.getSalt()).verify(unhashedCredValue, credential.getValue(), credential.getHashIterations()); if (validated) { int iterations = hashIterations(realm); if (iterations > -1 && iterations != credential.getHashIterations()) { @@ -65,7 +84,7 @@ public class CredentialValidation { newCred.setDevice(credential.getDevice()); newCred.setSalt(credential.getSalt()); newCred.setHashIterations(iterations); - newCred.setValue(new Pbkdf2PasswordEncoder(newCred.getSalt()).encode(unhashedCredValue, iterations)); + newCred.setValue(provider.encode(unhashedCredValue, salt, iterations)); user.updateCredentialDirectly(newCred); } @@ -157,9 +176,9 @@ public class CredentialValidation { * @param credentials * @return */ - public static boolean validCredentials(RealmModel realm, UserModel user, List credentials) { + public static boolean validCredentials(KeycloakSession session, RealmModel realm, UserModel user, List credentials) { for (UserCredentialModel credential : credentials) { - if (!validCredential(realm, user, credential)) return false; + if (!validCredential(session, realm, user, credential)) return false; } return true; } @@ -172,16 +191,16 @@ public class CredentialValidation { * @param credentials * @return */ - public static boolean validCredentials(RealmModel realm, UserModel user, UserCredentialModel... credentials) { + public static boolean validCredentials(KeycloakSession session, RealmModel realm, UserModel user, UserCredentialModel... credentials) { for (UserCredentialModel credential : credentials) { - if (!validCredential(realm, user, credential)) return false; + if (!validCredential(session, realm, user, credential)) return false; } return true; } - private static boolean validCredential(RealmModel realm, UserModel user, UserCredentialModel credential) { + private static boolean validCredential(KeycloakSession session, RealmModel realm, UserModel user, UserCredentialModel credential) { if (credential.getType().equals(UserCredentialModel.PASSWORD)) { - if (!validPassword(realm, user, credential.getValue())) { + if (!validPassword(session, realm, user, credential.getValue())) { return false; } } else if (credential.getType().equals(UserCredentialModel.PASSWORD_TOKEN)) { diff --git a/model/api/src/main/resources/META-INF/services/org.keycloak.hash.PasswordHashProviderFactory b/model/api/src/main/resources/META-INF/services/org.keycloak.hash.PasswordHashProviderFactory new file mode 100644 index 0000000000..56193f6010 --- /dev/null +++ b/model/api/src/main/resources/META-INF/services/org.keycloak.hash.PasswordHashProviderFactory @@ -0,0 +1 @@ +org.keycloak.hash.DefaultPasswordHashProviderFactory diff --git a/model/api/src/main/resources/META-INF/services/org.keycloak.provider.Spi b/model/api/src/main/resources/META-INF/services/org.keycloak.provider.Spi index be3982bce4..fd8c0fd6fd 100755 --- a/model/api/src/main/resources/META-INF/services/org.keycloak.provider.Spi +++ b/model/api/src/main/resources/META-INF/services/org.keycloak.provider.Spi @@ -4,4 +4,5 @@ org.keycloak.models.RealmSpi org.keycloak.models.UserSessionSpi org.keycloak.models.UserSpi org.keycloak.models.session.UserSessionPersisterSpi -org.keycloak.migration.MigrationSpi \ No newline at end of file +org.keycloak.migration.MigrationSpi +org.keycloak.hash.PasswordHashSpi diff --git a/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/DefaultCacheUserProvider.java b/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/DefaultCacheUserProvider.java index 44260a13ae..c7f5bf2325 100755 --- a/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/DefaultCacheUserProvider.java +++ b/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/DefaultCacheUserProvider.java @@ -299,18 +299,18 @@ public class DefaultCacheUserProvider implements CacheUserProvider { } @Override - public boolean validCredentials(RealmModel realm, UserModel user, List input) { - return getDelegate().validCredentials(realm, user, input); + public boolean validCredentials(KeycloakSession session, RealmModel realm, UserModel user, List input) { + return getDelegate().validCredentials(session, realm, user, input); } @Override - public boolean validCredentials(RealmModel realm, UserModel user, UserCredentialModel... input) { - return getDelegate().validCredentials(realm, user, input); + public boolean validCredentials(KeycloakSession session, RealmModel realm, UserModel user, UserCredentialModel... input) { + return getDelegate().validCredentials(session, realm, user, input); } @Override - public CredentialValidationOutput validCredentials(RealmModel realm, UserCredentialModel... input) { - return getDelegate().validCredentials(realm, input); + public CredentialValidationOutput validCredentials(KeycloakSession session, RealmModel realm, UserCredentialModel... input) { + return getDelegate().validCredentials(session, realm, input); } @Override diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/GroupAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/GroupAdapter.java index 8ef4dd20a6..9f6fdbabd5 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/GroupAdapter.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/GroupAdapter.java @@ -4,6 +4,7 @@ import org.keycloak.common.util.MultivaluedHashMap; import org.keycloak.common.util.Time; import org.keycloak.models.ClientModel; import org.keycloak.models.GroupModel; +import org.keycloak.models.KeycloakSession; import org.keycloak.models.ModelDuplicateException; import org.keycloak.models.ModelException; import org.keycloak.models.OTPPolicy; @@ -29,7 +30,6 @@ import org.keycloak.models.jpa.entities.UserEntity; import org.keycloak.models.jpa.entities.UserRequiredActionEntity; import org.keycloak.models.jpa.entities.UserRoleMappingEntity; import org.keycloak.models.utils.KeycloakModelUtils; -import org.keycloak.models.utils.Pbkdf2PasswordEncoder; import javax.persistence.EntityManager; import javax.persistence.TypedQuery; @@ -43,8 +43,6 @@ import java.util.List; import java.util.Map; import java.util.Set; -import static org.keycloak.models.utils.Pbkdf2PasswordEncoder.getSalt; - /** * @author Bill Burke * @version $Revision: 1 $ diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java index 64650c9d1d..b536a9c459 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java @@ -59,7 +59,7 @@ public class JpaUserProvider implements UserProvider { entity.setRealmId(realm.getId()); em.persist(entity); em.flush(); - UserModel userModel = new UserAdapter(realm, em, entity); + UserModel userModel = new UserAdapter(session, realm, em, entity); if (addDefaultRoles) { for (String r : realm.getDefaultRoles()) { @@ -241,7 +241,7 @@ public class JpaUserProvider implements UserProvider { List users = new ArrayList(); for (UserEntity user : results) { - users.add(new UserAdapter(realm, em, user)); + users.add(new UserAdapter(session, realm, em, user)); } return users; } @@ -259,7 +259,7 @@ public class JpaUserProvider implements UserProvider { query.setParameter("realmId", realm.getId()); List entities = query.getResultList(); if (entities.size() == 0) return null; - return new UserAdapter(realm, em, entities.get(0)); + return new UserAdapter(session, realm, em, entities.get(0)); } @Override @@ -269,7 +269,7 @@ public class JpaUserProvider implements UserProvider { query.setParameter("realmId", realm.getId()); List results = query.getResultList(); if (results.size() == 0) return null; - return new UserAdapter(realm, em, results.get(0)); + return new UserAdapter(session, realm, em, results.get(0)); } @Override @@ -278,7 +278,7 @@ public class JpaUserProvider implements UserProvider { query.setParameter("email", email.toLowerCase()); query.setParameter("realmId", realm.getId()); List results = query.getResultList(); - return results.isEmpty() ? null : new UserAdapter(realm, em, results.get(0)); + return results.isEmpty() ? null : new UserAdapter(session, realm, em, results.get(0)); } @Override @@ -299,7 +299,7 @@ public class JpaUserProvider implements UserProvider { ", userId=" + identity.getUserId() + ", results=" + results); } else { UserEntity user = results.get(0); - return new UserAdapter(realm, em, user); + return new UserAdapter(session, realm, em, user); } } @@ -316,7 +316,7 @@ public class JpaUserProvider implements UserProvider { ", results=" + results); } else { UserEntity user = results.get(0); - return new UserAdapter(client.getRealm(), em, user); + return new UserAdapter(session, client.getRealm(), em, user); } } @@ -347,7 +347,7 @@ public class JpaUserProvider implements UserProvider { } List results = query.getResultList(); List users = new ArrayList(); - for (UserEntity entity : results) users.add(new UserAdapter(realm, em, entity)); + for (UserEntity entity : results) users.add(new UserAdapter(session, realm, em, entity)); return users; } @@ -365,7 +365,7 @@ public class JpaUserProvider implements UserProvider { List users = new ArrayList(); for (UserEntity user : results) { - users.add(new UserAdapter(realm, em, user)); + users.add(new UserAdapter(session, realm, em, user)); } return users; } @@ -388,7 +388,7 @@ public class JpaUserProvider implements UserProvider { } List results = query.getResultList(); List users = new ArrayList(); - for (UserEntity entity : results) users.add(new UserAdapter(realm, em, entity)); + for (UserEntity entity : results) users.add(new UserAdapter(session, realm, em, entity)); return users; } @@ -446,7 +446,7 @@ public class JpaUserProvider implements UserProvider { } List results = query.getResultList(); List users = new ArrayList(); - for (UserEntity entity : results) users.add(new UserAdapter(realm, em, entity)); + for (UserEntity entity : results) users.add(new UserAdapter(session, realm, em, entity)); return users; } @@ -460,7 +460,7 @@ public class JpaUserProvider implements UserProvider { List users = new ArrayList(); for (UserAttributeEntity attr : results) { UserEntity user = attr.getUser(); - users.add(new UserAdapter(realm, em, user)); + users.add(new UserAdapter(session, realm, em, user)); } return users; } @@ -495,17 +495,17 @@ public class JpaUserProvider implements UserProvider { } @Override - public boolean validCredentials(RealmModel realm, UserModel user, List input) { - return CredentialValidation.validCredentials(realm, user, input); + public boolean validCredentials(KeycloakSession session, RealmModel realm, UserModel user, List input) { + return CredentialValidation.validCredentials(session, realm, user, input); } @Override - public boolean validCredentials(RealmModel realm, UserModel user, UserCredentialModel... input) { - return CredentialValidation.validCredentials(realm, user, input); + public boolean validCredentials(KeycloakSession session, RealmModel realm, UserModel user, UserCredentialModel... input) { + return CredentialValidation.validCredentials(session, realm, user, input); } @Override - public CredentialValidationOutput validCredentials(RealmModel realm, UserCredentialModel... input) { + public CredentialValidationOutput validCredentials(KeycloakSession session, RealmModel realm, UserCredentialModel... input) { // Not supported yet return null; } diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java index 5246890979..8594bcaa0c 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java @@ -2,6 +2,7 @@ package org.keycloak.models.jpa; import org.keycloak.models.ClientModel; import org.keycloak.models.GroupModel; +import org.keycloak.models.KeycloakSession; import org.keycloak.models.OTPPolicy; import org.keycloak.models.ProtocolMapperModel; import org.keycloak.models.UserConsentModel; @@ -24,9 +25,9 @@ import org.keycloak.models.jpa.entities.UserGroupMembershipEntity; import org.keycloak.models.jpa.entities.UserRequiredActionEntity; import org.keycloak.models.jpa.entities.UserRoleMappingEntity; import org.keycloak.models.utils.KeycloakModelUtils; -import org.keycloak.models.utils.Pbkdf2PasswordEncoder; import org.keycloak.common.util.MultivaluedHashMap; import org.keycloak.common.util.Time; +import org.keycloak.hash.PasswordHashProvider; import javax.persistence.EntityManager; import javax.persistence.TypedQuery; @@ -52,11 +53,13 @@ public class UserAdapter implements UserModel { protected UserEntity user; protected EntityManager em; protected RealmModel realm; + private final KeycloakSession session; - public UserAdapter(RealmModel realm, EntityManager em, UserEntity user) { + public UserAdapter(KeycloakSession session, RealmModel realm, EntityManager em, UserEntity user) { this.em = em; this.user = user; this.realm = realm; + this.session = session; } public UserEntity getUser() { @@ -389,6 +392,7 @@ public class UserAdapter implements UserModel { private void setValue(CredentialEntity credentialEntity, UserCredentialModel cred) { byte[] salt = getSalt(); int hashIterations = 1; + PasswordHashProvider provider = session.getProvider(PasswordHashProvider.class); PasswordPolicy policy = realm.getPasswordPolicy(); if (policy != null) { hashIterations = policy.getHashIterations(); @@ -396,7 +400,7 @@ public class UserAdapter implements UserModel { hashIterations = 1; } credentialEntity.setCreatedDate(Time.toMillis(Time.currentTime())); - credentialEntity.setValue(new Pbkdf2PasswordEncoder(salt).encode(cred.getValue(), hashIterations)); + credentialEntity.setValue(provider.encode(cred.getValue(), salt, hashIterations)); credentialEntity.setSalt(salt); credentialEntity.setHashIterations(hashIterations); } diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java index 44758b7fb3..3b5a1acf8d 100755 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java @@ -474,17 +474,17 @@ public class MongoUserProvider implements UserProvider { } @Override - public boolean validCredentials(RealmModel realm, UserModel user, List input) { - return CredentialValidation.validCredentials(realm, user, input); + public boolean validCredentials(KeycloakSession session, RealmModel realm, UserModel user, List input) { + return CredentialValidation.validCredentials(session, realm, user, input); } @Override - public boolean validCredentials(RealmModel realm, UserModel user, UserCredentialModel... input) { - return CredentialValidation.validCredentials(realm, user, input); + public boolean validCredentials(KeycloakSession session, RealmModel realm, UserModel user, UserCredentialModel... input) { + return CredentialValidation.validCredentials(session, realm, user, input); } @Override - public CredentialValidationOutput validCredentials(RealmModel realm, UserCredentialModel... input) { + public CredentialValidationOutput validCredentials(KeycloakSession session, RealmModel realm, UserCredentialModel... input) { // Not supported yet return null; } diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java index 83979c3dff..3137ab4407 100755 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java @@ -26,8 +26,8 @@ import org.keycloak.models.mongo.keycloak.entities.MongoUserConsentEntity; import org.keycloak.models.mongo.keycloak.entities.MongoUserEntity; import org.keycloak.models.mongo.utils.MongoModelUtils; import org.keycloak.models.utils.KeycloakModelUtils; -import org.keycloak.models.utils.Pbkdf2PasswordEncoder; import org.keycloak.common.util.Time; +import org.keycloak.hash.PasswordHashProvider; import java.util.ArrayList; import java.util.Collections; @@ -331,6 +331,7 @@ public class UserAdapter extends AbstractMongoAdapter implement private void setValue(CredentialEntity credentialEntity, UserCredentialModel cred) { byte[] salt = getSalt(); int hashIterations = 1; + PasswordHashProvider provider = session.getProvider(PasswordHashProvider.class); PasswordPolicy policy = realm.getPasswordPolicy(); if (policy != null) { hashIterations = policy.getHashIterations(); @@ -338,7 +339,7 @@ public class UserAdapter extends AbstractMongoAdapter implement hashIterations = 1; } credentialEntity.setCreatedDate(Time.toMillis(Time.currentTime())); - credentialEntity.setValue(new Pbkdf2PasswordEncoder(salt).encode(cred.getValue(), hashIterations)); + credentialEntity.setValue(provider.encode(cred.getValue(), salt, hashIterations)); credentialEntity.setSalt(salt); credentialEntity.setHashIterations(hashIterations); } diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/browser/AbstractUsernameFormAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/browser/AbstractUsernameFormAuthenticator.java index 5afaa3d6b7..68b036fa18 100755 --- a/services/src/main/java/org/keycloak/authentication/authenticators/browser/AbstractUsernameFormAuthenticator.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/browser/AbstractUsernameFormAuthenticator.java @@ -149,7 +149,7 @@ public abstract class AbstractUsernameFormAuthenticator extends AbstractFormAuth List credentials = new LinkedList<>(); String password = inputData.getFirst(CredentialRepresentation.PASSWORD); credentials.add(UserCredentialModel.password(password)); - boolean valid = context.getSession().users().validCredentials(context.getRealm(), user, credentials); + boolean valid = context.getSession().users().validCredentials(context.getSession(), context.getRealm(), user, credentials); if (!valid) { context.getEvent().user(user); context.getEvent().error(Errors.INVALID_USER_CREDENTIALS); diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/browser/OTPFormAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/browser/OTPFormAuthenticator.java index 4a0c48dc48..3991011178 100755 --- a/services/src/main/java/org/keycloak/authentication/authenticators/browser/OTPFormAuthenticator.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/browser/OTPFormAuthenticator.java @@ -49,7 +49,7 @@ public class OTPFormAuthenticator extends AbstractUsernameFormAuthenticator impl return; } credentials.add(UserCredentialModel.otp(context.getRealm().getOTPPolicy().getType(), password)); - boolean valid = context.getSession().users().validCredentials(context.getRealm(), context.getUser(), credentials); + boolean valid = context.getSession().users().validCredentials(context.getSession(), context.getRealm(), context.getUser(), credentials); if (!valid) { context.getEvent().user(context.getUser()) .error(Errors.INVALID_USER_CREDENTIALS); diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/browser/SpnegoAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/browser/SpnegoAuthenticator.java index 091c14d14c..92d3f2130f 100755 --- a/services/src/main/java/org/keycloak/authentication/authenticators/browser/SpnegoAuthenticator.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/browser/SpnegoAuthenticator.java @@ -69,7 +69,7 @@ public class SpnegoAuthenticator extends AbstractUsernameFormAuthenticator imple String spnegoToken = tokens[1]; UserCredentialModel spnegoCredential = UserCredentialModel.kerberos(spnegoToken); - CredentialValidationOutput output = context.getSession().users().validCredentials(context.getRealm(), spnegoCredential); + CredentialValidationOutput output = context.getSession().users().validCredentials(context.getSession(), context.getRealm(), spnegoCredential); if (output.getAuthStatus() == CredentialValidationOutput.Status.AUTHENTICATED) { context.setUser(output.getAuthenticatedUser()); diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/directgrant/ValidateOTP.java b/services/src/main/java/org/keycloak/authentication/authenticators/directgrant/ValidateOTP.java index c26da77354..f555ae6526 100755 --- a/services/src/main/java/org/keycloak/authentication/authenticators/directgrant/ValidateOTP.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/directgrant/ValidateOTP.java @@ -51,7 +51,7 @@ public class ValidateOTP extends AbstractDirectGrantAuthenticator { return; } credentials.add(UserCredentialModel.otp(context.getRealm().getOTPPolicy().getType(), otp)); - boolean valid = context.getSession().users().validCredentials(context.getRealm(), context.getUser(), credentials); + boolean valid = context.getSession().users().validCredentials(context.getSession(), context.getRealm(), context.getUser(), credentials); if (!valid) { context.getEvent().user(context.getUser()); context.getEvent().error(Errors.INVALID_USER_CREDENTIALS); diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/directgrant/ValidatePassword.java b/services/src/main/java/org/keycloak/authentication/authenticators/directgrant/ValidatePassword.java index 21aa18d259..0bb89e43ee 100755 --- a/services/src/main/java/org/keycloak/authentication/authenticators/directgrant/ValidatePassword.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/directgrant/ValidatePassword.java @@ -32,7 +32,7 @@ public class ValidatePassword extends AbstractDirectGrantAuthenticator { List credentials = new LinkedList<>(); String password = inputData.getFirst(CredentialRepresentation.PASSWORD); credentials.add(UserCredentialModel.password(password)); - boolean valid = context.getSession().users().validCredentials(context.getRealm(), context.getUser(), credentials); + boolean valid = context.getSession().users().validCredentials(context.getSession(), context.getRealm(), context.getUser(), credentials); if (!valid) { context.getEvent().user(context.getUser()); context.getEvent().error(Errors.INVALID_USER_CREDENTIALS); diff --git a/services/src/main/java/org/keycloak/authentication/requiredactions/UpdateTotp.java b/services/src/main/java/org/keycloak/authentication/requiredactions/UpdateTotp.java index 18e6682ecd..6c7415492a 100755 --- a/services/src/main/java/org/keycloak/authentication/requiredactions/UpdateTotp.java +++ b/services/src/main/java/org/keycloak/authentication/requiredactions/UpdateTotp.java @@ -73,7 +73,7 @@ public class UpdateTotp implements RequiredActionProvider, RequiredActionFactory UserCredentialModel cred = new UserCredentialModel(); cred.setType(context.getRealm().getOTPPolicy().getType()); cred.setValue(totp); - context.getSession().users().validCredentials(context.getRealm(), context.getUser(), cred); + context.getSession().users().validCredentials(context.getSession(), context.getRealm(), context.getUser(), cred); context.getUser().setOtpEnabled(true); context.success(); diff --git a/services/src/main/java/org/keycloak/services/resources/AccountService.java b/services/src/main/java/org/keycloak/services/resources/AccountService.java index 1394832ae4..08ad37a533 100755 --- a/services/src/main/java/org/keycloak/services/resources/AccountService.java +++ b/services/src/main/java/org/keycloak/services/resources/AccountService.java @@ -572,7 +572,7 @@ public class AccountService extends AbstractSecuredLocalService { UserCredentialModel cred = new UserCredentialModel(); cred.setType(realm.getOTPPolicy().getType()); cred.setValue(totp); - session.users().validCredentials(realm, user, cred); + session.users().validCredentials(session, realm, user, cred); event.event(EventType.UPDATE_TOTP).client(auth.getClient()).user(auth.getUser()).success(); @@ -619,7 +619,7 @@ public class AccountService extends AbstractSecuredLocalService { } UserCredentialModel cred = UserCredentialModel.password(password); - if (!session.users().validCredentials(realm, user, cred)) { + if (!session.users().validCredentials(session, realm, user, cred)) { setReferrerOnPage(); return account.setError(Messages.INVALID_PASSWORD_EXISTING).createResponse(AccountPages.PASSWORD); } diff --git a/services/src/main/resources/META-INF/services/org.keycloak.provider.Spi b/services/src/main/resources/META-INF/services/org.keycloak.provider.Spi index 511954861f..6189178bfe 100755 --- a/services/src/main/resources/META-INF/services/org.keycloak.provider.Spi +++ b/services/src/main/resources/META-INF/services/org.keycloak.provider.Spi @@ -8,4 +8,4 @@ org.keycloak.authentication.ClientAuthenticatorSpi org.keycloak.authentication.RequiredActionSpi org.keycloak.authentication.FormAuthenticatorSpi org.keycloak.authentication.FormActionSpi -org.keycloak.services.clientregistration.ClientRegistrationSpi \ No newline at end of file +org.keycloak.services.clientregistration.ClientRegistrationSpi diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/exportimport/ExportImportTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/exportimport/ExportImportTest.java index 2f89194d35..b01e1b9905 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/exportimport/ExportImportTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/exportimport/ExportImportTest.java @@ -322,7 +322,12 @@ public class ExportImportTest { Assert.fail("user " + username + " not found"); } - Assert.assertTrue(userProvider.validCredentials(realm, user, UserCredentialModel.password(password))); + KeycloakSession session = keycloakRule.startSession(); + try { + Assert.assertTrue(userProvider.validCredentials(session, realm, user, UserCredentialModel.password(password))); + } finally { + keycloakRule.stopSession(session, true); + } } private void assertNotAuthenticated(UserProvider userProvider, RealmProvider realmProvider, String realmName, String username, String password) { @@ -336,7 +341,12 @@ public class ExportImportTest { return; } - Assert.assertFalse(userProvider.validCredentials(realm, user, UserCredentialModel.password(password))); + KeycloakSession session = keycloakRule.startSession(); + try { + Assert.assertFalse(userProvider.validCredentials(session, realm, user, UserCredentialModel.password(password))); + } finally { + keycloakRule.stopSession(session, true); + } } private static void addUser(UserProvider userProvider, RealmModel appRealm, String username, String password) { diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/FederationProvidersIntegrationTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/FederationProvidersIntegrationTest.java index b67ffa3021..ae2e4bebd2 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/FederationProvidersIntegrationTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/FederationProvidersIntegrationTest.java @@ -678,7 +678,7 @@ public class FederationProvidersIntegrationTest { user.updateCredential(cred); UserCredentialValueModel userCredentialValueModel = user.getCredentialsDirectly().get(0); Assert.assertEquals(UserCredentialModel.PASSWORD, userCredentialValueModel.getType()); - Assert.assertTrue(session.users().validCredentials(appRealm, user, cred)); + Assert.assertTrue(session.users().validCredentials(session, appRealm, user, cred)); // LDAP password is still unchanged LDAPFederationProvider ldapProvider = FederationTestUtils.getLdapProvider(session, model); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/AdapterTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/AdapterTest.java index 8c84581ae7..435f22194a 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/AdapterTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/AdapterTest.java @@ -144,11 +144,11 @@ public class AdapterTest extends AbstractModelTest { cred.setType(CredentialRepresentation.PASSWORD); cred.setValue("geheim"); user.updateCredential(cred); - Assert.assertTrue(userProvider.validCredentials(realmModel, user, UserCredentialModel.password("geheim"))); + Assert.assertTrue(userProvider.validCredentials(session, realmModel, user, UserCredentialModel.password("geheim"))); List creds = user.getCredentialsDirectly(); Assert.assertEquals(creds.get(0).getHashIterations(), 1); realmModel.setPasswordPolicy(new PasswordPolicy("hashIterations(200)")); - Assert.assertTrue(userProvider.validCredentials(realmModel, user, UserCredentialModel.password("geheim"))); + Assert.assertTrue(userProvider.validCredentials(session, realmModel, user, UserCredentialModel.password("geheim"))); creds = user.getCredentialsDirectly(); Assert.assertEquals(creds.get(0).getHashIterations(), 200); realmModel.setPasswordPolicy(new PasswordPolicy("hashIterations(1)")); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/MultipleRealmsTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/MultipleRealmsTest.java index 1a4c660a37..0da22b0818 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/MultipleRealmsTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/MultipleRealmsTest.java @@ -39,10 +39,10 @@ public class MultipleRealmsTest extends AbstractModelTest { r1user1.updateCredential(UserCredentialModel.password("pass1")); r2user1.updateCredential(UserCredentialModel.password("pass2")); - Assert.assertTrue(session.users().validCredentials(realm1, r1user1, UserCredentialModel.password("pass1"))); - Assert.assertFalse(session.users().validCredentials(realm1, r1user1, UserCredentialModel.password("pass2"))); - Assert.assertFalse(session.users().validCredentials(realm2, r2user1, UserCredentialModel.password("pass1"))); - Assert.assertTrue(session.users().validCredentials(realm2, r2user1, UserCredentialModel.password("pass2"))); + Assert.assertTrue(session.users().validCredentials(session, realm1, r1user1, UserCredentialModel.password("pass1"))); + Assert.assertFalse(session.users().validCredentials(session, realm1, r1user1, UserCredentialModel.password("pass2"))); + Assert.assertFalse(session.users().validCredentials(session, realm2, r2user1, UserCredentialModel.password("pass1"))); + Assert.assertTrue(session.users().validCredentials(session, realm2, r2user1, UserCredentialModel.password("pass2"))); // Test searching Assert.assertEquals(2, session.users().searchForUser("user", realm1).size()); diff --git a/testsuite/performance/src/test/java/org/keycloak/testsuite/performance/ReadUsersWorker.java b/testsuite/performance/src/test/java/org/keycloak/testsuite/performance/ReadUsersWorker.java index 3f4a23bd2c..37e36e0836 100755 --- a/testsuite/performance/src/test/java/org/keycloak/testsuite/performance/ReadUsersWorker.java +++ b/testsuite/performance/src/test/java/org/keycloak/testsuite/performance/ReadUsersWorker.java @@ -102,7 +102,7 @@ public class ReadUsersWorker implements Worker { // Validate password (shoould be same as username) if (readPassword) { - session.users().validCredentials(realm, user, UserCredentialModel.password(username)); + session.users().validCredentials(session, realm, user, UserCredentialModel.password(username)); } // Read federatedIdentities of user 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 7be53c6473..47e14009e8 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 @@ -12,6 +12,7 @@ import org.jboss.aesh.console.command.invocation.CommandInvocation; 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.hash.DefaultPasswordHashProvider; import org.keycloak.models.Constants; import org.keycloak.models.utils.Pbkdf2PasswordEncoder; import org.keycloak.representations.idm.CredentialRepresentation; @@ -145,7 +146,7 @@ public class AddUser { credentials.setType(CredentialRepresentation.PASSWORD); credentials.setHashIterations(iterations); credentials.setSalt(Base64.encodeBytes(salt)); - credentials.setHashedSaltedValue(new Pbkdf2PasswordEncoder(salt).encode(password, iterations)); + credentials.setHashedSaltedValue(new DefaultPasswordHashProvider().encode(password, salt, iterations)); user.getCredentials().add(credentials); @@ -289,4 +290,4 @@ public class AddUser { } } -} \ No newline at end of file +} From 321cac2ab86c33d2a6ba9e594657581bc9ef9d78 Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Tue, 8 Dec 2015 13:25:50 +0100 Subject: [PATCH 2/2] KEYCLOAK-1900 Refactoring --- .../theme/base/admin/resources/js/services.js | 2 + .../hash/DefaultPasswordHashProvider.java | 41 ------ .../DefaultPasswordHashProviderFactory.java | 35 ----- .../keycloak/hash/PasswordHashManager.java | 30 +++++ .../keycloak/hash/PasswordHashProvider.java | 7 +- .../hash/Pbkdf2PasswordHashProvider.java | 91 +++++++++++++ .../org/keycloak/models/PasswordPolicy.java | 120 ++++++++++++------ .../models/UserFederationManager.java | 2 +- .../models/utils/CredentialValidation.java | 26 +--- .../models/utils/Pbkdf2PasswordEncoder.java | 106 ---------------- ....keycloak.hash.PasswordHashProviderFactory | 2 +- .../keycloak/models/PasswordPolicyTest.java | 88 ++++++------- .../org/keycloak/models/jpa/UserAdapter.java | 20 +-- .../mongo/keycloak/adapters/UserAdapter.java | 20 +-- .../forms/RegistrationPassword.java | 2 +- .../services/validation/Validation.java | 5 +- .../testsuite/adduser/AddUserTest.java | 18 ++- .../org/keycloak/wildfly/adduser/AddUser.java | 17 ++- 18 files changed, 293 insertions(+), 339 deletions(-) delete mode 100644 model/api/src/main/java/org/keycloak/hash/DefaultPasswordHashProvider.java delete mode 100644 model/api/src/main/java/org/keycloak/hash/DefaultPasswordHashProviderFactory.java create mode 100644 model/api/src/main/java/org/keycloak/hash/PasswordHashManager.java create mode 100644 model/api/src/main/java/org/keycloak/hash/Pbkdf2PasswordHashProvider.java delete mode 100755 model/api/src/main/java/org/keycloak/models/utils/Pbkdf2PasswordEncoder.java diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/services.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/services.js index 4365291c72..7b94680e79 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/services.js +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/services.js @@ -1103,6 +1103,7 @@ module.factory('PasswordPolicy', function() { var p = {}; p.policyMessages = { + hashAlgorithm: "Default hashing algorithm. Default is 'pbkdf2'.", hashIterations: "Number of hashing iterations. Default is 1. Recommended is 50000.", length: "Minimal password length (integer type). Default value is 8.", digits: "Minimal number (integer type) of digits in password. Default value is 1.", @@ -1116,6 +1117,7 @@ module.factory('PasswordPolicy', function() { } p.allPolicies = [ + { name: 'hashAlgorithm', value: 'pbkdf2' }, { name: 'hashIterations', value: 1 }, { name: 'length', value: 8 }, { name: 'digits', value: 1 }, diff --git a/model/api/src/main/java/org/keycloak/hash/DefaultPasswordHashProvider.java b/model/api/src/main/java/org/keycloak/hash/DefaultPasswordHashProvider.java deleted file mode 100644 index c8a5b38ba3..0000000000 --- a/model/api/src/main/java/org/keycloak/hash/DefaultPasswordHashProvider.java +++ /dev/null @@ -1,41 +0,0 @@ -package org.keycloak.hash; - -import static org.keycloak.models.utils.Pbkdf2PasswordEncoder.getSalt; - -import org.keycloak.models.utils.Pbkdf2PasswordEncoder; - -/** - * @author Kunal Kerkar - */ -public class DefaultPasswordHashProvider implements PasswordHashProvider { - - private final String algorithm; - private final int iterations; - - public DefaultPasswordHashProvider() { - this.algorithm = "pbkdf2"; - this.iterations = 1; - } - - public String encode(String rawPassword, byte[] salt) { - return this.encode(rawPassword, salt, this.iterations); - } - - public String encode(String rawPassword, byte[] salt, int iterations) { - Pbkdf2PasswordEncoder encoder = new Pbkdf2PasswordEncoder(salt, iterations); - return encoder.encode(rawPassword); - } - - public boolean verify(String rawPassword, String encodedPassword, byte[] salt) { - Pbkdf2PasswordEncoder encoder = new Pbkdf2PasswordEncoder(salt); - return encoder.verify(rawPassword, encodedPassword); - } - - public String getAlgorithm() { - return this.algorithm; - } - - public void close() { - } - -} diff --git a/model/api/src/main/java/org/keycloak/hash/DefaultPasswordHashProviderFactory.java b/model/api/src/main/java/org/keycloak/hash/DefaultPasswordHashProviderFactory.java deleted file mode 100644 index 8d3da0e8ea..0000000000 --- a/model/api/src/main/java/org/keycloak/hash/DefaultPasswordHashProviderFactory.java +++ /dev/null @@ -1,35 +0,0 @@ -package org.keycloak.hash; - -import org.keycloak.Config; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.KeycloakSessionFactory; - -/** - * @author Kunal Kerkar - */ -public class DefaultPasswordHashProviderFactory implements PasswordHashProviderFactory { - - @Override - public PasswordHashProvider create(KeycloakSession session) { - return new DefaultPasswordHashProvider(); - } - - @Override - public void init(Config.Scope config) { - } - - @Override - public void postInit(KeycloakSessionFactory factory) { - } - - @Override - public String getId() { - return "pbkdf2"; - } - - @Override - public void close() { - } - -} - diff --git a/model/api/src/main/java/org/keycloak/hash/PasswordHashManager.java b/model/api/src/main/java/org/keycloak/hash/PasswordHashManager.java new file mode 100644 index 0000000000..5988a41934 --- /dev/null +++ b/model/api/src/main/java/org/keycloak/hash/PasswordHashManager.java @@ -0,0 +1,30 @@ +package org.keycloak.hash; + +import org.keycloak.models.*; + +/** + * @author Stian Thorgersen + */ +public class PasswordHashManager { + + public static UserCredentialValueModel encode(KeycloakSession session, RealmModel realm, String rawPassword) { + PasswordPolicy passwordPolicy = realm.getPasswordPolicy(); + String algorithm = passwordPolicy.getHashAlgorithm(); + int iterations = passwordPolicy.getHashIterations(); + if (iterations < 1) { + iterations = 1; + } + PasswordHashProvider provider = session.getProvider(PasswordHashProvider.class, algorithm); + if (provider == null) { + throw new RuntimeException("Password hash provider for algorithm " + algorithm + " not found"); + } + return provider.encode(rawPassword, iterations); + } + + public static boolean verify(KeycloakSession session, RealmModel realm, String password, UserCredentialValueModel credential) { + String algorithm = credential.getAlgorithm() != null ? credential.getAlgorithm() : realm.getPasswordPolicy().getHashAlgorithm(); + PasswordHashProvider provider = session.getProvider(PasswordHashProvider.class, algorithm); + return provider.verify(password, credential); + } + +} diff --git a/model/api/src/main/java/org/keycloak/hash/PasswordHashProvider.java b/model/api/src/main/java/org/keycloak/hash/PasswordHashProvider.java index 34dc14af7a..df0e21d9ea 100644 --- a/model/api/src/main/java/org/keycloak/hash/PasswordHashProvider.java +++ b/model/api/src/main/java/org/keycloak/hash/PasswordHashProvider.java @@ -1,5 +1,6 @@ package org.keycloak.hash; +import org.keycloak.models.UserCredentialValueModel; import org.keycloak.provider.Provider; /** @@ -7,10 +8,8 @@ import org.keycloak.provider.Provider; */ public interface PasswordHashProvider extends Provider { - String encode(String rawPassword, byte[] salt); + UserCredentialValueModel encode(String rawPassword, int iterations); - String encode(String rawPassword, byte[] salt, int iterations); - - boolean verify(String rawPassword, String encodedPassword, byte[] salt); + boolean verify(String rawPassword, UserCredentialValueModel credential); } diff --git a/model/api/src/main/java/org/keycloak/hash/Pbkdf2PasswordHashProvider.java b/model/api/src/main/java/org/keycloak/hash/Pbkdf2PasswordHashProvider.java new file mode 100644 index 0000000000..2b2c9b7479 --- /dev/null +++ b/model/api/src/main/java/org/keycloak/hash/Pbkdf2PasswordHashProvider.java @@ -0,0 +1,91 @@ +package org.keycloak.hash; + +import org.keycloak.Config; +import org.keycloak.common.util.Base64; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.models.UserCredentialModel; +import org.keycloak.models.UserCredentialValueModel; + +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.PBEKeySpec; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.KeySpec; + +/** + * @author Kunal Kerkar + */ +public class Pbkdf2PasswordHashProvider implements PasswordHashProviderFactory, PasswordHashProvider { + + public static final String ID = "pbkdf2"; + + private static final String PBKDF2_ALGORITHM = "PBKDF2WithHmacSHA1"; + private static final int DERIVED_KEY_SIZE = 512; + + public UserCredentialValueModel encode(String rawPassword, int iterations) { + byte[] salt = getSalt(); + String encodedPassword = encode(rawPassword, iterations, salt); + + UserCredentialValueModel credentials = new UserCredentialValueModel(); + credentials.setAlgorithm(ID); + credentials.setType(UserCredentialModel.PASSWORD); + credentials.setSalt(salt); + credentials.setHashIterations(iterations); + credentials.setValue(encodedPassword); + return credentials; + } + + public boolean verify(String rawPassword, UserCredentialValueModel credential) { + 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); + + try { + byte[] key = getSecretKeyFactory().generateSecret(spec).getEncoded(); + return Base64.encodeBytes(key); + } catch (InvalidKeySpecException e) { + throw new RuntimeException("Credential could not be encoded"); + } + } + + private byte[] getSalt() { + byte[] buffer = new byte[16]; + SecureRandom secureRandom = new SecureRandom(); + secureRandom.nextBytes(buffer); + return buffer; + } + + private SecretKeyFactory getSecretKeyFactory() { + try { + return SecretKeyFactory.getInstance(PBKDF2_ALGORITHM); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("PBKDF2 algorithm not found"); + } + } + +} diff --git a/model/api/src/main/java/org/keycloak/models/PasswordPolicy.java b/model/api/src/main/java/org/keycloak/models/PasswordPolicy.java index 3674334ffe..b7ed682ae1 100755 --- a/model/api/src/main/java/org/keycloak/models/PasswordPolicy.java +++ b/model/api/src/main/java/org/keycloak/models/PasswordPolicy.java @@ -1,6 +1,7 @@ package org.keycloak.models; -import org.keycloak.models.utils.Pbkdf2PasswordEncoder; +import org.keycloak.hash.PasswordHashManager; +import org.keycloak.hash.PasswordHashProvider; import java.io.Serializable; import java.util.ArrayList; @@ -67,6 +68,8 @@ public class PasswordPolicy implements Serializable { list.add(new SpecialChars(arg)); } else if (name.equals(NotUsername.NAME)) { list.add(new NotUsername(arg)); + } else if (name.equals(HashAlgorithm.NAME)) { + list.add(new HashAlgorithm(arg)); } else if (name.equals(HashIterations.NAME)) { list.add(new HashIterations(arg)); } else if (name.equals(RegexPatterns.NAME)) { @@ -83,6 +86,18 @@ public class PasswordPolicy implements Serializable { return list; } + public String getHashAlgorithm() { + if (policies == null) + return Constants.DEFAULT_HASH_ALGORITHM; + for (Policy p : policies) { + if (p instanceof HashAlgorithm) { + return ((HashAlgorithm) p).algorithm; + } + + } + return Constants.DEFAULT_HASH_ALGORITHM; + } + /** * * @return -1 if no hash iterations setting @@ -131,9 +146,9 @@ public class PasswordPolicy implements Serializable { return -1; } - public Error validate(UserModel user, String password) { + public Error validate(KeycloakSession session, UserModel user, String password) { for (Policy p : policies) { - Error error = p.validate(user, password); + Error error = p.validate(session, user, password); if (error != null) { return error; } @@ -141,9 +156,9 @@ public class PasswordPolicy implements Serializable { return null; } - public Error validate(String user, String password) { + public Error validate(KeycloakSession session, String user, String password) { for (Policy p : policies) { - Error error = p.validate(user, password); + Error error = p.validate(session, user, password); if (error != null) { return error; } @@ -152,8 +167,8 @@ public class PasswordPolicy implements Serializable { } private static interface Policy extends Serializable { - public Error validate(UserModel user, String password); - public Error validate(String user, String password); + public Error validate(KeycloakSession session, UserModel user, String password); + public Error validate(KeycloakSession session, String user, String password); } public static class Error { @@ -174,6 +189,25 @@ public class PasswordPolicy implements Serializable { } } + private static class HashAlgorithm implements Policy { + private static final String NAME = "hashAlgorithm"; + private String algorithm; + + public HashAlgorithm(String arg) { + algorithm = stringArg(NAME, Constants.DEFAULT_HASH_ALGORITHM, arg); + } + + @Override + public Error validate(KeycloakSession session, String user, String password) { + return null; + } + + @Override + public Error validate(KeycloakSession session, UserModel user, String password) { + return null; + } + } + private static class HashIterations implements Policy { private static final String NAME = "hashIterations"; private int iterations; @@ -181,14 +215,14 @@ public class PasswordPolicy implements Serializable { public HashIterations(String arg) { iterations = intArg(NAME, 1, arg); } - + @Override - public Error validate(String user, String password) { + public Error validate(KeycloakSession session, String user, String password) { return null; } - + @Override - public Error validate(UserModel user, String password) { + public Error validate(KeycloakSession session, UserModel user, String password) { return null; } } @@ -200,13 +234,13 @@ public class PasswordPolicy implements Serializable { } @Override - public Error validate(String username, String password) { + public Error validate(KeycloakSession session, String username, String password) { return username.equals(password) ? new Error(INVALID_PASSWORD_NOT_USERNAME) : null; } @Override - public Error validate(UserModel user, String password) { - return validate(user.getUsername(), password); + public Error validate(KeycloakSession session, UserModel user, String password) { + return validate(session, user.getUsername(), password); } } @@ -221,13 +255,13 @@ public class PasswordPolicy implements Serializable { @Override - public Error validate(String username, String password) { + public Error validate(KeycloakSession session, String username, String password) { return password.length() < min ? new Error(INVALID_PASSWORD_MIN_LENGTH_MESSAGE, min) : null; } @Override - public Error validate(UserModel user, String password) { - return validate(user.getUsername(), password); + public Error validate(KeycloakSession session, UserModel user, String password) { + return validate(session, user.getUsername(), password); } } @@ -242,7 +276,7 @@ public class PasswordPolicy implements Serializable { @Override - public Error validate(String username, String password) { + public Error validate(KeycloakSession session, String username, String password) { int count = 0; for (char c : password.toCharArray()) { if (Character.isDigit(c)) { @@ -253,8 +287,8 @@ public class PasswordPolicy implements Serializable { } @Override - public Error validate(UserModel user, String password) { - return validate(user.getUsername(), password); + public Error validate(KeycloakSession session, UserModel user, String password) { + return validate(session, user.getUsername(), password); } } @@ -268,7 +302,7 @@ public class PasswordPolicy implements Serializable { } @Override - public Error validate(String username, String password) { + public Error validate(KeycloakSession session, String username, String password) { int count = 0; for (char c : password.toCharArray()) { if (Character.isLowerCase(c)) { @@ -279,8 +313,8 @@ public class PasswordPolicy implements Serializable { } @Override - public Error validate(UserModel user, String password) { - return validate(user.getUsername(), password); + public Error validate(KeycloakSession session, UserModel user, String password) { + return validate(session, user.getUsername(), password); } } @@ -293,7 +327,7 @@ public class PasswordPolicy implements Serializable { } @Override - public Error validate(String username, String password) { + public Error validate(KeycloakSession session, String username, String password) { int count = 0; for (char c : password.toCharArray()) { if (Character.isUpperCase(c)) { @@ -304,8 +338,8 @@ public class PasswordPolicy implements Serializable { } @Override - public Error validate(UserModel user, String password) { - return validate(user.getUsername(), password); + public Error validate(KeycloakSession session, UserModel user, String password) { + return validate(session, user.getUsername(), password); } } @@ -319,7 +353,7 @@ public class PasswordPolicy implements Serializable { } @Override - public Error validate(String username, String password) { + public Error validate(KeycloakSession session, String username, String password) { int count = 0; for (char c : password.toCharArray()) { if (!Character.isLetterOrDigit(c)) { @@ -330,8 +364,8 @@ public class PasswordPolicy implements Serializable { } @Override - public Error validate(UserModel user, String password) { - return validate(user.getUsername(), password); + public Error validate(KeycloakSession session, UserModel user, String password) { + return validate(session, user.getUsername(), password); } } @@ -345,7 +379,7 @@ public class PasswordPolicy implements Serializable { } @Override - public Error validate(String username, String password) { + public Error validate(KeycloakSession session, String username, String password) { Pattern pattern = Pattern.compile(regexPattern); Matcher matcher = pattern.matcher(password); if (!matcher.matches()) { @@ -355,8 +389,8 @@ public class PasswordPolicy implements Serializable { } @Override - public Error validate(UserModel user, String password) { - return validate(user.getUsername(), password); + public Error validate(KeycloakSession session, UserModel user, String password) { + return validate(session, user.getUsername(), password); } } @@ -370,18 +404,19 @@ public class PasswordPolicy implements Serializable { } @Override - public Error validate(String user, String password) { + public Error validate(KeycloakSession session, String user, String password) { return null; } @Override - public Error validate(UserModel user, String password) { + public Error validate(KeycloakSession session, UserModel user, String password) { if (passwordHistoryPolicyValue != -1) { UserCredentialValueModel cred = getCredentialValueModel(user, UserCredentialModel.PASSWORD); if (cred != null) { - if(new Pbkdf2PasswordEncoder(cred.getSalt()).verify(password, cred.getValue(), cred.getHashIterations())) { + PasswordHashProvider hashProvider = session.getProvider(PasswordHashProvider.class, cred.getAlgorithm()); + if(hashProvider.verify(password, cred)) { return new Error(INVALID_PASSWORD_HISTORY, passwordHistoryPolicyValue); } } @@ -389,7 +424,8 @@ public class PasswordPolicy implements Serializable { List passwordExpiredCredentials = getCredentialValueModels(user, passwordHistoryPolicyValue - 1, UserCredentialModel.PASSWORD_HISTORY); for (UserCredentialValueModel credential : passwordExpiredCredentials) { - if (new Pbkdf2PasswordEncoder(credential.getSalt()).verify(password, credential.getValue(), credential.getHashIterations())) { + PasswordHashProvider hashProvider = session.getProvider(PasswordHashProvider.class, cred.getAlgorithm()); + if (hashProvider.verify(password, credential)) { return new Error(INVALID_PASSWORD_HISTORY, passwordHistoryPolicyValue); } } @@ -444,12 +480,12 @@ public class PasswordPolicy implements Serializable { } @Override - public Error validate(String username, String password) { + public Error validate(KeycloakSession session, String username, String password) { return null; } @Override - public Error validate(UserModel user, String password) { + public Error validate(KeycloakSession session, UserModel user, String password) { return null; } } @@ -462,6 +498,14 @@ public class PasswordPolicy implements Serializable { } } + private static String stringArg(String policy, String defaultValue, String arg) { + if (arg == null) { + return defaultValue; + } else { + return arg; + } + } + @Override public String toString() { return policyString; diff --git a/model/api/src/main/java/org/keycloak/models/UserFederationManager.java b/model/api/src/main/java/org/keycloak/models/UserFederationManager.java index 8c64dafb2a..58344ccb7f 100755 --- a/model/api/src/main/java/org/keycloak/models/UserFederationManager.java +++ b/model/api/src/main/java/org/keycloak/models/UserFederationManager.java @@ -395,7 +395,7 @@ public class UserFederationManager implements UserProvider { public void updateCredential(RealmModel realm, UserModel user, UserCredentialModel credential) { if (credential.getType().equals(UserCredentialModel.PASSWORD)) { if (realm.getPasswordPolicy() != null) { - PasswordPolicy.Error error = realm.getPasswordPolicy().validate(user, credential.getValue()); + PasswordPolicy.Error error = realm.getPasswordPolicy().validate(session, user, credential.getValue()); if (error != null) throw new ModelException(error.getMessage(), error.getParameters()); } } diff --git a/model/api/src/main/java/org/keycloak/models/utils/CredentialValidation.java b/model/api/src/main/java/org/keycloak/models/utils/CredentialValidation.java index 49623e5331..6afc9ed2de 100755 --- a/model/api/src/main/java/org/keycloak/models/utils/CredentialValidation.java +++ b/model/api/src/main/java/org/keycloak/models/utils/CredentialValidation.java @@ -1,7 +1,6 @@ package org.keycloak.models.utils; -import static org.keycloak.models.utils.Pbkdf2PasswordEncoder.getSalt; - +import org.keycloak.hash.PasswordHashManager; import org.keycloak.jose.jws.JWSInput; import org.keycloak.jose.jws.JWSInputException; import org.keycloak.jose.jws.crypto.RSAProvider; @@ -17,7 +16,6 @@ import org.keycloak.representations.PasswordToken; import org.keycloak.common.util.Time; import org.keycloak.hash.PasswordHashProvider; -import java.io.IOException; import java.util.List; /** @@ -61,30 +59,14 @@ public class CredentialValidation { if(unhashedCredValue == null){ return false; } - String algorithm; - if (credential.getAlgorithm() == null || credential.getAlgorithm().trim().equals("")) { - algorithm = Constants.DEFAULT_HASH_ALGORITHM; - } else { - algorithm = credential.getAlgorithm(); - } - byte[] salt = getSalt(); - - PasswordHashProvider provider = session.getProvider(PasswordHashProvider.class, algorithm); - - byte[] credSalt = credential.getSalt(); - - boolean validated = provider.verify(unhashedCredValue, credential.getValue(), credSalt); + boolean validated = PasswordHashManager.verify(session, realm, unhashedCredValue, credential); if (validated) { int iterations = hashIterations(realm); if (iterations > -1 && iterations != credential.getHashIterations()) { - UserCredentialValueModel newCred = new UserCredentialValueModel(); - newCred.setType(credential.getType()); - newCred.setDevice(credential.getDevice()); - newCred.setSalt(credential.getSalt()); - newCred.setHashIterations(iterations); - newCred.setValue(provider.encode(unhashedCredValue, salt, iterations)); + + UserCredentialValueModel newCred = PasswordHashManager.encode(session, realm, unhashedCredValue); user.updateCredentialDirectly(newCred); } diff --git a/model/api/src/main/java/org/keycloak/models/utils/Pbkdf2PasswordEncoder.java b/model/api/src/main/java/org/keycloak/models/utils/Pbkdf2PasswordEncoder.java deleted file mode 100755 index 9558b06883..0000000000 --- a/model/api/src/main/java/org/keycloak/models/utils/Pbkdf2PasswordEncoder.java +++ /dev/null @@ -1,106 +0,0 @@ -package org.keycloak.models.utils; - -import org.keycloak.common.util.Base64; - -import javax.crypto.SecretKeyFactory; -import javax.crypto.spec.PBEKeySpec; -import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; -import java.security.spec.InvalidKeySpecException; -import java.security.spec.KeySpec; - -/** - *

- * Encoder that uses PBKDF2 function to cryptographically derive passwords. - *

- *

Passwords are returned with a Base64 encoding.

- * - * @author Bruno Oliveira - * - */ -public class Pbkdf2PasswordEncoder { - - public static final String PBKDF2_ALGORITHM = "PBKDF2WithHmacSHA1"; - public static final String RNG_ALGORITHM = "SHA1PRNG"; - - private static final int DERIVED_KEY_SIZE = 512; - private static final int ITERATIONS = 1; - - private final int iterations; - private byte[] salt; - - public Pbkdf2PasswordEncoder(byte[] salt, int iterations) { - this.salt = salt; - this.iterations = iterations; - } - - public Pbkdf2PasswordEncoder(byte[] salt) { - this(salt, ITERATIONS); - } - - /** - * Encode the raw password provided - * @param rawPassword The password used as a master key to derive into a session key - * @return encoded password in Base64 - */ - public String encode(String rawPassword, int iterations) { - - String encodedPassword; - - KeySpec spec = new PBEKeySpec(rawPassword.toCharArray(), salt, iterations, DERIVED_KEY_SIZE); - - try { - byte[] key = getSecretKeyFactory().generateSecret(spec).getEncoded(); - encodedPassword = Base64.encodeBytes(key); - } catch (InvalidKeySpecException e) { - throw new RuntimeException("Credential could not be encoded"); - } - - return encodedPassword; - } - - public String encode(String rawPassword) { - return encode(rawPassword, iterations); - } - - /** - * Encode the password provided and compare with the hash stored into the database - * @param rawPassword The password provided - * @param encodedPassword Encoded hash stored into the database - * @return true if the password is valid, otherwise false for invalid credentials - */ - public boolean verify(String rawPassword, String encodedPassword) { - return encode(rawPassword).equals(encodedPassword); - } - - /** - * Encode the password provided and compare with the hash stored into the database - * @param rawPassword The password provided - * @param encodedPassword Encoded hash stored into the database - * @return true if the password is valid, otherwise false for invalid credentials - */ - public boolean verify(String rawPassword, String encodedPassword, int iterations) { - return encode(rawPassword, iterations).equals(encodedPassword); - } - - /** - * Generate a salt for each password - * @return cryptographically strong random number - */ - public static byte[] getSalt() { - byte[] buffer = new byte[16]; - - SecureRandom secureRandom = new SecureRandom(); - secureRandom.nextBytes(buffer); - - return buffer; - } - - private static SecretKeyFactory getSecretKeyFactory() { - try { - return SecretKeyFactory.getInstance(PBKDF2_ALGORITHM); - } catch (NoSuchAlgorithmException e) { - throw new RuntimeException("PBKDF2 algorithm not found"); - } - } -} diff --git a/model/api/src/main/resources/META-INF/services/org.keycloak.hash.PasswordHashProviderFactory b/model/api/src/main/resources/META-INF/services/org.keycloak.hash.PasswordHashProviderFactory index 56193f6010..f29ba9cfda 100644 --- a/model/api/src/main/resources/META-INF/services/org.keycloak.hash.PasswordHashProviderFactory +++ b/model/api/src/main/resources/META-INF/services/org.keycloak.hash.PasswordHashProviderFactory @@ -1 +1 @@ -org.keycloak.hash.DefaultPasswordHashProviderFactory +org.keycloak.hash.Pbkdf2PasswordHashProvider \ No newline at end of file diff --git a/model/api/src/test/java/org/keycloak/models/PasswordPolicyTest.java b/model/api/src/test/java/org/keycloak/models/PasswordPolicyTest.java index 8c662fb8af..f910ea11c4 100755 --- a/model/api/src/test/java/org/keycloak/models/PasswordPolicyTest.java +++ b/model/api/src/test/java/org/keycloak/models/PasswordPolicyTest.java @@ -15,73 +15,73 @@ public class PasswordPolicyTest { @Test public void testLength() { PasswordPolicy policy = new PasswordPolicy("length"); - Assert.assertEquals("invalidPasswordMinLengthMessage", policy.validate("jdoe", "1234567").getMessage()); - Assert.assertArrayEquals(new Object[]{8}, policy.validate("jdoe", "1234567").getParameters()); - Assert.assertNull(policy.validate("jdoe", "12345678")); + Assert.assertEquals("invalidPasswordMinLengthMessage", policy.validate(null, "jdoe", "1234567").getMessage()); + Assert.assertArrayEquals(new Object[]{8}, policy.validate(null, "jdoe", "1234567").getParameters()); + Assert.assertNull(policy.validate(null, "jdoe", "12345678")); policy = new PasswordPolicy("length(4)"); - Assert.assertEquals("invalidPasswordMinLengthMessage", policy.validate("jdoe", "123").getMessage()); - Assert.assertArrayEquals(new Object[]{4}, policy.validate("jdoe", "123").getParameters()); - Assert.assertNull(policy.validate("jdoe", "1234")); + Assert.assertEquals("invalidPasswordMinLengthMessage", policy.validate(null, "jdoe", "123").getMessage()); + Assert.assertArrayEquals(new Object[]{4}, policy.validate(null, "jdoe", "123").getParameters()); + Assert.assertNull(policy.validate(null, "jdoe", "1234")); } @Test public void testDigits() { PasswordPolicy policy = new PasswordPolicy("digits"); - Assert.assertEquals("invalidPasswordMinDigitsMessage", policy.validate("jdoe", "abcd").getMessage()); - Assert.assertArrayEquals(new Object[]{1}, policy.validate("jdoe", "abcd").getParameters()); - Assert.assertNull(policy.validate("jdoe", "abcd1")); + Assert.assertEquals("invalidPasswordMinDigitsMessage", policy.validate(null, "jdoe", "abcd").getMessage()); + Assert.assertArrayEquals(new Object[]{1}, policy.validate(null, "jdoe", "abcd").getParameters()); + Assert.assertNull(policy.validate(null, "jdoe", "abcd1")); policy = new PasswordPolicy("digits(2)"); - Assert.assertEquals("invalidPasswordMinDigitsMessage", policy.validate("jdoe", "abcd1").getMessage()); - Assert.assertArrayEquals(new Object[]{2}, policy.validate("jdoe", "abcd1").getParameters()); - Assert.assertNull(policy.validate("jdoe", "abcd12")); + Assert.assertEquals("invalidPasswordMinDigitsMessage", policy.validate(null, "jdoe", "abcd1").getMessage()); + Assert.assertArrayEquals(new Object[]{2}, policy.validate(null, "jdoe", "abcd1").getParameters()); + Assert.assertNull(policy.validate(null, "jdoe", "abcd12")); } @Test public void testLowerCase() { PasswordPolicy policy = new PasswordPolicy("lowerCase"); - Assert.assertEquals("invalidPasswordMinLowerCaseCharsMessage", policy.validate("jdoe", "ABCD1234").getMessage()); - Assert.assertArrayEquals(new Object[]{1}, policy.validate("jdoe", "ABCD1234").getParameters()); - Assert.assertNull(policy.validate("jdoe", "ABcD1234")); + Assert.assertEquals("invalidPasswordMinLowerCaseCharsMessage", policy.validate(null, "jdoe", "ABCD1234").getMessage()); + Assert.assertArrayEquals(new Object[]{1}, policy.validate(null, "jdoe", "ABCD1234").getParameters()); + Assert.assertNull(policy.validate(null, "jdoe", "ABcD1234")); policy = new PasswordPolicy("lowerCase(2)"); - Assert.assertEquals("invalidPasswordMinLowerCaseCharsMessage", policy.validate("jdoe", "ABcD1234").getMessage()); - Assert.assertArrayEquals(new Object[]{2}, policy.validate("jdoe", "ABcD1234").getParameters()); - Assert.assertNull(policy.validate("jdoe", "aBcD1234")); + Assert.assertEquals("invalidPasswordMinLowerCaseCharsMessage", policy.validate(null, "jdoe", "ABcD1234").getMessage()); + Assert.assertArrayEquals(new Object[]{2}, policy.validate(null, "jdoe", "ABcD1234").getParameters()); + Assert.assertNull(policy.validate(null, "jdoe", "aBcD1234")); } @Test public void testUpperCase() { PasswordPolicy policy = new PasswordPolicy("upperCase"); - Assert.assertEquals("invalidPasswordMinUpperCaseCharsMessage", policy.validate("jdoe", "abcd1234").getMessage()); - Assert.assertArrayEquals(new Object[]{1}, policy.validate("jdoe", "abcd1234").getParameters()); - Assert.assertNull(policy.validate("jdoe", "abCd1234")); + Assert.assertEquals("invalidPasswordMinUpperCaseCharsMessage", policy.validate(null, "jdoe", "abcd1234").getMessage()); + Assert.assertArrayEquals(new Object[]{1}, policy.validate(null, "jdoe", "abcd1234").getParameters()); + Assert.assertNull(policy.validate(null, "jdoe", "abCd1234")); policy = new PasswordPolicy("upperCase(2)"); - Assert.assertEquals("invalidPasswordMinUpperCaseCharsMessage", policy.validate("jdoe", "abCd1234").getMessage()); - Assert.assertArrayEquals(new Object[]{2}, policy.validate("jdoe", "abCd1234").getParameters()); - Assert.assertNull(policy.validate("jdoe", "AbCd1234")); + Assert.assertEquals("invalidPasswordMinUpperCaseCharsMessage", policy.validate(null, "jdoe", "abCd1234").getMessage()); + Assert.assertArrayEquals(new Object[]{2}, policy.validate(null, "jdoe", "abCd1234").getParameters()); + Assert.assertNull(policy.validate(null, "jdoe", "AbCd1234")); } @Test public void testSpecialChars() { PasswordPolicy policy = new PasswordPolicy("specialChars"); - Assert.assertEquals("invalidPasswordMinSpecialCharsMessage", policy.validate("jdoe", "abcd1234").getMessage()); - Assert.assertArrayEquals(new Object[]{1}, policy.validate("jdoe", "abcd1234").getParameters()); - Assert.assertNull(policy.validate("jdoe", "ab&d1234")); + Assert.assertEquals("invalidPasswordMinSpecialCharsMessage", policy.validate(null, "jdoe", "abcd1234").getMessage()); + Assert.assertArrayEquals(new Object[]{1}, policy.validate(null, "jdoe", "abcd1234").getParameters()); + Assert.assertNull(policy.validate(null, "jdoe", "ab&d1234")); policy = new PasswordPolicy("specialChars(2)"); - Assert.assertEquals("invalidPasswordMinSpecialCharsMessage", policy.validate("jdoe", "ab&d1234").getMessage()); - Assert.assertArrayEquals(new Object[]{2}, policy.validate("jdoe", "ab&d1234").getParameters()); - Assert.assertNull(policy.validate("jdoe", "ab&d-234")); + Assert.assertEquals("invalidPasswordMinSpecialCharsMessage", policy.validate(null, "jdoe", "ab&d1234").getMessage()); + Assert.assertArrayEquals(new Object[]{2}, policy.validate(null, "jdoe", "ab&d1234").getParameters()); + Assert.assertNull(policy.validate(null, "jdoe", "ab&d-234")); } @Test public void testNotUsername() { PasswordPolicy policy = new PasswordPolicy("notUsername"); - Assert.assertEquals("invalidPasswordNotUsernameMessage", policy.validate("jdoe", "jdoe").getMessage()); - Assert.assertNull(policy.validate("jdoe", "ab&d1234")); + Assert.assertEquals("invalidPasswordNotUsernameMessage", policy.validate(null, "jdoe", "jdoe").getMessage()); + Assert.assertNull(policy.validate(null, "jdoe", "ab&d1234")); } @Test @@ -119,33 +119,33 @@ public class PasswordPolicyTest { //Fails to match one of the regex pattern policy = new PasswordPolicy("regexPattern(jdoe) and regexPattern(j*d)"); - Assert.assertEquals("invalidPasswordRegexPatternMessage", policy.validate("jdoe", "jdoe").getMessage()); + Assert.assertEquals("invalidPasswordRegexPatternMessage", policy.validate(null, "jdoe", "jdoe").getMessage()); ////Fails to match all of the regex patterns policy = new PasswordPolicy("regexPattern(j*p) and regexPattern(j*d) and regexPattern(adoe)"); - Assert.assertEquals("invalidPasswordRegexPatternMessage", policy.validate("jdoe", "jdoe").getMessage()); + Assert.assertEquals("invalidPasswordRegexPatternMessage", policy.validate(null, "jdoe", "jdoe").getMessage()); policy = new PasswordPolicy("regexPattern([a-z][a-z][a-z][a-z][0-9])"); - Assert.assertEquals("invalidPasswordRegexPatternMessage", policy.validate("jdoe", "jdoe").getMessage()); + Assert.assertEquals("invalidPasswordRegexPatternMessage", policy.validate(null, "jdoe", "jdoe").getMessage()); policy = new PasswordPolicy("regexPattern(jdoe)"); - Assert.assertNull(policy.validate("jdoe", "jdoe")); + Assert.assertNull(policy.validate(null, "jdoe", "jdoe")); policy = new PasswordPolicy("regexPattern([a-z][a-z][a-z][a-z][0-9])"); - Assert.assertNull(policy.validate("jdoe", "jdoe0")); + Assert.assertNull(policy.validate(null, "jdoe", "jdoe0")); } @Test public void testComplex() { PasswordPolicy policy = new PasswordPolicy("length(8) and digits(2) and lowerCase(2) and upperCase(2) and specialChars(2) and notUsername()"); - Assert.assertNotNull(policy.validate("jdoe", "12aaBB&")); - Assert.assertNotNull(policy.validate("jdoe", "aaaaBB&-")); - Assert.assertNotNull(policy.validate("jdoe", "12AABB&-")); - Assert.assertNotNull(policy.validate("jdoe", "12aabb&-")); - Assert.assertNotNull(policy.validate("jdoe", "12aaBBcc")); - Assert.assertNotNull(policy.validate("12aaBB&-", "12aaBB&-")); + Assert.assertNotNull(policy.validate(null, "jdoe", "12aaBB&")); + Assert.assertNotNull(policy.validate(null, "jdoe", "aaaaBB&-")); + Assert.assertNotNull(policy.validate(null, "jdoe", "12AABB&-")); + Assert.assertNotNull(policy.validate(null, "jdoe", "12aabb&-")); + Assert.assertNotNull(policy.validate(null, "jdoe", "12aaBBcc")); + Assert.assertNotNull(policy.validate(null, "12aaBB&-", "12aaBB&-")); - Assert.assertNull(policy.validate("jdoe", "12aaBB&-")); + Assert.assertNull(policy.validate(null, "jdoe", "12aaBB&-")); } } diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java index 8594bcaa0c..ded42a7047 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java @@ -1,5 +1,6 @@ package org.keycloak.models.jpa; +import org.keycloak.hash.PasswordHashManager; import org.keycloak.models.ClientModel; import org.keycloak.models.GroupModel; import org.keycloak.models.KeycloakSession; @@ -42,8 +43,6 @@ import java.util.List; import java.util.Map; import java.util.Set; -import static org.keycloak.models.utils.Pbkdf2PasswordEncoder.getSalt; - /** * @author Bill Burke * @version $Revision: 1 $ @@ -390,19 +389,12 @@ public class UserAdapter implements UserModel { } private void setValue(CredentialEntity credentialEntity, UserCredentialModel cred) { - byte[] salt = getSalt(); - int hashIterations = 1; - PasswordHashProvider provider = session.getProvider(PasswordHashProvider.class); - PasswordPolicy policy = realm.getPasswordPolicy(); - if (policy != null) { - hashIterations = policy.getHashIterations(); - if (hashIterations == -1) - hashIterations = 1; - } + UserCredentialValueModel encoded = PasswordHashManager.encode(session, realm, cred.getValue()); credentialEntity.setCreatedDate(Time.toMillis(Time.currentTime())); - credentialEntity.setValue(provider.encode(cred.getValue(), salt, hashIterations)); - credentialEntity.setSalt(salt); - credentialEntity.setHashIterations(hashIterations); + credentialEntity.setAlgorithm(encoded.getAlgorithm()); + credentialEntity.setValue(encoded.getValue()); + credentialEntity.setSalt(encoded.getSalt()); + credentialEntity.setHashIterations(encoded.getHashIterations()); } private CredentialEntity getCredentialEntity(UserEntity userEntity, String credType) { diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java index 3137ab4407..5e822b0ffa 100755 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java @@ -1,11 +1,10 @@ package org.keycloak.models.mongo.keycloak.adapters; -import static org.keycloak.models.utils.Pbkdf2PasswordEncoder.getSalt; - import com.mongodb.DBObject; import com.mongodb.QueryBuilder; import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext; +import org.keycloak.hash.PasswordHashManager; import org.keycloak.models.ClientModel; import org.keycloak.models.GroupModel; import org.keycloak.models.OTPPolicy; @@ -329,19 +328,12 @@ public class UserAdapter extends AbstractMongoAdapter implement } private void setValue(CredentialEntity credentialEntity, UserCredentialModel cred) { - byte[] salt = getSalt(); - int hashIterations = 1; - PasswordHashProvider provider = session.getProvider(PasswordHashProvider.class); - PasswordPolicy policy = realm.getPasswordPolicy(); - if (policy != null) { - hashIterations = policy.getHashIterations(); - if (hashIterations == -1) - hashIterations = 1; - } + UserCredentialValueModel encoded = PasswordHashManager.encode(session, realm, cred.getValue()); credentialEntity.setCreatedDate(Time.toMillis(Time.currentTime())); - credentialEntity.setValue(provider.encode(cred.getValue(), salt, hashIterations)); - credentialEntity.setSalt(salt); - credentialEntity.setHashIterations(hashIterations); + credentialEntity.setAlgorithm(encoded.getAlgorithm()); + credentialEntity.setValue(encoded.getValue()); + credentialEntity.setSalt(encoded.getSalt()); + credentialEntity.setHashIterations(encoded.getHashIterations()); } private CredentialEntity getCredentialEntity(MongoUserEntity userEntity, String credType) { diff --git a/services/src/main/java/org/keycloak/authentication/forms/RegistrationPassword.java b/services/src/main/java/org/keycloak/authentication/forms/RegistrationPassword.java index ff2442f3d5..027419c827 100755 --- a/services/src/main/java/org/keycloak/authentication/forms/RegistrationPassword.java +++ b/services/src/main/java/org/keycloak/authentication/forms/RegistrationPassword.java @@ -53,7 +53,7 @@ public class RegistrationPassword implements FormAction, FormActionFactory { errors.add(new FormMessage(RegistrationPage.FIELD_PASSWORD_CONFIRM, Messages.INVALID_PASSWORD_CONFIRM)); } if (formData.getFirst(RegistrationPage.FIELD_PASSWORD) != null) { - PasswordPolicy.Error err = context.getRealm().getPasswordPolicy().validate(context.getRealm().isRegistrationEmailAsUsername() ? formData.getFirst(RegistrationPage.FIELD_EMAIL) : formData.getFirst(RegistrationPage.FIELD_USERNAME), formData.getFirst(RegistrationPage.FIELD_PASSWORD)); + PasswordPolicy.Error err = context.getRealm().getPasswordPolicy().validate(context.getSession(), context.getRealm().isRegistrationEmailAsUsername() ? formData.getFirst(RegistrationPage.FIELD_EMAIL) : formData.getFirst(RegistrationPage.FIELD_USERNAME), formData.getFirst(RegistrationPage.FIELD_PASSWORD)); if (err != null) errors.add(new FormMessage(RegistrationPage.FIELD_PASSWORD, err.getMessage(), err.getParameters())); } diff --git a/services/src/main/java/org/keycloak/services/validation/Validation.java b/services/src/main/java/org/keycloak/services/validation/Validation.java index 0a3c9bb7fc..0c3dcc0b0b 100755 --- a/services/src/main/java/org/keycloak/services/validation/Validation.java +++ b/services/src/main/java/org/keycloak/services/validation/Validation.java @@ -1,6 +1,7 @@ package org.keycloak.services.validation; import org.keycloak.authentication.requiredactions.util.UpdateProfileContext; +import org.keycloak.models.KeycloakSession; import org.keycloak.models.PasswordPolicy; import org.keycloak.models.RealmModel; import org.keycloak.models.utils.FormMessage; @@ -25,7 +26,7 @@ public class Validation { // Actually allow same emails like angular. See ValidationTest.testEmailValidation() private static final Pattern EMAIL_PATTERN = Pattern.compile("[a-zA-Z0-9!#$%&'*+/=?^_`{|}~.-]+@[a-zA-Z0-9-]+(\\.[a-zA-Z0-9-]+)*"); - public static List validateRegistrationForm(RealmModel realm, MultivaluedMap formData, List requiredCredentialTypes, PasswordPolicy policy) { + public static List validateRegistrationForm(KeycloakSession session, RealmModel realm, MultivaluedMap formData, List requiredCredentialTypes, PasswordPolicy policy) { List errors = new ArrayList<>(); if (!realm.isRegistrationEmailAsUsername() && isBlank(formData.getFirst(FIELD_USERNAME))) { @@ -55,7 +56,7 @@ public class Validation { } if (formData.getFirst(FIELD_PASSWORD) != null) { - PasswordPolicy.Error err = policy.validate(realm.isRegistrationEmailAsUsername()?formData.getFirst(FIELD_EMAIL):formData.getFirst(FIELD_USERNAME), formData.getFirst(FIELD_PASSWORD)); + PasswordPolicy.Error err = policy.validate(session, realm.isRegistrationEmailAsUsername()?formData.getFirst(FIELD_EMAIL):formData.getFirst(FIELD_USERNAME), formData.getFirst(FIELD_PASSWORD)); if (err != null) errors.add(new FormMessage(FIELD_PASSWORD, err.getMessage(), err.getParameters())); } 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 dea6ebcd6e..464f376caa 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 @@ -1,16 +1,17 @@ package org.keycloak.testsuite.adduser; import org.codehaus.jackson.type.TypeReference; -import org.junit.*; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; import org.junit.rules.TemporaryFolder; import org.keycloak.admin.client.Keycloak; import org.keycloak.admin.client.resource.RealmResource; -import org.keycloak.admin.client.resource.RoleMappingResource; -import org.keycloak.admin.client.resource.RoleScopeResource; import org.keycloak.admin.client.resource.UserResource; -import org.keycloak.common.util.Base64Url; +import org.keycloak.common.util.Base64; +import org.keycloak.hash.Pbkdf2PasswordHashProvider; import org.keycloak.models.Constants; -import org.keycloak.models.utils.Pbkdf2PasswordEncoder; import org.keycloak.representations.idm.*; import org.keycloak.testsuite.KeycloakServer; import org.keycloak.util.JsonSerialization; @@ -19,7 +20,6 @@ import org.keycloak.wildfly.adduser.AddUser; import java.io.File; import java.io.FileInputStream; import java.io.IOException; -import java.util.Collections; import java.util.List; import static org.junit.Assert.*; @@ -59,7 +59,11 @@ public class AddUserTest { UserRepresentation user = realms.get(0).getUsers().get(0); assertEquals(new Integer(100000), user.getCredentials().get(0).getHashIterations()); assertNull(user.getCredentials().get(0).getValue()); - assertEquals(new Pbkdf2PasswordEncoder(Base64Url.decode(user.getCredentials().get(0).getSalt()), 100000).encode("password"), user.getCredentials().get(0).getHashedSaltedValue()); + + CredentialRepresentation credentials = user.getCredentials().get(0); + + assertEquals(Pbkdf2PasswordHashProvider.ID, credentials.getAlgorithm()); + assertEquals(new Integer(100000), credentials.getHashIterations()); KeycloakServer server = new KeycloakServer(); try { 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 47e14009e8..d741333ccd 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 @@ -12,9 +12,8 @@ import org.jboss.aesh.console.command.invocation.CommandInvocation; 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.hash.DefaultPasswordHashProvider; -import org.keycloak.models.Constants; -import org.keycloak.models.utils.Pbkdf2PasswordEncoder; +import org.keycloak.hash.Pbkdf2PasswordHashProvider; +import org.keycloak.models.UserCredentialValueModel; import org.keycloak.representations.idm.CredentialRepresentation; import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.UserRepresentation; @@ -139,14 +138,14 @@ public class AddUser { user.setUsername(userName); user.setCredentials(new LinkedList()); - byte[] salt = Pbkdf2PasswordEncoder.getSalt(); - iterations = iterations > 0 ? iterations : DEFAULT_HASH_ITERATIONS; + UserCredentialValueModel credentialValueModel = new Pbkdf2PasswordHashProvider().encode(password, iterations > 0 ? iterations : DEFAULT_HASH_ITERATIONS); CredentialRepresentation credentials = new CredentialRepresentation(); - credentials.setType(CredentialRepresentation.PASSWORD); - credentials.setHashIterations(iterations); - credentials.setSalt(Base64.encodeBytes(salt)); - credentials.setHashedSaltedValue(new DefaultPasswordHashProvider().encode(password, salt, iterations)); + credentials.setType(credentialValueModel.getType()); + credentials.setAlgorithm(credentialValueModel.getAlgorithm()); + credentials.setHashIterations(credentialValueModel.getHashIterations()); + credentials.setSalt(Base64.encodeBytes(credentialValueModel.getSalt())); + credentials.setHashedSaltedValue(credentialValueModel.getValue()); user.getCredentials().add(credentials);