Add client certificate authentication for Windows

pull/500/head
Navod Zoysa 3 months ago
parent 35ab8ceb97
commit 31e9a6e457

@ -823,8 +823,14 @@ public class CertificateGenerator {
} }
String subjectDn = joiner.toString(); String subjectDn = joiner.toString();
X500Name issuerName = new X500Name(subjectDn); X500Name issuerName = new X500Name(subjectDn);
String commonName = certificationRequest.getSubject().getRDNs(BCStyle.CN)[0].getFirst() String commonName = certificationRequest.getSubject().getRDNs(BCStyle.CN)[0].getFirst()
.getValue().toString(); .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(); int tenantId = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantId();
X500Name subjectName = new X500Name("O=" + commonName + " ,CN=" + X500Name subjectName = new X500Name("O=" + commonName + " ,CN=" +
serialNumber + ", OU=tenant_" + tenantId); serialNumber + ", OU=tenant_" + tenantId);

@ -42,5 +42,4 @@ public class CommonUtil {
public static synchronized BigInteger generateSerialNumber() { public static synchronized BigInteger generateSerialNumber() {
return BigInteger.valueOf(System.currentTimeMillis()); return BigInteger.valueOf(System.currentTimeMillis());
} }
} }

@ -32,6 +32,9 @@ public final class Constants {
public static final String HEADER_HTTP_ACCEPT = "Accept"; public static final String HEADER_HTTP_ACCEPT = "Accept";
public static final String HEADER_HTTP_AUTHORIZATION = "Authorization"; public static final String HEADER_HTTP_AUTHORIZATION = "Authorization";
public static final String ONE_TIME_TOKEN_HEADER = "one-time-token"; 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 { public static final class ContentTypes {
@ -53,4 +56,12 @@ public final class Constants {
public static final String DELETE = "delete"; public static final String DELETE = "delete";
public static final String ACTION = "action"; public static final String ACTION = "action";
} }
public static final class Certificate {
private Certificate() {
throw new AssertionError();
}
public static final String ORGANIZATION_ATTRIBUTE = "O=";
}
} }

@ -18,6 +18,12 @@
package io.entgra.device.mgt.core.webapp.authenticator.framework.Utils; 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.device.mgt.core.util.DeviceManagerUtil;
import io.entgra.device.mgt.core.webapp.authenticator.framework.AuthenticationException; 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.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.OAuthValidationResponse;
import io.entgra.device.mgt.core.webapp.authenticator.framework.authenticator.oauth.OAuthValidatorFactory; import io.entgra.device.mgt.core.webapp.authenticator.framework.authenticator.oauth.OAuthValidatorFactory;
import io.entgra.device.mgt.core.webapp.authenticator.framework.internal.AuthenticatorFrameworkDataHolder; 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.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.context.PrivilegedCarbonContext; 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.user.core.service.RealmService;
import org.wso2.carbon.utils.multitenancy.MultitenantUtils; import org.wso2.carbon.utils.multitenancy.MultitenantUtils;
import java.security.cert.X509Certificate;
import java.util.Properties; import java.util.Properties;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -151,10 +159,60 @@ public class Utils {
String sysPropertyName = matchPattern.group(1); String sysPropertyName = matchPattern.group(1);
String sysPropertyValue = System.getProperty(sysPropertyName); String sysPropertyValue = System.getProperty(sysPropertyName);
if (sysPropertyValue != null && !sysPropertyName.isEmpty()) { if (sysPropertyValue != null && !sysPropertyName.isEmpty()) {
urlWithPlaceholders = urlWithPlaceholders.replaceAll("\\$\\{(" + sysPropertyName + ")\\}", sysPropertyValue); urlWithPlaceholders = urlWithPlaceholders.replaceAll("\\$\\{(" + sysPropertyName + ")\\}",
sysPropertyValue);
} }
} }
return urlWithPlaceholders; 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());
}
}
} }

