From bfd2d21677bfd822160a4400242096762e912633 Mon Sep 17 00:00:00 2001 From: Martin Fietz Date: Fri, 27 Feb 2015 14:22:37 +0100 Subject: [PATCH] Fix for AntennaPod/AntennaPod#629 On older devices, crucial intermediate certificates are missing. In this fix, we create a custom trustmanager. The algorithm follows the certificate chain and checks validity and issuer signature. In the last step, we take the root CA's certificate from the system's keystore and verify the chain's last certificate thus validating the whole chain. --- .../core/gpoddernet/GpodnetService.java | 95 ++++++++++++++++++- 1 file changed, 93 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/GpodnetService.java b/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/GpodnetService.java index 5ee40186f..791ccd5ec 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/GpodnetService.java +++ b/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/GpodnetService.java @@ -1,5 +1,8 @@ package de.danoeh.antennapod.core.gpoddernet; +import android.os.Build; +import android.util.Log; + import com.squareup.okhttp.Credentials; import com.squareup.okhttp.MediaType; import com.squareup.okhttp.OkHttpClient; @@ -18,16 +21,27 @@ import org.json.JSONObject; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; -import java.net.CookieManager; -import java.net.CookiePolicy; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; +import java.security.KeyStore; +import java.security.Principal; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Collection; +import java.util.HashMap; import java.util.LinkedList; import java.util.List; +import java.util.Map; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509TrustManager; +import javax.security.auth.x500.X500Principal; import de.danoeh.antennapod.core.ClientConfig; import de.danoeh.antennapod.core.gpoddernet.model.GpodnetDevice; @@ -43,6 +57,8 @@ import de.danoeh.antennapod.core.service.download.AntennapodHttpClient; */ public class GpodnetService { + private static final String TAG = "GpodnetService"; + private static final String BASE_SCHEME = "https"; public static final String DEFAULT_BASE_HOST = "gpodder.net"; @@ -56,9 +72,84 @@ public class GpodnetService { public GpodnetService() { httpClient = AntennapodHttpClient.getHttpClient(); + if (Build.VERSION.SDK_INT <= 10) { + Log.d(TAG, "Use custom SSL factory"); + SSLSocketFactory factory = getCustomSslSocketFactory(); + httpClient.setSslSocketFactory(factory); + } BASE_HOST = GpodnetPreferences.getHostname(); } + private synchronized static SSLSocketFactory getCustomSslSocketFactory() { + try { + TrustManagerFactory defaultTrustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + defaultTrustManagerFactory.init((KeyStore) null); // use system keystore + final X509TrustManager defaultTrustManager = (X509TrustManager) defaultTrustManagerFactory.getTrustManagers()[0]; + TrustManager[] customTrustManagers = new TrustManager[]{new X509TrustManager() { + @Override + public java.security.cert.X509Certificate[] getAcceptedIssuers() { + return null; + } + @Override + public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException { + } + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType) + throws CertificateException { + // chain may out of order - construct data structures to walk from server certificate to root certificate + Map certificates = new HashMap(chain.length - 1); + X509Certificate subject = null; + for (X509Certificate cert : chain) { + cert.checkValidity(); + if (cert.getSubjectDN().toString().startsWith("CN=" + DEFAULT_BASE_HOST)) { + subject = cert; + } else { + certificates.put(cert.getSubjectDN(), cert); + } + } + if (subject == null) { + throw new CertificateException("Chain does not contain a certificate for " + DEFAULT_BASE_HOST); + } + // follow chain to root CA + while (certificates.get(subject.getIssuerDN()) != null) { + subject.checkValidity(); + X509Certificate issuer = certificates.get(subject.getIssuerDN()); + try { + subject.verify(issuer.getPublicKey()); + } catch (Exception e) { + Log.d(TAG, "failed: " + issuer.getSubjectDN() + " -> " + subject.getSubjectDN()); + throw new CertificateException("Could not verify certificate"); + } + subject = issuer; + } + X500Principal rootAuthority = subject.getIssuerX500Principal(); + boolean accepted = false; + for (X509Certificate cert : + defaultTrustManager.getAcceptedIssuers()) { + if (cert.getSubjectX500Principal().equals(rootAuthority)) { + try { + subject.verify(cert.getPublicKey()); + accepted = true; + } catch (Exception e) { + Log.d(TAG, "failed: " + cert.getSubjectDN() + " -> " + subject.getSubjectDN()); + throw new CertificateException("Could not verify root certificate"); + } + } + } + if (accepted == false) { + throw new CertificateException("Could not verify root certificate"); + } + } + }}; + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, customTrustManagers, null); + return sslContext.getSocketFactory(); + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + /** * Returns the [count] most used tags. */