Schalter setzen um sebstsignierten Zertifikaten zu vertrauen

vfl_freak

Premium-User
Guten Morgen,
ich stehe hier vor einem Problem, dass mit leider an den Rand meiner Kenntnis bringt (oder vermutlich sogar darüber hinaus ;))

Ich soll von einem Java-Programm aus per REST Daten von unserer Telefonanlage abfragen.
Ich habe zunächst eine Methode gebastelt, die zu einem Benutzerkürzel die IP des von ihm angemeldeten SNOM-Telefons ermittelt und zurückgibt.
Java:
    public static void initialzeSnomFonData( String userKuerzel )
    {
       URL url = null;
       HttpURLConnection httpCon = null;

       try
       {
           // xxx.yyy.zzz = Adresse der Telefonanlage
           String abfrageString = "[URL]http://xxx.yyy.zzz/services/identity/[/URL]" + userKuerzel + "/defaultdevice";
           url = new URL( abfrageString );
           httpCon = (HttpURLConnection)url.openConnection();
           httpCon.setReadTimeout( 10000 );
           httpCon.setConnectTimeout( 10000 );

           final String userName = "myUserName";  // anonymisiert
           final String password = "MyPasswort";    // anonymisiert
           byte[] encodedPassword = ( userName + ":" + password ).getBytes();

           String base64encodedString = Base64.getEncoder().encodeToString( encodedPassword );
           httpCon.setRequestProperty( "Authorization", "Basic " + base64encodedString );

           httpCon.setRequestProperty( "Content-Type", "application/json" );
           httpCon.setRequestProperty( "Accept", "application/json" );
           httpCon.setRequestMethod( "GET" );

           // ################################################
           // hier fliegt in der  HTTPS-Variante die unten beschriebene Exception
           // ################################################
           BufferedReader reader = new BufferedReader( new InputStreamReader(httpCon.getInputStream()) );

           String inputLine;
           StringBuffer response = new StringBuffer();
           while( (inputLine = reader.readLine()) != null )
           {
               response.append(inputLine);
           }
           reader.close();

           String[] ergList = null;
           if( response.length() > 0 )
           {
               // alle '[', ']' und '"' durch Blanks ersetzen
               String s1 = response.toString().replace( "[", "" );
               String s2 = s1.replace( "]", "" );
               String s3 = s2.replace( "\"", "" );

               // ergList füllen
               Pattern p = Pattern.compile( "," ); // splitten nach dem Komma
                 String[] sData = p.split( s3, 0 );
               ergList = new String[sData.length];
               for( int i = 0; i < sData.length; i++ )
               {
                   ergList[I] = sData[I];
//System.out.println( "ergList[I]=<" + ergList[I] + ">" );
                   if( ergList[I].startsWith("ip:") )
                   {
                       // hier wird die gefundene IP auf eine globale Variable gesetzt
                       SNOMFON_IP_MOBYDICK = ergList[I].substring( 3, ergList[I].length() );
                       SNOM_IS_ACTIVE = true;
                       break;
                   }
               }
           }
           else
           {
               ergList = new String[] { " " };
               SNOM_IS_ACTIVE = false;
           }
       }
       catch (Exception e)
       {
           System.out.println( "Exception in der Rest-Abfrage" );
           e.printStackTrace();
       }
       finally
       {
           if( httpCon != null )
           {
               httpCon.disconnect();
               httpCon = null;
           }
       }
       System.out.println( "http: SNOMFON_IP_MOBYDICK=<" + SNOMFON_IP_MOBYDICK + ">" );
    } // initialzeSnonFonData
So weit, so gut - das funktioniert auch einwandfrei :)

Jetzt versuche ich, diese HTTP-Variante durch HTTPS zu ersetzen.
Es ist genau der gleiche Code, nur das die HttpURLConnection durch HttpsURLConnection ersetzt wurde. Wenn sie dann ausführe, fliegt an der oben markierten Stelle folgende Exception:
javax.net.ssl.SSLProtocolException: handshake alert: unrecognized_name
at sun.security.ssl.ClientHandshaker.handshakeAlert(ClientHandshaker.java:1542)
at sun.security.ssl.SSLSocketImpl.recvAlert(SSLSocketImpl.java:2026)
at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1135)
at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1385)
at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1413)
at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1397)
at sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:559)
at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:185)
at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1564)
at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1492)
at sun.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:263)

