Java-Client-Zertifikate über HTTPS / SSL

116

Ich verwende Java 6 und versuche HttpsURLConnection, mithilfe eines Client-Zertifikats ein gegen einen Remote-Server zu erstellen .
Der Server verwendet ein selbstsigniertes Stammzertifikat und erfordert, dass ein kennwortgeschütztes Clientzertifikat vorgelegt wird. Ich habe das Server-Stammzertifikat und das Client-Zertifikat einem Standard-Java-Keystore hinzugefügt, den ich in /System/Library/Frameworks/JavaVM.framework/Versions/1.6.0/Home/lib/security/cacerts(OSX 10.5) gefunden habe. Der Name der Keystore-Datei scheint darauf hinzudeuten, dass das Client-Zertifikat dort nicht eingehen soll.

Wie auch immer, das Hinzufügen des Stammzertifikats zu diesem Speicher löste das berüchtigte Problem javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed' problem.

Jetzt bin ich jedoch nicht mehr sicher, wie ich das Client-Zertifikat verwenden soll. Ich habe zwei Ansätze ausprobiert und keiner bringt mich irgendwohin.
Versuchen Sie zuerst und bevorzugt:

SSLSocketFactory sslsocketfactory = (SSLSocketFactory) SSLSocketFactory.getDefault();
URL url = new URL("https://somehost.dk:3049");
HttpsURLConnection conn = (HttpsURLConnection)url.openConnection();
conn.setSSLSocketFactory(sslsocketfactory);
InputStream inputstream = conn.getInputStream();
// The last line fails, and gives:
// javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure

Ich habe versucht, die HttpsURLConnection-Klasse zu überspringen (nicht ideal, da ich HTTP mit dem Server sprechen möchte), und mache stattdessen Folgendes:

SSLSocketFactory sslsocketfactory = (SSLSocketFactory) SSLSocketFactory.getDefault();
SSLSocket sslsocket = (SSLSocket) sslsocketfactory.createSocket("somehost.dk", 3049);
InputStream inputstream = sslsocket.getInputStream();
// do anything with the inputstream results in:
// java.net.SocketTimeoutException: Read timed out

Ich bin mir nicht einmal sicher, ob das Client-Zertifikat hier das Problem ist.

Jan.
quelle
Ich habe zwei Zertifikate vom Kunden gegeben, um zu identifizieren, welche in Keystore und Truststore hinzugefügt werden müssen. Könnten Sie bitte helfen, dieses Problem zu identifizieren, da Sie bereits ein ähnliches Problem durchlaufen haben. stackoverflow.com/questions/61374276/…
henrycharles

Antworten:

100

Endlich gelöst;). Habe hier einen starken Hinweis (Gandalfs Antwort hat es auch ein bisschen berührt). Die fehlenden Links waren (meistens) der erste der folgenden Parameter, und bis zu einem gewissen Grad habe ich den Unterschied zwischen Keystores und Truststores übersehen.

Das selbstsignierte Serverzertifikat muss in einen Truststore importiert werden:

keytool -import -alias gridserver -file gridserver.crt -storepass $ PASS -keystore gridserver.keystore

Diese Eigenschaften müssen festgelegt werden (entweder in der Befehlszeile oder im Code):

-Djavax.net.ssl.keyStoreType=pkcs12
-Djavax.net.ssl.trustStoreType=jks
-Djavax.net.ssl.keyStore=clientcertificate.p12
-Djavax.net.ssl.trustStore=gridserver.keystore
-Djavax.net.debug=ssl # very verbose debug
-Djavax.net.ssl.keyStorePassword=$PASS
-Djavax.net.ssl.trustStorePassword=$PASS

Arbeitsbeispielcode:

SSLSocketFactory sslsocketfactory = (SSLSocketFactory) SSLSocketFactory.getDefault();
URL url = new URL("https://gridserver:3049/cgi-bin/ls.py");
HttpsURLConnection conn = (HttpsURLConnection)url.openConnection();
conn.setSSLSocketFactory(sslsocketfactory);
InputStream inputstream = conn.getInputStream();
InputStreamReader inputstreamreader = new InputStreamReader(inputstream);
BufferedReader bufferedreader = new BufferedReader(inputstreamreader);

