2010-10-31 16 views
41

Sto lavorando a un'app Android che richiede l'autenticazione dei certificati client e server. Ho una classe SSLClient che ho creato che funziona magnificamente sul normale desktop Java SE 6. L'ho spostato nel mio progetto Android e ho ricevuto il seguente errore: "Impossibile trovare l'implementazione di KeyStore JKS".Utilizzo dei certificati client/server per l'autenticazione SSL a due vie Presa SSL su Android

Ho guardato un po 'online e sembra che ci sia una possibilità che i Keystore Java non siano supportati su Android (fantastico!) Ma ho la sensazione che ci sia molto di più perché non c'è nessuno del codice di esempio che ho trovato somiglia a quello che sto cercando di fare. Tutto ciò che ho trovato parla dell'utilizzo di un client http piuttosto che di socket SSL grezzi. Ho bisogno di socket SSL per questa applicazione.

Di seguito è riportato il codice nel mio file SSLClient.java. Legge il keystore e il truststore, crea una connessione socket SSL al server, quindi esegue un ciclo in attesa di linee di input dal server e le gestisce man mano che arrivano chiamando un metodo in una classe diversa. Sono molto interessato a sapere da chiunque abbia esperienza con i socket SSL sulla piattaforma Android.

import java.io.BufferedReader; 
import java.io.IOException; 
import java.io.InputStream; 
import java.io.InputStreamReader; 
import java.io.OutputStreamWriter; 
import java.io.PrintWriter; 
import java.security.AccessControlException; 
import java.security.KeyManagementException; 
import java.security.KeyStore; 
import java.security.KeyStoreException; 
import java.security.NoSuchAlgorithmException; 
import java.security.UnrecoverableKeyException; 
import java.security.cert.CertificateException; 

import javax.net.ssl.KeyManagerFactory; 
import javax.net.ssl.SSLContext; 
import javax.net.ssl.SSLSocket; 
import javax.net.ssl.SSLSocketFactory; 
import javax.net.ssl.TrustManagerFactory; 
import otherpackege.OtherClass; 

import android.content.Context; 
import android.util.Log; 

public class SSLClient 
{ 
    static SSLContext ssl_ctx; 

    public SSLClient(Context context) 
    { 
     try 
     { 
      // Setup truststore 
      KeyStore trustStore = KeyStore.getInstance("BKS"); 
      TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); 
      InputStream trustStoreStream = context.getResources().openRawResource(R.raw.mysrvtruststore); 
      trustStore.load(trustStoreStream, "testtest".toCharArray()); 
      trustManagerFactory.init(trustStore); 

      // Setup keystore 
      KeyStore keyStore = KeyStore.getInstance("BKS"); 
      KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); 
      InputStream keyStoreStream = context.getResources().openRawResource(R.raw.clientkeystore); 
keyStore.load(keyStoreStream, "testtest".toCharArray()); 
      keyManagerFactory.init(keyStore, "testtest".toCharArray()); 

      Log.d("SSL", "Key " + keyStore.size()); 
      Log.d("SSL", "Trust " + trustStore.size()); 

      // Setup the SSL context to use the truststore and keystore 
      ssl_ctx = SSLContext.getInstance("TLS"); 
      ssl_ctx.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null); 

      Log.d("SSL", "keyManagerFactory " + keyManagerFactory.getKeyManagers().length); 
      Log.d("SSL", "trustManagerFactory " + trustManagerFactory.getTrustManagers().length); 
     } 
     catch (NoSuchAlgorithmException nsae) 
     { 
      Log.d("SSL", nsae.getMessage()); 
     } 
     catch (KeyStoreException kse) 
     { 
      Log.d("SSL", kse.getMessage()); 
     } 
     catch (IOException ioe) 
     { 
      Log.d("SSL", ioe.getMessage()); 
     } 
     catch (CertificateException ce) 
     { 
      Log.d("SSL", ce.getMessage()); 
     } 
     catch (KeyManagementException kme) 
     { 
      Log.d("SSL", kme.getMessage()); 
     } 
     catch(AccessControlException ace) 
     { 
      Log.d("SSL", ace.getMessage()); 
     } 
     catch(UnrecoverableKeyException uke) 
     { 
      Log.d("SSL", uke.getMessage()); 
     } 

     try 
     { 
      Handler handler = new Handler(); 
      handler.start(); 
     } 
     catch (IOException ioException) 
     { 
      ioException.printStackTrace(); 
     } 
    } 
} 

//class Handler implements Runnable 
class Handler extends Thread 
{ 
    private SSLSocket socket; 
    private BufferedReader input; 
    static public PrintWriter output; 

    private String serverUrl = "174.61.103.206"; 
    private String serverPort = "6000"; 

    Handler(SSLSocket socket) throws IOException 
    { 

    } 
    Handler() throws IOException 
    { 

    } 

