2014-04-16 12 views
6

Sto tentando di impostare un SSL reciprocamente autenticato tra un server Linux e un'app Android. Finora, sono riuscito a far funzionare l'app con il certificato del server per comunicare tramite SSL, ma una volta impostato il server in modo che accetti solo i certificati client smette di funzionare. La configurazione del server sembra ok, ma sono una specie di bloccato. La mia ipotesi migliore è che il certificato client non venga presentato correttamente al server ma non ha idea di come testarlo successivamente. Ho provato ad usare il .pem per il client nel mio portachiavi OS X ma i browser non sembrano funzionare con quel certificato. Poi di nuovo il certificato del server funziona perfettamente perché posso ottenere una connessione https e l'APP accetta il mio certificato del server non firmato.Autenticazione reciproca SSL FAIL su client Android accetta certificati server ma il server non ottiene il certificato cliente

Il codice sono stato io sto usando è la combinazione di vari tutorial, risponde a questa è il principale quelli che ho segnalibri:

Questo sono le due classi principali sto usando per la connessione: 1) Questa classe gestisce il parsing JSON e fa le RICHIESTE

package edu.hci.additional; 

import java.io.BufferedReader; 
import java.io.IOException; 
import java.io.InputStream; 
import java.io.InputStreamReader; 
import java.io.UnsupportedEncodingException; 
import java.util.List; 

import android.content.Context; 
import org.apache.http.HttpEntity; 
import org.apache.http.HttpResponse; 
import org.apache.http.NameValuePair; 
import org.apache.http.client.ClientProtocolException; 
import org.apache.http.client.entity.UrlEncodedFormEntity; 
import org.apache.http.client.methods.HttpGet; 
import org.apache.http.client.methods.HttpPost; 
import org.apache.http.client.utils.URLEncodedUtils; 
import org.json.JSONException; 
import org.json.JSONObject; 

import android.util.Log; 

public class JSONParser { 

    static InputStream is = null; 
    static JSONObject jObj = null; 
    static String json = ""; 

    // constructor 
    public JSONParser() { 

    } 

    // function get json from url 
    // by making HTTP POST or GET mehtod 
    public JSONObject makeHttpRequest(String url, String method, 
      List<NameValuePair> params, Context context) { 

     // Making HTTP request 
     try { 

      // check for request method 
      if(method == "POST"){ 
       // request method is POST 
       // defaultHttpClient 
       SecureHttpClient httpClient = new SecureHttpClient(context); 
       HttpPost httpPost = new HttpPost(url); 
       httpPost.setEntity(new UrlEncodedFormEntity(params)); 

       HttpResponse httpResponse = httpClient.execute(httpPost); 
       HttpEntity httpEntity = httpResponse.getEntity(); 
       is = httpEntity.getContent(); 

      }else if(method == "GET"){ 
       // request method is GET 
       SecureHttpClient httpClient = new SecureHttpClient(context); 
       String paramString = URLEncodedUtils.format(params, "utf-8"); 
       url += "?" + paramString; 
       HttpGet httpGet = new HttpGet(url); 

       HttpResponse httpResponse = httpClient.execute(httpGet); 
       HttpEntity httpEntity = httpResponse.getEntity(); 
       is = httpEntity.getContent(); 
      }   


     } catch (UnsupportedEncodingException e) { 
      e.printStackTrace(); 
     } catch (ClientProtocolException e) { 
      e.printStackTrace(); 
     } catch (IOException e) { 
      e.printStackTrace(); 
     } 

     try { 
      BufferedReader reader = new BufferedReader(new InputStreamReader(
        is, "iso-8859-1"), 8); 
      StringBuilder sb = new StringBuilder(); 
      String line = null; 
      while ((line = reader.readLine()) != null) { 
       sb.append(line + "\n"); 
      } 
      is.close(); 
      json = sb.toString(); 
     } catch (Exception e) { 
      Log.e("Buffer Error", "Error converting result " + e.toString()); 
     } 

     // try parse the string to a JSON object 
     try { 
      jObj = new JSONObject(json); 
     } catch (JSONException e) { 
      Log.e("JSON Parser", "Error parsing data " + e.toString()); 
     } 

     // return JSON String 
     return jObj; 

    } 
} 

