Uso de certificados de cliente / servidor para autenticación bidireccional Socket SSL en Android

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”.

Miré en línea un poco y parece que existe la posibilidad de que Java Keystores no sea compatible 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 parece a lo que Estoy tratando de hacerlo 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 { } } } } 

Actualizar:
Haciendo un buen progreso 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.


Actualización2:
Estaba haciendo mi almacén de claves y la carga de archivos de truststore en una computadora de escritorio de Java en lugar de la forma correcta de 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.


Actualización3:
Parece que todo está funcionando ahora, con la excepción de que 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 una handling exception: javax.net.ssl.SSLHandshakeException: null cert chain error de handling exception: javax.net.ssl.SSLHandshakeException: null cert chain . Entonces parece que no estoy configurando la cadena de certificados correctamente. He publicado otra pregunta sobre cómo crear un almacén de claves del cliente en el formato BKS con la cadena de certificados adecuada: Cómo crear un formato de BKS (BouncyCastle) Java Keystore que contiene una cadena de certificados del cliente

Android admite certificados en BKS, P12 y otros formatos.

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

Necesita 2 archivos en su carpeta /res/raw : certificado de confianza truststore.bks para el servidor (convertido desde el archivo .cer)

client.bks/client.p12 – el certificado del cliente (convertido a partir de un archivo .p12 que contiene el certificado del cliente y la clave del 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(); } } } 

Actualizar:

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 certificates = new ArrayList(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 mismo ocurre con KeyStore con el certificado del cliente, puede usar el archivo .p12 directamente sin convertirlo a BKS.