@ -94,6 +94,16 @@ public class WebappAuthenticationValve extends CarbonTomcatValve {
return; return;
} }
AuthenticationInfo authenticationInfo = authenticator.authenticate(request, response); 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 || if (isManagedAPI(request) && (authenticationInfo.getStatus() == WebappAuthenticator.Status.CONTINUE ||
authenticationInfo.getStatus() == WebappAuthenticator.Status.SUCCESS)) { authenticationInfo.getStatus() == WebappAuthenticator.Status.SUCCESS)) {
WebappAuthenticator.Status status = WebappTenantAuthorizer.authorize(request, authenticationInfo); WebappAuthenticator.Status status = WebappTenantAuthorizer.authorize(request, authenticationInfo);

@ -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.dto.CertificateResponse;
import io.entgra.device.mgt.core.certificate.mgt.core.exception.KeystoreException; 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.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.DeviceIdentifier;
import io.entgra.device.mgt.core.device.mgt.common.DeviceManagementConstants; 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.AuthenticationException;
import io.entgra.device.mgt.core.webapp.authenticator.framework.AuthenticationInfo; 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.Utils.Utils;
import io.entgra.device.mgt.core.webapp.authenticator.framework.internal.AuthenticatorFrameworkDataHolder; import io.entgra.device.mgt.core.webapp.authenticator.framework.internal.AuthenticatorFrameworkDataHolder;
import org.apache.catalina.connector.Request; 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 Log log = LogFactory.getLog(CertificateAuthenticator.class);
private static final String CERTIFICATE_AUTHENTICATOR = "CertificateAuth"; 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"; private static final String CLIENT_CERTIFICATE_ATTRIBUTE = "javax.servlet.request.X509Certificate";
@Override @Override
@ -57,8 +52,9 @@ public class CertificateAuthenticator implements WebappAuthenticator {
@Override @Override
public boolean canHandle(Request request) { public boolean canHandle(Request request) {
return request.getHeader(CERTIFICATE_VERIFICATION_HEADER) != null return request.getHeader(Constants.HTTPHeaders.CERTIFICATE_VERIFICATION_HEADER) != null
|| request.getHeader(MUTUAL_AUTH_HEADER) != null || request.getHeader(PROXY_MUTUAL_AUTH_HEADER) != null; || request.getHeader(Constants.HTTPHeaders.MUTUAL_AUTH_HEADER) != null ||
request.getHeader(Constants.HTTPHeaders.PROXY_MUTUAL_AUTH_HEADER) != null;
} }
@Override @Override
@ -73,12 +69,14 @@ public class CertificateAuthenticator implements WebappAuthenticator {
try { try {
// When there is a load balancer terminating mutual SSL, it should pass this header along and // 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. // 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()) { 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(). 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); authenticationInfo = checkCertificateResponse(certificateResponse);
if (log.isDebugEnabled()) { if (log.isDebugEnabled()) {
log.debug("Certificate Serial : " + certificateResponse.getSerialNumber() log.debug("Certificate Serial : " + certificateResponse.getSerialNumber()
@ -86,7 +84,7 @@ public class CertificateAuthenticator implements WebappAuthenticator {
+ " , username" + authenticationInfo.getUsername()); + " , 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); Object object = request.getAttribute(CLIENT_CERTIFICATE_ATTRIBUTE);
X509Certificate[] clientCertificate = null; X509Certificate[] clientCertificate = null;
if (object instanceof X509Certificate[]) { if (object instanceof X509Certificate[]) {
@ -101,13 +99,11 @@ public class CertificateAuthenticator implements WebappAuthenticator {
authenticationInfo.setStatus(Status.FAILURE); authenticationInfo.setStatus(Status.FAILURE);
authenticationInfo.setMessage("No client certificate is present"); authenticationInfo.setMessage("No client certificate is present");
} }
} else if (request.getHeader(CERTIFICATE_VERIFICATION_HEADER) != null) { } else if (request.getHeader(Constants.HTTPHeaders.CERTIFICATE_VERIFICATION_HEADER) != null) {
String certHeader = request.getHeader(CERTIFICATE_VERIFICATION_HEADER); String certHeader = request.getHeader(Constants.HTTPHeaders.CERTIFICATE_VERIFICATION_HEADER);
if (certHeader != null && if (certHeader != null &&
AuthenticatorFrameworkDataHolder.getInstance().getCertificateManagementService(). AuthenticatorFrameworkDataHolder.getInstance().getCertificateManagementService().
verifySignature(certHeader)) { verifySignature(certHeader)) {
AuthenticatorFrameworkDataHolder.getInstance().getCertificateManagementService().
extractCertificateFromSignature(certHeader);
X509Certificate certificate = X509Certificate certificate =
AuthenticatorFrameworkDataHolder.getInstance().getCertificateManagementService(). AuthenticatorFrameworkDataHolder.getInstance().getCertificateManagementService().
extractCertificateFromSignature(certHeader); extractCertificateFromSignature(certHeader);
@ -116,30 +112,37 @@ public class CertificateAuthenticator implements WebappAuthenticator {
if (challengeToken != null) { if (challengeToken != null) {
challengeToken = challengeToken.substring(challengeToken.indexOf("(") + 1).trim(); challengeToken = challengeToken.substring(challengeToken.indexOf("(") + 1).trim();
SCEPManager scepManager = AuthenticatorFrameworkDataHolder.getInstance().getScepManager();
DeviceIdentifier deviceIdentifier = new DeviceIdentifier(); DeviceIdentifier deviceIdentifier = new DeviceIdentifier();
deviceIdentifier.setId(challengeToken); deviceIdentifier.setId(challengeToken);
deviceIdentifier.setType(DeviceManagementConstants.MobileDeviceTypes.MOBILE_DEVICE_TYPE_IOS); deviceIdentifier.setType(DeviceManagementConstants.MobileDeviceTypes.MOBILE_DEVICE_TYPE_IOS);
TenantedDeviceWrapper tenantedDeviceWrapper = scepManager.getValidatedDevice(deviceIdentifier); Utils.validateScepDevice(deviceIdentifier, authenticationInfo);
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());
}
authenticationInfo.setStatus(Status.CONTINUE); 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) { } 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) { } 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; return authenticationInfo;
} }

@ -139,9 +139,7 @@ public class PermissionAuthorizer {
} else { } else {
return WebappAuthenticator.Status.FAILURE; return WebappAuthenticator.Status.FAILURE;
} }
} }
} }

Loading…
Cancel
Save