web-dev-qa-db-fra.com

Utilisation de certificats client / serveur pour le socket SSL d'authentification bidirectionnelle sur Android

Je travaille sur une application Android qui nécessite à la fois une authentification par certificat client et serveur. J'ai une classe SSLClient que j'ai créée qui fonctionne à merveille sur le bureau normal Java SE 6. Je l'ai déplacé dans mon Android et j'obtiens l'erreur suivante: "L'implémentation de KeyStore JKS est introuvable".

J'ai un peu regardé en ligne et il semble qu'il y ait une possibilité que Java Keystores ne soient pas pris en charge sur Android (génial!)) Mais j'ai le sentiment qu'il y a plus que cela, car aucun exemple de code que j'ai trouvé ne ressemble à ce que j'essaie de faire. Tout ce que j'ai trouvé parle de l'utilisation d'un client http plutôt que de sockets SSL bruts. J'ai besoin de sockets SSL pour cette application.

Voici le code dans mon fichier SSLClient.Java. Il lit le magasin de clés et le magasin de clés de confiance, crée une connexion de socket SSL au serveur, puis exécute une boucle en attendant les lignes d'entrée du serveur, puis les gère au fur et à mesure de leur entrée en appelant une méthode dans une classe différente. Je suis très intéressé d'entendre toute personne ayant une expérience dans les sockets SSL sur la plate-forme 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 
            {

            }
        }
    }
}

Mise à jour:
Progresser correctement sur ce problème. A découvert que JKS n'est en effet pas pris en charge, ni ne choisit directement le type SunX509. J'ai mis à jour mon code ci-dessus pour refléter ces changements. J'ai toujours un problème avec, apparemment, il ne charge pas le magasin de clés et le magasin de confiance. Je mettrai à jour au fur et à mesure que je découvrirai plus.


Update2:
Je faisais le chargement de mon fichier de clés et de fichiers de clés de confiance dans un bureau Java façon plutôt que la bonne Android façon. Les fichiers doivent être placés dans le dossier res/raw et chargé à l'aide de getResources (). Je reçois maintenant un nombre de 1 et 1 pour la taille du magasin de clés et du magasin de clés de confiance, ce qui signifie qu'ils se chargent. Je plante toujours sur une exception, mais je me rapproche! mettrai à jour quand j'obtiendrai ce travail.


Update3:
Il semble que tout fonctionne maintenant, à l'exception de la configuration incorrecte de mon magasin de clés. Si je désactive l'authentification côté client sur le serveur, il se connecte sans problème. Lorsque je le laisse activé, j'obtiens un handling exception: javax.net.ssl.SSLHandshakeException: null cert chain Erreur. Il semble donc que je ne configure pas correctement la chaîne de certificats. J'ai posté une autre question demandant comment créer un magasin de clés client au format BKS avec la chaîne de certificats appropriée: Comment créer un format BKS (BouncyCastle) Java Keystore qui contient un chaîne de certificats client

43
Ben Baron

Android prend en charge les certificats aux formats BKS, P12 et autres.

Pour le format BKS: utilisez portecle pour convertir vos certificats (.p12 et .crt) en .bks.

Vous avez besoin de 2 fichiers dans votre /res/raw dossier: truststore.bks certificat de confiance pour le serveur (converti à partir du fichier .cer)

client.bks/client.p12 - le certificat client (converti à partir d'un fichier .p12 qui contient le certificat client et la clé 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();
    }
  }

}

Mise à jour:

Vous pouvez également charger des fichiers .crt pour le magasin de clés de confiance directement sans les convertir en 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;
  }

Il en va de même pour le KeyStore avec le certificat client, vous pouvez utiliser le fichier .p12 directement sans le convertir en BKS.

46
peceps