Client Certificate Authentication
Bei der "Client Certificate Authentication" machen Client und Server einen Handshake, bei dem sich beide Parteien kennen müssen.
Dazu muss neben den normalen Keystores auch jeweils ein Truststore mit den Zertifikaten des jeweils anderen zur Verfügung stehen.
In Kürze: Der Client kennt den Server und der Server kennt den Client.
Client Certificate Authentication kommt aktuell nur bei den Log Service-Endpunkten zum Einsatz. |
Zertifikate, Keystores und Truststores
Hier werden exemplarisch die Schritte zur Erstellung der Zertifikate, Keystores und Truststores sowie die Konfiguration im Karaf gezeigt.
Die Schritte werden in folgendem Verzeichnis ausgeführt: [karaf]/etc/virtimo/ssl
1. Virtimo Keystore für den Karaf
Generierung eines Paars selbst-signierter Public und Private Keys für den Karaf.
Dieser Schritt kann übersprungen werden, da der Keystore bereits existiert.
Er wird in der [karaf]/etc/org.ops4j.pax.web.cfg
referenziert.
keytool -genkey \
-alias virtimo \
-keystore virtimo_keystore.jks \
-dname "EMAILADDRESS=support@virtimo.de, CN=Virtimo AG, OU=Lab, O=Virtimo AG, L=Berlin, ST=Germany, C=DE" \
-storepass virtimo \
-keypass virtimo \
-storetype jks \
-validity 1000 \
-keyalg RSA
Zum Verständnis den Inhalt unseres Keystores anzeigen lassen:
keytool -list -keystore virtimo_keystore.jks
2. Export des Virtimo-Zertifikats
Dieses wird aus dem Virtimo Keystore exportiert.
keytool -export \
-alias virtimo \
-file virtimo.cer \
-keystore virtimo_keystore.jks \
-storepass virtimo \
-storetype jks
Zum Verständnis den Inhalt des Virtimo Zertifikats anzeigen lassen:
keytool -printcert -file virtimo.cer
...
> EMAILADDRESS=support@virtimo.de, CN=Virtimo AG, OU=Lab, O=Virtimo AG, L=Berlin, ST=Germany, C=DE
3. Import des Virtimo-Zertifikats in den Client Truststore
Dieser wird angelegt, wenn noch nicht vorhanden.
keytool -import \
-file virtimo.cer \
-alias default \
-keystore clienttrust.jks \
-storepass passw0rd \
-keypass passw0rd \
-storetype jks
4. Erstellung des Client Keystores
Generierung eines Paars selbst-signierter Public und Private Keys für den Client, welcher der Aufrufer unserer Endpunkte ist. Hier exemplarisch am Beispiel mit zwei Aliasen.
keytool -genkey \
-alias user0 \
-keystore clientkey.jks \
-dname "CN=employee0, OU=Lab, O=Virtimo AG, L=Berlin, ST=Germany, C=DE" \
-storepass passw0rd \
-keypass passw0rd \
-storetype jks \
-validity 1000 \
-keyalg RSA
keytool -genkey \
-alias admin0 \
-keystore clientkey.jks \
-dname "CN=manager0, OU=Lab, O=Virtimo AG, L=Berlin, ST=Germany, C=DE" \
-storepass passw0rd \
-keypass passw0rd \
-storetype jks \
-validity 1000 \
-keyalg RSA
Zum Verständnis den Inhalt des Keystores anzeigen lassen:
keytool -list -keystore clientkey.jks
5. Client Zertifikat Export und Virtimo Truststore Import
Export der Client Zertifikate per oben gesetzten Aliases und Import dieser in den Virtimo Truststore.
keytool -export \
-alias user0 \
-file user0.cer \
-keystore clientkey.jks \
-storepass passw0rd \
-storetype jks
keytool -export \
-alias admin0 \
-file admin0.cer \
-keystore clientkey.jks \
-storepass passw0rd \
-storetype jks
keytool -import \
-file user0.cer \
-alias user0 \
-keystore virtimo_truststore.jks \
-storepass virtimo \
-keypass virtimo \
-storetype jks
keytool -import \
-file admin0.cer \
-alias admin0 \
-keystore virtimo_truststore.jks \
-storepass virtimo \
-keypass virtimo \
-storetype jks
Anpassung Karaf
Der Karaf muss den oben angelegten Truststore kennen und wissen, dass er ihn benutzen soll.
Dazu die [karaf]/etc/org.ops4j.pax.web.cfg
erweitern.
org.ops4j.pax.web.ssl.truststore = ${karaf.etc}/virtimo/ssl/virtimo_truststore.jks
org.ops4j.pax.web.ssl.truststore.password = virtimo
org.ops4j.pax.web.ssl.clientauthwanted = true
org.ops4j.pax.web.ssl.clientauthneeded = false
Sind org.ops4j.pax.web.ssl.clientauthwanted
und org.ops4j.pax.web.ssl.clientauthneeded
auf false
gesetzt (Default), dann kommt am Endpunkt kein Client Zertifikat zur Überprüfung an!
Wenn es in Zukunft mal Probleme gibt (Zertifikat kommt nicht am Endpunkt an): Im aktuellen (26.11.2021) PAX Web Source Code stehen andere property-Namen und die funktionieren nicht mit dem Karaf 4.3.3.
org.ops4j.pax.web.ssl.clientauth.wanted = true
org.ops4j.pax.web.ssl.clientauth.needed = false
Als Default ist ein Truststore nach der Installation von BPC immer vorhanden.
Dieser Trust-Store befindet sich im |
Java Client Code
Hier noch zwei Beispiele (einfach und komplex) wie ein Java Client (Aufrufer eines BPC Endpunktes) dann die dafür vorbereiteten BPC Endpunkte mit den oben erstellen Client Keystore und Client Truststore aufrufen kann.
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.URI;
import org.apache.commons.httpclient.methods.GetMethod;
public class SSLMutualAuthTestSimple {
private final static String KEYSTORE_FILE_PATH = "/home/<user>/bpc/karaf/etc/virtimo/ssl/clientkey.jks";
private final static String KEYSTORE_PASSWORD = "passw0rd";
private final static String TRUSTSTORE_FILE_PATH = "/home/<user>/bpc/karaf/etc/virtimo/ssl/clienttrust.jks";
private final static String TRUSTSTORE_PASSWORD = "passw0rd";
public static void main(String[] args) {
try {
// System.setProperty("javax.net.debug", "ssl,handshake"); // for debugging ssl and handshake stuff
System.setProperty("javax.net.ssl.keyStore", KEYSTORE_FILE_PATH);
System.setProperty("javax.net.ssl.keyStorePassword", KEYSTORE_PASSWORD);
System.setProperty("javax.net.ssl.trustStore", TRUSTSTORE_FILE_PATH);
System.setProperty("javax.net.ssl.trustStorePassword", TRUSTSTORE_PASSWORD);
GetMethod method = new GetMethod();
method.setURI(new URI("https://localhost:8282/cxf/bpc-logservice/log/1234567890?config=true", false));
HttpClient client = new HttpClient();
client.executeMethod(method);
System.out.println(method.getResponseBodyAsString());
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.PrivateKeyDetails;
import org.apache.http.ssl.PrivateKeyStrategy;
import org.apache.http.ssl.SSLContexts;
import javax.net.ssl.*;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.net.Socket;
import java.security.KeyStore;
import java.util.Map;
public class SSLMutualAuthTestComplex {
private final static String KEYSTORE_FILE_PATH = "/home/<user>/bpc/karaf/etc/virtimo/ssl/clientkey.jks";
private final static String KEYSTORE_PASSWORD = "passw0rd";
private final static String TRUSTSTORE_FILE_PATH = "/home/<user>/bpc/karaf/etc/virtimo/ssl/clienttrust.jks";
private final static String TRUSTSTORE_PASSWORD = "passw0rd";
public static void main(String[] args) {
try {
// System.setProperty("javax.net.debug", "all"); // to debug all "net" related stuff
String CERT_ALIAS = "admin0";
KeyStore keyStore = KeyStore.getInstance("JKS");
FileInputStream keyStoreFile = new FileInputStream(new File(KEYSTORE_FILE_PATH));
keyStore.load(keyStoreFile, KEYSTORE_PASSWORD.toCharArray());
KeyStore trustStore = KeyStore.getInstance("JKS");
FileInputStream trustStoreFile = new FileInputStream(new File(TRUSTSTORE_FILE_PATH));
trustStore.load(trustStoreFile, TRUSTSTORE_PASSWORD.toCharArray());
// Create all-trusting host name verifier
// Das sollte produktiv nicht verwendet werden!!!
HostnameVerifier allHostsValid = new HostnameVerifier() {
public boolean verify(String hostname, SSLSession session) {
return true;
}
};
SSLContext sslContext = SSLContexts.custom()
.loadKeyMaterial(keyStore, KEYSTORE_PASSWORD.toCharArray(), new PrivateKeyStrategy() {
@Override
public String chooseAlias(Map<String, PrivateKeyDetails> aliases, Socket socket) {
return CERT_ALIAS;
}
})
.loadTrustMaterial(trustStore, null)
.build();
SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(
sslContext,
new String[]{"TLSv1.2", "TLSv1.1", "TLSv1"},
null,
allHostsValid /* SSLConnectionSocketFactory.getDefaultHostnameVerifier() */
);
try (CloseableHttpClient client = HttpClients.custom().setSSLSocketFactory(sslConnectionSocketFactory).build()) {
HttpGet request = new HttpGet("https://localhost:8282/cxf/bpc-logservice/log/1234567890?config=true");
request.setHeader("Accept", "application/json");
HttpResponse response = client.execute(request);
System.out.println("Response code: " + response.getStatusLine().getStatusCode());
System.out.println("Response content:");
BufferedReader rd = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
String line;
while ((line = rd.readLine()) != null) {
System.out.println(line);
}
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
}