# Socket in SSL - Mode bringen



## REDBARON (14. Okt 2009)

Hallo Community,

es existiert eine selbstsignierte CA. Das Serverzertifikat und das Clientzertifikat
ist von dieser CA signiert. Die Zertifikate sind in Ordnung und funktionieren mit
einer anderen Anwendung. Die Zertifikate liegen im PEM Format als base46 String
vor.

Aufgabe:
Ein verbundener Socket soll in SSL-Mode versetzt werden soll.

Problem:
ich bin noch recht neu bei Java, Zielplatform ist "Android" - OS.

Die bestehende Lösung sieht wie Folgt aus: ( C++ Code, openSSL )

// Methode festlegen
m_pSSLMethod = TLSv1_client_method();
// Context erstellen
m_pSSLContext = SSL_CTX_new(m_pSSLMethod);
// Serverzertifikat, nicht in Cert-chain einer der großen bekannten CAs enthalten !
SSL_CTX_load_verify_locations(m_pSSLContext, CA_CERT);
// Clientzertifikat laden
SSL_CTX_use_certificate_file(m_pSSLContext, CERT_CLIENT, SSL_FILETYPE_PEM);
// Clientkey laden
SSL_CTX_use_PrivateKey_file(m_pSSLContext, PRIV_CLIENT, SSL_FILETYPE_PEM);

soweit die Vorbereitungen, und nun der SSL-Handshake

// SSL initalisieren ...
m_pSSL = SSL_new(m_pSSLContext);
// ... den bereits verbundenen Socket an SSL Layer übergeben
SSL_set_fd(m_pSSL, m_sock);
// ... Status setzten
SSL_set_connect_state(m_pSSL);
// Handshake
SSL_do_handshake(m_pSSL);


Wie sieht aber nun der Ablauf in Java aus ?
Ich habe bisher nur folgenden Einstieg:

SSLContext ctx = SSLContext.getInstance("TLS");
SSLSessionContext sessctx = ctx.getClientSessionContext();

// wie läd man hier die Zertifikat-Strings in den Context ?
// wie wird der Context mit dem SSL-Socket "verbunden"

SSLSocketFactory f = (SSLSocketFactory) SSLSocketFactory.getDefault();
SSLSocket c =(SSLSocket)f.createSocket(mSocket, mHost, mPort, false);

c.startHandshake();

Ergebnis:
Der Server sendet sein Zertifikat, es wird eine "not trusted"-Exception gewurfen.
Trustet kann das Zerti auch erst sein, nachdem der Client das ServerZert *irgendwie*
geladen hat. Zudem muss der Client auch sein Zertifikat und private Key laden um
Daten verschlüsselt senden zu können.

Anwendung soll das ganze finden beim Datenaustausch mit einem MySQL-Server.
Das Protokoll ist soweit schon implementiert, nur die Umschaltung in SSL fehlt noch.


