Merge pull request #3286 from patriot1burke/master

remove UserCredValueModel and hold hash providers
This commit is contained in:
Bill Burke 2016-10-04 14:07:26 -04:00 committed by GitHub
commit a89dbabc92
19 changed files with 16 additions and 542 deletions

View file

@ -27,6 +27,7 @@
<dependencies>
<module name="org.keycloak.keycloak-common"/>
<module name="org.keycloak.keycloak-core"/>
<module name="org.keycloak.keycloak-services"/>
<module name="org.keycloak.keycloak-server-spi"/>
<module name="org.mongodb.mongo-java-driver"/>
<module name="org.jboss.logging"/>

View file

@ -52,6 +52,11 @@
<artifactId>keycloak-server-spi</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-services</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>

View file

@ -21,7 +21,7 @@ import com.mongodb.BasicDBList;
import com.mongodb.BasicDBObject;
import com.mongodb.DBCollection;
import com.mongodb.WriteResult;
import org.keycloak.hash.Pbkdf2PasswordHashProvider;
import org.keycloak.credential.hash.Pbkdf2PasswordHashProvider;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.UserCredentialModel;

View file

@ -21,7 +21,7 @@ import com.mongodb.BasicDBList;
import com.mongodb.BasicDBObject;
import com.mongodb.DBCollection;
import com.mongodb.WriteResult;
import org.keycloak.hash.Pbkdf2PasswordHashProvider;
import org.keycloak.credential.hash.Pbkdf2PasswordHashProvider;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.utils.HmacOTP;

View file

@ -24,7 +24,6 @@ import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.PasswordPolicy;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserCredentialValueModel;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
@ -43,11 +42,11 @@ public class Pbkdf2PasswordHashProvider implements PasswordHashProviderFactory,
private static final String PBKDF2_ALGORITHM = "PBKDF2WithHmacSHA1";
private static final int DERIVED_KEY_SIZE = 512;
public UserCredentialValueModel encode(String rawPassword, int iterations) {
public CredentialModel encode(String rawPassword, int iterations) {
byte[] salt = getSalt();
String encodedPassword = encode(rawPassword, iterations, salt);
UserCredentialValueModel credentials = new UserCredentialValueModel();
CredentialModel credentials = new CredentialModel();
credentials.setAlgorithm(ID);
credentials.setType(UserCredentialModel.PASSWORD);
credentials.setSalt(salt);
@ -56,10 +55,6 @@ public class Pbkdf2PasswordHashProvider implements PasswordHashProviderFactory,
return credentials;
}
public boolean verify(String rawPassword, UserCredentialValueModel credential) {
return encode(rawPassword, credential.getHashIterations(), credential.getSalt()).equals(credential.getValue());
}
@Override
public boolean policyCheck(PasswordPolicy policy, CredentialModel credential) {
return credential.getHashIterations() == policy.getHashIterations() && ID.equals(credential.getAlgorithm());

View file

@ -1,61 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.hash;
import org.jboss.logging.Logger;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.PasswordPolicy;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserCredentialValueModel;
import org.keycloak.policy.HashAlgorithmPasswordPolicyProviderFactory;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class PasswordHashManager {
private static final Logger log = Logger.getLogger(PasswordHashManager.class);
public static UserCredentialValueModel encode(KeycloakSession session, RealmModel realm, String rawPassword) {
return encode(session, realm.getPasswordPolicy(), rawPassword);
}
public static UserCredentialValueModel encode(KeycloakSession session, PasswordPolicy passwordPolicy, String rawPassword) {
PasswordHashProvider provider = session.getProvider(PasswordHashProvider.class, passwordPolicy.getHashAlgorithm());
if (provider == null) {
log.warnv("Could not find hash provider {0} from password policy, using default provider {1}", passwordPolicy.getHashAlgorithm(), HashAlgorithmPasswordPolicyProviderFactory.DEFAULT_VALUE);
provider = session.getProvider(PasswordHashProvider.class, HashAlgorithmPasswordPolicyProviderFactory.DEFAULT_VALUE);
}
return provider.encode(rawPassword, passwordPolicy.getHashIterations());
}
public static boolean verify(KeycloakSession session, RealmModel realm, String password, UserCredentialValueModel credential) {
return verify(session, realm.getPasswordPolicy(), password, credential);
}
public static boolean verify(KeycloakSession session, PasswordPolicy passwordPolicy, String password, UserCredentialValueModel credential) {
String algorithm = credential.getAlgorithm() != null ? credential.getAlgorithm() : passwordPolicy.getHashAlgorithm();
PasswordHashProvider provider = session.getProvider(PasswordHashProvider.class, algorithm);
if (provider == null) {
log.warnv("Could not find hash provider {0} for password", algorithm);
return false;
}
return provider.verify(password, credential);
}
}

View file

@ -1,32 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.hash;
import org.keycloak.models.UserCredentialValueModel;
import org.keycloak.provider.Provider;
/**
* @author <a href="mailto:me@tsudot.com">Kunal Kerkar</a>
*/
public interface PasswordHashProvider extends Provider {
UserCredentialValueModel encode(String rawPassword, int iterations);
boolean verify(String rawPassword, UserCredentialValueModel credential);
}

View file

@ -1,27 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.hash;
import org.keycloak.provider.ProviderFactory;
/**
* @author <a href="mailto:me@tsudot.com">Kunal Kerkar</a>
*/
public interface PasswordHashProviderFactory extends ProviderFactory<PasswordHashProvider> {
}

View file

@ -1,48 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.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;
}
}