    public void sendMessagameInfoge(String message) 
    { 
     Handler.output.println(message); 
    } 

    @Override 
    public void run() 
    { 
     String line; 

     try 
     { 
      SSLSocketFactory socketFactory = (SSLSocketFactory) SSLClient.ssl_ctx.getSocketFactory(); 
      socket = (SSLSocket) socketFactory.createSocket(serverUrl, Integer.parseInt(serverPort)); 
      this.input = new BufferedReader(new InputStreamReader(socket.getInputStream())); 
      Handler.output = new PrintWriter(new OutputStreamWriter(socket.getOutputStream())); 
      Log.d("SSL", "Created the socket, input, and output!!"); 

      do 
      { 
       line = input.readLine(); 
       while (line == null) 
       { 
        line = input.readLine(); 
       } 

       // Parse the message and do something with it 
       // Done in a different class 
       OtherClass.parseMessageString(line); 
      } 
      while (!line.equals("exit|")); 
     } 
     catch (IOException ioe) 
     { 
      System.out.println(ioe); 
     } 
     finally 
     { 
      try 
      { 
       input.close(); 
       output.close(); 
       socket.close(); 
      } 
      catch(IOException ioe) 
      { 
      } 
      finally 
      { 

      } 
     } 
    } 
} 

Aggiornamento:
Fare dei buoni progressi su questo problema. Scoperto che JKS non è supportato, né sta scegliendo direttamente il tipo SunX509. Ho aggiornato il mio codice sopra per riflettere questi cambiamenti. Sto ancora avendo un problema con esso apparentemente non caricare il keystore e truststore. Aggiornerò mentre trovo di più.


Update2:
Stavo facendo il mio chiavi e file truststore carico in un desktop Java modo piuttosto che il modo in cui Android corretto. I file devono essere messi nella cartella res/raw e caricati usando getResources(). Ora sto ottenendo un conteggio di 1 e 1 per il keystore e il truststore che significa che stanno caricando. Sto ancora bloccando un'eccezione, ma mi sto avvicinando! Aggiornerò quando avrò questo lavoro.


Update3:
Sembra che tutto sta funzionando ora con l'eccezione del mio chiavi in ​​fase di realizzazione in modo non corretto. Se disattivo l'autenticazione lato client sul server, si connette senza problemi. Quando lo lascio abilitato, ottengo un errore handling exception: javax.net.ssl.SSLHandshakeException: null cert chain. Quindi sembra che non stia configurando correttamente la catena di certificati. Ho postato un'altra domanda che chiede come creare un keystore del client nel formato BKS con la catena di certificati corretta: How to create a BKS (BouncyCastle) format Java Keystore that contains a client certificate chain

risposta

43

Android supporta i certificati in BKS, P12 e altri formati.

Per il formato BKS: Utilizzare portecle per convertire i certificati (.p12 e .crt) in file .bks.

avete bisogno di 2 file nella cartella /res/raw: certificato truststore.bks fiducia per il server (convertito da cer file)

client.bks/client.p12 - il certificato client (convertito da un.file P12 che contiene il certificato del client e la chiave del client)

import java.io.*; 
import java.security.KeyStore; 

import javax.net.ssl.*; 

import org.apache.http.*; 
import org.apache.http.client.methods.HttpGet; 
import org.apache.http.client.params.HttpClientParams; 
import org.apache.http.conn.ClientConnectionManager; 
import org.apache.http.conn.params.*; 
import org.apache.http.conn.scheme.*; 
import org.apache.http.conn.ssl.SSLSocketFactory; 
import org.apache.http.impl.client.DefaultHttpClient; 
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager; 
import org.apache.http.params.*; 

import android.app.Activity; 
import android.os.Bundle; 

public class SslTestActivity extends Activity { 

    /** Called when the activity is first created. */ 
    @Override 
    public void onCreate(Bundle savedInstanceState) { 
    super.onCreate(savedInstanceState); 
    setContentView(R.layout.main); 

    try { 
     // setup truststore to provide trust for the server certificate 

     // load truststore certificate 
     InputStream clientTruststoreIs = getResources().openRawResource(R.raw.truststore); 
     KeyStore trustStore = null; 
     trustStore = KeyStore.getInstance("BKS"); 
     trustStore.load(clientTruststoreIs, "MyPassword".toCharArray()); 

     System.out.println("Loaded server certificates: " + trustStore.size()); 

     // initialize trust manager factory with the read truststore 
     TrustManagerFactory trustManagerFactory = null; 
     trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); 
     trustManagerFactory.init(trustStore); 

     // setup client certificate 

     // load client certificate 
     InputStream keyStoreStream = getResources().openRawResource(R.raw.client); 
     KeyStore keyStore = null; 
     keyStore = KeyStore.getInstance("BKS"); 
     keyStore.load(keyStoreStream, "MyPassword".toCharArray()); 

     System.out.println("Loaded client certificates: " + keyStore.size()); 

     // initialize key manager factory with the read client certificate 
     KeyManagerFactory keyManagerFactory = null; 
     keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); 
     keyManagerFactory.init(keyStore, "MyPassword".toCharArray()); 