// #################
// 1080 ist die oben markierte Zeile !!
// #################
at com.gselectronic.worker.config.Config.initialzeSnomFonDataViaHttps(Config.java:1080)
at com.gselectronic.worker.dialogs.DlgLogin.actionPerformed(DlgLogin.java:306)

at javax.swing.AbstractButton.fireActionPerformed(AbstractButton.java:2022)
at javax.swing.AbstractButton$Handler.actionPerformed(AbstractButton.java:2348)
at javax.swing.DefaultButtonModel.fireActionPerformed(DefaultButtonModel.java:402)
at javax.swing.DefaultButtonModel.setPressed(DefaultButtonModel.java:259)
at javax.swing.AbstractButton.doClick(AbstractButton.java:376)
at javax.swing.plaf.basic.BasicRootPaneUI$Actions.actionPerformed(BasicRootPaneUI.java:208)
at javax.swing.SwingUtilities.notifyAction(SwingUtilities.java:1663)
at javax.swing.JComponent.processKeyBinding(JComponent.java:2882)
at javax.swing.KeyboardManager.fireBinding(KeyboardManager.java:307)
at javax.swing.KeyboardManager.fireKeyboardAction(KeyboardManager.java:250)
at javax.swing.JComponent.processKeyBindingsForAllComponents(JComponent.java:2974)
at javax.swing.JComponent.processKeyBindings(JComponent.java:2966)
at javax.swing.JComponent.processKeyEvent(JComponent.java:2845)
at java.awt.Component.processEvent(Component.java:6310)
at java.awt.Container.processEvent(Container.java:2237)
at java.awt.Component.dispatchEventImpl(Component.java:4889)
at java.awt.Container.dispatchEventImpl(Container.java:2295)
at java.awt.Component.dispatchEvent(Component.java:4711)
at java.awt.KeyboardFocusManager.redispatchEvent(KeyboardFocusManager.java:1954)
at java.awt.DefaultKeyboardFocusManager.dispatchKeyEvent(DefaultKeyboardFocusManager.java:806)
at java.awt.DefaultKeyboardFocusManager.preDispatchKeyEvent(DefaultKeyboardFocusManager.java:1074)
at java.awt.DefaultKeyboardFocusManager.typeAheadAssertions(DefaultKeyboardFocusManager.java:945)
at java.awt.DefaultKeyboardFocusManager.dispatchEvent(DefaultKeyboardFocusManager.java:771)
at java.awt.Component.dispatchEventImpl(Component.java:4760)
at java.awt.Container.dispatchEventImpl(Container.java:2295)
at java.awt.Window.dispatchEventImpl(Window.java:2746)
at java.awt.Component.dispatchEvent(Component.java:4711)
at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:758)
at java.awt.EventQueue.access$500(EventQueue.java:97)
at java.awt.EventQueue$3.run(EventQueue.java:709)
at java.awt.EventQueue$3.run(EventQueue.java:703)
at java.security.AccessController.doPrivileged(Native Method)
at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:80)
at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:90)
at java.awt.EventQueue$4.run(EventQueue.java:731)
at java.awt.EventQueue$4.run(EventQueue.java:729)
at java.security.AccessController.doPrivileged(Native Method)
at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:80)
at java.awt.EventQueue.dispatchEvent(EventQueue.java:728)
at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:201)
at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:116)
at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:109)
at java.awt.WaitDispatchSupport$2.run(WaitDispatchSupport.java:190)
at java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:311)
at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:756)
at java.awt.EventQueue.access$500(EventQueue.java:97)
at java.awt.EventQueue$3.run(EventQueue.java:709)
at java.awt.EventQueue$3.run(EventQueue.java:703)
at java.security.AccessController.doPrivileged(Native Method)
at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:80)
at java.awt.EventQueue.dispatchEvent(EventQueue.java:726)
at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:201)
at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:116)
at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:105)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:93)
at java.awt.EventDispatchThread.run(EventDispatchThread.java:82)
// Ausgabe:
HTTPS: SNOMFON_IP_MOBYDICK=<0.0.0.0>

