Cómo crear un formato de BKS (BouncyCastle) Java Keystore que contiene una cadena de certificados de cliente

Estoy escribiendo una aplicación de Android que requiere autenticación de cliente SSL. Sé cómo crear un almacén de claves JKS para una aplicación Java de escritorio, pero Android solo admite el formato BKS. Cada forma en que he intentado crear el almacén de claves da como resultado el siguiente error:
handling exception: javax.net.ssl.SSLHandshakeException: null cert chain

Parece que el cliente nunca envía una cadena de certificados adecuada, probablemente porque no estoy creando correctamente el almacén de claves. No puedo habilitar la depuración de SSL como puedo en el dekstop, por lo que es mucho más difícil de lo que debería ser.

A modo de referencia, el siguiente es el comando que está trabajando para crear un almacén de confianza BKS:
keytool -importcert -v -trustcacerts -file "cacert.pem" -alias ca -keystore "mySrvTruststore.bks" -provider org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath "bcprov-jdk16-145.jar" -storetype BKS -storepass testtest


Aquí está el comando que he probado que NO está funcionando para crear un almacén de claves del cliente BKS:

 cat clientkey.pem clientcert.pem cacert.pem > client.pem keytool -import -v -file <(openssl x509 -in client.pem) -alias client -keystore "clientkeystore" -provider org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath "bcprov-jdk16-145.jar" -storetype BKS -storepass testtest 

