Java SSL Socket

Sp4rky

Cadet 4th Year
Registriert
März 2019
Beiträge
77
Hey, ich würde ganz gern zwei Clients über einen Server kommunizieren lassen mit SSLSockets in Java, mit entsprechenden Zertifikat.
Allerdings scheint das nicht ganz so zu funktionieren wie ich mir das denke.
Mein Code sieht in etwa so aus für den Teil der die Verbindung annimmt:
Java:
SSLContext sslContext = createSSLContext();
        try{
            SSLServerSocketFactory sslServerSocketFactory = sslContext.getServerSocketFactory();
            //Create server socket
            SSLServerSocket sslServerSocket = (SSLServerSocket) sslServerSocketFactory.createServerSocket(9999);

            while(true){
                SSLSocket sslSocket = (SSLSocket) sslServerSocket.accept();

                //Handle connection
                Thread nsh = new Thread(new ComH(sslSocket));
                nsh.start();
            }
        }catch (Exception e){
            e.printStackTrace();
        }
In ComH wird dann der Handshake durchgeführt und dann die Daten verarbeitet.
createSSLContext():
Java:
private SSLContext createSSLContext(){
        try{
            InputStream certfile = new FileInputStream("./cert/localhost.cert");

            CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
            Certificate certificate = certificateFactory.generateCertificate(certfile);

            String keyStoreType = KeyStore.getDefaultType();
            KeyStore keyStore = KeyStore.getInstance(keyStoreType);
            keyStore.load(null, null);
            keyStore.setCertificateEntry("certificate", certificate);

            String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
            TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
            tmf.init(keyStore);

            SSLContext context = SSLContext.getInstance("TLSv1.2");
            context.init(null, tmf.getTrustManagers(), null);
            return context;

        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }

Wenn ich nun versuche z.Bsp via Browser darauf zuzugreifen bekomme ich zwei Handshake Exceptions: No available authentication scheme und no cipher suites in common.
Ich weiß leider nicht wo der Fehler ist, vermutlich im SSLContext. Das Zertifikat ist zwar selbst signiert, sollte aber trotzdem funktionieren.
Eventuell kann man mir da weiter helfen.
 
Vielleicht beim casten, sieht irgendwie komisch aus.
 
Die Zertifikate sind in der keystore Datei?


Der keystore Explorer hilft dir dabei.


Edit:

Ich sehe gerade Du holst das Zertifikat direkt. kA ob das geht. Zwischen Zertifikate sind drin?
 
Du kannst mit Hilfe von
Code:
-Djavax.net.debug=ssl
das Du beim Ausführen Deines Servers etwa in der Art
Code:
java -Djavax.net.debug=ssl  DeinSSLServer
einfügst die SSL-Kommunikation debuggen.
Gut beschrieben ist das auf https://stackoverflow.com/questions/23659564/limiting-java-ssl-debug-logging , auch wie man zuviel Infos auf das Interessante beschränkt.

Meine Vermutung ist allerdings, daß Du beim Zertifikat noch etwas übersehen hast.
Falls Du trotz eingeschaltetem Debugging (wie oben beschrieben) nicht zum Erfolg kommst, wäre es gut, wenn Du zum einen beschreibst wie Du Dein Zertifikat erstellt hast und zum anderen ein "minimal lauffähiges Beipspiel" (evtl. ohne Starten eines eigenen Threads) hier einstellst.


HTH

BigNum
 
Danke für die Antworten bisher.

Ich hab das Zertifikat nochmal über openssl erstellt (alles leer bis auf CN für 127.0.0.1) und dann nochmal zu crt umgewandelt falls Java da was bestimmtes möchte:
Code:
openssl req -newkey rsa:2048 -nodes -keyout key.pem -x509 -days 365 -out certificate.pem
openssl x509 -outform der -in certificate.pem -out certificate.crt

Gut beschrieben ist das auf https://stackoverflow.com/questions/23659564/limiting-java-ssl-debug-logging , auch wie man zuviel Infos auf das Interessante beschränkt.
Da der Handshake schief zu laufen scheint hab ich mir das mal angesehen
Mit CURL aufgerufen (paste42) | Mit Chrome (paste42)
Bei beiden scheint irgendwie das ServerHello zu fehlen, neben dem was sonst noch alles 'nicht richtig' ist.
Wenn man den Socket nicht schließt, scheint das ganze (mit Chrome) sogar nen ServerHello zurück zu geben, aber leider erst nach nem retry Chrome 2 (paste42)

Das erste ClientHello wird vermutlich aus dem Request für das favicon sein welches Chrome immer mit sendet.
Scheinbar passen dann nur die Authentifizierungsschemas nicht, aber wieso braucht es überhaupt nen erneuten Versuch...

So wirklich schlau werde ich da leider nicht draus, dass Zertifikat sollte eigentlich passen und durch getSupportedCipherSuites sollte die auch passend benutzen.

Falls das jemand ausprobieren möchte ist hier der Code dafür
Java:
package test;

import javax.net.ssl.*;
import java.io.FileInputStream;
import java.io.InputStream;
import java.security.KeyStore;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;

public class Sandbox{

    public static void main(String[] args){
        SSLContext sslContext = createSSLContext();
        try{
            SSLServerSocketFactory sslServerSocketFactory = sslContext.getServerSocketFactory();
            //Create server socket
            SSLServerSocket sslServerSocket = (SSLServerSocket) sslServerSocketFactory.createServerSocket(9999);

            while(true){
                SSLSocket sslSocket = (SSLSocket) sslServerSocket.accept();

                //Handle
                sslSocket.setEnabledCipherSuites(sslSocket.getSupportedCipherSuites());
                sslSocket.startHandshake();


               // sslSocket.close();
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }


    private static SSLContext createSSLContext(){
        try{
            InputStream certfile = new FileInputStream("./certificate.crt");
            // CertificateFactory
            CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
            Certificate certificate = certificateFactory.generateCertificate(certfile);

            // Create a KeyStore
            String keyStoreType = KeyStore.getDefaultType();
            KeyStore keyStore = KeyStore.getInstance(keyStoreType);
            keyStore.load(null, null);
            keyStore.setCertificateEntry("certificate", certificate);

            // Create a TrustManager that trusts the CAs in our KeyStore
            String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
            TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
            tmf.init(keyStore);

            // Create an SSLContext that uses our TrustManager
            SSLContext context = SSLContext.getInstance("TLSv1.2");
            context.init(null, tmf.getTrustManagers(), null);
            return context;

        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }
}
-----BEGIN CERTIFICATE-----
MIIC+zCCAeOgAwIBAgIJALf2qSYoxljJMA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNV
BAMMCTEyNy4wLjAuMTAeFw0xOTAzMTExMTAxMjlaFw0yMDAzMTAxMTAxMjlaMBQx
EjAQBgNVBAMMCTEyNy4wLjAuMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
ggEBAKd/CzPWVmpkN8L+nTcpV8+Nydtpq2YtNhD2jUVRRrttBWJdfj5uLvL63/H2
+mdM2O45+6Jgqut+Arv+OAqo/T89CSB02yIOPHcom+I6Zh8YDfbHQNty2qK4FSv6
1WkXLjr/MaqkD0NsEGED8Hmexuu67U7Mh+wMRf7fZQ5twdsWFrwMRYC2FgHxf5Pm
wsZMs2b7xaaWXF85LuGcb6rU/FB0AG2srIYeU04tgmg7MhrMS9Xs6jyWYq1rYYbA
n8yWAG2A12X72VEscLStMZQM0XdhQrIMLIqTyuJAICCMbYJf999B8dJczEXpwjbI
tL04u8tMlZjwNxyItESwlD5RT6cCAwEAAaNQME4wHQYDVR0OBBYEFOOFw8ms+Zgf
SSMSnOZCrfCSB1yZMB8GA1UdIwQYMBaAFOOFw8ms+ZgfSSMSnOZCrfCSB1yZMAwG
A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBACUS0Ni6qeEZe+tcftTseDWY
UMhKwoRbeDSvf/XNjhgKpjCXqcVEZu+eelpnJQbf5fZP7ZcNhcxAemhdXSYyJV8h
1BIHs23ua+BY6W/wtAcInXuuBn17hWNirn4WcYCJ84ZCQxjMEzlTBo9MvFqkxipA
ZMVc4QWMYEh/BPNFpPW0UdIqerRUGYW9xnzSptAvQwXh1AySLB06u778oKxVbEcF
ecljZp9DheD2mxlNnGVWL+Dzga4V164luVBH+QQnIyAY2njGdc009FJklJK71cbJ
MivoHKcCLaX2LeuECs8qaMF1TP0xXyv8xjMUrU+F6XqCjAMJRWqNPIoFFgq1N9k=
-----END CERTIFICATE-----

//Edit
Anscheinend war das erste Zertifikat mit sha1 hash und hat somit "no cipher suites in common" als Fehler erzeugt. Mit dem neuen scheint zumindest dieser Fehler weg zu sein (egal ob .crt oder .pem beide landen richtig im KeyStore). Auffallend ist nun "No X.509 cert selected for # " was scheinbar dafür sorgt das "Unavailable authentication scheme: #" auftritt. Wie man das nun löst hab ich noch nicht raus gefunden, ebenso wenig wieso noch ein HelloRetryRequest gesendet wird.
 
Zuletzt bearbeitet:
Ich habe mal Deinen Code ein bisschen abgeändert:
Code:
import javax.net.ssl.*;
import java.io.FileInputStream;
import java.io.InputStream;
import java.security.KeyStore;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.io.OutputStream;

public class Sandbox{

    public static void main(String[] args){
        SSLContext sslContext = createSSLContext();
        try{
            SSLServerSocketFactory sslServerSocketFactory = sslContext.getServerSocketFactory();
            //Create server socket
            SSLServerSocket sslServerSocket = (SSLServerSocket) sslServerSocketFactory.createServerSocket(9999);

            while(true){
                SSLSocket sslSocket = (SSLSocket) sslServerSocket.accept();

                //Handle
                sslSocket.setEnabledCipherSuites(sslSocket.getSupportedCipherSuites());
                sslSocket.startHandshake();
                
                //sslSocket.close();
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }


    private static SSLContext createSSLContext(){
        try{
                InputStream certfile = new FileInputStream("./certificate.jks"); // CHANGED
            // CertificateFactory
            CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");

            // Create a KeyStore
            KeyStore keyStore = KeyStore.getInstance("JKS"); // CHANGED
            keyStore.load(certfile, "password".toCharArray()); // CHANGED

            // MISSING
            KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
            kmf.init(keyStore, "password".toCharArray() );


            // Create a TrustManager that trusts the CAs in our KeyStore
            TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
            tmf.init(keyStore);

            // Create an SSLContext that uses our TrustManager
            SSLContext context = SSLContext.getInstance("TLS"); // CHANGED
            context.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); // CHANGED
            return context;

        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }
}
Es fehlte das

Code:
            // MISSING
            KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
            kmf.init(keyStore, "password".toCharArray() );

wie in https://stackoverflow.com/questions/15076820/java-sslhandshakeexception-no-cipher-suites-in-common beschrieben.

Außerdem musst Du noch die beiden Befehle
Code:
openssl pkcs12 -export -inkey key.pem -in certificate.pem -name certificate -out certificate.p12
keytool -importkeystore -srckeystore certificate.p12 -srcstoretype pkcs12 -deststoretype pkcs12 -storepass password -destkeystore certificate.jks
eingeben um die Datei "certificate.jks" zu erhalten. Wenn nach Passwörtern gefragt wird immer "password" eingeben (siehe auch zweimal im Code "password".toCharArray() ).

Nach dieser Änderung gebe ich die weitere Entwicklung wieder in Deine Hände...

Wenn das ganze letztlich funktioniert solltest Du die fertige Lösung wieder hier posten.


HTH

BigNum
 
  • Gefällt mir
Reaktionen: konkretor
Vielen Dank, so funktioniert das ganze :)
Java:
package test;

import javax.net.ssl.*;
import java.io.FileInputStream;
import java.io.InputStream;
import java.security.KeyStore;


public class Sandbox{

    public static void main(String[] args){
        SSLContext sslContext = createSSLContext();
        try{
            SSLServerSocketFactory sslServerSocketFactory = sslContext.getServerSocketFactory();
            //Create server socket
            SSLServerSocket sslServerSocket = (SSLServerSocket) sslContext.getServerSocketFactory().createServerSocket(9999);

            while(true){
                SSLSocket sslSocket = (SSLSocket) sslServerSocket.accept();

                //Handle
                sslSocket.setEnabledCipherSuites(sslSocket.getSupportedCipherSuites());
                sslSocket.startHandshake();


            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    private static SSLContext createSSLContext(){
        try{
            InputStream jks = new FileInputStream("./cert.jks");

            // Create a KeyStore
            KeyStore keyStore = KeyStore.getInstance("JKS");
            keyStore.load(jks, "password".toCharArray());

            // KeyManagerFactory
            KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
            kmf.init(keyStore, "password".toCharArray() );

            // Create a TrustManager that trusts the CAs in our KeyStore
            TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
            tmf.init(keyStore);

            // Create an SSLContext that uses our TrustManager
            SSLContext context = SSLContext.getInstance("TLS");
            context.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
            return context;

        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }
}

//openssl req -newkey rsa:2048 -nodes -keyout key.pem -subj "/C=/ST=/L=/O=/CN=127.0.0.1" -x509 -days 365 -out certificate.pem
//openssl pkcs12 -export -inkey key.pem -in certificate.pem -name certificate -out certificate.p12
//keytool -importkeystore -srckeystore certificate.p12 -srcstoretype pkcs12 -deststoretype pkcs12 -storepass password -destkeystore certificate.jks
Das man das Zertifikat von Hand umwandeln muss ist zwar nicht so schön, aber eventuell findet sich da später noch eine Lösung
 
Ich bin mir nicht ganz sicher was du meinst. Nach dem Handshake müsste doch eigentlich das ganze so wie bei normalen Sockets funktionieren, also in und output stream nutzen etc.
 
Ja, eben. Du willst eigentlich, daß der Client und der Server kommunizieren, d.h. das ganze "Vorgeplänkel" mit Socket öffen, Zertifikate hin- und herschicken usw. ist "nur" Verwaltung, das eigentliche Ziel ist aber die Kommunikation und die fehlt in Deinem Beispiel.
Es würde ja ein einfaches "Hallo Server" vom Client und als Antwort darauf ein "Schöner Gruß zurück vom Server" reichen.

Das ist in etwa so, als würde ich mich umziehen, Geld/EC-Karte einstecken mit de(m|r) Auto/Fahrrad/Bus/U-Bahn in die Stadt bis zum Geschäft fahren, kurz reingehen und ohne etwas zu kaufen wieder den Weg zurück nach Hause antreten. Weil ich wollte ja nur testen, ob ich was einkaufen könnte.

Anyway, Hauptsache es funktioniert so wie es soll...
 
Zurück
Oben