在 Android 上使用客户端/服务器证书进行双向身份验证 SSL 套接字
我正在开发一个需要客户端和服务器证书身份验证的Android应用程序。我有一个我创建的SSLClient类,它可以在常规桌面Java SE 6上很好地工作。我已将其移动到我的Android项目中,但收到以下错误:“找不到KeyStore JKS实现”。
我在网上看了一下,看起来Android上可能不支持Java Keystore(真棒!)但我有一种感觉,它比这更多,因为我找到的任何示例代码都不像我想要做的。我发现的所有内容都在谈论使用http客户端而不是原始SSL套接字。我需要此应用程序的 SSL 套接字。
以下是我的SSLClient.java文件中的代码。它读取密钥库和信任库,创建与服务器的 SSL 套接字连接,然后在等待来自服务器的输入行时运行循环,然后在它们传入时通过调用不同类中的方法来处理它们。我非常有兴趣听到任何在Android平台上做SSL套接字经验的人的意见。
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型。我已经更新了上面的代码以反映这些更改。我仍然遇到一个问题,它显然没有加载密钥库和信任库。我会随着我的发现而更新。
Update2:
我以桌面Java方式而不是正确的Android方式加载密钥库和信任库文件。文件必须放在 res/raw 文件夹中,并使用 getResources() 加载。我现在得到密钥库和信任库大小的计数为1和1,这意味着它们正在加载。我仍然在一个例外上崩溃,但越来越近了!当我开始工作时,我会更新。
Update3:
看起来现在一切正常,除了我的密钥库设置不正确。如果我在服务器上禁用客户端身份验证,则连接没有问题。当我将其保持启用状态时,我收到错误。因此,看起来我没有正确设置证书链。我发布了另一个问题,询问如何使用正确的证书链以 BKS 格式创建客户端密钥库:如何创建包含客户端证书链的 BKS(BouncyCastle)格式的 Java 密钥库handling exception: javax.net.ssl.SSLHandshakeException: null cert chain