diff --git a/android/src/av/imageview/AVImageView.java b/android/src/av/imageview/AVImageView.java index 77c2db9..08c9c98 100644 --- a/android/src/av/imageview/AVImageView.java +++ b/android/src/av/imageview/AVImageView.java @@ -53,6 +53,10 @@ import java.util.concurrent.TimeUnit; import av.imageview.utils.CookiesHelper; +import av.imageview.utils.SSLHelper; +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLSession; +import javax.net.ssl.X509TrustManager; public class AVImageView extends TiUIView { private static final String LCAT = "AVImageView"; @@ -77,6 +81,7 @@ public class AVImageView extends TiUIView { private boolean handleCookies; private boolean dontAnimate; private String signature; + private boolean validatesSecureCertificate; private RequestListener requestListener; @@ -92,6 +97,7 @@ public AVImageView(TiViewProxy proxy) { this.memoryCache = true; this.dontAnimate = false; this.handleCookies = true; + this.validatesSecureCertificate = true; this.signature = ""; this.okHttpClient = new OkHttpClient .Builder() // default timeouts are 5 seconds @@ -142,7 +148,8 @@ public AVImageView(TiViewProxy proxy) { public void processProperties(KrollDict args) { super.processProperties(args); - String[] properties = {"loadingIndicator", + String[] properties = {"validatesSecureCertificate", // Needs to be at the very begining, at least before request is launched + "loadingIndicator", "loadingIndicatorColor", "enableMemoryCache", "contentMode", @@ -192,14 +199,27 @@ public void applyPropertyChanges(String key, Object value) { } } if (key.equals("timeout")) { - this.okHttpClient = - new OkHttpClient.Builder() - .connectTimeout(TiConvert.toInt(value), TimeUnit.MILLISECONDS) - .readTimeout(TiConvert.toInt(value), TimeUnit.MILLISECONDS) - .build(); + okhttp3.OkHttpClient.Builder builder = new OkHttpClient.Builder() + .connectTimeout(TiConvert.toInt(value), TimeUnit.MILLISECONDS) + .readTimeout(TiConvert.toInt(value), TimeUnit.MILLISECONDS); + if(!this.validatesSecureCertificate) + { + Log.d(LCAT, "Not validating SSL"); + builder.sslSocketFactory(SSLHelper.trustAllSslSocketFactory, (X509TrustManager)SSLHelper.trustAllCerts[0]); + builder.hostnameVerifier(new HostnameVerifier() { + @Override + public boolean verify(String hostname, SSLSession session) { + return true; + } + }); + } + + this.okHttpClient = builder.build(); } if (key.equals("handleCookies")) this.setHandleCookies(TiConvert.toBoolean(value)); + if (key.equals("validatesSecureCertificate")) + this.setValidatesSecureCertificate(TiConvert.toBoolean(value)); } @Override @@ -403,10 +423,20 @@ public void startRequest(String url, Boolean loadingIndicator) { } public void setTimeout(int timeout) { - this.okHttpClient = new OkHttpClient.Builder() - .connectTimeout(timeout, TimeUnit.MILLISECONDS) - .readTimeout(timeout, TimeUnit.MILLISECONDS) - .build(); + okhttp3.OkHttpClient.Builder builder = new OkHttpClient.Builder() + .connectTimeout(TiConvert.toInt(timeout), TimeUnit.MILLISECONDS) + .readTimeout(TiConvert.toInt(timeout), TimeUnit.MILLISECONDS); + if(!this.validatesSecureCertificate) + { + builder.sslSocketFactory(SSLHelper.trustAllSslSocketFactory, (X509TrustManager)SSLHelper.trustAllCerts[0]); + builder.hostnameVerifier(new HostnameVerifier() { + @Override + public boolean verify(String hostname, SSLSession session) { + return true; + } + }); + } + this.okHttpClient = builder.build(); } private String sanitizeUrl(String url) { @@ -539,6 +569,57 @@ synchronized public void setHandleCookies(boolean handleCookies) { synchronized public boolean getHandleCookies() { return this.handleCookies; } + synchronized public void setValidatesSecureCertificate(boolean validatesSecureCertificate) { + if(this.validatesSecureCertificate == validatesSecureCertificate) + { + return; // Nothing to do + } + + if(this.okHttpClient != null) + { + if(validatesSecureCertificate) + { // Recreate a standard OkHttpClient with pre-set timeouts + this.okHttpClient = new OkHttpClient.Builder() + .connectTimeout(this.okHttpClient.connectTimeoutMillis(), TimeUnit.MILLISECONDS) + .readTimeout(this.okHttpClient.readTimeoutMillis(), TimeUnit.MILLISECONDS) + .build(); + } + else + { // Clone existing OkHttpClient with alltrustedcertificates + this.okHttpClient = SSLHelper.trustAllSslClient(this.okHttpClient); + } + } + else + { + if(validatesSecureCertificate) + { + this.okHttpClient = new OkHttpClient.Builder() // default timeouts are 5 seconds + .connectTimeout(5, TimeUnit.SECONDS) + .readTimeout(5, TimeUnit.SECONDS) + .build(); + } + else + { + this.okHttpClient = new OkHttpClient.Builder() // default timeouts are 5 seconds + .connectTimeout(5, TimeUnit.SECONDS) + .readTimeout(5, TimeUnit.SECONDS) + .sslSocketFactory(SSLHelper.trustAllSslSocketFactory, (X509TrustManager)SSLHelper.trustAllCerts[0]) + .hostnameVerifier(new HostnameVerifier() { + @Override + public boolean verify(String hostname, SSLSession session) { + return true; + } + }) + .build(); + } + } + this.validatesSecureCertificate = validatesSecureCertificate; + } + + synchronized public boolean getValidatesSecureCertificate() { + return this.validatesSecureCertificate; + } + // Utility to create a specific request listener private class RequestListenerBuilder { private String LCAT = "RequestListenerBuilder"; diff --git a/android/src/av/imageview/ImageViewProxy.java b/android/src/av/imageview/ImageViewProxy.java index 963659c..d728901 100644 --- a/android/src/av/imageview/ImageViewProxy.java +++ b/android/src/av/imageview/ImageViewProxy.java @@ -30,7 +30,7 @@ propertyAccessors = {"defaultImage", "brokenLinkImage", "image", "contentMode", "enableMemoryCache", "signature", "loadingIndicator", "rounded", "requestHeader", - "handleCookies", "dontAnimate"}) + "handleCookies", "dontAnimate", "validatesSecureCertificate"}) public class ImageViewProxy extends TiViewProxy { // Standard Debugging variables private static final String LCAT = "AVImageViewProxy"; @@ -344,4 +344,16 @@ public void setHandleCookies(Boolean handleCookies) { // clang-format on getView().setHandleCookies(handleCookies); } + + @Kroll.getProperty + @Kroll.method + public Boolean getValidatesSecureCertificate() { + return getView().getValidatesSecureCertificate(); + } + + @Kroll.setProperty + @Kroll.method + public void setValidatesSecureCertificate(Boolean validatesSecureCertificate) { + getView().setValidatesSecureCertificate(validatesSecureCertificate); + } } diff --git a/android/src/av/imageview/utils/SSLHelper.java b/android/src/av/imageview/utils/SSLHelper.java new file mode 100644 index 0000000..4adab1e --- /dev/null +++ b/android/src/av/imageview/utils/SSLHelper.java @@ -0,0 +1,63 @@ +package av.imageview.utils; + +import okhttp3.OkHttpClient; + +import javax.net.ssl.X509TrustManager; +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLSession; +import javax.net.ssl.TrustManager; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocketFactory; +import java.security.cert.CertificateException; +import java.security.NoSuchAlgorithmException; +import java.security.KeyManagementException; + +/** + * Helper to avoid oversizing AvImageView class + */ +public class SSLHelper +{ + private static final String LCAT = "SSLHelper"; + + public static OkHttpClient trustAllSslClient(OkHttpClient client) { + okhttp3.OkHttpClient.Builder builder = client.newBuilder(); + builder.sslSocketFactory(trustAllSslSocketFactory, (X509TrustManager)trustAllCerts[0]); + builder.hostnameVerifier(new HostnameVerifier() { + @Override + public boolean verify(String hostname, SSLSession session) { + return true; + } + }); + return builder.build(); + } + + public static final TrustManager[] trustAllCerts = new TrustManager[] { + new X509TrustManager() { + @Override + public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException { + } + + @Override + public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException { + } + + @Override + public java.security.cert.X509Certificate[] getAcceptedIssuers() { + return new java.security.cert.X509Certificate[]{}; + } + } + }; + + private static final SSLContext trustAllSslContext; + + static { + try { + trustAllSslContext = SSLContext.getInstance("SSL"); + trustAllSslContext.init(null, trustAllCerts, new java.security.SecureRandom()); + } catch (NoSuchAlgorithmException | KeyManagementException e) { + throw new RuntimeException(e); + } + } + + public static final SSLSocketFactory trustAllSslSocketFactory = trustAllSslContext.getSocketFactory(); +}