     // initialize SSLSocketFactory to use the certificates 
     SSLSocketFactory socketFactory = null; 
     socketFactory = new SSLSocketFactory(SSLSocketFactory.TLS, keyStore, "MyTestPassword2010", 
      trustStore, null, null); 

     // Set basic data 
     HttpParams params = new BasicHttpParams(); 
     HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1); 
     HttpProtocolParams.setContentCharset(params, "UTF-8"); 
     HttpProtocolParams.setUseExpectContinue(params, true); 
     HttpProtocolParams.setUserAgent(params, "Android app/1.0.0"); 

     // Make pool 
     ConnPerRoute connPerRoute = new ConnPerRouteBean(12); 
     ConnManagerParams.setMaxConnectionsPerRoute(params, connPerRoute); 
     ConnManagerParams.setMaxTotalConnections(params, 20); 

     // Set timeout 
     HttpConnectionParams.setStaleCheckingEnabled(params, false); 
     HttpConnectionParams.setConnectionTimeout(params, 20 * 1000); 
     HttpConnectionParams.setSoTimeout(params, 20 * 1000); 
     HttpConnectionParams.setSocketBufferSize(params, 8192); 

     // Some client params 
     HttpClientParams.setRedirecting(params, false); 

     // Register http/s shemas! 
     SchemeRegistry schReg = new SchemeRegistry(); 
     schReg.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80)); 
     schReg.register(new Scheme("https", socketFactory, 443)); 
     ClientConnectionManager conMgr = new ThreadSafeClientConnManager(params, schReg); 
     DefaultHttpClient sClient = new DefaultHttpClient(conMgr, params); 

     HttpGet httpGet = new HttpGet("https://server/path/service.wsdl"); 
     HttpResponse response = sClient.execute(httpGet); 
     HttpEntity httpEntity = response.getEntity(); 

     InputStream is = httpEntity.getContent(); 
     BufferedReader read = new BufferedReader(new InputStreamReader(is)); 
     String query = null; 
     while ((query = read.readLine()) != null) 
     System.out.println(query); 

    } catch (Exception e) { 
     e.printStackTrace(); 
    } 
    } 

} 

Aggiornamento:

è possibile caricare i file CRT per il negozio di fiducia direttamente senza convertirli in BKS:

private static KeyStore loadTrustStore(String[] certificateFilenames) { 
     AssetManager assetsManager = GirdersApp.getInstance().getAssets(); 

     int length = certificateFilenames.length; 
     List<Certificate> certificates = new ArrayList<Certificate>(length); 
     for (String certificateFilename : certificateFilenames) { 
      InputStream is; 
      try { 
      is = assetsManager.open(certificateFilename, AssetManager.ACCESS_BUFFER); 
      Certificate certificate = KeyStoreManager.loadX509Certificate(is); 
      certificates.add(certificate); 
      } catch (Exception e) { 
      throw new RuntimeException(e); 
      } 
     } 

     Certificate[] certificatesArray = certificates.toArray(new Certificate[certificates.size()]); 
      return new generateKeystore(certificatesArray); 
     } 

/** 
    * Generates keystore congaing the specified certificates. 
    * 
    * @param certificates certificates to add in keystore 
    * @return keystore with the specified certificates 
    * @throws KeyStoreException if keystore can not be generated. 
    */ 
    public KeyStore generateKeystore(Certificate[] certificates) throws RuntimeException { 
     // construct empty keystore 
     KeyStore keyStore = KeyStore.getInstance(keyStoreType); 

     // initialize keystore 
     keyStore.load(null, null); 

     // load certificates into keystore 
     int length = certificates.length; 
     for (int i = 0; i < length; i++) { 
     Certificate certificate = certificates[i]; 
     keyStore.setEntry(String.valueOf(i), new KeyStore.TrustedCertificateEntry(certificate), 
      null); 
     } 
     return keyStore; 
    } 

Lo stesso vale per il KeyStore con il certificato client, è possibile utilizzare direttamente il file .p12 senza convertirlo in BKS.

+0

Non funziona su Android 2.2 (funziona su 2.3) – CelinHC

+0

Sono sicuro che questo codice funzioni su Android 1.6+. È usato nella nostra vecchia app che è sul mercato Android da qualche anno. Forse ho modificato qualcosa, ma in generale dovrebbe funzionare. – peceps

+1

Non sto più lavorando a questo progetto, quindi non ho personalmente confermato questa risposta, ma lo accetto perché contiene informazioni sulla riproduzione dei dettagli, e in base ai voti sembra essere una soluzione funzionante. Grazie. –