String string = null;
while ((string = bufferedreader.readLine()) != null) {
    System.out.println("Received " + string);
}
Jan.
quelle
Ich habe eine URL wie: localhost: 8443 / Application_Name / getAttributes verwendet . Ich habe eine Methode mit / getAttribute-URL-Zuordnung. Diese Methode gibt eine Liste von Elementen zurück. Ich habe HttpsUrlConnection verwendet. Der Antwortcode für die Verbindung ist 200, aber wenn ich inputStream verwende, wird mir die Attributliste nicht angezeigt. Er gibt mir den HTML-Inhalt meiner Anmeldeseite. Ich habe die Authentifizierung durchgeführt und den Inhaltstyp als JSON festgelegt. Bitte vorschlagen
Deepak
83

Obwohl dies nicht empfohlen wird, können Sie die SSL-Zertifikatsüberprüfung auch insgesamt deaktivieren:

import javax.net.ssl.*;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;

public class SSLTool {

  public static void disableCertificateValidation() {
    // Create a trust manager that does not validate certificate chains
    TrustManager[] trustAllCerts = new TrustManager[] { 
      new X509TrustManager() {
        public X509Certificate[] getAcceptedIssuers() { 
          return new X509Certificate[0]; 
        }
        public void checkClientTrusted(X509Certificate[] certs, String authType) {}
        public void checkServerTrusted(X509Certificate[] certs, String authType) {}
    }};

    // Ignore differences between given hostname and certificate hostname
    HostnameVerifier hv = new HostnameVerifier() {
      public boolean verify(String hostname, SSLSession session) { return true; }
    };

    // Install the all-trusting trust manager
    try {
      SSLContext sc = SSLContext.getInstance("SSL");
      sc.init(null, trustAllCerts, new SecureRandom());
      HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
      HttpsURLConnection.setDefaultHostnameVerifier(hv);
    } catch (Exception e) {}
  }
}
neu242
quelle
72
Es sollte beachtet werden, dass das Deaktivieren der Zertifikatvalidierung wie diese die Verbindung zu möglichen MITM-Angriffen öffnet: Nicht in der Produktion verwenden .
Bruno
3
Zum Glück wird der Code nicht kompiliert. Diese "Lösung" ist radikal unsicher.
Marquis von Lorne
5
@ neu242, nein, es hängt nicht wirklich davon ab, wofür du es verwendest. Wenn Sie SSL / TLS verwenden möchten, möchten Sie Ihre Verbindung gegen MITM-Angriffe sichern, das ist der springende Punkt. Eine Serverauthentifizierung ist nicht erforderlich, wenn Sie garantieren können, dass niemand den Datenverkehr ändern kann. Situationen, in denen Sie den Verdacht haben, dass es Lauschangriffe gibt, die nicht auch in der Lage wären, den Netzwerkverkehr zu ändern, sind jedoch eher selten.
Bruno
1
@ neu242 Danke für das Code-Snippet. Ich denke tatsächlich darüber nach, dies in der Produktion für einen ganz bestimmten Zweck zu verwenden (Web-Crawlen), und ich habe Ihre Implementierung in einer Frage erwähnt ( stackoverflow.com/questions/13076511/… ). Wenn Sie Zeit haben, können Sie sich das bitte ansehen und mich wissen lassen, ob es Sicherheitsrisiken gibt, die ich verpasst habe?
Sal
1
@Bruno Wenn ich nur den Server anpinge, würde mich das durch MITM-Angriffe überhaupt beeinflussen?
PhoonOne
21

Haben Sie die KeyStore- und / oder TrustStore-Systemeigenschaften festgelegt?

java -Djavax.net.ssl.keyStore=pathToKeystore -Djavax.net.ssl.keyStorePassword=123456

oder von mit dem Code

System.setProperty("javax.net.ssl.keyStore", pathToKeyStore);

Gleiches gilt für javax.net.ssl.trustStore

Gandalf
quelle
13

Wenn Sie einen Webdienstaufruf mit dem Axis-Framework bearbeiten, gibt es eine viel einfachere Antwort. Wenn Ihr Client nur den SSL-Webdienst aufrufen und SSL-Zertifikatfehler ignorieren kann, geben Sie einfach diese Anweisung ein, bevor Sie Webdienste aufrufen:

System.setProperty("axis.socketSecureFactory", "org.apache.axis.components.net.SunFakeTrustSocketFactory");

Es gelten die üblichen Haftungsausschlüsse, dass dies in einer Produktionsumgebung eine sehr schlechte Sache ist.

