HTTPS on Volley - Android

Da qualche settimana, grazie al mio "nuovo" Mac, torno a scrivere un pò di codice in notturna. Cambio ancora tema, spostandomi come si evince dal titolo su Android.

Finalmente ci sono arrivato anche io e, dopo tanti anni di temporeggiamenti, mi sono deciso a studiare e realizzare qualcosa su piattaforma mobile.

Volley

Durante le mie letture su Android Studio, mi sono imbattuto su una libreria evoluta per eseguire chiamate REST.
Volley, presentato per la prima volta a Google I/O 2013, permette di eseguire richieste a servizi, mettendo a disposizione una notevole quantità di funzionalità.

Di seguito riporto una singola chiamata per introdurre il suo utilizzo:

RequestQueue queue = Volley.newRequestQueue(this);
String url ="http://www.google.com";

// Request a string response from the provided URL.
StringRequest stringRequest = new StringRequest(Request.Method.GET, url,
new Response.Listener<String>() {
@Override
public void onResponse(String response) {
// Display the first 500 characters of the response string.
    String text = "Response is: "+ response.substring(0,500);
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
mTextView.setText("That didn't work!");
}
});
// Add the request to the RequestQueue.
queue.add(stringRequest);

L'esempio, riporta una chiamata a google.com tramite il protocollo HTTP.
Le cose si complicano un pò se il protocollo è quello "sicuro" (HTTPS).

In FoootballGuru infatti (si, ancora lui), tutte le API sono state esposte tramite SSL al mondo esterno.

Girovagando, scavando negli esempi della libreria e su vari blog, sono riuscito a trovare le informazioni necessarie per eseguire chiamate HTTPS.

Il certificato

La prima cosa di cui abbiamo bisogno, è il certificato fornito dal servizio SSL, il famoso .cer
Android però usa un keystore in formato BKS (Bouncy Castle).

Sarà necessario quindi eseguire la conversione, scaricando il jdk del provider.

Copiamo il certificato .cer e il jdk di bouncy castle, in una directory e lanciamo il comando seguente

keytool -importcert -v -trustcacerts -file "my_server_cert.crt" -alias imeto_alias -keystore "my.bks" -provider org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath "bcprov-jdk16-146.jar" -storetype BKS

Ci verrà chiesta una password, che dovremmo ricordarci per l'utilizzo del keystore.

Android project

Importiamo il keystore my.bks, nella directory res/row.

Creiamo il provider importando la seguente classe:

public class VolleyProvider{
    private static VolleyProvider instance;
    private RequestQueue queue;
    private HurlStack hurlStack;

    private VolleyProvider(Context ctx){
        try {
            InputStream instream = ctx.getApplicationContext().getResources().openRawResource(R.raw.my);
            KeyStore trustStore = KeyStore.getInstance("BKS");
            try {
                trustStore.load(instream, "mypassword".toCharArray());

            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                try {instream.close();} catch (Exception ignore) {}
            }

            String algorithm = TrustManagerFactory.getDefaultAlgorithm();
            TrustManagerFactory tmf = TrustManagerFactory
                    .getInstance(algorithm);
            tmf.init(trustStore);
            SSLContext context = SSLContext.getInstance("TLS");
            context.init(null, tmf.getTrustManagers(), null);

            SSLSocketFactory sslFactory = context.getSocketFactory();
            hurlStack = new HurlStack(null, sslFactory);
            queue = Volley.newRequestQueue(ctx, hurlStack);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static VolleyProvider getInstance(Context ctx){
        if(instance == null){
            instance = new VolleyProvider(ctx);
        }
        return instance;
    }

    public RequestQueue getQueue(){
        return this.queue;
    }

    public <T> Request<T> addRequest(Request<T> req) {
        return getQueue().add(req);
    }

    public <T> Request<T> addRequest(Request<T> req, String tag) {
        req.setTag(tag);
        return getQueue().add(req);
    }

}

La nostra classe provider è pronta, possiamo eseguire un test di chiamata direttamente sulla nostra applicazione.

String _url = "https://sume.url.com";

JsonArrayRequest jsObjRequest = new JsonArrayRequest
                (Request.Method.GET, _url, null, new Response.Listener<JSONArray>() {

                    @Override
                    public void onResponse(JSONArray response) {
                        try {
                            String name = ((JSONObject)response.get(0)).get("name").toString();
                        }
                        catch (JSONException ex){

                        }
                    }
                }, new Response.ErrorListener() {

                    @Override
                    public void onErrorResponse(VolleyError error) {
                        // TODO Auto-generated method stub

                    }
                });

        VolleyProvider.getInstance(this).addRequest(jsObjRequest);