Der Kollege aus der IT, der sich um die Telefonanlage kümmert, meinte nur lax
"... Du musst dem Java cert manuell vertrauen oder einen Schalter setzten, dass selbstsignierten Zertifikaten vertraut wird ..."
Nur sagt mir dies beides, auch nach dem Lesen div. Webseiten, herzlich wenig!
Das ich das Java-Zertifikat nicht kenne, ist mein Vertrauen doch ziemlich getrübt ;)
Wie sähe denn der genannte Schalter aus?
Er müsste ja vermutlich als Argument mit "-D" übergeben werden, oder?

Ich hoffe, dass mich hier irgendwer auf die richtige Spur bringen kann :)
Bei Fragen fragen ;)

VG Klaus
 
Servus Klaus,

als erstes würde ich dir von der HttpUrlConnection abraten. Auch wenn diese direkt in Java integriert ist, so ist diese selbst bei einfacheren Sache nicht so einfach zu verwenden. Ausserdem hatte ich schon des öfteren Probleme mit Timeouts oder komischen Verhalten.

Ich würde dir hier zu einer "richtigen" HTTP-Library raten, z.b. https://hc.apache.org/
Ausserdem parst du das ankommende JSON nicht, du arbeitest mit Regex drauf. Keine so gute Idee, ich würde hier z.b. GSON verwenden. Macht den Code dadurch auch lesbarer / wartbarer.

Aber zurück zum Thema:
Wenn du mit Java eine SSL-Verbindung machst, werden die konfigurierten Truststores befragt ob das andere Zertifikat gültig ist und ob man dem vertrauen kann. Du musst also das Serverzertifikat (oder besser dessen CA) runterladen und in einen Java-Keystore verpacken. Diesen kannst du dann über die System-Properties setzen (entweder im Programm per System.setProperty("javax.net.ssl.trustStrore", "foobar.jks") oder beim Start der JVM mit "-Djavax.net.ssl.trustStrore=foobar.jks")

Grüsse,
BK
 
Moin BK,
erst mal Danke für Deine Antwort :)

als erstes würde ich dir von der HttpUrlConnection abraten. Auch wenn diese direkt in Java integriert ist, so ist diese selbst bei einfacheren Sache nicht so einfach zu verwenden. Ausserdem hatte ich schon des öfteren Probleme mit Timeouts oder komischen Verhalten.
optimal ist es vielleicht nicht, aber für die wenigen Fälle in unserem Programm (an anderen Stellen) hat es bislang immer gereicht und auch problemlos funktioniert!

Ich würde dir hier zu einer "richtigen" HTTP-Library raten, z.b. https://hc.apache.org/
Ausserdem parst du das ankommende JSON nicht, du arbeitest mit Regex drauf. Keine so gute Idee, ich würde hier z.b. GSON verwenden. Macht den Code dadurch auch lesbarer / wartbarer.
Ist da beides nicht für diesen einen Anwendungsfall ein wenig overloaded??
Habe die Infos zur Lib mal überflogen ... da ist aber auch nirgendwo von https die Rede, oder?
Es geht eigentlich nur darum, die IP des betroffenen Telefons zu ermitteln ....

Wenn du mit Java eine SSL-Verbindung machst, werden die konfigurierten Truststores befragt ob das andere Zertifikat gültig ist und ob man dem vertrauen kann. Du musst also das Serverzertifikat (oder besser dessen CA) runterladen und in einen Java-Keystore verpacken. Diesen kannst du dann über die System-Properties setzen (entweder im Programm per System.setProperty("javax.net.ssl.trustStrore", "foobar.jks") oder beim Start der JVM mit "-Djavax.net.ssl.trustStrore=foobar.jks")
hmm, sorry, bin wie gesagt ein wenig unbeleckt mit diesen Dingen, da ich sie noch nie verwendet habe!
Was genau meinst Du jetzt mit "das andere Zertifikat" rsp. "das Serverzertifikat" (von "CA" mal ganz zu schwiegen ;)) ?
Das der Gegenseite (hier halt die Telefonanlage)?
Ich weiß weder ob sie eines hat noch wie ich da dran käme ....
Für unsere Anwendung rsp. die VM gibt es wohl eins. Im Control Panel sind (mit Zertifikatstyp "vertrauenswürdig") vier Stück eingetragen, wobei nur eines davon ("COMODO RSA Code Signing CA") noch gültig ist!
Heißt das, dass unsere IT ein anderes besorgen muss ??