Questa seconda classe gestisce l'autenticazione SSL:

package edu.hci.additional; 

import android.content.Context; 
import android.util.Log; 
import edu.hci.R; 
import org.apache.http.conn.scheme.PlainSocketFactory; 
import org.apache.http.conn.scheme.Scheme; 
import org.apache.http.conn.scheme.SchemeRegistry; 
import org.apache.http.conn.ssl.SSLSocketFactory; 
import org.apache.http.impl.client.DefaultHttpClient; 
import org.apache.http.params.HttpParams; 

import java.io.IOException; 
import java.io.InputStream; 
import java.security.*; 


public class SecureHttpClient extends DefaultHttpClient { 

    private static Context appContext = null; 
    private static HttpParams params = null; 
    private static SchemeRegistry schmReg = null; 
    private static Scheme httpsScheme = null; 
    private static Scheme httpScheme = null; 
    private static String TAG = "MyHttpClient"; 

    public SecureHttpClient(Context myContext) { 

     appContext = myContext; 

     if (httpScheme == null || httpsScheme == null) { 
      httpScheme = new Scheme("http", PlainSocketFactory.getSocketFactory(), 80); 
      httpsScheme = new Scheme("https", mySSLSocketFactory(), 443); 
     } 

     getConnectionManager().getSchemeRegistry().register(httpScheme); 
     getConnectionManager().getSchemeRegistry().register(httpsScheme); 

    } 

    private SSLSocketFactory mySSLSocketFactory() { 
     SSLSocketFactory ret = null; 
     try { 

      final KeyStore clientCert = KeyStore.getInstance("BKS"); 
      final KeyStore serverCert = KeyStore.getInstance("BKS"); 

      final InputStream client_inputStream = appContext.getResources().openRawResource(R.raw.authclientcerts); 
      final InputStream server_inputStream = appContext.getResources().openRawResource(R.raw.certs); 

      clientCert.load(client_inputStream, appContext.getString(R.string.client_store_pass).toCharArray()); 
      serverCert.load(server_inputStream, appContext.getString(R.string.server_store_pass).toCharArray()); 

      String client_password = appContext.getString(R.string.client_store_pass); 

      server_inputStream.close(); 
      client_inputStream.close(); 

      ret = new SSLSocketFactory(clientCert,client_password,serverCert); 
     } catch (UnrecoverableKeyException ex) { 
      Log.d(TAG, ex.getMessage()); 
     } catch (KeyStoreException ex) { 
      Log.d(TAG, ex.getMessage()); 
     } catch (KeyManagementException ex) { 
      Log.d(TAG, ex.getMessage()); 
     } catch (NoSuchAlgorithmException ex) { 
      Log.d(TAG, ex.getMessage()); 
     } catch (IOException ex) { 
      Log.d(TAG, ex.getMessage()); 
     } catch (Exception ex) { 
      Log.d(TAG, ex.getMessage()); 
     } finally { 
      return ret; 
     } 
    } 

} 

Per creare le chiavi che ho usato OpenSSL con questo comando:

openssl req -nodes -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 500 

per ottenere le chiavi per BKS per Android ho usato il castello gonfiabile bcprov-jdk15on-150.jar all'indirizzo: http://www.bouncycastle.org/latest_releases.html

ed utilizzato il comando:

keytool -import -v -trustcacerts -alias 0 -file ~/cert.pem -keystore ~/Downloads/authclientcerts.bks -storetype BKS -provider org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath ~/Downloads/bcprov-jdk15on-150.jar -storepass passWORD 

Infine le linee che ho aggiunto al /etc/httpd/conf.d/ssl.conf richiedere il certificato client e verificare la validità del certificato (che corrispondono al certificato client ho creato) in Fedora 19 sono:

