Skip to content

Commit

Permalink
Merge pull request #323 from JanssenProject/feat/mtls-for-registratio…
Browse files Browse the repository at this point in the history
…n-endpoint

Added MTLS verification during client registration
  • Loading branch information
yuriyz authored Nov 18, 2021
2 parents 273c3ef + 1fecf42 commit d77d0d0
Show file tree
Hide file tree
Showing 5 changed files with 171 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,7 @@ public class AppConfiguration implements Configuration {
private String dcrSignatureValidationJwksUri;
private Boolean dcrAuthorizationWithClientCredentials = false;
private Boolean dcrSkipSignatureValidation = false;
private Boolean dcrAuthorizationWithMTLS = false;

private Boolean useLocalCache = false;
private Boolean fapiCompatibility = false;
Expand Down Expand Up @@ -662,6 +663,15 @@ public void setDcrSignatureValidationJwksUri(String dcrSignatureValidationJwksUr
this.dcrSignatureValidationJwksUri = dcrSignatureValidationJwksUri;
}

public Boolean getDcrAuthorizationWithMTLS() {
if (dcrAuthorizationWithMTLS == null) dcrAuthorizationWithMTLS = false;
return dcrAuthorizationWithMTLS;
}

public void setDcrAuthorizationWithMTLS(Boolean dcrAuthorizationWithMTLS) {
this.dcrAuthorizationWithMTLS = dcrAuthorizationWithMTLS;
}

public Boolean getForceIdTokenHintPrecense() {
if (forceIdTokenHintPrecense == null) forceIdTokenHintPrecense = false;
return forceIdTokenHintPrecense;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,9 @@
"/restv1/revoke_session",
"/restv1/bc-authorize",
"/restv1/par",
"/restv1/device_authorization"},
"/restv1/device_authorization",
"/restv1/register"
},
displayName = "oxAuth")
public class AuthenticationFilter implements Filter {

Expand Down Expand Up @@ -262,6 +264,13 @@ private boolean processMTLS(HttpServletRequest httpRequest, HttpServletResponse
client.getAuthenticationMethod() == io.jans.as.model.common.AuthenticationMethod.SELF_SIGNED_TLS_CLIENT_AUTH)) {
return mtlsService.processMTLS(httpRequest, httpResponse, filterChain, client);
}
} else {
final String requestUrl = httpRequest.getRequestURL().toString();
boolean isRegisterEndpoint = requestUrl.endsWith("/register");
boolean isRegistration = "POST".equalsIgnoreCase(httpRequest.getMethod());
if (appConfiguration.getDcrAuthorizationWithMTLS() && isRegistration && isRegisterEndpoint) {
return mtlsService.processRegisterMTLS(httpRequest);
}
}
return false;
}
Expand Down
32 changes: 32 additions & 0 deletions server/src/main/java/io/jans/as/server/auth/MTLSService.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import io.jans.as.server.service.SessionIdService;
import io.jans.as.server.service.external.ExternalDynamicClientRegistrationService;
import io.jans.as.server.service.external.context.DynamicClientRegistrationContext;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.json.JSONObject;
Expand All @@ -39,10 +40,13 @@
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
import java.io.IOException;
import java.security.PublicKey;
import java.security.cert.X509Certificate;
import java.util.List;

import static io.jans.as.model.register.RegisterRequestParam.TLS_CLIENT_AUTH_SUBJECT_DN;

/**
* @author Yuriy Zabrovarnyy
*/
Expand Down Expand Up @@ -169,4 +173,32 @@ private void authenticatedSuccessfully(Client client, HttpServletRequest httpReq

authenticator.authenticateBySessionId(sessionIdObject);
}

public boolean processRegisterMTLS(HttpServletRequest httpRequest) {
log.debug("Trying to authenticate client registration request via MTLS");

String tlsClientAuthSubjectDn = null;
try {
String request = IOUtils.toString(httpRequest.getReader());
JSONObject jsonObject = new JSONObject(request);
tlsClientAuthSubjectDn = jsonObject.optString(TLS_CLIENT_AUTH_SUBJECT_DN.toString());
} catch (Exception exception) {
log.error("Error getting TLS_CLIENT_AUTH_SUBJECT_DN field from request registration body", exception);
}

final String clientCertAsPem = httpRequest.getHeader("X-ClientCert");
if (StringUtils.isBlank(clientCertAsPem)) {
log.debug("Client certificate is missed in `X-ClientCert` header");
return false;
}

X509Certificate cert = CertUtils.x509CertificateFromPem(clientCertAsPem);
if (cert == null) {
log.debug("Failed to parse client certificate");
return false;
}

log.debug("MTLS client authentication tlsClientAuthSubjectDn = {}", tlsClientAuthSubjectDn);
return CertUtils.equalsRdn(tlsClientAuthSubjectDn, cert.getSubjectDN().getName());
}
}
118 changes: 118 additions & 0 deletions server/src/test/java/io/jans/as/server/service/MTLSServiceTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package io.jans.as.server.service;

