SSL-Client-Authentifizierung mit Zertifikat in Android
Ich schreibe eine Android-App, die eine Verbindung zu einem Server herstellt, um einige Webservices aufzurufen. Dieser Server verwendet ein selbstsigniertes Zertifikat für SSL und erfordert ein Client-Zertifikat für die Authentifizierung.
Wenn ich mit dem Android Chrome-Browser oder mit dem Safari-Browser von einem iPhone aus eine Verbindung zum Server herstelle, funktioniert dies einwandfrei. Die SSL-Verbindung wird hergestellt und die Client-Zertifikat-Authentifizierung wird erfolgreich durchgeführt und an meine Webservices umgeleitet. Aber wenn ich mich mit anderen Browsern verbinde, wie dem Standard-Android-Browser, Delphin usw., führt dies zu einem Netzwerk-Timeout.
Auch wenn ich mich mit meiner Android App verbinde, erhalte ich folgende Fehlermeldung:
javax.net.ssl.SSLException: Read error: ssl=0x12746b8: I/O error during system call, Connection reset by peer
at org.apache.harmony.xnet.provider.jsse.NativeCrypto.SSL_read(Native Method)
at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl$SSLInputStream.read(OpenSSLSocketImpl.java:671)
at libcore.io.Streams.readSingleByte(Streams.java:41)
at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl$SSLInputStream.read(OpenSSLSocketImpl.java:655)
at libcore.io.Streams.readAsciiLine(Streams.java:201)
at libcore.net.http.HttpEngine.readResponseHeaders(HttpEngine.java:544)
Ich bin dem Blog von chariotsolutions gefolgt:http://chariotsolutions.com/blog/post/https-with-client-certificates-on/
Ich habe auch das Client-Zertifikat in das BKS-Format mit portecle GUI konvertiert.
Hier ist mein Code:
private static final String USER_AGENT = "Mozilla/5.0 (Linux; U; Android 4.0.3; et-ee; GT-P5100 Build/IML74K) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Safari/534.30";
....
@Override
protected Object doInBackground(String... urlString) {
....
URL url = new URL(urlString[0]);
HttpsURLConnection urlConnection =(HttpsURLConnection)url.openConnection();
urlConnection.setSSLSocketFactory(getSSLContext().getSocketFactory());
urlConnection.addRequestProperty("User-Agent", USER_AGENT);
urlConnection.setDoInput(true);
urlConnection.connect();
....
}
private SSLContext getSSLContext() throws CertificateException,IOException, KeyStoreException, NoSuchAlgorithmException,KeyManagementException, java.security.cert.CertificateException {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
InputStream caRootInput = context.getAssets().open("MySSLCertificate_here.crt");
InputStream caClientAuth = context.getAssets().open("MyCientCertificate_here");
....
Certificate caRoot;
caRoot = cf.generateCertificate(caRootInput);
// KeyStore and KeyManager for Client Certificate
KeyStore keystore_client = KeyStore.getInstance("BKS");
keystore_client.load(caClientAuth, "My_passWord_here".toCharArray());
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(keystore_client, "My_passWord_here".toCharArray());
KeyManager[] keyManagers = kmf.getKeyManagers();
// KeyStore and Trustmanager for SSL Certificate
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null, null);
keyStore.setCertificateEntry("caRoot", caRoot);
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(keyStore);
SSLContext context = SSLContext.getInstance("TLSv1");
context.init(keyManagers, tmf.getTrustManagers(), null);
return context;
....
}
Beachten: Ich habe auch die Möglichkeit, zu Testzwecken eine Verbindung zum Server herzustellen, ohne dass eine Clientzertifikatauthentifizierung erforderlich ist (was zu einer anderen URL führt). Der obige Code ist in diesem Fall der gleiche, jedoch nur mitcontext.init (null, tmf.getTrustManagers (), null). Dies funktioniert einwandfrei, baut eine SSL-Verbindung auf und leitet mich zu meinen Webservices weiter.
Was mich wirklich stört ist, dass alles mit Chrome und Safari auf dem Iphone funktioniert. Ich vermute also, das Problem liegt möglicherweise nur in meinem Android-Code.
Hat jemand ein ähnliches Problem und hat eine Idee oder vielleicht eine Lösung für sein?
Vielen Dank!
BEARBEITEN:
Ich habe vergessen zu erwähnen, dass ich auch einen Cookie-Manager eingerichtet habe, der überhaupt für die SSL-Verbindung benötigt wurde. (In der MainActivity in onCreate () einrichten)
// Required for SSL connection
CookieHandler.setDefault(new CookieManager());
Ohne das habe ich den gleichen Fehler beim Versuch, eine SSL-Verbindung (ohne Client-Zertifikat) herzustellen, erhalten, aber leider hilft es auch nicht mit Client-Zertifikat.
UPDATE # 2
Mit vielen Hintergrundinformationen von nelenkov.blogspot.de/2011/12/using-custom-certificate-trust-store-on.html (Danke für diesen Artikel @ Nikolay) und seiner Android Connectiontest-Anwendung auf Github habe ich einige interessante Dinge gefunden.
Der Server ist so eingerichtet, dass ich beim Verbindungsaufbau ein Cookie erhalte. Das für die Verbindung "funktioniert", wenn ich in einem vorherigen Schritt eine Verbindung ohne Clientauthentifizierung herstelle, sodass er bei der zweiten Verbindung (mit Clientauthentifizierung) nur dieses Cookie überprüft und mich passieren lässt.
Zweitens ist mir endlich aufgefallen, dass der Server mir einen gibtNonce. Ich denke, die Verbindung sollte dieser Umleitung automatisch folgen, indem sie dieses Nonce zurückgibt. Einige Browser, wie Firefox und der Standardbrowser, beenden die Verbindung (nachdem sie mit Responsecode 302 umgeleitet wurden) und beenden schließlich mit 404, was zu einer URL wie ..policy? Nonce = fno3i9Hsfn3uz führt. Dann muss ich diese URL manuell neu laden, um zu den Webservices zu gelangen. Leider funktioniert dies nicht mit meiner Android-Anwendung.
Ich vermute, dieses Problem könnte dann eine Fehlkonfiguration auf der Serverseite sein?