1

I have a java application running in Tomcat. I added https listener on port 8443 as described here:

https://docs.spring.io/spring-boot/docs/2.2.x/reference/pdf/spring-boot-reference.pdf (9.3.13. Enable Multiple Connectors with Tomcat)

I additionally configured the listener to challenge clients to present certificates (2-way-TLS / mutual TLS) and have a truststore with trusted entries. All of this works and I can see the whole TLS-Handshake in the logs, along with the client certificate presented.

I have a WebSocket server endpoint (@javax.websocket.server.ServerEndpoint) which does get called when a client is connecting via "wss://....." after the secure tls tunnel is established, the @OnOpen method is called and it has javax.websocket.Session object. So the http(s) upgrade to ws works.

My Question: After the TLS-Handshake is performed (triggered by WSS: ws over https in my case), I need to extract client certificate (X509) information (subject / issuer etc..) and have it available in the @OnOpen method. I was searching for some Interceptors or another way to access and extract certificate data and make it available after the upgrade to ws is done. Is there any way to access HttpServletRequest from the @OnOpen web socket handling method? Appreciate your help.

Yordan Boev
  • 301
  • 1
  • 9

1 Answers1

0

After looking furthermore I was able to do it.

Step 1: Tap into modifyHandshake(...) method and obtain ServletRequest object, HttpSession will not do the job. Credits go to this StackOverflow answer.

package example.com;

import java.lang.reflect.Field;
import java.security.cert.X509Certificate;
import javax.security.auth.x500.X500Principal;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpSession;
import javax.websocket.HandshakeResponse;
import javax.websocket.server.HandshakeRequest;
import javax.websocket.server.ServerEndpointConfig;    

public class InjectAttributesIntoWebSocketConfigurator extends ServerEndpointConfig.Configurator {

  @Override
  public void modifyHandshake(ServerEndpointConfig config, HandshakeRequest request, HandshakeResponse response) {
    ServletRequest servletRequest = getField(request, ServletRequest.class);
    X509Certificate[] certificates = (X509Certificate[]) servletRequest.getAttribute("javax.servlet.request.X509Certificate");
    // certificates[0] is client certificate
    // do null/error/empty array handling here
    config.getUserProperties().put("clientcert", certificates[0]);
  }

  private static <I, F> F getField(I instance, Class<F> fieldType) {
    try {
      for (Class<?> type = instance.getClass(); type != Object.class; type = type.getSuperclass()) {
        for (Field field : type.getDeclaredFields()) {
          if (fieldType.isAssignableFrom(field.getType())) {
            field.setAccessible(true);
            return (F) field.get(instance);
          }
        }
      }
    } catch (Exception e) {
      // Handle?
    }
     return null;
  }
}

Step 2: Configure WS to use the configurator above and read whatever parameters from the Session object (you have to had previously added those in the modifyHandshake() metod).

@ServerEndpoint(
        value = "/some/endpoint/here",
        configurator = InjectAttributesIntoWebSocketConfigurator.class
)

Step 3: Done :). Now, the WS endpoint has client certificate with which the TLS (2-way TLS in my case) underlying HTTPS connection was established.

Yordan Boev
  • 301
  • 1
  • 9