From 31e9a6e45721a5b8ffa95ad03cf4e46e67d90158 Mon Sep 17 00:00:00 2001 From: navodzoysa Date: Tue, 20 Aug 2024 14:18:30 +0530 Subject: [PATCH] Add client certificate authentication for Windows --- .../mgt/core/impl/CertificateGenerator.java | 6 ++ .../certificate/mgt/core/util/CommonUtil.java | 1 - .../authenticator/framework/Constants.java | 11 ++++ .../authenticator/framework/Utils/Utils.java | 60 ++++++++++++++++- .../framework/WebappAuthenticationValve.java | 10 +++ .../CertificateAuthenticator.java | 65 ++++++++++--------- .../authorizer/PermissionAuthorizer.java | 2 - 7 files changed, 120 insertions(+), 35 deletions(-) diff --git a/components/certificate-mgt/io.entgra.device.mgt.core.certificate.mgt.core/src/main/java/io/entgra/device/mgt/core/certificate/mgt/core/impl/CertificateGenerator.java b/components/certificate-mgt/io.entgra.device.mgt.core.certificate.mgt.core/src/main/java/io/entgra/device/mgt/core/certificate/mgt/core/impl/CertificateGenerator.java index e8a29ebc29..75f4266b1c 100755 --- a/components/certificate-mgt/io.entgra.device.mgt.core.certificate.mgt.core/src/main/java/io/entgra/device/mgt/core/certificate/mgt/core/impl/CertificateGenerator.java +++ b/components/certificate-mgt/io.entgra.device.mgt.core.certificate.mgt.core/src/main/java/io/entgra/device/mgt/core/certificate/mgt/core/impl/CertificateGenerator.java @@ -823,8 +823,14 @@ public class CertificateGenerator { } String subjectDn = joiner.toString(); X500Name issuerName = new X500Name(subjectDn); + String commonName = certificationRequest.getSubject().getRDNs(BCStyle.CN)[0].getFirst() .getValue().toString(); + // CSR sent from a Windows device will have an '!' followed by the device ID in the CN + if (commonName.contains("!")) { + commonName = commonName.split("!")[1]; + } + int tenantId = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantId(); X500Name subjectName = new X500Name("O=" + commonName + " ,CN=" + serialNumber + ", OU=tenant_" + tenantId); diff --git a/components/certificate-mgt/io.entgra.device.mgt.core.certificate.mgt.core/src/main/java/io/entgra/device/mgt/core/certificate/mgt/core/util/CommonUtil.java b/components/certificate-mgt/io.entgra.device.mgt.core.certificate.mgt.core/src/main/java/io/entgra/device/mgt/core/certificate/mgt/core/util/CommonUtil.java index 0aee44954d..0b1d317721 100755 --- a/components/certificate-mgt/io.entgra.device.mgt.core.certificate.mgt.core/src/main/java/io/entgra/device/mgt/core/certificate/mgt/core/util/CommonUtil.java +++ b/components/certificate-mgt/io.entgra.device.mgt.core.certificate.mgt.core/src/main/java/io/entgra/device/mgt/core/certificate/mgt/core/util/CommonUtil.java @@ -42,5 +42,4 @@ public class CommonUtil { public static synchronized BigInteger generateSerialNumber() { return BigInteger.valueOf(System.currentTimeMillis()); } - } diff --git a/components/webapp-authenticator-framework/io.entgra.device.mgt.core.webapp.authenticator.framework/src/main/java/io/entgra/device/mgt/core/webapp/authenticator/framework/Constants.java b/components/webapp-authenticator-framework/io.entgra.device.mgt.core.webapp.authenticator.framework/src/main/java/io/entgra/device/mgt/core/webapp/authenticator/framework/Constants.java index 3a62f2f3eb..e38bb76412 100644 --- a/components/webapp-authenticator-framework/io.entgra.device.mgt.core.webapp.authenticator.framework/src/main/java/io/entgra/device/mgt/core/webapp/authenticator/framework/Constants.java +++ b/components/webapp-authenticator-framework/io.entgra.device.mgt.core.webapp.authenticator.framework/src/main/java/io/entgra/device/mgt/core/webapp/authenticator/framework/Constants.java @@ -32,6 +32,9 @@ public final class Constants { public static final String HEADER_HTTP_ACCEPT = "Accept"; public static final String HEADER_HTTP_AUTHORIZATION = "Authorization"; public static final String ONE_TIME_TOKEN_HEADER = "one-time-token"; + public static final String MUTUAL_AUTH_HEADER = "mutual-auth-header"; + public static final String PROXY_MUTUAL_AUTH_HEADER = "proxy-mutual-auth-header"; + public static final String CERTIFICATE_VERIFICATION_HEADER = "Mdm-Signature"; } public static final class ContentTypes { @@ -53,4 +56,12 @@ public final class Constants { public static final String DELETE = "delete"; public static final String ACTION = "action"; } + + public static final class Certificate { + private Certificate() { + throw new AssertionError(); + } + + public static final String ORGANIZATION_ATTRIBUTE = "O="; + } } diff --git a/components/webapp-authenticator-framework/io.entgra.device.mgt.core.webapp.authenticator.framework/src/main/java/io/entgra/device/mgt/core/webapp/authenticator/framework/Utils/Utils.java b/components/webapp-authenticator-framework/io.entgra.device.mgt.core.webapp.authenticator.framework/src/main/java/io/entgra/device/mgt/core/webapp/authenticator/framework/Utils/Utils.java index 014b2fffed..e93c8009c9 100644 --- a/components/webapp-authenticator-framework/io.entgra.device.mgt.core.webapp.authenticator.framework/src/main/java/io/entgra/device/mgt/core/webapp/authenticator/framework/Utils/Utils.java +++ b/components/webapp-authenticator-framework/io.entgra.device.mgt.core.webapp.authenticator.framework/src/main/java/io/entgra/device/mgt/core/webapp/authenticator/framework/Utils/Utils.java @@ -18,6 +18,12 @@ package io.entgra.device.mgt.core.webapp.authenticator.framework.Utils; +import io.entgra.device.mgt.core.certificate.mgt.core.scep.SCEPException; +import io.entgra.device.mgt.core.certificate.mgt.core.scep.SCEPManager; +import io.entgra.device.mgt.core.certificate.mgt.core.scep.TenantedDeviceWrapper; +import io.entgra.device.mgt.core.device.mgt.common.DeviceIdentifier; +import io.entgra.device.mgt.core.device.mgt.common.DeviceManagementConstants; +import io.entgra.device.mgt.core.device.mgt.common.EnrolmentInfo; import io.entgra.device.mgt.core.device.mgt.core.util.DeviceManagerUtil; import io.entgra.device.mgt.core.webapp.authenticator.framework.AuthenticationException; import io.entgra.device.mgt.core.webapp.authenticator.framework.AuthenticationInfo; @@ -26,6 +32,7 @@ import io.entgra.device.mgt.core.webapp.authenticator.framework.authenticator.oa import io.entgra.device.mgt.core.webapp.authenticator.framework.authenticator.oauth.OAuthValidationResponse; import io.entgra.device.mgt.core.webapp.authenticator.framework.authenticator.oauth.OAuthValidatorFactory; import io.entgra.device.mgt.core.webapp.authenticator.framework.internal.AuthenticatorFrameworkDataHolder; +import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.wso2.carbon.context.PrivilegedCarbonContext; @@ -34,6 +41,7 @@ import org.wso2.carbon.user.api.UserStoreException; import org.wso2.carbon.user.core.service.RealmService; import org.wso2.carbon.utils.multitenancy.MultitenantUtils; +import java.security.cert.X509Certificate; import java.util.Properties; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -151,10 +159,60 @@ public class Utils { String sysPropertyName = matchPattern.group(1); String sysPropertyValue = System.getProperty(sysPropertyName); if (sysPropertyValue != null && !sysPropertyName.isEmpty()) { - urlWithPlaceholders = urlWithPlaceholders.replaceAll("\\$\\{(" + sysPropertyName + ")\\}", sysPropertyValue); + urlWithPlaceholders = urlWithPlaceholders.replaceAll("\\$\\{(" + sysPropertyName + ")\\}", + sysPropertyValue); } } return urlWithPlaceholders; } + /** + * Returns the value of the given attribute from the subject distinguished name. eg: "entgra.net" + * from "CN=entgra.net" + * @param requestCertificate {@link X509Certificate} that needs to extract an attribute from + * @param attribute the attribute name that needs to be extracted from the cert. eg: "CN=" + * @return the value of the attribute + */ + public static String getSubjectDnAttribute(X509Certificate requestCertificate, String attribute) { + String distinguishedName = requestCertificate.getSubjectDN().getName(); + if (StringUtils.isNotEmpty(distinguishedName)) { + String[] dnSplits = distinguishedName.split(","); + for (String dnSplit : dnSplits) { + if (dnSplit.contains(attribute)) { + String[] cnSplits = dnSplit.split("="); + if (StringUtils.isNotEmpty(cnSplits[1])) { + return cnSplits[1]; + } + } + } + } + return null; + } + + /** + * Check if the device identifier is valid and set the authentication info such as the tenant domain, + * tenant id and username of the enrolled device. + * @param deviceIdentifier {@link DeviceIdentifier} containing device id and type + * @param authenticationInfo {@link AuthenticationInfo} containing tenant and user details + * @throws SCEPException if the device or tenant does not exist + */ + public static void validateScepDevice(DeviceIdentifier deviceIdentifier, AuthenticationInfo authenticationInfo) + throws SCEPException { + SCEPManager scepManager = AuthenticatorFrameworkDataHolder.getInstance().getScepManager(); + TenantedDeviceWrapper tenantedDeviceWrapper = scepManager.getValidatedDevice(deviceIdentifier); + authenticationInfo.setTenantDomain(tenantedDeviceWrapper.getTenantDomain()); + authenticationInfo.setTenantId(tenantedDeviceWrapper.getTenantId()); + + // To make sure the tenant flow is not initiated in the valve as the + // tenant flows are initiated at the API level on iOS + if (deviceIdentifier.getType().equals(DeviceManagementConstants.MobileDeviceTypes.MOBILE_DEVICE_TYPE_IOS)) { + authenticationInfo.setTenantId(-1); + } + + if (tenantedDeviceWrapper.getDevice() != null && + tenantedDeviceWrapper.getDevice().getEnrolmentInfo() != null) { + EnrolmentInfo enrolmentInfo = tenantedDeviceWrapper.getDevice().getEnrolmentInfo(); + authenticationInfo.setUsername(enrolmentInfo.getOwner()); + } + } } diff --git a/components/webapp-authenticator-framework/io.entgra.device.mgt.core.webapp.authenticator.framework/src/main/java/io/entgra/device/mgt/core/webapp/authenticator/framework/WebappAuthenticationValve.java b/components/webapp-authenticator-framework/io.entgra.device.mgt.core.webapp.authenticator.framework/src/main/java/io/entgra/device/mgt/core/webapp/authenticator/framework/WebappAuthenticationValve.java index 8b430ba012..3f4ce9f9fc 100644 --- a/components/webapp-authenticator-framework/io.entgra.device.mgt.core.webapp.authenticator.framework/src/main/java/io/entgra/device/mgt/core/webapp/authenticator/framework/WebappAuthenticationValve.java +++ b/components/webapp-authenticator-framework/io.entgra.device.mgt.core.webapp.authenticator.framework/src/main/java/io/entgra/device/mgt/core/webapp/authenticator/framework/WebappAuthenticationValve.java @@ -94,6 +94,16 @@ public class WebappAuthenticationValve extends CarbonTomcatValve { return; } AuthenticationInfo authenticationInfo = authenticator.authenticate(request, response); + + // If the request header contains 'Mdm-Signature', then it is a iOS or Windows device trying to sync and + // it is already authenticated from the CertificateAuthenticator. Hence, there is no need to check + // if the user associated with the device has permission. + if (request.getHeader(Constants.HTTPHeaders.CERTIFICATE_VERIFICATION_HEADER) != null && + authenticationInfo.getStatus() == WebappAuthenticator.Status.SUCCESS) { + this.getNext().invoke(request, response, compositeValve); + return; + } + if (isManagedAPI(request) && (authenticationInfo.getStatus() == WebappAuthenticator.Status.CONTINUE || authenticationInfo.getStatus() == WebappAuthenticator.Status.SUCCESS)) { WebappAuthenticator.Status status = WebappTenantAuthorizer.authorize(request, authenticationInfo); diff --git a/components/webapp-authenticator-framework/io.entgra.device.mgt.core.webapp.authenticator.framework/src/main/java/io/entgra/device/mgt/core/webapp/authenticator/framework/authenticator/CertificateAuthenticator.java b/components/webapp-authenticator-framework/io.entgra.device.mgt.core.webapp.authenticator.framework/src/main/java/io/entgra/device/mgt/core/webapp/authenticator/framework/authenticator/CertificateAuthenticator.java index cb9bf50215..1df91c7c30 100644 --- a/components/webapp-authenticator-framework/io.entgra.device.mgt.core.webapp.authenticator.framework/src/main/java/io/entgra/device/mgt/core/webapp/authenticator/framework/authenticator/CertificateAuthenticator.java +++ b/components/webapp-authenticator-framework/io.entgra.device.mgt.core.webapp.authenticator.framework/src/main/java/io/entgra/device/mgt/core/webapp/authenticator/framework/authenticator/CertificateAuthenticator.java @@ -21,13 +21,11 @@ package io.entgra.device.mgt.core.webapp.authenticator.framework.authenticator; import io.entgra.device.mgt.core.certificate.mgt.core.dto.CertificateResponse; import io.entgra.device.mgt.core.certificate.mgt.core.exception.KeystoreException; import io.entgra.device.mgt.core.certificate.mgt.core.scep.SCEPException; -import io.entgra.device.mgt.core.certificate.mgt.core.scep.SCEPManager; -import io.entgra.device.mgt.core.certificate.mgt.core.scep.TenantedDeviceWrapper; import io.entgra.device.mgt.core.device.mgt.common.DeviceIdentifier; import io.entgra.device.mgt.core.device.mgt.common.DeviceManagementConstants; -import io.entgra.device.mgt.core.device.mgt.common.EnrolmentInfo; import io.entgra.device.mgt.core.webapp.authenticator.framework.AuthenticationException; import io.entgra.device.mgt.core.webapp.authenticator.framework.AuthenticationInfo; +import io.entgra.device.mgt.core.webapp.authenticator.framework.Constants; import io.entgra.device.mgt.core.webapp.authenticator.framework.Utils.Utils; import io.entgra.device.mgt.core.webapp.authenticator.framework.internal.AuthenticatorFrameworkDataHolder; import org.apache.catalina.connector.Request; @@ -45,9 +43,6 @@ public class CertificateAuthenticator implements WebappAuthenticator { private static final Log log = LogFactory.getLog(CertificateAuthenticator.class); private static final String CERTIFICATE_AUTHENTICATOR = "CertificateAuth"; - private static final String MUTUAL_AUTH_HEADER = "mutual-auth-header"; - private static final String PROXY_MUTUAL_AUTH_HEADER = "proxy-mutual-auth-header"; - private static final String CERTIFICATE_VERIFICATION_HEADER = "Mdm-Signature"; private static final String CLIENT_CERTIFICATE_ATTRIBUTE = "javax.servlet.request.X509Certificate"; @Override @@ -57,8 +52,9 @@ public class CertificateAuthenticator implements WebappAuthenticator { @Override public boolean canHandle(Request request) { - return request.getHeader(CERTIFICATE_VERIFICATION_HEADER) != null - || request.getHeader(MUTUAL_AUTH_HEADER) != null || request.getHeader(PROXY_MUTUAL_AUTH_HEADER) != null; + return request.getHeader(Constants.HTTPHeaders.CERTIFICATE_VERIFICATION_HEADER) != null + || request.getHeader(Constants.HTTPHeaders.MUTUAL_AUTH_HEADER) != null || + request.getHeader(Constants.HTTPHeaders.PROXY_MUTUAL_AUTH_HEADER) != null; } @Override @@ -73,12 +69,14 @@ public class CertificateAuthenticator implements WebappAuthenticator { try { // When there is a load balancer terminating mutual SSL, it should pass this header along and // as the value of this header, the client certificate subject dn should be passed. - if (request.getHeader(PROXY_MUTUAL_AUTH_HEADER) != null) { + if (request.getHeader(Constants.HTTPHeaders.PROXY_MUTUAL_AUTH_HEADER) != null) { if (log.isDebugEnabled()) { - log.debug("PROXY_MUTUAL_AUTH_HEADER " + request.getHeader(PROXY_MUTUAL_AUTH_HEADER)); + log.debug("PROXY_MUTUAL_AUTH_HEADER " + + request.getHeader(Constants.HTTPHeaders.PROXY_MUTUAL_AUTH_HEADER)); } CertificateResponse certificateResponse = AuthenticatorFrameworkDataHolder.getInstance(). - getCertificateManagementService().verifySubjectDN(request.getHeader(PROXY_MUTUAL_AUTH_HEADER)); + getCertificateManagementService().verifySubjectDN(request.getHeader( + Constants.HTTPHeaders.PROXY_MUTUAL_AUTH_HEADER)); authenticationInfo = checkCertificateResponse(certificateResponse); if (log.isDebugEnabled()) { log.debug("Certificate Serial : " + certificateResponse.getSerialNumber() @@ -86,7 +84,7 @@ public class CertificateAuthenticator implements WebappAuthenticator { + " , username" + authenticationInfo.getUsername()); } } - else if (request.getHeader(MUTUAL_AUTH_HEADER) != null) { + else if (request.getHeader(Constants.HTTPHeaders.MUTUAL_AUTH_HEADER) != null) { Object object = request.getAttribute(CLIENT_CERTIFICATE_ATTRIBUTE); X509Certificate[] clientCertificate = null; if (object instanceof X509Certificate[]) { @@ -101,13 +99,11 @@ public class CertificateAuthenticator implements WebappAuthenticator { authenticationInfo.setStatus(Status.FAILURE); authenticationInfo.setMessage("No client certificate is present"); } - } else if (request.getHeader(CERTIFICATE_VERIFICATION_HEADER) != null) { - String certHeader = request.getHeader(CERTIFICATE_VERIFICATION_HEADER); + } else if (request.getHeader(Constants.HTTPHeaders.CERTIFICATE_VERIFICATION_HEADER) != null) { + String certHeader = request.getHeader(Constants.HTTPHeaders.CERTIFICATE_VERIFICATION_HEADER); if (certHeader != null && AuthenticatorFrameworkDataHolder.getInstance().getCertificateManagementService(). verifySignature(certHeader)) { - AuthenticatorFrameworkDataHolder.getInstance().getCertificateManagementService(). - extractCertificateFromSignature(certHeader); X509Certificate certificate = AuthenticatorFrameworkDataHolder.getInstance().getCertificateManagementService(). extractCertificateFromSignature(certHeader); @@ -116,30 +112,37 @@ public class CertificateAuthenticator implements WebappAuthenticator { if (challengeToken != null) { challengeToken = challengeToken.substring(challengeToken.indexOf("(") + 1).trim(); - SCEPManager scepManager = AuthenticatorFrameworkDataHolder.getInstance().getScepManager(); DeviceIdentifier deviceIdentifier = new DeviceIdentifier(); deviceIdentifier.setId(challengeToken); deviceIdentifier.setType(DeviceManagementConstants.MobileDeviceTypes.MOBILE_DEVICE_TYPE_IOS); - TenantedDeviceWrapper tenantedDeviceWrapper = scepManager.getValidatedDevice(deviceIdentifier); - authenticationInfo.setTenantDomain(tenantedDeviceWrapper.getTenantDomain()); - // To make sure the tenant flow is not initiated in the valve as the - // tenant flows are initiated at the API level on iOS - authenticationInfo.setTenantId(-1); - - if (tenantedDeviceWrapper.getDevice() != null && - tenantedDeviceWrapper.getDevice().getEnrolmentInfo() != null) { - - EnrolmentInfo enrolmentInfo = tenantedDeviceWrapper.getDevice().getEnrolmentInfo(); - authenticationInfo.setUsername(enrolmentInfo.getOwner()); - } + Utils.validateScepDevice(deviceIdentifier, authenticationInfo); authenticationInfo.setStatus(Status.CONTINUE); + } else { + DeviceIdentifier deviceIdentifier = new DeviceIdentifier(); + String deviceId = Utils.getSubjectDnAttribute(certificate, + Constants.Certificate.ORGANIZATION_ATTRIBUTE); + if (deviceId == null) { + authenticationInfo.setStatus(Status.FAILURE); + return authenticationInfo; + } + deviceIdentifier.setId(deviceId); + deviceIdentifier.setType( + DeviceManagementConstants.MobileDeviceTypes.MOBILE_DEVICE_TYPE_WINDOWS); + Utils.validateScepDevice(deviceIdentifier, authenticationInfo); + authenticationInfo.setStatus(Status.SUCCESS); } } } } catch (KeystoreException e) { - log.error("KeystoreException occurred ", e); + String msg = "Error occurred while validating device client certificate."; + log.error(msg, e); + authenticationInfo.setStatus(Status.FAILURE); + authenticationInfo.setMessage(msg); } catch (SCEPException e) { - log.error("SCEPException occurred ", e); + String msg = "Error occurred while validating device identification."; + log.error(msg, e); + authenticationInfo.setStatus(Status.FAILURE); + authenticationInfo.setMessage(msg); } return authenticationInfo; } diff --git a/components/webapp-authenticator-framework/io.entgra.device.mgt.core.webapp.authenticator.framework/src/main/java/io/entgra/device/mgt/core/webapp/authenticator/framework/authorizer/PermissionAuthorizer.java b/components/webapp-authenticator-framework/io.entgra.device.mgt.core.webapp.authenticator.framework/src/main/java/io/entgra/device/mgt/core/webapp/authenticator/framework/authorizer/PermissionAuthorizer.java index c9bf8974a7..c274184e55 100644 --- a/components/webapp-authenticator-framework/io.entgra.device.mgt.core.webapp.authenticator.framework/src/main/java/io/entgra/device/mgt/core/webapp/authenticator/framework/authorizer/PermissionAuthorizer.java +++ b/components/webapp-authenticator-framework/io.entgra.device.mgt.core.webapp.authenticator.framework/src/main/java/io/entgra/device/mgt/core/webapp/authenticator/framework/authorizer/PermissionAuthorizer.java @@ -139,9 +139,7 @@ public class PermissionAuthorizer { } else { return WebappAuthenticator.Status.FAILURE; } - } - }