it-swarm.com.de

Authentifizierung des Java HTTPS-Clientzertifikats

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).

200
tmbrggmn

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

  1. Das public-Zertifikat des Clients (in diesem Fall von einer selbstsignierten Zertifizierungsstelle signiert)
  2. Der private-Schlüssel des Clients

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

  1. Client Certificate Authentication kann nur vom Server erzwungen werden.
  2. (Important!) Wenn der Server (als Teil des TLS-Handshakes) ein Clientzertifikat anfordert, stellt er auch eine Liste der vertrauenswürdigen Zertifizierungsstellen als Teil der Zertifikatsanforderung bereit. Wenn das Clientzertifikat, das Sie zur Authentifizierung vorlegen möchten, nicht von einer dieser Zertifizierungsstellen signiert ist, wird es überhaupt nicht präsentiert (meiner Meinung nach ist dies ein merkwürdiges Verhalten, aber ich bin sicher, dass es ein Grund dafür). Dies war die Hauptursache für meine Probleme, da die andere Partei ihren Server nicht ordnungsgemäß konfiguriert hatte, um mein selbstsigniertes Clientzertifikat zu akzeptieren, und wir nahmen an, dass das Problem bei mir lag, weil das Clientzertifikat in der Anforderung nicht ordnungsgemäß bereitgestellt wurde.
  3. Holen Sie sich Wireshark. Es verfügt über eine hervorragende SSL/HTTPS-Paketanalyse und wird das Debuggen und das Finden des Problems enorm erleichtern. Es ähnelt -Djavax.net.debug=ssl, ist aber strukturierter und (wahrscheinlich) einfacher zu interpretieren, wenn Sie mit der Java-SSL-Debug-Ausgabe nicht zufrieden sind.
  4. 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
210
tmbrggmn

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"));
42
Magnus

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:

  • Der Speicher von client enthält das Schlüsselpaar private und public des Kunden. Es wird als Keystore bezeichnet.
  • Der Speicher von server enthält den public - Schlüssel des Clients. Es wird als truststore bezeichnet.

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
30
mhaller

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;
}

}
9
wildloop

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: 

https://www.digitalocean.com/community/tutorials/how-to-create-a-ssl-certificate-on-Apache-for-ubuntu-14-04

Beachten Sie, dass Sie bereits die Server .crt und .key erstellt haben, damit Sie diesen Schritt nicht mehr ausführen müssen. 

7
hans

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.

0
ObiWanKenobi