在 Android 上使用客户端/服务器证书进行双向身份验证 SSL 套接字 [英] Using client/server certificates for two way authentication SSL socket on Android

查看:76
本文介绍了在 Android 上使用客户端/服务器证书进行双向身份验证 SSL 套接字的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在开发一个需要客户端和服务器证书身份验证的 Android 应用程序.我创建了一个 SSLClient 类,它在常规桌面 Java SE 6 上运行良好.我已将它移到我的 Android 项目中,但出现以下错误:找不到 KeyStore JKS 实现".

I'm working on an Android app that requires both client and server certificate authentication. I have an SSLClient class that I created that works beautifully on regular desktop Java SE 6. I've moved it into my Android project and I'm getting the following error: "KeyStore JKS implementation not found".

我在网上查了一下,看起来 Android 上可能不支持 Java 密钥库(太棒了!)但我觉得还有更多,因为我没有找到任何示例代码类似于我正在尝试做的事情.我发现的所有内容都在谈论使用 http 客户端而不是原始 SSL 套接字.我需要此应用程序的 SSL 套接字.

I've looked online a bit and it looks like there's a possibility that Java Keystores are not supported on Android (awesome!) but I have a feeling there's more to it than that because none of the sample code I've found resembles what I'm trying to do at all. Everything I found talks about using an http client rather than raw SSL sockets. I need SSL sockets for this application.

以下是我的 SSLClient.java 文件中的代码.它读取密钥库和信任库,创建到服务器的 SSL 套接字连接,然后在等待来自服务器的输入行时运行一个循环,然后通过调用不同类中的方法来处理它们.我很想听听任何有在 Android 平台上执行 SSL 套接字经验的人的意见.

Below is the code in my SSLClient.java file. It reads the keystore and truststore, creates an SSL socket connection to the server, then runs a loop while waiting for input lines from the server then handles them as they come in by calling a method in a different class. I'm very interested to hear from anyone with any experience doing SSL sockets on the Android platform.

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 
            {

            }
        }
    }
}

<小时>

更新:
在这个问题上取得了一些不错的进展.发现确实不支持JKS,直接选择SunX509类型也不支持.我已经更新了上面的代码以反映这些更改.我仍然遇到问题,显然没有加载密钥库和信任库.我会在发现更多信息时更新.


Update:
Making some good progress on this problem. Found out that JKS is indeed not supported, neither is directly choosing the SunX509 type. I've updated my code above to reflect these changes. I'm still having an issue with it apparently not loading the keystore and truststore. I'll update as I figure out more.

更新 2:
我正在以桌面 Java 方式而不是正确的 Android 方式加载我的密钥库和信任库文件.这些文件必须放在 res/raw 文件夹中并使用 getResources() 加载.我现在得到密钥库和信任库大小的 1 和 1 计数,这意味着它们正在加载.我仍然因异常而崩溃,但越来越近了!当我开始工作时,我会更新.

Update2:
I was doing my keystore and truststore file loading in a desktop Java way rather than the correct Android way. The files must be put in the res/raw folder and loaded using getResources(). I'm now getting a count of 1 and 1 for the keystore and truststore size which means they're loading. I'm still crashing on an exception, but getting closer! I'll update when I get this working.

更新 3:
看起来现在一切正常,除了我的密钥库设置不正确.如果我在服务器上禁用客户端身份验证,它连接没有问题.当我启用它时,我收到一个 handling exception: javax.net.ssl.SSLHandshakeException: null cert chain 错误.所以看起来我没有正确设置证书链.我发布了另一个问题,询问如何使用正确的证书链以 BKS 格式创建客户端密钥库:如何创建包含客户端证书链的BKS(BouncyCastle)格式Java Keystore

Update3:
Looks like everything is working now with the exception of my keystore being set up incorrectly. If I disable client side authentication on the server, it connects without issue. When I leave it enabled, I get a handling exception: javax.net.ssl.SSLHandshakeException: null cert chain error. So it looks like I'm not setting up the certificate chain correctly. I've posted another question asking how to create a client keystore in the BKS format with the proper certificate chain: How to create a BKS (BouncyCastle) format Java Keystore that contains a client certificate chain

推荐答案

Android 支持 BKS、P12 等格式的证书.

Android supports certificates in the BKS, P12 and other formats.

对于 BKS 格式:使用 portecle 将您的证书(.p12 和 .crt)转换为 .bks.

For BKS format: Use portecle to convert your certificates (.p12 and .crt) to .bks.

您的 /res/raw 文件夹中需要 2 个文件:truststore.bks 服务器的信任证书(从 .cer 文件转换而来)

You need 2 files in your /res/raw folder: truststore.bks trust certificate for the server (converted from .cer file)

client.bks/client.p12 - 客户端证书(从包含客户端证书和客户端密钥的 .p12 文件转换而来)

client.bks/client.p12 - the client certificate (converted from a .p12 file that contains the client certificate and the client key)

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

}

<小时>

更新:

您也可以直接为信任存储加载 .crt 文件,而无需将它们转换为 BKS:

You can also load .crt files for the trust store directly without converting them to 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;
  }

带有客户端证书的 KeyStore 也是如此,您可以直接使用 .p12 文件,而无需将其转换为 BKS.

Same goes for the KeyStore with the client certificate, you can use the .p12 file directly without converting it to BKS.

这篇关于在 Android 上使用客户端/服务器证书进行双向身份验证 SSL 套接字的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