Client Certificate Authentication
With "Client Certificate Authentication", the client and server perform a handshake in which both parties must know each other.
In addition to the normal keystores, a truststore with the certificates of the other party must also be available.
In brief: The client knows the server and the server knows the client.
|
Client Certificate Authentication is currently only used for the Log Service endpoints. |
Certificates, keystores and truststores
The steps for creating the certificates, keystores and truststores as well as the configuration in Karaf are shown here as an example.
The steps are carried out in the following directory: [karaf]/etc/virtimo/ssl
1. Virtimo keystore for the Karaf
Generation of a pair of self-signed public and private keys for the Karaf.
This step can be skipped as the keystore already exists.
It is referenced in the [karaf]/etc/org.ops4j.pax.web.cfg.
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
To understand the content of our keystore:
keytool -list -keystore virtimo_keystore.jks
2. Export of the Virtimo certificate
This is exported from the Virtimo keystore.
keytool -export \
-alias virtimo \
-file virtimo.cer \
-keystore virtimo_keystore.jks \
-storepass virtimo \
-storetype jks
To understand the content of the Virtimo certificate:
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 the Virtimo certificate into the client truststore
This is created if it does not already exist.
keytool -import \
-file virtimo.cer \
-alias default \
-keystore clienttrust.jks \
-storepass passw0rd \
-keypass passw0rd \
-storetype jks
4. Creation of the client keystore
Generation of a pair of self-signed public and private keys for the client, which is the caller of our endpoints. Here is an example with two aliases.
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
To understand the content of the keystore:
keytool -list -keystore clientkey.jks
5. Client certificate export and Virtimo Truststore import
Export the client certificates via the aliases set above and import them into the 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
Customization Karaf
The Karaf must know the truststore created above and know that it should use it.
To do this, extend the [karaf]/etc/org.ops4j.pax.web.cfg.
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
If org.ops4j.pax.web.ssl.clientauthwanted and org.ops4j.pax.web.ssl.clientauthneeded are set to false (default), then no client certificate arrives at the endpoint for verification!
If there are problems in the future (certificate does not arrive at the endpoint): In the current (26.11.2021) PAX web source code contains other property names and these do not work with Karaf 4.3.3.
org.ops4j.pax.web.ssl.clientauth.wanted = true
org.ops4j.pax.web.ssl.clientauth.needed = false
|
A trust store is always present by default after the installation of BPC.
This trust store is located in |
Java Client Code
Here are two more examples (simple and complex) of how a Java client (caller of a BPC endpoint) can then call the BPC endpoints prepared for this with the client keystore and client truststore created above.
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();
}
}
}