VG Klaus
 
Hi

kann schon sein, dass Lib overkill ist - aber, einmal drin, kann sie immerhin auch für alles anderen verwendet werden.

Auch wenn Https auf der Startseite nicht erwähnt ist gehört das trotzdem zur Basisausstattung einer brauchbaren HTTP-Lib ... und siehe zB. den Code hier: https://stackoverflow.com/a/24370091/3134621

"Das andere Zertifikat" bedeutet "das Zertifikat auf der anderen Seite der Verbindung" bzw. eben das Serverzertifikat auf der Telefonanlage.
Vom Titel der Frage ausgehend sollte da eigentlich ein selbstsigniertes Zertifikat sein :D
Die Exception im ersten Post kann zwar was anderes auch sein, aber scheinbar kennt dein Kollege das Problem ja schon...

Wie man da rankommt: Wenn man es nicht einfach irgendwo als Datei bekommen kann (zB. vom Kollegen), und hoffentlich irgendein nicht-Windows-System verfügbar ist, "openssl s_client -connect telefonanlagenip >eine.datei" und dann in der gespeicherten Datei nur den Teil nehmen, der mit Begin/end als Zertifikat markiert ist (mit den markierern). Braucht man aber nur wenn man die -D - Option zum Ignorieren, oder das Entsprechende für die Library, nicht verwendet. (was man imho tun sollte - weil, wofür hat man das Zertifikat dann überhaupt, wenn man es nicht zur Absicherung der Verbindung verwendet)

Da die Exception sich über den Name beschwert ist das Ablaufdatum nicht das direkte Problem - könnte aber zusätzlich abgelaufen sein auch, ja. Wissen wir zurzeit einfach nicht. Falls es abgelaufen ist muss ein neues her, aber das ist kein Problem - "selbstsigniert" ist im Wesentlichen "komplett selbst erstellt", von irgendeinem eurer Mitarbeiter. Ganz ohne externe CA und Bezahlung etc.

Siehe auch https://www.tutorials.de/threads/neuland-zertifikate.406401/ für ein paar mehr oder weniger relevante Infos zu Zertifikaten.
 
Zuletzt bearbeitet:
Moin sheel,

Die Exception im ersten Post kann zwar was anderes auch sein, aber scheinbar kennt dein Kollege das Problem ja schon...
na, das glaube ich nun weniger ;)
Unsere IT - im Prinzip 'nur' Techniker und keine Programmierer - sind mit solchen Aussagen schnell zur Hand ... :rolleyes:

hoffentlich irgendein nicht-Windows-System verfügbar ist
Keine Ahnung - das ist dann eine Sache, um die wir in Softwareentwicklung uns eh' nicht kümmern (dürfen) !!

Aber erst nochmal Danke für die beiden Links. Klingen beim ersten Überfliegen sehr interessant!

VG Klaus
 
Guten Morgen :)

so, nach längerem Hin und Her bin ich mit Unterstützung eines anderen Forms zu folgender, funktionierenden Lösung gekommen :D
(mit der HTTP-Lib muss ich mich später befassen)

Letzlich war es dies:
https://www.naschenweng.info/2017/0...lexception-handshake-alert-unrecognized_name/
PLUS Setzen des Parameters mit -D