import io.jans.as.model.crypto.AbstractCryptoProvider;
import io.jans.as.model.error.ErrorResponseFactory;
import io.jans.as.server.auth.Authenticator;
import io.jans.as.server.auth.MTLSService;
import io.jans.as.server.service.external.ExternalDynamicClientRegistrationService;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.testng.MockitoTestNGListener;
import org.slf4j.Logger;
import org.testng.annotations.Listeners;
import org.testng.annotations.Test;

import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;

import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.*;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertTrue;

@Listeners(MockitoTestNGListener.class)
public class MTLSServiceTest {

private static final String tlsClientAuthSubjectDn = "UID=4b18f6a7-2972-455c-948d-b0e59a8c1da9,1.3.6.1.4.1.311.60.2.1.3=#13024252,2.5.4.15=#131450726976617465204f7267616e697a6174696f6e,2.5.4.5=#130e3133383834373735303030313139,CN=hubfintech.com.br,OU=7eebe017-cb01-498c-81e4-6b4149b18e93,O=HUB PAGAMENTOS S.A,L=Tambore,ST=SP,C=BR";
private static final String certPem = "-----BEGIN CERTIFICATE-----\n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\n-----END CERTIFICATE-----";

@InjectMocks
private MTLSService mtlsService;

@Mock
private Logger log;

@Mock
private Authenticator authenticator;

@Mock
private SessionIdService sessionIdService;

@Mock
private AbstractCryptoProvider cryptoProvider;

@Mock
private ErrorResponseFactory errorResponseFactory;

@Mock
private ExternalDynamicClientRegistrationService externalDynamicClientRegistrationService;

@Test
public void processRegisterMTLS_HappyFlow_ReturnsTrue() throws Exception {
HttpServletRequest httpServletRequest = mock(HttpServletRequest.class);
String jsonRequest = "{ \"tls_client_auth_subject_dn\":\"" + tlsClientAuthSubjectDn + "\" }";
BufferedReader requestReader = new BufferedReader(new StringReader(jsonRequest));
when(httpServletRequest.getReader()).thenReturn(requestReader);
when(httpServletRequest.getHeader(eq("X-ClientCert"))).thenReturn(certPem);

boolean result = mtlsService.processRegisterMTLS(httpServletRequest);

assertTrue(result);
verify(log).debug("Trying to authenticate client registration request via MTLS");
verify(log).debug(anyString(), anyString());

verifyNoMoreInteractions(log);
}

@Test
public void processRegisterMTLS_ErrorReadingJsonRequest_ReturnsFalse() throws Exception {
HttpServletRequest httpServletRequest = mock(HttpServletRequest.class);
when(httpServletRequest.getReader()).thenThrow(new IOException());
when(httpServletRequest.getHeader(eq("X-ClientCert"))).thenReturn(certPem);

boolean result = mtlsService.processRegisterMTLS(httpServletRequest);

assertFalse(result);
verify(log).debug("Trying to authenticate client registration request via MTLS");
verify(log).error(eq("Error getting TLS_CLIENT_AUTH_SUBJECT_DN field from request registration body"), any(IOException.class));
verify(log).debug(anyString(), (String)isNull());

verifyNoMoreInteractions(log);
}

@Test
public void processRegisterMTLS_CouldntCreateX509Cert_ReturnsFalse() throws Exception {
HttpServletRequest httpServletRequest = mock(HttpServletRequest.class);
String jsonRequest = "{ \"tls_client_auth_subject_dn\":\"" + tlsClientAuthSubjectDn + "\" }";
BufferedReader requestReader = new BufferedReader(new StringReader(jsonRequest));
when(httpServletRequest.getReader()).thenReturn(requestReader);
when(httpServletRequest.getHeader(eq("X-ClientCert"))).thenReturn("ABC");

boolean result = mtlsService.processRegisterMTLS(httpServletRequest);

assertFalse(result);
verify(log).debug("Trying to authenticate client registration request via MTLS");
verify(log).debug("Failed to parse client certificate");

verifyNoMoreInteractions(log);
}

@Test
public void processRegisterMTLS_HeaderXClientCertNull_ReturnsFalse() throws Exception {
HttpServletRequest httpServletRequest = mock(HttpServletRequest.class);
String jsonRequest = "{ \"tls_client_auth_subject_dn\":\"" + tlsClientAuthSubjectDn + "\" }";
BufferedReader requestReader = new BufferedReader(new StringReader(jsonRequest));
when(httpServletRequest.getReader()).thenReturn(requestReader);

boolean result = mtlsService.processRegisterMTLS(httpServletRequest);

assertFalse(result);
verify(log).debug("Trying to authenticate client registration request via MTLS");
verify(log).debug("Client certificate is missed in `X-ClientCert` header");

verifyNoMoreInteractions(log);
}

}
1 change: 1 addition & 0 deletions server/src/test/resources/testng.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

<test name="Unit Tests" enabled="true">
<classes>
<class name="io.jans.as.server.service.MTLSServiceTest" />
<class name="io.jans.as.server.model.authorize.JwtAuthorizationRequestTest" />
<class name="io.jans.as.server.service.ScopeServiceTest" />
<class name="io.jans.as.server.model.CIBAGrantTest" />
Expand Down

0 comments on commit d77d0d0

Please sign in to comment.