diff --git a/keycloak/sms-provider/pom.xml b/keycloak/sms-provider/pom.xml index 5a62df19..0e446ad9 100644 --- a/keycloak/sms-provider/pom.xml +++ b/keycloak/sms-provider/pom.xml @@ -121,5 +121,10 @@ + + org.springframework.security + spring-security-core + 5.5.0 + diff --git a/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/login/PasswordAndOtpAuthenticator.java b/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/login/PasswordAndOtpAuthenticator.java index 132073a6..c7168fdc 100644 --- a/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/login/PasswordAndOtpAuthenticator.java +++ b/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/login/PasswordAndOtpAuthenticator.java @@ -45,6 +45,7 @@ import org.keycloak.services.ServicesLogger; import org.keycloak.services.managers.AuthenticationManager; import org.keycloak.services.messages.Messages; +import org.springframework.security.crypto.password.Pbkdf2PasswordEncoder; import org.sunbird.keycloak.resetcredential.sms.KeycloakSmsAuthenticatorConstants; import org.sunbird.keycloak.resetcredential.sms.KeycloakSmsAuthenticatorUtil; import org.sunbird.keycloak.utils.Constants; @@ -61,6 +62,8 @@ public class PasswordAndOtpAuthenticator extends AbstractUsernameFormAuthenticat Logger logger = Logger.getLogger(PasswordAndOtpAuthenticator.class); private static final SecureRandom random = new SecureRandom(); + private Pbkdf2PasswordEncoder passwordEncoder = new Pbkdf2PasswordEncoder(); + private enum CODE_STATUS { VALID, INVALID, EXPIRED @@ -670,6 +673,9 @@ public boolean validatePassword(AuthenticationFlowContext context, UserModel use List credentials = new LinkedList<>(); credentials.add(UserCredentialModel.password(decryptedPassword)); + boolean isValid = validateHashedPassword(context, user, decryptedPassword); + logger.info(String.format("PasswordAndOtpAuthenticator::validateHashedPassword returns : %s", isValid)); + if (decryptedPassword != null && !decryptedPassword.isEmpty() && context.getSession().userCredentialManager().isValid(context.getRealm(), user, credentials)) { return true; @@ -695,4 +701,43 @@ private String decryptPassword(String encryptedPassword, String secretKey, Strin throw new RuntimeException("Error while decrypting password", e); } } + + private boolean validateHashedPassword(AuthenticationFlowContext context, UserModel user, + String clientHashedPassword) { + try { + + // Fetch the stored password credential + List credentials = context.getSession().userCredentialManager() + .getStoredCredentialsByType(context.getRealm(), user, CredentialModel.PASSWORD); + + if (!credentials.isEmpty()) { + CredentialModel storedCredential = credentials.get(0); + logger.info(String.format( + "PasswordAndOtpAuthenticator::validateHashedPassword storedPasswordHash from list : %s", + storedCredential.getValue())); + } else { + logger.info(String.format("PasswordAndOtpAuthenticator::validateHashedPassword null value from type")); + } + + // Fetch the stored password from Keycloak + CredentialModel storedCredential = context.getSession().userCredentialManager() + .getStoredCredentialById(context.getRealm(), user, CredentialModel.PASSWORD); + + // The password stored in Keycloak is hashed with PBKDF2 + if (storedCredential != null) { + String storedPasswordHash = storedCredential.getValue(); + logger.info(String.format("PasswordAndOtpAuthenticator::validateHashedPassword storedPasswordHash : %s", + storedPasswordHash)); + // Compare the PBKDF2-hashed password from client with Keycloak's stored PBKDF2 + // hash + return passwordEncoder.matches(clientHashedPassword, storedPasswordHash); + } else { + logger.info(String.format("PasswordAndOtpAuthenticator::validateHashedPassword null value from id")); + } + + } catch (Exception e) { + logger.error("Failed to validateHashedPassword. Exception: ", e); + } + return false; + } }