mercoledì 30 gennaio 2013

Android - Connessione HTTPS con certificati client e server

Qualche giorno fa ho fatto un applicazione in cui dovevo invocare un web service in https. Scenario:
  • Il certificato del server (pub_server.crt) è self signed (firmato) da una CA personale (personal_ca.crt)
  • Il client si autentica utilizzando il proprio certificato p12 (client.p12) e in più con il proprio user e password.
Non mi metto a spiegare come si creano i certificati perché non è questo lo scopo dell'articolo.
Vi spiego i passi che ho fatto:
  1. Siccome Android utilizza il provider JCE di Bouncy Castle ho scaricato il jar bcprov-jdk16-146.jar e l'ho salvato sotto la directory lib del progetto ($workspace/mioProgetto/libs/).
  2. Importo personal_ca.crt e pub_server.crt in un unico file chiamato server_cert di tipo BKS nel miProgetto come risorsa raw ($workspace/mioProgetto/res/raw/) : 
    
    #importo il CA personale
    keytool -import -alias personalCa -file [percorso]\personal_ca.crt -keypass provapass -keystore $workspace\mioProgetto\res\raw\server_cert -storetype BKS -storepass provapass -providerClass org.bouncycastle2.jce.provider.BouncyCastleProvider -providerpath $workspace\mioProgetto\libs\bcprov-jdk16-146.jar
    #importo il certificato server
    keytool -importcert -v -trustcacerts -file [percorso]\pub_server.crt -alias serverCert -keystore $workspace\mioProgetto\res\raw\server_cert -providerClass org.bouncycastle2.jce.provider.BouncyCastleProvider -providerpath $workspace\mioProgetto\libs\bcprov-jdk16-146.jar -storetype BKS -storepass provapass
    
    
    Basta fare un refresh sulla directory res/raw per vedere il certificato importato.
  3. Salvo anche il certificato client client.p12 sotto $workspace/mioProgetto/res/raw/ 
  4.  Ora utilizzo questi certificati per fare l'autenticazione.
    
    try {
       
     SSLContext sslContext = SSLContext.getInstance("TLS");
     
     // truststore
     KeyStore trustStore = KeyStore.getInstance("BKS");
     TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
     InputStream trustStoreStream = this.getResources().openRawResource(R.raw.server_cert);
     // passo la password utilizzata quando ho importato il certificato
     trustStore.load(trustStoreStream, "provapass".toCharArray());
     trustManagerFactory.init(trustStore);
     // keystore
     KeyStore keyStore = KeyStore.getInstance("pkcs12");
     KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
     InputStream keyStoreStream = this.getResources().openRawResource(R.raw.client);
     // passo la password del certificato client
     keyStore.load(keyStoreStream, "clientpass".toCharArray());
     keyManagerFactory.init(keyStore, "clientpass".toCharArray());
    
     sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null);   
     
     URL url = new URL("https://webserviceUrl");
     
     // utilizzo HttpsURLConnection per fare connessioni https
     HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
     
     // username e password
     String usernamePass = "userName:password";
     String basicAuth = new String(Base64.encode(usernamePass.getBytes()));
     
     // basic authentication
     conn.setRequestProperty("Authorization", "Basic " + basicAuth);
     conn.setRequestProperty("User-Agent", "Android Application");
      
     // non la faccio la verifica del hostname
     conn.setHostnameVerifier(new SkipVerifier());
      
     conn.setSSLSocketFactory(sslContext.getSocketFactory());
     System.out.println("Response code is " + conn.getResponseCode());
       
    } catch (Exception e) {
     largeTxt.setText(e.getMessage());
     e.printStackTrace();
    }
    
    
  5. Creo la classe SkipVerifier che implementa HostnameVerifier, che non fa altro che ritorna true per ogni hostname.
    
    public class SkipVerifier implements HostnameVerifier {
        
        @Override
        public final boolean verify(final String hostname, final SSLSession sslSession) {
      
           return true;
           
        }
    
    }
    
    
Provate e dovreste ottenere la connessione SSL con il web service.