Ich habe schon einige Tuturial angesehen und auch im Forum gesucht, nur vll.
kennt das jemand - den Wald vor Bäumen nicht zu sehen ;(

Ich freue mich jedenfalls über jede sachdienliche Hinweise 

Viele Grüße, RB

Edit: neuer Code in Anlehnung an:
http://www.java-forum.org/netzwerkprogrammierung/79529-https-einloggen.html#post491246



```
final TrustManager[] trustAllCerts = new TrustManager[] {new X509TrustManager(){	 
            public java.security.cert.X509Certificate[] getAcceptedIssuers(){return null; }
            public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType){}
            public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType){}
 
        }};
		
			new MySQLLoginPacket40(user, passwd, mServer, mSocket.getOutputStream());
			if( (mServer.mCapabilities & MySQLServerPacket.CLIENT_SSL) > 0)
			{
				
				SSLContext ctx = SSLContext.getInstance("TLS");
				ctx.init(null, trustAllCerts, new java.security.SecureRandom());
				
				SSLSocketFactory f = (SSLSocketFactory) SSLSocketFactory.getDefault();
				SSLSocket c =(SSLSocket)f.createSocket(mSocket, mHost, mPort, false);
				
				
				c.startHandshake();
				c.getOutputStream();
			}
```

Ergebnis: Exception: not trusted server certificate


----------



## FArt (14. Okt 2009)

Die VM sucht gültige Zertifikate (oder Zertifikatsketten) aus dem certstore zu holen Default ist cacert aus der JRE (da sind so Standard-Authorities drin).
Du kannst aber auch einen eigenen Truststore angeben.

Installing and Configuring SSL Support


----------



## REDBARON (14. Okt 2009)

Danke, ja eigener TrustStore ist leicht gesagt  

Neuer Code im wesentlichen jetzt:

```
final TrustManager[] trustAllCerts = new TrustManager[] {new X509TrustManager(){	 
            public java.security.cert.X509Certificate[] getAcceptedIssuers(){return null; }
            public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType){}
            public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType){}


				SSLContext ctx = SSLContext.getInstance("TLS");
				ctx.init(null, trustAllCerts, new java.security.SecureRandom());
				
				SSLSocketFactory f = ctx.getSocketFactory();
				//SSLSocketFactory f = (SSLSocketFactory) SSLSocketFactory.getDefault();
				SSLSocket c =(SSLSocket)f.createSocket(mSocket, mHost, mPort, false);
						
				c.startHandshake();
```

die Exception "not trusted server certificate" erscheint nicht mehr, die Verbindung
zwischen Ctx und SocketFactory ist mir jetzt auch irgendwie klar gewurden :toll:

Ich werde erstmal schauen warum der Client auch ein Zerti braucht ... hatte mein
C++ Beispiel aus den MySQL Quellen abgeschaut. Vll. ist es technisch auch nicht
ganz so wichtig ... wenn der Client seine Daten mit dem Server public Key verwurstet
sollte es dem Server auch möglich sein die Daten zu entschlüsseln ...


----------



## REDBARON (14. Okt 2009)

Das Problem was sich mir jetzt noch darstellt, ist:
der neue SSLSocket ist nicht verbunden nach :


```
SSLContext ctx = SSLContext.getInstance("TLS");
				ctx.init(null, trustAllCerts, new java.security.SecureRandom());
				SSLSocketFactory f = ctx.getSocketFactory();
				mSSLSocket =(SSLSocket)f.createSocket(mSocket, mHost, mPort, false);
				mSSLSocket.startHandshake();
```

mSSLSocket.HandshakeStartet ist true. Ich sehe auch das Server Zertifikat
in einem der Context Member, der Server hat definitiv sein Zertifikat übermittelt.

Nur Senden und Empfangen funktioniert über den SSLSocket nicht. Beim Senden
passiert nichts und beim Empfangen kommt, Exception I/O Error .

mSSLSocket.isConnected = false;

Seltsam. :rtfm:


----------



## REDBARON (16. Okt 2009)

```
mSSLContext = SSLContext.getInstance("TLS");
mSSLContext.init(null, trustAllCerts, new java.security.SecureRandom());
mSSLSocketFactory = mSSLContext.getSocketFactory();
mSSLSocket = (SSLSocket)mSSLSocketFactory.createSocket(mSocket, mHost, mPort, false);
mSSLSocket.addHandshakeCompletedListener(new MyHandshakeListener());
mSSLSocket.startHandshake();
				
mServer.mSSLLogin = true;
new MySQLLoginPacket40(user, passwd, mServer, mSSLSocket.getOutputStream());
```

Das Problem ist wie ich festgestellt habe weniger die SLL Mimik, sondern das ich etwas
anderes erwarte von mSSLSocket.getOutputStream() als mir geliefert wird.

Der SSL HandShakeCompleteHandler wird aufgerufen, keine Exceptions treten auf.
mSSLSocket.isConnected = false
mSSLSocket.Socket.isConnected = true

ein mSSLSocket.getOutputStream()  liefert nicht den darunterliegenden Socketstream
welcher als verbunden markiert ist sondern den ssl-stream welcher nicht verbunden ist.

egal über welchen stream ich sende, exception: broken pipe.
Wenn irgendwas mit dem SSL Zeugs nicht stimmen würde aus Richtung Zertis sollte
doch eine Exception kommen !

Könnte es sein, die Darwin VM von Android OS hat ne Macke ?


----------



## FArt (16. Okt 2009)

Kann ich dir nicht sagen. Probiere es halt erst mal von einer "normalen VM" aus...

noch mal zu vorher: der Client muss das Zertifkat nur dann importieren, wenn es als nicht vertrauenswürdig angesehen wird, wie z.B. selbsterstellte Zertifikate.


----------



## REDBARON (19. Okt 2009)

Hallo, ich habe Teilerfolg zu verkünden 

meine Socketumschaltung sieht jetzt so aus:

```
if( (mServer.mCapabilities & MySQLServerPacket.CLIENT_SSL) > 0)
{	
	mSSLContext = SSLContext.getInstance("TLS");
	mSSLContext.init(null, trustAllCerts, null); //new java.security.SecureRandom());
	mSSLSocketFactory = mSSLContext.getSocketFactory();
	mSocket = (SSLSocket)mSSLSocketFactory.createSocket(mSocket, mHost, mPort, true);
	//((javax.net.ssl.SSLSocket)mSocket).setEnabledProtocols(new String[] { "TLSv1" });
	((javax.net.ssl.SSLSocket)mSocket).addHandshakeCompletedListener(new MyHandshakeListener());
	((javax.net.ssl.SSLSocket)mSocket).startHandshake();				
	mSocket.getOutputStream().flush();
				
	mServer.mSSLLogin = true;
	new MySQLLoginPacket40(user, passwd, mServer, mSocket.getOutputStream());
				
	mCommand = new MySQLCommand(mServer, mSocket);
	packet = new MySQLPacket(mSocket.getInputStream(), true);
}
```

habe etwas im jdbc Treiber gestöbert und bin in ExportControlled.java fündig gewurden.

Diese Zeile stammt daraus, macht aber Probleme auf Android, daher kommentiert
//((javax.net.ssl.SSLSocket)mSocket).setEnabledProtocols(new String[] { "TLSv1" });

Fehler: unknown protocol

... und trozdem funktioniert es :autsch: obwohl im jdbc als Komentar verfasst ist zur Zeile:
// need to force TLSv1, or else JSSE tries to do a SSLv2 handshake
// which MySQL doesn't understand

ob nun daran lag weiß ich nicht aber, die Zeile
mSocket.getOutputStream().flush();

nach dem handshake ist auch neu und dem jdbc Treiber entnommen.

und die letzte Änderung betrifft
MySQLLoginPacket40(user, passwd, mServer, mSocket.getOutputStream());

hier wurde die Paketnummer 1 verwendet, obwohl es Paket 2 ist, was aber auch
nur für den Fall "SSL" zutrifft, sonst ist es Paket 1.

Ergebnis:

Anmeldung am Server : ok
Selektierung einer DB : ok
Absenden einer Abfrage : ok

( laut process list des servers )

was noch fehlt sind die Resultpackets ... :rtfm:

Das clientseitige Zertifikat wird übrigens nicht verwendet wie es beim jdbc
notwendig ist. Dadurch ist allerdings eine MITM-Attack möglich !


----------

