2010-10-31 16 views
41

Estoy trabajando en una aplicación de Android que requiere autenticación de certificado de cliente y servidor. Tengo una clase SSLClient que creé que funciona muy bien en el escritorio regular de Java SE 6. Lo he movido a mi proyecto de Android y me aparece el siguiente error: "Implementación de KeyStore JKS no encontrada".Uso de certificados de cliente/servidor para autenticación bidireccional Socket SSL en Android

He buscado en línea un poco y parece que existe la posibilidad de que Java Keystores no sean compatibles con Android (¡increíble!) Pero tengo la sensación de que hay más que eso porque ninguno de los códigos de muestra que he encontrado se asemeja a lo que estoy tratando de hacer en absoluto. Todo lo que encontré habla sobre el uso de un cliente http en lugar de sockets SSL sin formato. Necesito conectores SSL para esta aplicación.

A continuación se muestra el código en mi archivo SSLClient.java. Lee el almacén de claves y el almacén de confianza, crea una conexión de socket SSL con el servidor, luego ejecuta un ciclo mientras espera las líneas de entrada del servidor y luego las maneja cuando ingresan llamando a un método en una clase diferente. Estoy muy interesado en saber de alguien con experiencia en SSL en la plataforma 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 
      { 

      } 
     } 
    } 
} 

Actualización:
realizó algunos avances en este problema. Descubrí que JKS de hecho no es compatible, tampoco está eligiendo directamente el tipo SunX509. He actualizado mi código anterior para reflejar estos cambios. Todavía estoy teniendo un problema aparentemente sin cargar el almacén de claves y el almacén de confianza. Actualizaré mientras descubro más.


Update2:
estaba haciendo mi almacén de claves y la carga de archivos en un almacén de confianza de Java Desktop manera en lugar de la forma correcta Android. Los archivos deben colocarse en la carpeta res/raw y cargarse usando getResources(). Ahora recibo un recuento de 1 y 1 para el almacén de claves y el tamaño del almacén de confianza, lo que significa que se están cargando. Todavía estoy chocando con una excepción, ¡pero cada vez más cerca! Lo actualizaré cuando lo haga funcionar.


Update3:
Parece que todo está funcionando ahora con la excepción de mi almacén de claves está configurado incorrectamente. Si deshabilito la autenticación del lado del cliente en el servidor, se conecta sin problemas. Cuando lo dejo habilitado, recibo un error handling exception: javax.net.ssl.SSLHandshakeException: null cert chain. Entonces parece que no estoy configurando la cadena de certificados correctamente. He publicado otra pregunta preguntando cómo crear un almacén de claves del cliente en el formato BKS con la cadena de certificados adecuada: How to create a BKS (BouncyCastle) format Java Keystore that contains a client certificate chain

Respuesta

43

Android admite certificados en el BKS, P12 y otros formatos.

Para el formato BKS: Utilice portecle para convertir sus certificados (.p12 y .crt) a .bks.

Se necesitan 2 archivos de la carpeta /res/raw: certificado truststore.bks confianza para el servidor (convertido de archivo .cer)

client.bks/client.p12 - el certificado de cliente (convertido de una.p12 archivo que contiene el certificado de cliente y la clave de cliente)

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(); 
    } 
    } 

} 

Actualización:

También puede cargar archivos .crt para el almacén de confianza directamente sin convertirlos a 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; 
    } 

Igual va para KeyStore con el certificado del cliente, puede usar el archivo .p12 directamente sin convertirlo a BKS.

+0

No funciona en Android 2.2 (funciona en 2.3) – CelinHC

+0

Estoy seguro de que este código funciona en Android 1.6+. Se usa en nuestra aplicación anterior, que está en el mercado de Android desde hace algunos años. Tal vez he editado algo pero, en general, debería funcionar. – peceps

+1

Ya no trabajo en este proyecto, por lo que no he confirmado personalmente esta respuesta, pero la acepto porque contiene información de reproducción detallada, y según los votos, parece ser una solución funcional. Gracias. –

Cuestiones relacionadas