KEYCLOAK-1900 Add password hash SPI and provider

- Default encoder set to Pbkdf2PasswordEncoder
This commit is contained in:
tsudot authored and Stian Thorgersen committed 2015-12-08 13:25:59 +01:00
1 parent 4a57bcc0b4
commit 162dd4051d
32 files changed
+257 -78

No files matched your search

@@ -0,0 +1,41 @@
package org.keycloak.hash;
import static org.keycloak.models.utils.Pbkdf2PasswordEncoder.getSalt;
import org.keycloak.models.utils.Pbkdf2PasswordEncoder;
/**
* @author <a href="mailto:me@tsudot.com">Kunal Kerkar</a>
*/
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() {
}
}
@@ -0,0 +1,35 @@
package org.keycloak.hash;
import org.keycloak.Config;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
/**
* @author <a href="mailto:me@tsudot.com">Kunal Kerkar</a>
*/
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() {
}
}
@@ -0,0 +1,16 @@
package org.keycloak.hash;
import org.keycloak.provider.Provider;
/**
* @author <a href="mailto:me@tsudot.com">Kunal Kerkar</a>
*/
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);
}
@@ -0,0 +1,10 @@
package org.keycloak.hash;
import org.keycloak.provider.ProviderFactory;
/**
* @author <a href="mailto:me@tsudot.com">Kunal Kerkar</a>
*/
public interface PasswordHashProviderFactory extends ProviderFactory<PasswordHashProvider> {
}
@@ -0,0 +1,31 @@
package org.keycloak.hash;
import org.keycloak.provider.Provider;
import org.keycloak.provider.ProviderFactory;
import org.keycloak.provider.Spi;
/**
* @author <a href="mailto:me@tsudot.com">Kunal Kerkar</a>
*/
public class PasswordHashSpi implements Spi {
@Override
public boolean isInternal() {
return false;
}
@Override
public String getName() {
return "password-hash";
}
@Override
public Class<? extends Provider> getProviderClass() {
return PasswordHashProvider.class;
}
@Override
public Class<? extends ProviderFactory> getProviderFactoryClass() {
return PasswordHashProviderFactory.class;
}
}
@@ -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
@@ -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;
}
}
@@ -403,7 +403,7 @@ public class UserFederationManager implements UserProvider {
}
@Override
public boolean validCredentials(RealmModel realm, UserModel user, List<UserCredentialModel> input) {
public boolean validCredentials(KeycloakSession session, RealmModel realm, UserModel user, List<UserCredentialModel> 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<UserFederationProviderModel> fedProviderModels = realm.getUserFederationProviders();
List<UserFederationProvider> fedProviders = new ArrayList<UserFederationProvider>();
for (UserFederationProviderModel fedProviderModel : fedProviderModels) {
@@ -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<UserCredentialModel> input);
boolean validCredentials(RealmModel realm, UserModel user, UserCredentialModel... input);
CredentialValidationOutput validCredentials(RealmModel realm, UserCredentialModel... input);
boolean validCredentials(KeycloakSession session, RealmModel realm, UserModel user, List<UserCredentialModel> input);
boolean validCredentials(KeycloakSession session, RealmModel realm, UserModel user, UserCredentialModel... input);
CredentialValidationOutput validCredentials(KeycloakSession session, RealmModel realm, UserCredentialModel... input);
void close();
}
@@ -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<UserCredentialModel> credentials) {
public static boolean validCredentials(KeycloakSession session, RealmModel realm, UserModel user, List<UserCredentialModel> 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)) {
@@ -0,0 +1 @@
org.keycloak.hash.DefaultPasswordHashProviderFactory
@@ -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
org.keycloak.migration.MigrationSpi
org.keycloak.hash.PasswordHashSpi