Merge pull request 'Add client certificate authentication for Windows' (#500) from navodzoysa/device-mgt-core:issue-10462/secure-pending-operation into master

Reviewed-on: community/device-mgt-core#500
status
Pahansith Gunathilake 2 months ago
commit cec3ff580e

@ -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);

@ -42,5 +42,4 @@ public class CommonUtil {
public static synchronized BigInteger generateSerialNumber() {
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_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=";
}
}

@ -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());
}
}
}

@ -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);

@ -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;
}

Loading…
Cancel
Save