Ich habe das im Axis Wiki gefunden .

Mark Meuer
quelle
Das OP befasst sich mit einer HttpsURLConnection, nicht Axis
neu242
2
Ich verstehe. Ich wollte nicht implizieren, dass meine Antwort im allgemeinen Fall besser war. Es ist nur , dass , wenn Sie sind die Axis - Framework verwenden, können Sie die OP Frage in diesem Zusammenhang haben. (So ​​habe ich diese Frage überhaupt gefunden.) In diesem Fall ist die Art und Weise, wie ich sie bereitgestellt habe, einfacher.
Mark Meuer
5

Für mich hat dies mit Apache HttpComponents ~ HttpClient 4.x funktioniert:

    KeyStore keyStore  = KeyStore.getInstance("PKCS12");
    FileInputStream instream = new FileInputStream(new File("client-p12-keystore.p12"));
    try {
        keyStore.load(instream, "helloworld".toCharArray());
    } finally {
        instream.close();
    }

    // Trust own CA and all self-signed certs
    SSLContext sslcontext = SSLContexts.custom()
        .loadKeyMaterial(keyStore, "helloworld".toCharArray())
        //.loadTrustMaterial(trustStore, new TrustSelfSignedStrategy()) //custom trust store
        .build();
    // Allow TLSv1 protocol only
    SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
        sslcontext,
        new String[] { "TLSv1" },
        null,
        SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); //TODO
    CloseableHttpClient httpclient = HttpClients.custom()
        .setHostnameVerifier(SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER) //TODO
        .setSSLSocketFactory(sslsf)
        .build();
    try {

        HttpGet httpget = new HttpGet("https://localhost:8443/secure/index");

        System.out.println("executing request" + httpget.getRequestLine());

        CloseableHttpResponse response = httpclient.execute(httpget);
        try {
            HttpEntity entity = response.getEntity();

            System.out.println("----------------------------------------");
            System.out.println(response.getStatusLine());
            if (entity != null) {
                System.out.println("Response content length: " + entity.getContentLength());
            }
            EntityUtils.consume(entity);
        } finally {
            response.close();
        }
    } finally {
        httpclient.close();
    }

Die P12-Datei enthält das mit BouncyCastle erstellte Client-Zertifikat und den privaten Client-Schlüssel:

public static byte[] convertPEMToPKCS12(final String keyFile, final String cerFile,
    final String password)
    throws IOException, CertificateException, KeyStoreException, NoSuchAlgorithmException,
    NoSuchProviderException
{
    // Get the private key
    FileReader reader = new FileReader(keyFile);

    PEMParser pem = new PEMParser(reader);
    PEMKeyPair pemKeyPair = ((PEMKeyPair)pem.readObject());
    JcaPEMKeyConverter jcaPEMKeyConverter = new JcaPEMKeyConverter().setProvider("BC");
    KeyPair keyPair = jcaPEMKeyConverter.getKeyPair(pemKeyPair);

    PrivateKey key = keyPair.getPrivate();

    pem.close();
    reader.close();

    // Get the certificate
    reader = new FileReader(cerFile);
    pem = new PEMParser(reader);

    X509CertificateHolder certHolder = (X509CertificateHolder) pem.readObject();
    java.security.cert.Certificate x509Certificate =
        new JcaX509CertificateConverter().setProvider("BC")
            .getCertificate(certHolder);

    pem.close();
    reader.close();

    // Put them into a PKCS12 keystore and write it to a byte[]
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    KeyStore ks = KeyStore.getInstance("PKCS12", "BC");
    ks.load(null);
    ks.setKeyEntry("key-alias", (Key) key, password.toCharArray(),
        new java.security.cert.Certificate[]{x509Certificate});
    ks.store(bos, password.toCharArray());
    bos.close();
    return bos.toByteArray();
}
EpicPandaForce
quelle
Das keyStoreist, was den privaten Schlüssel und das Zertifikat enthält.
EpicPandaForce
1
Sie müssen diese beiden Abhängigkeiten einschließen, damit der Code convertPEMtoP12 funktioniert: <dependency> <groupId> org.bouncycastle </ groupId> <artifactId> bcprov-jdk15on </ifactId> <version> 1.53 </ version> </ dependency> < Abhängigkeit> <groupId> org.bouncycastle </ groupId> <artifactId> bcpkix-jdk15on </ifactId> <version> 1.53 </ version> </ dependency>
BirdOfPrey
@EpicPandaForce Ich erhalte die folgende Fehlermeldung: Ca. setKeyEntry - alle Hinweise, was falsch sein könnte
Vishal Biyani
Ja, Sie verwenden Groovy anstelle einer streng typisierten Sprache. (Technisch gesehen benötigt diese Methode eine ID und ein Zertifikat, nicht nur ein Zertifikat)
EpicPandaForce
4

