# Jetty mit Client-Zertifikat nur bei spezifischer URL



## threadi (24. Aug 2021)

Hi,

ich stehe vor folgender Herausforderung:
Ich habe eine Anwendung die über Jetty im Web bereitgestellt wird. Eine spezifische URL dieser Anwendung soll nun mit einem Client-Zertifikat geschützt werden.

Beispiel:
Beim Aufruf von https://www.example.com ist kein Client-Zertifikat erforderlich.
Beim Aufruf von https://www.example.com/api/ ist ein Client-Zertifikat erforderlich.

Die start.ini von Jetty sieht so aus:

```
--module=server
jetty.httpConfig.sendServerVersion=false
--module=jsp
--module=annotations
--module=deploy
--module=logging-jetty
--module=console-capture
--module=ext
--module=requestlog
--module=http-forwarded
--module=plus
--module=rewrite
--module=jstl
--module=servlets
--module=http
--module=ssl
--module=https
jetty.sslContext.keyStorePath=credentials/server.keystore
jetty.sslContext.keyStorePassword=mypassword
jetty.sslContext.keyManagerPassword=mypassword
jetty.sslContext.trustStorePath=credentials/server.keystore
jetty.sslContext.trustStorePassword=mypassword
jetty.sslContext.needClientAuth=true
```

Die Anwendung wird per xml-Datei in Jetty eingebunden:

```
<?xml version="1.0"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
<Configure class="org.eclipse.jetty.webapp.WebAppContext">
  <Set name="war">/path/to/app.war</Set>
  <Set name="contextPath">/</Set>
  <Set name="extractWAR">false</Set>
  <Set name="copyWebDir">false</Set>
  <Set name="copyWebInf">true</Set>
  <Set name="persistTempDirectory">false</Set>
</Configure>
```

Es gibt eine Java-Datei die per Servlet die API-URL bereitstellt. Hier der Kopf:


```
@WebServlet(
        name = "api",
        urlPatterns = {"spapi"},
        loadOnStartup = 1
)
public class api extends HttpServlet {
   // Logging-Objekt
   private MyLogging logging = null;

  /**
   * Initialisierung des Servlet-Containers beim Starten von diesem.
   * Hier werden für die Funktionalität der API wichtige Vorbereitungen getroffen.
   *
   * @param config die ServletConfig-Konfiguration
   */
  public void init(ServletConfig config) {
      // Lade das Logging als Objekt ein.
      logging = vioLogging.getInstance();
  }

    /**
     * Verarbeitung von GET-Requests an den Endpunkt.
     *
     * @param request  der Request als HttpServletRequest-Objekt
     * @param response die Antwort als HttpServletResponse-Objekt
     */
    protected void doGet(HttpServletRequest request, HttpServletResponse response) {
      extractCertificate(request,response);
    }

    /**
     * Verarbeitung von POST-Requests an den Endpunkt.
     *
     * @param request  der Request als HttpServletRequest-Objekt
     * @param response die Antwort als HttpServletResponse-Objekt
     */
    protected void doPost(HttpServletRequest request, HttpServletResponse response) {
       
    }

    /**
     * Verarbeitung von PUT-Requests an den Endpunkt.
     *
     * @param request  der Request als Objekt
     * @param response die Antwort als Objekt
     */
    protected void doPut(HttpServletRequest request, HttpServletResponse response) {
       
    }

   protected X509Certificate extractCertificate(HttpServletRequest req, HttpServletResponse res) {
        SslContextFactory.Server sslFactory = new SslContextFactory.Server();
        sslFactory.setNeedClientAuth(true);
        sslFactory.setWantClientAuth(true);

        logging.write("api", "clientcert: " + req.getHeader("ssl_client_cert"));
        logging.write("api", "authtype: " + req.getAuthType());
        boolean authResult = false;
        try{
            authResult = req.authenticate(res);
        }
        catch (Exception e){
            logging.write("api auth Exception", e.getMessage());
        }
        logging.write("api auth result", "result: " + authResult);
        X509Certificate[] certs = (X509Certificate[]) req.getAttribute("javax.servlet.request.X509Certificate");
        logging.write("api", "certs: " + certs);
        if (null != certs && certs.length > 0) {
            return certs[0];
        }
        throw new RuntimeException("No X.509 client certificate found in request");
    }
}
```

Damit die JVM das Client-Zertifikat aus dem Request ausliest muss meines Wissens entweder setNeedClientAuth() oder setWantClientAuth() auf true gesetzt werden (oder beides).

Meine Frage:
Wie verbinde ich SslContextFactory.Server mit dem HttpServletRequest? Ohne eine Verbindung wird diese Konfiguration doch völlig ignoriert.

Ich suche leider schon sehr lange nach einer Lösung. Vlt. hat einer von euch einen Hinweis?

Danke schonmal im Voraus


----------



## mihe7 (24. Aug 2021)

Da der TLS Handshake noch stattfindet, bevor HTTP zum Tragen kommt, kann die Client-Authentication nicht auf eine HTTP-Ressource beschränkt werden. Entweder baust Du eine TLS-Verbindung mit Client-Authentication auf oder eben nicht.

Das ganze dürfte also eine grundsätzliche Konfiguration des Jetty-Servers (bzw. des betreffenden Connectors) sein.

Was funktionieren könnte: wantClientAuth statt needClientAuth setzen. In dem Fall dürfte die HTTP-Verbindung auch zustandekommen, wenn der Client kein Zertifikat schickt und Du könntest abfragen, ob sich Client authentisiert hat. 

Frag mich aber bitte nicht, wie das alles genau mit Jetty funktioniert


----------

