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.
This commit is contained in:
parent
c9e8af52c2
commit
bfd2d21677
|
@ -1,5 +1,8 @@
|
||||||
package de.danoeh.antennapod.core.gpoddernet;
|
package de.danoeh.antennapod.core.gpoddernet;
|
||||||
|
|
||||||
|
import android.os.Build;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
import com.squareup.okhttp.Credentials;
|
import com.squareup.okhttp.Credentials;
|
||||||
import com.squareup.okhttp.MediaType;
|
import com.squareup.okhttp.MediaType;
|
||||||
import com.squareup.okhttp.OkHttpClient;
|
import com.squareup.okhttp.OkHttpClient;
|
||||||
|
@ -18,16 +21,27 @@ import org.json.JSONObject;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.net.CookieManager;
|
|
||||||
import java.net.CookiePolicy;
|
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
import java.net.URL;
|
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.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
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.ClientConfig;
|
||||||
import de.danoeh.antennapod.core.gpoddernet.model.GpodnetDevice;
|
import de.danoeh.antennapod.core.gpoddernet.model.GpodnetDevice;
|
||||||
|
@ -43,6 +57,8 @@ import de.danoeh.antennapod.core.service.download.AntennapodHttpClient;
|
||||||
*/
|
*/
|
||||||
public class GpodnetService {
|
public class GpodnetService {
|
||||||
|
|
||||||
|
private static final String TAG = "GpodnetService";
|
||||||
|
|
||||||
private static final String BASE_SCHEME = "https";
|
private static final String BASE_SCHEME = "https";
|
||||||
|
|
||||||
public static final String DEFAULT_BASE_HOST = "gpodder.net";
|
public static final String DEFAULT_BASE_HOST = "gpodder.net";
|
||||||
|
@ -56,9 +72,84 @@ public class GpodnetService {
|
||||||
|
|
||||||
public GpodnetService() {
|
public GpodnetService() {
|
||||||
httpClient = AntennapodHttpClient.getHttpClient();
|
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();
|
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<Principal, X509Certificate> certificates = new HashMap<Principal, X509Certificate>(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.
|
* Returns the [count] most used tags.
|
||||||
*/
|
*/
|
||||||
|
|
Loading…
Reference in New Issue