Ich bin relativ neu bei HTTPS/SSL/TLS und bin etwas verwirrt darüber, was genau die Clients beim Authentifizieren mit Zertifikaten darstellen sollen.
Ich schreibe einen Java-Client, der ein einfaches POST von Daten an eine bestimmte URL ausführen muss. Dieser Teil funktioniert gut, das einzige Problem ist, dass er über HTTPS gemacht werden soll. Der HTTPS-Teil ist relativ einfach zu handhaben (entweder mit HTTPclient oder mit der integrierten HTTPS-Unterstützung von Java), aber ich bin bei der Authentifizierung mit Clientzertifikaten fest. Mir ist aufgefallen, dass es hier bereits eine sehr ähnliche Frage gibt, die ich mit meinem Code noch nicht ausprobiert habe (werde dies früh genug tun). Mein aktuelles Problem ist, dass der Java-Client das Zertifikat - was immer ich auch tue - niemals mitschickt (ich kann das mit PCAP-Dumps überprüfen).
Ich möchte gerne wissen, was der Client beim Authentifizieren mit Zertifikaten (speziell für Java - falls überhaupt) auf dem Server präsentieren soll? Ist dies eine JKS-Datei oder PKCS # 12? Was soll in ihnen sein? Nur das Client-Zertifikat oder einen Schlüssel? Wenn ja, welcher Schlüssel? Es gibt einige Verwirrung über die verschiedenen Arten von Dateien, Zertifikattypen und dergleichen.
Wie gesagt, ich bin neu in HTTPS/SSL/TLS, daher würde ich mich auch über Hintergrundinformationen freuen (es muss kein Essay sein; ich werde mich mit Links zu guten Artikeln zufrieden geben).
Endlich gelang es, alle Probleme zu lösen, also beantworte ich meine eigene Frage. Dies sind die Einstellungen/Dateien, mit denen ich meine Probleme gelöst habe.
Der Client-Keystore ist eine PKCS # 12-Format -Datei, die enthält
Um es zu generieren, habe ich zum Beispiel den pkcs12
-Befehl von OpenSSL verwendet.
openssl pkcs12 -export -in client.crt -inkey client.key -out client.p12 -name "Whatever"
Tipp: Vergewissern Sie sich, dass Sie die neueste Version von OpenSSL, nicht Version 0.9.8h erhalten, da dies unter einem Fehler zu leiden scheint, der es Ihnen nicht ermöglicht, PKCS # 12-Dateien ordnungsgemäß zu generieren.
Diese PKCS # 12-Datei wird vom Java-Client verwendet, um das Clientzertifikat dem Server zu präsentieren, wenn der Server den Client explizit zur Authentifizierung aufgefordert hat. Im Wikipedia-Artikel zu TLS finden Sie einen Überblick darüber, wie das Protokoll für die Authentifizierung von Clientzertifikaten tatsächlich funktioniert (auch erklärt, warum wir hier den privaten Schlüssel des Clients benötigen).
Der Truststore des Kunden ist eine einfache JKS-Format -Datei, die die Root oder Zwischen-CA-Zertifikate enthält. Diese CA-Zertifikate bestimmen, mit welchen Endpunkten Sie kommunizieren dürfen. In diesem Fall kann Ihr Client eine Verbindung zu dem Server herstellen, der ein Zertifikat vorlegt, das von einer der Zertifizierungsstellen des Truststores signiert wurde.
Zur Generierung können Sie beispielsweise das Standard-Java-Keytool verwenden.
keytool -genkey -dname "cn=CLIENT" -alias truststorekey -keyalg RSA -keystore ./client-truststore.jks -keypass whatever -storepass whatever
keytool -import -keystore ./client-truststore.jks -file myca.crt -alias myca
Mit diesem Truststore versucht Ihr Client, einen vollständigen SSL-Handshake mit allen Servern durchzuführen, die ein von der mit myca.crt
identifiziertes Zertifikat signiertes Zertifikat vorlegen.
Die obigen Dateien sind ausschließlich für den Kunden bestimmt. Wenn Sie auch einen Server einrichten möchten, benötigt der Server eigene Schlüssel- und Truststore-Dateien. Auf dieser Website finden Sie eine großartige Anleitung zum Einrichten eines vollständig funktionierenden Beispiels für einen Java-Client und einen Server (mit Tomcat).
Probleme/Anmerkungen/Tipps
-Djavax.net.debug=ssl
, ist aber strukturierter und (wahrscheinlich) einfacher zu interpretieren, wenn Sie mit der Java-SSL-Debug-Ausgabe nicht zufrieden sind.Es ist durchaus möglich, die Apache-httpclient-Bibliothek zu verwenden. Wenn Sie httpclient verwenden möchten, ersetzen Sie einfach die Ziel-URL durch die HTTPS-Entsprechung und fügen Sie die folgenden JVM-Argumente hinzu (die für jeden anderen Client gleich sind, unabhängig von der Bibliothek, die Sie zum Senden/Empfangen von Daten über HTTP/HTTPS verwenden möchten). :
-Djavax.net.debug=ssl
-Djavax.net.ssl.keyStoreType=pkcs12
-Djavax.net.ssl.keyStore=client.p12
-Djavax.net.ssl.keyStorePassword=whatever
-Djavax.net.ssl.trustStoreType=jks
-Djavax.net.ssl.trustStore=client-truststore.jks
-Djavax.net.ssl.trustStorePassword=whatever
Andere Antworten zeigen, wie Clientzertifikate global konfiguriert werden. Wenn Sie jedoch den Clientschlüssel für eine bestimmte Verbindung programmgesteuert definieren möchten, anstatt ihn global für jede Anwendung zu definieren, die in Ihrer JVM ausgeführt wird, können Sie Ihren eigenen SSLContext so konfigurieren :
String keyPassphrase = "";
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(new FileInputStream("cert-key-pair.pfx"), keyPassphrase.toCharArray());
SSLContext sslContext = SSLContexts.custom()
.loadKeyMaterial(keyStore, null)
.build();
HttpClient httpClient = HttpClients.custom().setSSLContext(sslContext).build();
HttpResponse response = httpClient.execute(new HttpGet("https://example.com"));
Die JKS-Datei ist nur ein Container für Zertifikate und Schlüsselpaare. In einem clientseitigen Authentifizierungsszenario befinden sich die verschiedenen Teile der Schlüssel hier:
Die Trennung von Truststore und Keystore ist nicht zwingend, wird jedoch empfohlen. Sie können dieselbe physische Datei sein.
Verwenden Sie die folgenden Systemeigenschaften, um die Dateisystemspeicherorte der beiden Stores festzulegen:
-Djavax.net.ssl.keyStore=clientsidestore.jks
und auf dem Server:
-Djavax.net.ssl.trustStore=serversidestore.jks
Verwenden Sie, um das Client-Zertifikat (öffentlicher Schlüssel) in eine Datei zu exportieren, damit Sie es auf den Server kopieren können
keytool -export -alias MYKEY -file publicclientkey.cer -store clientsidestore.jks
Um den öffentlichen Schlüssel des Clients in den Keystore des Servers zu importieren, verwenden Sie (wie im Poster erwähnt, dies wurde bereits von den Server-Administratoren durchgeführt)
keytool -import -file publicclientkey.cer -store serversidestore.jks
Maven pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.Apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.Apache.org/POM/4.0.0 http://maven.Apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>some.examples</groupId>
<artifactId>sslcliauth</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>sslcliauth</name>
<dependencies>
<dependency>
<groupId>org.Apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.4</version>
</dependency>
</dependencies>
</project>
Java-Code:
package some.examples;
import Java.io.FileInputStream;
import Java.io.IOException;
import Java.security.KeyManagementException;
import Java.security.KeyStore;
import Java.security.KeyStoreException;
import Java.security.NoSuchAlgorithmException;
import Java.security.UnrecoverableKeyException;
import Java.security.cert.CertificateException;
import Java.util.logging.Level;
import Java.util.logging.Logger;
import javax.net.ssl.SSLContext;
import org.Apache.http.HttpEntity;
import org.Apache.http.HttpHost;
import org.Apache.http.client.config.RequestConfig;
import org.Apache.http.client.methods.CloseableHttpResponse;
import org.Apache.http.client.methods.HttpPost;
import org.Apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.Apache.http.ssl.SSLContexts;
import org.Apache.http.impl.client.CloseableHttpClient;
import org.Apache.http.impl.client.HttpClients;
import org.Apache.http.util.EntityUtils;
import org.Apache.http.entity.InputStreamEntity;
public class SSLCliAuthExample {
private static final Logger LOG = Logger.getLogger(SSLCliAuthExample.class.getName());
private static final String CA_KEYSTORE_TYPE = KeyStore.getDefaultType(); //"JKS";
private static final String CA_KEYSTORE_PATH = "./cacert.jks";
private static final String CA_KEYSTORE_PASS = "changeit";
private static final String CLIENT_KEYSTORE_TYPE = "PKCS12";
private static final String CLIENT_KEYSTORE_PATH = "./client.p12";
private static final String CLIENT_KEYSTORE_PASS = "changeit";
public static void main(String[] args) throws Exception {
requestTimestamp();
}
public final static void requestTimestamp() throws Exception {
SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(
createSslCustomContext(),
new String[]{"TLSv1"}, // Allow TLSv1 protocol only
null,
SSLConnectionSocketFactory.getDefaultHostnameVerifier());
try (CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(csf).build()) {
HttpPost req = new HttpPost("https://changeit.com/changeit");
req.setConfig(configureRequest());
HttpEntity ent = new InputStreamEntity(new FileInputStream("./bytes.bin"));
req.setEntity(ent);
try (CloseableHttpResponse response = httpclient.execute(req)) {
HttpEntity entity = response.getEntity();
LOG.log(Level.INFO, "*** Reponse status: {0}", response.getStatusLine());
EntityUtils.consume(entity);
LOG.log(Level.INFO, "*** Response entity: {0}", entity.toString());
}
}
}
public static RequestConfig configureRequest() {
HttpHost proxy = new HttpHost("changeit.local", 8080, "http");
RequestConfig config = RequestConfig.custom()
.setProxy(proxy)
.build();
return config;
}
public static SSLContext createSslCustomContext() throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException, KeyManagementException, UnrecoverableKeyException {
// Trusted CA keystore
KeyStore tks = KeyStore.getInstance(CA_KEYSTORE_TYPE);
tks.load(new FileInputStream(CA_KEYSTORE_PATH), CA_KEYSTORE_PASS.toCharArray());
// Client keystore
KeyStore cks = KeyStore.getInstance(CLIENT_KEYSTORE_TYPE);
cks.load(new FileInputStream(CLIENT_KEYSTORE_PATH), CLIENT_KEYSTORE_PASS.toCharArray());
SSLContext sslcontext = SSLContexts.custom()
//.loadTrustMaterial(tks, new TrustSelfSignedStrategy()) // use it to customize
.loadKeyMaterial(cks, CLIENT_KEYSTORE_PASS.toCharArray()) // load client certificate
.build();
return sslcontext;
}
}
Für diejenigen von Ihnen, die einfach eine bidirektionale Authentifizierung (Server- und Client-Zertifikate) einrichten möchten, führt eine Kombination dieser beiden Links zu Ihnen:
Zwei-Wege-Auth Setup:
https://linuxconfig.org/Apache-web-server-ssl-authentication
Sie brauchen nicht die Konfigurationsdatei "openssl" zu verwenden, die sie erwähnen. benutz einfach
$ openssl genrsa -des3 -out ca.key 4096
$ openssl req -new -x509 -days 365 -key ca.key -out ca.crt
so erstellen Sie ein eigenes CA-Zertifikat und generieren und signieren Sie die Server- und Client-Schlüssel über:
$ openssl genrsa -des3 -out server.key 4096
$ openssl req -new -key server.key -out server.csr
$ openssl x509 -req -days 365 -in server.csr -CA ca.crt -CAkey ca.key -set_serial 100 -out server.crt
und
$ openssl genrsa -des3 -out client.key 4096
$ openssl req -new -key client.key -out client.csr
$ openssl x509 -req -days 365 -in client.csr -CA ca.crt -CAkey ca.key -set_serial 101 -out client.crt
Für den Rest folgen Sie den Schritten im Link. Die Verwaltung der Zertifikate für Chrome funktioniert genauso wie in dem genannten Beispiel für Firefox.
Als Nächstes konfigurieren Sie den Server über:
Beachten Sie, dass Sie bereits die Server .crt und .key erstellt haben, damit Sie diesen Schritt nicht mehr ausführen müssen.
Ich denke, der Fix hier war der Keystore-Typ, pkcs12 (pfx) hat immer einen privaten Schlüssel und der JKS-Typ kann ohne privaten Schlüssel existieren. Wenn Sie nicht in Ihrem Code angeben oder ein Zertifikat über den Browser auswählen, kann der Server nicht feststellen, dass er einen Client am anderen Ende darstellt.