代码之家  ›  专栏  ›  技术社区  ›  Ben Baron

在Android上使用客户端/服务器证书进行双向认证SSL套接字

  •  43
  • Ben Baron  · 技术社区  · 14 年前

    我正在开发一个Android应用程序,它需要客户端和服务器证书身份验证。我创建了一个SSLClient类,它在普通桌面Java SE 6上工作得很好。我已经把它移到我的Android项目中,得到了以下错误:“找不到KeyStore JKS实现”。

    下面是我的SSLClient.java文件中的代码。它读取keystore和truststore,创建一个到服务器的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类型。我已经更新了上面的代码以反映这些更改。我仍然有一个问题,它显然没有加载密钥库和信任库。我会及时更新。


    更新2:
    我用桌面Java的方式而不是正确的Android方式加载keystore和truststore文件。这些文件必须放在res/raw文件夹中,并使用getResources()加载。我现在得到了keystore和truststore大小的1和1的计数,这意味着它们正在加载。我仍在破例,但越来越近了!我会在工作后更新的。



    看起来除了密钥库设置不正确外,一切都正常。如果我在服务器上禁用客户端身份验证,则它连接时不会出现问题。当我启用它时,我得到一个 handling exception: javax.net.ssl.SSLHandshakeException: null cert chain How to create a BKS (BouncyCastle) format Java Keystore that contains a client certificate chain

    1 回复  |  直到 8 年前
        1
  •  46
  •   peceps    10 年前

    Android支持BKS、P12和其他格式的证书。

    对于BKS格式: 使用 portecle

    你需要两个文件 /res/raw 文件夹: truststore.bks 服务器的信任证书(从.cer文件转换)

    client.bks/client.p12

    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:

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

    对于带有客户机证书的密钥库也是如此,您可以直接使用.p12文件,而无需将其转换为BKS。