Ich verwende das Apache Commons HTTP-Client-Paket, um dies in meinem aktuellen Projekt zu tun, und es funktioniert gut mit SSL und einem selbstsignierten Zertifikat (nachdem es wie erwähnt in cacerts installiert wurde). Bitte schauen Sie es sich hier an:

http://hc.apache.org/httpclient-3.x/tutorial.html

http://hc.apache.org/httpclient-3.x/sslguide.html

Taylor Leese
quelle
1
Dies scheint ein ziemlich ordentliches Paket zu sein, aber die Klasse, mit der alles funktionieren soll, 'AuthSSLProtocolSocketFactory', ist offenbar weder in 4.0beta (trotz der Versionshinweise) noch in Version 3.1 Teil der offiziellen Distribution. Ich habe ein bisschen damit herumgehackt und scheine jetzt permanent mit einem 5-minütigen Hang festzuhalten, bevor die Verbindung einfach unterbrochen wird. Es ist wirklich seltsam - wenn ich die Zertifizierungsstelle und das Client-Zertifikat in einen Browser lade, fliegt es einfach.
Jan
1
Apache HTTP Client 4 kann einen SSLContextdirekt verwenden, sodass Sie dies alles auf diese Weise konfigurieren können, anstatt es zu verwenden AuthSSLProtocolSocketFactory.
Bruno
1
Gibt es eine Möglichkeit, das gesamte Client-Zertifikat im Speicher anstatt über einen externen Schlüsselspeicher zu erledigen?
Sridhar Sarnobat
4

Ich denke, Sie haben ein Problem mit Ihrem Serverzertifikat, es ist kein gültiges Zertifikat (ich denke, dies ist, was "handshake_failure" in diesem Fall bedeutet):

Importieren Sie Ihr Serverzertifikat in Ihren Trustcacerts-Keystore in der JRE des Clients. Dies ist mit Keytool ganz einfach möglich :

keytool
    -import
    -alias <provide_an_alias>
    -file <certificate_file>
    -keystore <your_path_to_jre>/lib/security/cacerts
Sourcerebels
quelle
Ich habe versucht aufzuräumen und von vorne zu beginnen, und der Handschlagfehler ist verschwunden. Jetzt bekomme ich nur 5 Minuten von Totenstille , bevor die Verbindung beendet wird: o
Jan
1

Verwenden Sie den folgenden Code

-Djavax.net.ssl.keyStoreType=pkcs12

oder

System.setProperty("javax.net.ssl.keyStore", pathToKeyStore);

ist überhaupt nicht erforderlich. Außerdem müssen Sie keine eigene benutzerdefinierte SSL-Factory erstellen.

Ich habe auch das gleiche Problem festgestellt. In meinem Fall gab es ein Problem, bei dem die vollständige Zertifikatkette nicht in Truststores importiert wurde. Importieren Sie Zertifikate mit dem Dienstprogramm keytool direkt aus dem Stammzertifikat. Sie können auch die Datei cacerts im Editor öffnen und prüfen, ob die gesamte Zertifikatkette importiert wurde oder nicht. Überprüfen Sie anhand des Aliasnamens, den Sie beim Importieren von Zertifikaten angegeben haben, öffnen Sie die Zertifikate und prüfen Sie, wie viele darin enthalten sind. Die gleiche Anzahl von Zertifikaten sollte in der Cacerts-Datei enthalten sein.

Außerdem sollte die cacerts-Datei auf dem Server konfiguriert sein, auf dem Sie Ihre Anwendung ausführen. Die beiden Server authentifizieren sich gegenseitig mit öffentlichen / privaten Schlüsseln.

Ankur Singhal
quelle
Das Erstellen einer eigenen benutzerdefinierten SSL-Factory ist viel komplizierter und fehleranfälliger als das Festlegen von zwei Systemeigenschaften.
Marquis von Lorne