使用AndroidKeyStore和Apache服务器实现HTTPS双向认证

打印服务器证书信息:

openssl s_client -connect www.baidu.com:443

AndroidKeyStore创建RSA的Keypair,并使用它进行签名和验证:

https://github.com/amitaymolko/react-native-rsa-native/blob/f106025e3d7724a25834e79b99561fdbd07dc389/android/src/main/java/com/RNRSA/RSA.java

查看PEM格式的证书信息:

openssl x509 -in certificate.pem -text -noout

查看DER格式的证书信息:

openssl x509 -in certificate.der -inform der -text -noout

创建双向认证的证书链:

1.产生公私钥对

openssl genrsa > root.key
openssl genrsa > server.key
openssl genrsa > client.key

2.生成根证书

openssl req -x509 -new -key root.key >root.crt

3.生成二级证书请求文件

openssl req -new -key server.key -out server.csr
openssl req -new -key client.key -out client.csr

4.为二级证书签名

openssl ca -in server.csr -cert root.crt -keyfile root.key -out ./server.crt
openssl ca -in client.csr -cert root.crt -keyfile root.key -out client.crt

此时会出错,解决方法:

mkdir -p ./demoCA/newcerts
touch ./demoCA/index.txt
touch ./demoCA/serial
echo '01' > ./demoCA/serial
cd ~
openssl rand -writerand .rnd

5.将客户端证书和密钥打包成pfx格式(可以导入浏览器)

openssl pkcs12 -export -clcerts -in client.crt -inkey client.key -out client.pfx

6.将客户端密钥导出为PKCS8格式(可以导入AndroidKeyStore)

openssl pkcs8 -topk8 -inform PEM -in client.key -outform PEM -nocrypt > client.pem

Apache服务器端配置双向认证:

1.修改/usr/local/apache2/conf/httpd.conf

LoadModule socache_shmcb_module modules/mod_socache_shmcb.so
LoadModule ssl_module modules/mod_ssl.so
Include conf/extra/httpd-ssl.conf

2.修改/usr/local/apache2/conf/extra/httpd-ssl.conf

ServerName 192.168.169.227:443
SSLCertificateFile "/usr/local/apache2/conf/server.crt"
SSLCertificateKeyFile "/usr/local/apache2/conf/server.key"
SSLCACertificateFile "/usr/local/apache2/conf/root.crt"
SSLVerifyClient require
SSLVerifyDepth 10

Android客户端实现双向认证:

public static void httpsDevPrivateKey() {
    new Thread(new Runnable(){
        @Override
        public void run() {
            URL url = null;
            try {
                // 证书格式设置为X.509,从浏览器下载的证书就是该格式,可以直接导入。
                CertificateFactory cf = CertificateFactory.getInstance("X.509");

                // 加载https://192.168.169.227的服务器证书
                InputStream caInput = new BufferedInputStream(new FileInputStream("/mnt/sdcard/227/server.crt"));
                Certificate ca;
                ca = cf.generateCertificate(caInput);
                System.out.println("服务器证书信息:" + ((X509Certificate) ca).getSubjectDN());
                caInput.close();

                // 加载客户端证书,证书格式为X.509。
                InputStream clientCaInput = new BufferedInputStream(new FileInputStream("/mnt/sdcard/client/client.crt"));
                Certificate clientCa;
                clientCa = cf.generateCertificate(clientCaInput);
                System.out.println("客户端证书信息:" + ((X509Certificate) clientCa).getSubjectDN());
                clientCaInput.close();

                // 加载客户端私钥,格式为PKCS8。
                // 注意:需要去掉-----BEGIN RSA PRIVATE KEY-----和-----END RSA PRIVATE KEY-----。
                InputStream clientKeyInput = new BufferedInputStream(new FileInputStream("/mnt/sdcard/client/client.pem"));
                byte[] clientKey = new byte[clientKeyInput.available()];
                clientKeyInput.read(clientKey);
                clientKeyInput.close();

                // 解析BASE64编码中的数据。
                clientKey = Base64.decode(clientKey, Base64.DEFAULT);

                // 解析PKCS8格式的私钥。
                KeyFactory keyFactory = KeyFactory.getInstance("RSA");
                PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(clientKey);
                PrivateKey clientPrivateKey = keyFactory.generatePrivate(keySpec);

                // 获取"AndroidKeyStore"。
                KeyStore keyStore = KeyStore.getInstance(KEYSTORE_TYPE);
                keyStore.load(null, null);

                // 导入服务器的证书到AndroidKeyStore。
                keyStore.setCertificateEntry("test_ca", ca);

                // 导入客户端私钥和证书到AndroidKeyStore。
                keyStore.setKeyEntry("test_client", clientPrivateKey, null, new Certificate[]{clientCa});

                // 使用AndroidKeyStore初始化TrustyManagerFactory。
                String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
                TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
                tmf.init(keyStore);

                // 使用AndroidKeyStore初始化KeyManagerFactory。
                String kmfAlgorithm = KeyManagerFactory.getDefaultAlgorithm();
                KeyManagerFactory kmf = KeyManagerFactory.getInstance(kmfAlgorithm);
                kmf.init(keyStore, null);

                // 用AndroidKeyStore初始化过的TrustManagerFactory和KeyManagerFactory初始化SSLContext。
                SSLContext context = SSLContext.getInstance("TLS");
                context.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);

                url = new URL("https://192.168.169.227");
                HttpsURLConnection urlConnection = (HttpsURLConnection)url.openConnection();

                // 因为SSL只支持域名的认证,不支持IP的认证,所以这里我们手动认证。
                urlConnection.setHostnameVerifier(new HostnameVerifier()
                {
                    @Override
                    public boolean verify(String hostname, SSLSession session)
                    {
                        if(hostname.equals("192.168.169.227")) {
                            return true;
                        } else {
                            return false;
                        }
                    }
                });

                // 将https的连接绑定到AndroidKeyStore上。
                urlConnection.setSSLSocketFactory(context.getSocketFactory());

                // HTTPS双向认证的方式网站的数据。
                InputStream in = urlConnection.getInputStream();
                copyInputStreamToOutputStream(in, System.out);
            } catch (MalformedURLException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } catch (CertificateException e) {
                e.printStackTrace();
            } catch (NoSuchAlgorithmException e) {
                e.printStackTrace();
            } catch (KeyStoreException e) {
                e.printStackTrace();
            } catch (KeyManagementException e) {
                e.printStackTrace();
            } catch (UnrecoverableKeyException e) {
                e.printStackTrace();
            } catch (InvalidKeySpecException e) {
                e.printStackTrace();
            }
        }
    }).start();
}

private static void copyInputStreamToOutputStream(InputStream in, PrintStream out) throws IOException {
    byte[] buffer = new byte[4096];
    int readCount = 0;
    while ((readCount = in.read(buffer)) > 0) {
        out.write(buffer, 0, readCount);
    }
}

 

 

 

此条目发表在Android分类目录,贴了, , , , , 标签。将固定链接加入收藏夹。