Instrucciones detalladas paso a paso que seguí para lograr esto

  • Descargue bouncycastle JAR de http://repo2.maven.org/maven2/org/bouncycastle/bcprov-ext-jdk15on/1.46/bcprov-ext-jdk15on-1.46.jar o tómelo de la carpeta “doc”.
  • Configure BouncyCastle para PC usando uno de los métodos a continuación.
    • Agregar el proveedor BC estáticamente (recomendado)
      • Copie el bcprov-ext-jdk15on-1.46.jar en cada uno
        • D: \ tools \ jdk1.5.0_09 \ jre \ lib \ ext (JDK (paquete JRE)
        • D: \ tools \ jre1.5.0_09 \ lib \ ext (JRE)
        • C: \ (ubicación que se utilizará en la variable env)
      • Modificar el archivo java.security en
        • D: \ tools \ jdk1.5.0_09 \ jre \ lib \ security
        • D: \ tools \ jre1.5.0_09 \ lib \ security
        • y agrega la siguiente entrada
          • security.provider.7 = org.bouncycastle.jce.provider.BouncyCastleProvider
      • Agregue la siguiente variable de entorno en la sección “Variables de usuario”
        • CLASSPATH =% CLASSPATH%; c: \ bcprov-ext-jdk15on-1.46.jar
    • Agregue bcprov-ext-jdk15on-1.46.jar a CLASSPATH de su proyecto y agregue la siguiente línea en su código
      • Security.addProvider (nuevo BouncyCastleProvider ());
  • Genera la Keystore usando Bouncy Castle
    • Ejecute el siguiente comando
      • keytool -genkey -alias myproject -keystore C: /myproject.keystore -storepass myproject -storetype BKS -provider org.bouncycastle.jce.provider.BouncyCastleProvider
    • Esto genera el archivo C: \ myproject.keystore
    • Ejecute el siguiente comando para verificar si se genera correctamente o no
      • keytool -list -keystore C: \ myproject.keystore -storetype BKS
  • Configurar BouncyCastle para TOMCAT

    • Abra D: \ tools \ apache-tomcat-6.0.35 \ conf \ server.xml y agregue la siguiente entrada

    • Reinicie el servidor después de estos cambios.

  • Configurar BouncyCastle para Android Client
    • No es necesario configurarlo ya que Android admite la versión 1.46 de Bouncy Castle internamente en el “android.jar” proporcionado.
    • Simplemente implemente su versión de Cliente HTTP (MyHttpClient.java se puede encontrar a continuación) y configure lo siguiente en el código
      • SSLSocketFactory.setHostnameVerifier (SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
    • Si no haces esto, se da una excepción como la siguiente
      • javax.net.ssl.SSLException: el nombre de host en el certificado no coincide: <192.168.104.66>! =
    • En el modo de producción, cambie el código anterior a
      • SSLSocketFactory.setHostnameVerifier (SSLSocketFactory.STRICT_HOSTNAME_VERIFIER);

MyHttpClient.java

 package com.arisglobal.aglite.network; import java.io.InputStream; import java.security.KeyStore; import org.apache.http.conn.ClientConnectionManager; 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.impl.conn.SingleClientConnManager; import com.arisglobal.aglite.activity.R; import android.content.Context; public class MyHttpClient extends DefaultHttpClient { final Context context; public MyHttpClient(Context context) { this.context = context; } @Override protected ClientConnectionManager createClientConnectionManager() { SchemeRegistry registry = new SchemeRegistry(); registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80)); // Register for port 443 our SSLSocketFactory with our keystore to the ConnectionManager registry.register(new Scheme("https", newSslSocketFactory(), 443)); return new SingleClientConnManager(getParams(), registry); } private SSLSocketFactory newSslSocketFactory() { try { // Get an instance of the Bouncy Castle KeyStore format KeyStore trusted = KeyStore.getInstance("BKS"); // Get the raw resource, which contains the keystore with your trusted certificates (root and any intermediate certs) InputStream in = context.getResources().openRawResource(R.raw.aglite); try { // Initialize the keystore with the provided trusted certificates. // Also provide the password of the keystore trusted.load(in, "aglite".toCharArray()); } finally { in.close(); } // Pass the keystore to the SSLSocketFactory. The factory is responsible for the verification of the server certificate. SSLSocketFactory sf = new SSLSocketFactory(trusted); // Hostname verification from certificate // http://hc.apache.org/httpcomponents-client-ga/tutorial/html/connmgmt.html#d4e506 sf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); return sf; } catch (Exception e) { throw new AssertionError(e); } } } 

Cómo invocar el código anterior en su clase de actividad:

 DefaultHttpClient client = new MyHttpClient(getApplicationContext()); HttpResponse response = client.execute(...); 

Yo uso Portecle , y funciona como un encanto.

No creo que su problema sea con el almacén de claves BouncyCastle; Creo que el problema es con un paquete javax.net.ssl ​​roto en Android. El almacén de claves BouncyCastle es una molestia suprema porque Android modificó un comportamiento predeterminado de Java sin documentarlo en ninguna parte, y eliminó el proveedor predeterminado, pero funciona.

Tenga en cuenta que para la autenticación SSL puede necesitar 2 almacenes de claves. El almacén de claves “TrustManager”, que contiene los certificados de CA, y el almacén de claves “KeyManager”, que contiene las claves públicas / privadas de su sitio cliente. (La documentación es algo vaga sobre lo que debe estar en el almacén de claves de KeyManager). En teoría, no debería necesitar el almacén de claves de TrustManager si todos sus certificados están firmados por autoridades de certifcación “conocidas”, por ejemplo, Verisign, Thawte, y así. Déjame saber cómo funciona para usted. Su servidor también requerirá la CA para lo que se utilizó para firmar a su cliente.

No pude crear una conexión SSL usando javax.net.ssl ​​en absoluto. Inhabilité la autenticación SSL del cliente en el lado del servidor, y aún no pude crear la conexión. Como mi objective final era un HTTPS GET, di un puntapié y probé a usar Apache HTTP Client incluido con Android. Ese tipo de trabajo. Podría hacer la conexión HTTPS, pero aún no podría usar SSL auth. Si habilité la autenticación SSL del cliente en mi servidor, la conexión fallaría. No he comprobado el código de Apache HTTP Client, pero sospecho que están usando su propia implementación de SSL y no usan javax.net.ssl.

No estoy seguro de haber resuelto este problema o no, pero así es como lo hago y funciona en Android:

  1. Use openssl para fusionar el certificado del cliente (el certificado debe estar firmado por una CA aceptada por el servidor) y la clave privada en un par de claves de formato PCKS12: openssl pkcs12 -export -in clientcert.pem -inkey clientkey.pem -out client.p12
  2. Es posible que necesite parchear su JRE a umlimited strength encryption depende de la fortaleza de su clave: copie los archivos jar de JCE 5.0 Fortaleza ilimitada Jurisdiction Policy FIlls y anule los de su JRE (por ejemplo, C: \ Program Files \ Java \ jre6 \ lib \ security )
  3. Utilice la herramienta Portecle mencionada anteriormente y cree un nuevo almacén de claves con formato BKS
  4. Importe el par de claves PCKS12 generado en el paso 1 y guárdelo como almacén de claves BKS. Este almacén de claves funciona con la autenticación de cliente de Android.
  5. Si necesita hacer una cadena de certificados, puede utilizar esta herramienta de IBM: KeyMan para fusionar el par de claves PCKS12 del cliente con el certificado de CA. Pero solo genera el almacén de claves JKS, por lo que necesita Protecle para convertirlo al formato BKS.

línea de comando:

 keytool -genseckey -alias aliasName -keystore truststore.bks -providerclass org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath /path/to/jar/bcprov-jdk16-1.46.jar -storetype BKS 

Tu comando para crear el almacén de claves BKS parece correcto para mí.

¿Cómo inicializas el almacén de claves?

Necesita crear y pasar su propia SSLSocketFactory. Aquí hay un ejemplo que usa org.apache.http.conn.ssl.SSLSocketFactory de Apache

Pero creo que puedes hacer lo mismo en el javax.net.ssl.SSLSocketFactory

  private SSLSocketFactory newSslSocketFactory() { try { // Get an instance of the Bouncy Castle KeyStore format KeyStore trusted = KeyStore.getInstance("BKS"); // Get the raw resource, which contains the keystore with // your trusted certificates (root and any intermediate certs) InputStream in = context.getResources().openRawResource(R.raw.mykeystore); try { // Initialize the keystore with the provided trusted certificates // Also provide the password of the keystore trusted.load(in, "testtest".toCharArray()); } finally { in.close(); } // Pass the keystore to the SSLSocketFactory. The factory is responsible // for the verification of the server certificate. SSLSocketFactory sf = new SSLSocketFactory(trusted); // Hostname verification from certificate // http://hc.apache.org/httpcomponents-client-ga/tutorial/html/connmgmt.html#d4e506 sf.setHostnameVerifier(SSLSocketFactory.STRICT_HOSTNAME_VERIFIER); return sf; } catch (Exception e) { throw new AssertionError(e); } } 

Por favor, avíseme si funcionó.

Use este manual http://blog.antoine.li/2010/10/22/android-trusting-ssl-certificates/ Esta guía realmente me ayudó. Es importante observar una secuencia de certificados en la tienda. Por ejemplo: importe primero el certificado de CA intermediario más bajo y luego hasta el certificado de CA raíz .