From 36981a234e1c3cdfd72e9c673270d0a905c60c40 Mon Sep 17 00:00:00 2001 From: Matthias Zobrist Date: Thu, 15 Feb 2024 21:05:12 +0100 Subject: [PATCH] Work in progress --- README.md | 23 ++- .../authenticator/RegistrationMosparo.java | 155 +++++++++++++----- 2 files changed, 131 insertions(+), 47 deletions(-) diff --git a/README.md b/README.md index 683a597..bdd27ab 100644 --- a/README.md +++ b/README.md @@ -34,12 +34,25 @@ Authorizing Iframes To show the hCaptcha you need to modify the registration template. You can find the files in your Keycloak installation under `themes/base/login/`. If you use the user profile preview (you start your Keycloak with the `-Dkeycloak.profile=preview` flag), you need to edit the `register-user-profile.ftl`, else the `register.ftl`. Add the following code beneith the reCaptcha code: ```html -<#if hcaptchaRequired??> -
-
-
compact<#else>normal" data-sitekey="${hcaptchaSiteKey}">
-
+<#if mosparoRequired??> +
+
+
+ +
+
``` diff --git a/src/main/java/at/schuerz.keycloak/authenticator/RegistrationMosparo.java b/src/main/java/at/schuerz.keycloak/authenticator/RegistrationMosparo.java index f0094d4..b770e7d 100644 --- a/src/main/java/at/schuerz.keycloak/authenticator/RegistrationMosparo.java +++ b/src/main/java/at/schuerz.keycloak/authenticator/RegistrationMosparo.java @@ -17,6 +17,9 @@ package at.schuerz.keycloak.authenticator; +import jakarta.ws.rs.core.MultivaluedHashMap; +import org.apache.commons.collections4.MultiValuedMap; +import org.apache.commons.collections4.multimap.ArrayListValuedHashMap; import org.apache.http.NameValuePair; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpPost; @@ -47,6 +50,9 @@ import org.keycloak.util.JsonSerialization; import java.io.InputStream; import jakarta.ws.rs.core.MultivaluedMap; + +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; @@ -65,9 +71,13 @@ import org.apache.http.util.EntityUtils; public class RegistrationMosparo implements FormAction, FormActionFactory { public static final String MOSPARO_RESPONSE = "mosparo-response"; public static final String MOSPARO_REFERENCE_CATEGORY = "mosparo"; - public static final String SITE_PUBKEY = "mosparo-public-key"; - public static final String SITE_UUID = "uuid"; + public static final String MOSPARO_HOST = "hostname"; + public static final String MOSPARO_UUID = "uuid"; + public static final String MOSPARO_PUBLIC_KEY = "mosparo-public-key"; + public static final String MOSPARO_PRIVATE_KEY = "mosparo-private-key"; + public static final String MOSPARO_VERIFY_SSL = "mosparo-verify-ssl"; + private static final Logger logger = Logger.getLogger(RegistrationMosparo.class); public static final String PROVIDER_ID = "registration-mosparo-action"; @@ -91,31 +101,32 @@ public class RegistrationMosparo implements FormAction, FormActionFactory { AuthenticationExecutionModel.Requirement.REQUIRED, AuthenticationExecutionModel.Requirement.DISABLED }; + @Override public AuthenticationExecutionModel.Requirement[] getRequirementChoices() { return REQUIREMENT_CHOICES; } + @Override public void buildPage(FormContext context, LoginFormsProvider form) { AuthenticatorConfigModel captchaConfig = context.getAuthenticatorConfig(); - String userLanguageTag = context.getSession().getContext().resolveLocale(context.getUser()).toLanguageTag(); if (captchaConfig == null || captchaConfig.getConfig() == null - || captchaConfig.getConfig().get(SITE_UUID) == null - || captchaConfig.getConfig().get(SITE_PUBKEY) == null || captchaConfig.getConfig().get(MOSPARO_HOST) == null + || captchaConfig.getConfig().get(MOSPARO_UUID) == null + || captchaConfig.getConfig().get(MOSPARO_PUBLIC_KEY) == null + || captchaConfig.getConfig().get(MOSPARO_PRIVATE_KEY) == null ) { - form.addError(new FormMessage(null, "Mosparo not configured properly.")); + form.addError(new FormMessage(null, "mosparo not configured properly.")); return; } - String siteUUID = captchaConfig.getConfig().get(SITE_UUID); - form.addScript("https://" + getMosparoHostname(captchaConfig) + "/build/mosparo-frontend.js"); - form.addScript( "var m; " + - "window.onload = function(){ " + - "m = new mosparo( " + - "'mosparo-box'," + - " captchaConfig.getConfig().get(MOSPARO_HOST)," + - " captchaConfig.getConfig().get(SITE_UUID)," + - " captchaConfig.getConfig().get(SITE_PUBKEY)); };"); + + form.setAttribute("mosparoRequired", true); + form.setAttribute("mosparoHost", captchaConfig.getConfig().get(MOSPARO_HOST)); + form.setAttribute("mosparoUuid", captchaConfig.getConfig().get(MOSPARO_UUID)); + form.setAttribute("mosparoPublicKey", captchaConfig.getConfig().get(MOSPARO_PUBLIC_KEY)); + + // The hostname should always start with https:// + form.addScript(getMosparoHostname(captchaConfig) + "/build/mosparo-frontend.js"); } @Override @@ -125,13 +136,8 @@ public class RegistrationMosparo implements FormAction, FormActionFactory { boolean success = false; context.getEvent().detail(Details.REGISTER_METHOD, "form"); - String captcha = formData.getFirst(MOSPARO_RESPONSE); - if (!Validation.isBlank(captcha)) { - AuthenticatorConfigModel captchaConfig = context.getAuthenticatorConfig(); - String pubkey = captchaConfig.getConfig().get(SITE_PUBKEY); + success = verifyFormData(context, formData); - success = validateMosparo(context, success, captcha, pubkey); - } if (success) { context.success(); } else { @@ -141,20 +147,54 @@ public class RegistrationMosparo implements FormAction, FormActionFactory { context.validationError(formData, errors); context.excludeOtherErrors(); return; - - } } private String getMosparoHostname(AuthenticatorConfigModel config) { - - System.out.println(MOSPARO_HOST); - return config.getConfig().get(MOSPARO_HOST); + return config.getConfig().get(MOSPARO_HOST); } - protected boolean validateMosparo(ValidationContext context, boolean success, String captcha, String pubkey) { - CloseableHttpClient httpClient = context.getSession().getProvider(HttpClientProvider.class).getHttpClient(); - HttpPost post = new HttpPost("https://" + getMosparoHostname(context.getAuthenticatorConfig()) + "/api/v1/frontend/verification/verify"); + protected boolean validateMosparo(ValidationContext context, MultivaluedMap formData) { + // 1. Remove the ignored fields from the form data + formData.remove("password"); + formData.remove("password-confirm"); + + // 2. Extract the submit and validation token from the form data + String mosparoSubmitToken = formData.getFirst("_mosparo_submitToken"); + String mosparoValidationToken = formData.getFirst("_mosparo_validationToken"); + + // 3. Prepare the form data + MultivaluedMap preparedFormData = new MultivaluedHashMap<>(); + for (Map.Entry> entry : formData.entrySet()) { + if (entry.getKey().startsWith("_mosparo_")) { + continue; + } + + String value = entry.getValue().getFirst(); + preparedFormData.add(entry.getKey(), value.replace("\r\n", "\n")); + } + + // 4. Generate the hashes + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + MultivaluedMap hashedFormData = new MultivaluedHashMap<>(); + for (Map.Entry> entry : preparedFormData.entrySet()) { + String value = entry.getValue().getFirst(); + byte[] hashedValue = digest.digest(value.getBytes(StandardCharsets.UTF_8)); + hashedFormData.add(entry.getKey(), convertBytesToHex(hashedValue)); + } + + // 5. Generate the form data signature + // 6. Generate the validation signature + // 7. Prepare the verification signature + // 8. Collect the request data + // 9. Generate the request signature + // 10. Send the API request + // 11. Check the response + + boolean success = false; + /*CloseableHttpClient httpClient = context.getSession().getProvider(HttpClientProvider.class).getHttpClient(); + HttpPost post = new HttpPost(getMosparoHostname(context.getAuthenticatorConfig()) + "/api/v1/verification/verify"); + List formparams = new LinkedList<>(); formparams.add(new BasicNameValuePair("pubkey", pubkey)); formparams.add(new BasicNameValuePair("response", captcha)); @@ -174,10 +214,25 @@ public class RegistrationMosparo implements FormAction, FormActionFactory { } } catch (Exception e) { ServicesLogger.LOGGER.recaptchaFailed(e); - } + }*/ return success; } + private static String convertBytesToHex(byte[] hash) { + StringBuilder hexString = new StringBuilder(2 * hash.length); + for (int i = 0; i < hash.length; i++) { + String hex = Integer.toHexString(0xff & hash[i]); + + if (hex.length() == 1) { + hexString.append('0'); + } + + hexString.append(hex); + } + + return hexString.toString(); + } + @Override public void success(FormContext context) { @@ -238,18 +293,6 @@ public class RegistrationMosparo implements FormAction, FormActionFactory { static { ProviderConfigProperty property; - property = new ProviderConfigProperty(); - property.setName(SITE_UUID); - property.setLabel("UUID"); - property.setType(ProviderConfigProperty.STRING_TYPE); - property.setHelpText("UUID"); - CONFIG_PROPERTIES.add(property); - property = new ProviderConfigProperty(); - property.setName(SITE_PUBKEY); - property.setLabel("Mosparo Public Key"); - property.setType(ProviderConfigProperty.STRING_TYPE); - property.setHelpText("Mosparo Public Key (Site Key)"); - CONFIG_PROPERTIES.add(property); property = new ProviderConfigProperty(); property.setName(MOSPARO_HOST); @@ -257,6 +300,34 @@ public class RegistrationMosparo implements FormAction, FormActionFactory { property.setType(ProviderConfigProperty.STRING_TYPE); property.setHelpText("Hostname mosparo-host"); CONFIG_PROPERTIES.add(property); + + property = new ProviderConfigProperty(); + property.setName(MOSPARO_UUID); + property.setLabel("UUID"); + property.setType(ProviderConfigProperty.STRING_TYPE); + property.setHelpText("UUID"); + CONFIG_PROPERTIES.add(property); + + property = new ProviderConfigProperty(); + property.setName(MOSPARO_PUBLIC_KEY); + property.setLabel("mosparo Public Key"); + property.setType(ProviderConfigProperty.STRING_TYPE); + property.setHelpText("Mosparo Public Key (Site Key)"); + CONFIG_PROPERTIES.add(property); + + property = new ProviderConfigProperty(); + property.setName(MOSPARO_PRIVATE_KEY); + property.setLabel("mosparo Private Key"); + property.setType(ProviderConfigProperty.STRING_TYPE); + property.setHelpText("mosparo Private Key"); + CONFIG_PROPERTIES.add(property); + + property = new ProviderConfigProperty(); + property.setName(MOSPARO_VERIFY_SSL); + property.setLabel("mosparo Verify SSL"); + property.setType(ProviderConfigProperty.BOOLEAN_TYPE); + property.setHelpText("mosparo Verify SSL"); + CONFIG_PROPERTIES.add(property); }