View file

@ -1,108 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.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 <a href="mailto:me@tsudot.com">Kunal Kerkar</a>
*/
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", e);
}
}
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", e);
}
}
}

View file

@ -1,130 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.models;
import java.io.Serializable;
/**
* Used just in cases when we want to "directly" update or retrieve the hash or salt of user credential (For example during export/import)
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class UserCredentialValueModel implements Serializable {
private String id;
private String type;
private String value;
private String device;
private byte[] salt;
private int hashIterations;
private Long createdDate;
// otp stuff
private int counter;
private String algorithm;
private int digits;
private int period;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public String getDevice() {
return device;
}
public void setDevice(String device) {
this.device = device;
}
public byte[] getSalt() {
return salt;
}
public void setSalt(byte[] salt) {
this.salt = salt;
}
public int getHashIterations() {
return hashIterations;
}
public void setHashIterations(int iterations) {
this.hashIterations = iterations;
}
public Long getCreatedDate() {
return createdDate;
}
public void setCreatedDate(Long createdDate) {
this.createdDate = createdDate;
}
public int getCounter() {
return counter;
}
public void setCounter(int counter) {
this.counter = counter;
}
public String getAlgorithm() {
return algorithm;
}
public void setAlgorithm(String algorithm) {
this.algorithm = algorithm;
}
public int getDigits() {
return digits;
}
public void setDigits(int digits) {
this.digits = digits;
}
public int getPeriod() {
return period;
}
public void setPeriod(int period) {
this.period = period;
}
}

View file

@ -35,7 +35,6 @@ import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.common.util.UriUtils;
import org.keycloak.component.ComponentModel;
import org.keycloak.credential.CredentialModel;
import org.keycloak.hash.Pbkdf2PasswordHashProvider;
import org.keycloak.migration.MigrationProvider;
import org.keycloak.migration.migrators.MigrationUtils;
import org.keycloak.models.AuthenticationExecutionModel;
@ -1434,14 +1433,14 @@ public class RepresentationToModel {
// Could happen when migrating from some early version
if ((UserCredentialModel.PASSWORD.equals(cred.getType()) || UserCredentialModel.PASSWORD_HISTORY.equals(cred.getType())) &&
(cred.getAlgorithm().equals(HmacOTP.HMAC_SHA1))) {
hashedCred.setAlgorithm(Pbkdf2PasswordHashProvider.ID);
hashedCred.setAlgorithm("pbkdf2");
} else {
hashedCred.setAlgorithm(cred.getAlgorithm());
}
} else {
if (UserCredentialModel.PASSWORD.equals(cred.getType()) || UserCredentialModel.PASSWORD_HISTORY.equals(cred.getType())) {
hashedCred.setAlgorithm(Pbkdf2PasswordHashProvider.ID);
hashedCred.setAlgorithm("pbkdf2");
} else if (UserCredentialModel.isOtp(cred.getType())) {
hashedCred.setAlgorithm(HmacOTP.HMAC_SHA1);
}

View file

@ -1,18 +0,0 @@
#
# Copyright 2016 Red Hat, Inc. and/or its affiliates
# and other contributors as indicated by the @author tags.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
org.keycloak.hash.Pbkdf2PasswordHashProvider

View file

@ -25,7 +25,6 @@ org.keycloak.models.UserSpi
org.keycloak.models.session.UserSessionPersisterSpi
org.keycloak.models.dblock.DBLockSpi
org.keycloak.migration.MigrationSpi
org.keycloak.hash.PasswordHashSpi
org.keycloak.events.EventListenerSpi
org.keycloak.events.EventStoreSpi
org.keycloak.exportimport.ExportSpi

View file

@ -26,7 +26,7 @@ import org.junit.rules.TemporaryFolder;
import org.keycloak.admin.client.Keycloak;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.hash.Pbkdf2PasswordHashProvider;
import org.keycloak.credential.hash.Pbkdf2PasswordHashProvider;
import org.keycloak.models.Constants;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.CredentialRepresentation;

View file

@ -1,46 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.testsuite.federation.storage;
import org.keycloak.hash.PasswordHashProvider;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserCredentialValueModel;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class PlainTextPasswordProvider implements PasswordHashProvider {
@Override
public UserCredentialValueModel encode(String rawPassword, int iterations) {
UserCredentialValueModel model = new UserCredentialValueModel();
model.setType(UserCredentialModel.PASSWORD);
model.setValue(rawPassword);
model.setAlgorithm("text");
return model;
}
@Override
public boolean verify(String rawPassword, UserCredentialValueModel credential) {
return rawPassword.equals(credential.getValue());
}
@Override
public void close() {
}
}

View file

@ -1,54 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.testsuite.federation.storage;
import org.keycloak.Config;
import org.keycloak.hash.PasswordHashProvider;
import org.keycloak.hash.PasswordHashProviderFactory;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class PlainTextPasswordProviderFactory implements PasswordHashProviderFactory {
@Override
public PasswordHashProvider create(KeycloakSession session) {
return new PlainTextPasswordProvider();
}
@Override
public void init(Config.Scope config) {
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public void close() {
}
@Override
public String getId() {
return "text";
}
}

View file

@ -1 +0,0 @@
org.keycloak.testsuite.federation.storage.PlainTextPasswordProviderFactory

View file

@ -29,8 +29,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.Pbkdf2PasswordHashProvider;
import org.keycloak.models.UserCredentialValueModel;
import org.keycloak.credential.CredentialModel;
import org.keycloak.credential.hash.Pbkdf2PasswordHashProvider;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
@ -153,7 +153,7 @@ public class AddUser {
user.setUsername(userName);
user.setCredentials(new LinkedList<CredentialRepresentation>());
UserCredentialValueModel credentialValueModel = new Pbkdf2PasswordHashProvider().encode(password, iterations > 0 ? iterations : DEFAULT_HASH_ITERATIONS);
CredentialModel credentialValueModel = new Pbkdf2PasswordHashProvider().encode(password, iterations > 0 ? iterations : DEFAULT_HASH_ITERATIONS);
CredentialRepresentation credentials = new CredentialRepresentation();
credentials.setType(credentialValueModel.getType());