So sieht mein Code jetzt aus:
Java:
    public static void initialzeSnomFonDataViaHttps( String userKuerzel )
    {
       URL url = null;
       HttpsURLConnection httpsUrlConnection = null;
       String https_url = "https://xxx.yyy.zzz/services/identity/" + userKuerzel + "/defaultdevice";
       try
       {
           url = new URL( https_url );
           URLConnection urlConnection = url.openConnection();
           urlConnection.setReadTimeout( 10000 );
           urlConnection.setConnectTimeout( 10000 );

           final String userName = "myUser";
           final String password = "myPW";
           byte[] encodedPassword = ( userName + ":" + password ).getBytes();
           
           httpsUrlConnection = (HttpsURLConnection)urlConnection;

           String base64encodedString = Base64.getEncoder().encodeToString( encodedPassword );
           httpsUrlConnection.setRequestProperty( "Authorization", "Basic " + base64encodedString );
           httpsUrlConnection.setRequestProperty( "Content-Type", "application/json" );
           httpsUrlConnection.setRequestProperty( "Accept", "application/json" );
           httpsUrlConnection.setRequestMethod( "GET" );
           
           // ### NEU !!! ###
           // Inhalt weiter unten ...
           SSLSocketFactory sslSocketFactory = createSslSocketFactory();
           httpsUrlConnection.setSSLSocketFactory( sslSocketFactory );
           httpsUrlConnection.setHostnameVerifier( new SSLSkipSNIHostnameVerifier() );
           
           StringBuffer ergebnis = new StringBuffer();
           try( InputStream inputStream = httpsUrlConnection.getInputStream() )
           {
               BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
               String inputLine = null;
               while( (inputLine=reader.readLine()) != null )
               {
                   ergebnis.append( inputLine );
               }
           }
           
           String[] ergList = null;
           if( ergebnis.length() > 0 )
           {
               // alle '[', ']' und '"' entfernen
               String s1 = ergebnis.toString().replace( "[", "" );
               String s2 = s1.replace( "]", "" );
               String s3 = s2.replace( "\"", "" );

               // ergList füllen
               Pattern p = Pattern.compile( "," ); // nach dem Komma splitten
                 String[] sData = p.split( s3, 0 );
               ergList = new String[sData.length];
               for( int i = 0; i < sData.length; i++ )
               {
                   ergList[i] = sData[i];
//System.out.println( "ergList[i]=<" + ergList[i] + ">" );
                   if( ergList[i].startsWith("ip:") )
                   {
                       SNOMFON_IP_MOBYDICK = ergList[i].substring( 3, ergList[i].length() );
                       SNOM_IS_ACTIVE = true;
                       break;
                   }
               }
           }
           else
           {
               ergList = new String[] { " " };
               SNOM_IS_ACTIVE = false;
           }
       }
       catch (Exception e)
       {
           System.out.println( "Exception in der Rest-Abfrage" );
           e.printStackTrace();
       }
       finally
       {
           if( httpsUrlConnection != null )
           {
               httpsUrlConnection.disconnect();
               httpsUrlConnection = null;
           }
       }
       System.out.println( "HTTPS: SNOMFON_IP_MOBYDICK=<" + SNOMFON_IP_MOBYDICK + ">" );
    } // initialzeSnonFonDataViaHttps

    // #########################################################################################################################

    private static SSLSocketFactory createSslSocketFactory()
        throws Exception
    {
       TrustManager[] byPassTrustManagers = new TrustManager[]
       {
           new X509TrustManager()
           {
               public java.security.cert.X509Certificate[ ] getAcceptedIssuers()
               {
                   return new java.security.cert.X509Certificate[0];
               }

               @Override
               public void checkClientTrusted( java.security.cert.X509Certificate[ ] chain, String authType )
                       throws CertificateException
               {
                   // Auto-generated method stub
               }

               @Override
               public void checkServerTrusted( java.security.cert.X509Certificate[ ] chain, String authType )
                       throws CertificateException
               {
                   // Auto-generated method stub
               }
           }
       };
       
       SSLContext sslContext = SSLContext.getInstance( "TLS" );
       sslContext.init( null, byPassTrustManagers, new SecureRandom() );
       return sslContext.getSocketFactory();
    } // createSslSocketFactory
und zusätzlich muss das Argument -Djsse.enableSNIExtension=false an die VM übergeben werden!!

VG Klaus
 
Guten Morgen,

du hast zwar eine "funktionierende" Lösung, aber du hast die Sicherheitsmechanismen die von SSL/TLS geboten werden "abgeschaltet". Diese Lösung ist anfällig für Man-in-the-Middle, wodurch dir der Umbau auf SSL kaum mehr Sicherheit bietet. Ich würde dir da eher zu dem Ansatz von Bratkartoffel raten.

Viele Grüße
Sascha
 
Zurück