... 
SSLVerifyClient require 
SSLVerifyDepth 5 
... 
<Location /> 
SSLRequire ( %{SSL_CLIENT_S_DN_O} eq "Develop" \ 
      and %{SSL_CLIENT_S_DN_OU} in {"Staff", "Operations", "Dev"}) 
</Location> 
... 
SSLOptions +FakeBasicAuth +StrictRequire 

ho provato un sacco di combinazioni su questo file di configurazione e tutto finì nello stesso risultato mi twrowing un "SSLPeerUnverifiedException: Nessun certificato pari" Exception. Io commento queste righe sul file di configurazione SSL del server e tutto funziona alla grande ma il server accetta tutti i client che non è quello che mi serve.

Grazie in anticipo :)

UPDATE

risposta @EJP s' ha fatto il trucco

Per prima cosa ho dovuto aggiungere il certificato al corretto (/ etc/pki/tls/certs /) percorso e caricarlo utilizzando: rinominare il cert: mv ca-andr.pem ca-andr.crt E ora caricare il certificato:

ln -s ca-andr.crt $(openssl x509 -hash -noout -in ca-andr.crt)".0" 

questo creerà un link simbolico leggibile openSSL con un nome simile a “f3f24175.0”

Poi ho impostato il nuovo file certificato nel file/etc/httpd/conf.d/ssl.conf File di configurazione.

… 
SSLCACertificateFile /etc/pki/tls/certs/f2f62175.0 
… 

Ora riavviare il servizio HTTP e test se il certificato è caricato con:

openssl verify -CApath /etc/pki/tls/certs/ f2f62175.0 

Se tutto è ok si dovrebbe vedere:

f3f24175.0: OK

E puoi terminare il test con:

openssl s_client -connect example.com:443 -CApath /etc/pki/tls/certs 

Questo dovrebbe restituire l'elenco dei certificati client di fiducia (Se vedete quello aggiunto, sta lavorando)

Ora la seconda parte del problema è che i miei authclientcerts.BKS din't contengono la chiave privata, in modo da la password che ho fornito non è mai stata utilizzata e il server non autentica il certificato. Così ho esportato la mia chiave e cert in pkcs12 e aggiornato di conseguenza il codice JAVA.

comando Export:

openssl pkcs12 -export -in ~/cert.pem -inkey ~/key.pem > android_client_p12.p12 

Poi l'ho cambiato le parti della classe SecureHttpClient.java per rendere il certificato client con PKCS12 invece di BKS.

Per cambiare il tipo di archivio chiavi da BKS a PKCS12 ho sostituito:

final KeyStore clientCert = KeyStore.getInstance("BKS”); 

Per questo:

final KeyStore clientCert = KeyStore.getInstance("PKCS12"); 

E poi ho aggiornato dei riferimenti ai file dell'archivio chiavi effettivi situato sulla res/raw/ sostituendo:

final InputStream client_inputStream = appContext.getResources().openRawResource(R.raw.authclientcerts); 

Per questo:

final InputStream client_inputStream = appContext.getResources().openRawResource(R.raw.android_client_p12); 

E che ha fatto il trucco: D

risposta

3

Quando il server richiede il certificato client, fornisce un elenco di CA che accetterà certificati firmati da. Se il client non ha un certificato firmato da uno di questi, non invierà un certificato in risposta. Se il server è configurato per richiedere un certificato client, invece di volerne solo uno, chiuderà la connessione.

Pertanto, assicurarsi che il client disponga di un certificato accettabile per il truststore del server.

+1

Grazie a questa risposta ho chiarito le cose per me. Sono riuscito a connettere l'App Android con il server utilizzando l'autenticazione reciproca. Grazie mille per aver dedicato del tempo a rispondere. Aggiungo tra qualche minuto un aggiornamento con la soluzione completa e le cose che ho dovuto correggere aggiungere. – jmarrero

Problemi correlati