Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a way to enable TLS 1.2 in Picasso library on older devices?

My app is using images from HTTPS source, Solar Dynamics Observatory, and these images are loading just fine on Android versions with API 21 (5.0) (I think that API 20 would work as well) or higher, but I can't load these images on Android versions from API 16 (min. API that I set for my app) to API 19. Here's the code that's loading the images, it's pretty basic -

Picasso.with(this)
                .load("https://sdo.gsfc.nasa.gov/assets/img/latest/latest_2048_HMIIC.jpg")
                .placeholder(R.drawable.placeholdernew)
                .error(R.drawable.errornew)
                .into(mImageView, new com.squareup.picasso.Callback() {
                    @Override
                    public void onSuccess() {
                        mAttacher = new PhotoViewAttacher(mImageView); //this adds the zoom function after the picture is loaded successfully, so the user couldn't zoom the placeholder or error picture :)
                    }

                    @Override
                    public void onError() {
                        Log.d("Pic Error", "Loading Error");
                    }
                });

As you can see, there are no errors in my code (I think :P). I checked the SDO site with SSLLabs site, and this site said that this SDO server is not accepting TLS handshake on Android devices older than API 20. Is there any way to enable TLS 1.2 in Picasso on older Android versions? Help would be appreciated! Thanks in advance :)

like image 996
Hamstersztyk Avatar asked Feb 10 '17 18:02

Hamstersztyk


1 Answers

Well, this works, but it's ugly.

In your project's dependencies, add:

compile 'com.squareup.picasso:picasso:2.5.2'
compile 'com.squareup.okhttp3:okhttp:3.6.0'
compile 'com.jakewharton.picasso:picasso2-okhttp3-downloader:1.0.2'

(you may already have the picasso line — I am just making sure that you are on the latest version)

Next, add this class to your project (based on this answer):

  public static class TLSSocketFactory extends SSLSocketFactory {

    private SSLSocketFactory internalSSLSocketFactory;

    public TLSSocketFactory(SSLSocketFactory delegate) throws
      KeyManagementException, NoSuchAlgorithmException {
      internalSSLSocketFactory = delegate;
    }

    @Override
    public String[] getDefaultCipherSuites() {
      return internalSSLSocketFactory.getDefaultCipherSuites();
    }

    @Override
    public String[] getSupportedCipherSuites() {
      return internalSSLSocketFactory.getSupportedCipherSuites();
    }

    @Override
    public Socket createSocket(Socket s, String host, int port, boolean autoClose)
      throws IOException {
      return enableTLSOnSocket(internalSSLSocketFactory.createSocket(s, host, port, autoClose));
    }

    @Override
    public Socket createSocket(String host, int port) throws IOException {
      return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port));
    }

    @Override
    public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException {
      return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port, localHost, localPort));
    }

    @Override
    public Socket createSocket(InetAddress host, int port) throws IOException {
      return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port));
    }

    @Override
    public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
      return enableTLSOnSocket(internalSSLSocketFactory.createSocket(address, port, localAddress, localPort));
    }

    /*
     * Utility methods
     */

    private static Socket enableTLSOnSocket(Socket socket) {
      if (socket != null && (socket instanceof SSLSocket)
        && isTLSServerEnabled((SSLSocket) socket)) { // skip the fix if server doesn't provide there TLS version
        ((SSLSocket) socket).setEnabledProtocols(new String[]{"TLSv1.1", "TLSv1.2"});
      }
      return socket;
    }

    private static boolean isTLSServerEnabled(SSLSocket sslSocket) {
      System.out.println("__prova__ :: " + sslSocket.getSupportedProtocols().toString());
      for (String protocol : sslSocket.getSupportedProtocols()) {
        if (protocol.equals("TLSv1.1") || protocol.equals("TLSv1.2")) {
          return true;
        }
      }
      return false;
    }
  }

(that class is public static, and so is designed to be a nested class inside something else — just get rid of the static if you want it to be a standalone class)

Then, in your class that is using Picasso, add this method, based on this issue comment:

  public X509TrustManager provideX509TrustManager() {
    try {
      TrustManagerFactory factory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
      factory.init((KeyStore) null);
      TrustManager[] trustManagers = factory.getTrustManagers();
      return (X509TrustManager) trustManagers[0];
    }
    catch (NoSuchAlgorithmException exception) {
      Log.e(getClass().getSimpleName(), "not trust manager available", exception);
    }
    catch (KeyStoreException exception) {
      Log.e(getClass().getSimpleName(), "not trust manager available", exception);
    }

    return null;
  }

Finally, this code should successfully download your image:

    SSLContext sslContext=SSLContext.getInstance("TLS");
    sslContext.init(null, null, null);
    SSLSocketFactory noSSLv3Factory;

    if (Build.VERSION.SDK_INT<=Build.VERSION_CODES.KITKAT) {
      noSSLv3Factory=new TLSSocketFactory(sslContext.getSocketFactory());
    }
    else {
      noSSLv3Factory=sslContext.getSocketFactory();
    }

    OkHttpClient.Builder okb=new OkHttpClient.Builder()
      .sslSocketFactory(noSSLv3Factory, provideX509TrustManager());
    OkHttpClient ok=okb.build();

    Picasso p=new Picasso.Builder(getActivity())
      .downloader(new OkHttp3Downloader(ok))
      .build();

    p.load(
      "https://sdo.gsfc.nasa.gov/assets/img/latest/latest_2048_HMIIC.jpg")
      .fit().centerCrop()
      .placeholder(R.drawable.owner_placeholder)
      .error(R.drawable.owner_error).into(icon);

(where you would replace my fit() and subsequent calls with the right ones for your project)

If you happen to know the people maintaining that NASA server... they really ought to upgrade their SSL support. Just sayin'.

like image 73
CommonsWare Avatar answered Sep 18 '22 02:09

CommonsWare