CA双向认证补充:java客户端使用优化及证书链和Android证书
Posted Tatum_99999
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了CA双向认证补充:java客户端使用优化及证书链和Android证书相关的知识,希望对你有一定的参考价值。
说明
上篇详细描述了自定义ca证书的步骤以及浏览器作为客户端和java作为客户端的使用方法。
但是之前的java客户端使用代码还存在一定的问题:
首先,之前的客户端根证书是在代码外部使用keytool安装到jdk证书库,次数多了就显得麻烦;
其次,之前的代码只能支持域名访问,这样没有真实域名时就必须更改host文件;
于是通过查找网络资料修改之后,便有了新的操作方式,使得java客户端可以支持ip访问https,同时不用直接侵入jdk。
支持ip访问https的java客户端
http请求有多种框架,java常用的可能就是httpclient,而目前公司安卓那边使用的是okhttp,因此除了尝试我所熟悉的httpclient之外,也做了okhttp在java环境下的尝试。
httpclient方式代码
以下代码基本来自网络,由于搜索资料过多,已经无法找到原出处。
import java.io.BufferedReader; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.net.HttpURLConnection; import java.net.URL; import java.security.KeyStore; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.Map; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509TrustManager; /** * @Description:TODO类描述 * @since JDK 1.8 * @author tuzongxun * @Email 1160569243@qq.com * @version: v1.0.0 * @date: 2019年4月9日 下午2:43:35 */ public class CaTest // private String serverUrl = "https://blog.tzx.cn"; private String serverUrl = "https://192.168.0.205"; private SSLSocketFactory sslFactory = null; /** * @author: tuzongxun * @date: 2019年4月9日 下午2:42:03
*/ public void run() try HttpURLConnection connection = doHttpRequest(serverUrl, "GET", "", null); int responseCode = getResponseCode(connection); String responseBody = getResponseBodyAsString(connection); connection.disconnect(); System.out.println("response code=" + responseCode); System.out.println(responseBody); catch (Exception e) e.printStackTrace(); public static void main(String[] args) CaTest test = new CaTest(); test.run(); private synchronized SSLSocketFactory getSSLFactory() throws Exception if (sslFactory == null) SSLContext sslContext = SSLContext.getInstance("SSL"); TrustManager[] tm = new MyX509TrustManager(); KeyStore truststore = KeyStore.getInstance("JKS"); //这里加载的是客户端证书 truststore.load(new FileInputStream("C:\\\\Users\\\\tzx\\\\Desktop\\\\client.jks"), "12345678" .toCharArray()); //上边的密码是生成jks时的密码 KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); //这个密码是证书密码 kmf.init(truststore, "12345678".toCharArray()); sslContext.init(kmf.getKeyManagers(), tm, new java.security.SecureRandom()); sslFactory = sslContext.getSocketFactory(); return sslFactory; private HttpURLConnection doHttpRequest(String requestUrl, String method, String body, Map<String, String> header) throws Exception HttpURLConnection conn; if (method == null || method.length() == 0) method = "GET"; if ("GET".equals(method) && body != null && !body.isEmpty()) requestUrl = requestUrl + "?" + body; URL url = new URL(requestUrl); // start这一段代码必须加在open之前,即支持ip访问的关键代码 javax.net.ssl.HttpsURLConnection.setDefaultHostnameVerifier(new javax.net.ssl.HostnameVerifier() public boolean verify(String hostname, SSLSession sslsession) return true; ); //end conn = (HttpURLConnection)url.openConnection(); conn.setDoOutput(true); conn.setDoInput(true); conn.setUseCaches(false); conn.setInstanceFollowRedirects(true); conn.setRequestMethod(method); if (requestUrl.matches("^(https?)://.*$")) ((HttpsURLConnection)conn).setSSLSocketFactory(this.getSSLFactory()); if (header != null) for (String key : header.keySet()) conn.setRequestProperty(key, header.get(key)); if (body != null && !body.isEmpty()) if (!method.equals("GET")) OutputStreamWriter wr = new OutputStreamWriter(conn.getOutputStream()); wr.write(body); wr.close(); conn.connect(); return conn; public int getResponseCode(HttpURLConnection connection) throws IOException return connection.getResponseCode(); public String getResponseBodyAsString(HttpURLConnection connection) throws Exception BufferedReader reader = null; if (connection.getResponseCode() == 200) reader = new BufferedReader(new InputStreamReader(connection.getInputStream())); else reader = new BufferedReader(new InputStreamReader(connection.getErrorStream())); StringBuffer buffer = new StringBuffer(); String line = null; while ((line = reader.readLine()) != null) buffer.append(line); return buffer.toString(); class MyX509TrustManager implements X509TrustManager private X509TrustManager sunJSSEX509TrustManager; MyX509TrustManager() throws Exception // create a "default" JSSE X509TrustManager. KeyStore ks = KeyStore.getInstance("JKS"); //这里加载的是根证书链 ks.load(new FileInputStream("C:\\\\Users\\\\tzx\\\\Desktop\\\\caroot.jks"), "12345678".toCharArray()); TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509", "SunJSSE"); tmf.init(ks); TrustManager tms[] = tmf.getTrustManagers(); for (int i = 0; i < tms.length; i++) if (tms[i] instanceof X509TrustManager) sunJSSEX509TrustManager = (X509TrustManager)tms[i]; return; throw new Exception("Couldn't initialize"); @Override public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException try sunJSSEX509TrustManager.checkClientTrusted(chain, authType); catch (CertificateException excep) @Override public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException try sunJSSEX509TrustManager.checkServerTrusted(chain, authType); catch (CertificateException excep) @Override public X509Certificate[] getAcceptedIssuers() return sunJSSEX509TrustManager.getAcceptedIssuers();
okhttp代码
okhttp使用时和httpclient基本一样,区别只是run方法里的httpclient替换一下:
OkHttpClient client = new OkHttpClient.Builder().sslSocketFactory(this.getSSLFactory()).build(); Request request = new Request.Builder().url(serverUrl).build(); Call call = client.newCall(request); Response response = call.execute(); System.out.println(response); System.out.println(response.body().string().toString()); response.close();
使用时要注意导入okhttp的依赖,例如:
<dependency> <groupId>com.squareup.okhttp3</groupId> <artifactId>okhttp</artifactId> <version>3.3.0</version> </dependency>
客户端通信证书jks文件生成
上一篇说了如何生成p12文件,p12实际就是pkcs12格式的文件,我的理解就是一个压缩包,里边包含了crt证书和key文件。
而pkcs12格式的这种文件有时候也会命名为pfx,可以用命令转换为jks格式:
keytool -importkeystore -srckeystore client.pfx -srcstoretype pkcs12 -destkeystore client.jks -deststoretype JKS
客户端根证书链jks文件生成
上一遍中使用的根证书只包含单一的一个crt文件,这种证书称作单证书,而实际项目中为了更加安全,则很可能使用根证书链。
据我个人理解,根证书链大概的意思就是,首先如上一篇说的那样生成一个顶级根证书,然后再由这个根证书签发下级根证书,甚至再下一级的根证书。
最终校验的时候是根据整个证书链进行校验,而不是单证书校验,从而进一步增加安全性。
根证书链一般可能会以pem作为文件结尾,文件里包含了层层根证书以及对应关系,pem根证书链也可以生成jks格式的证书链:
keytool -import -noprompt -file caroot.pem -keystore caroot.jks -storepass 11111111
上述操作我理解为也是一个打包的过程,需要指定一个打包密码。
那么上边两个生成的文件即我们java客户端双向认证需要的两个jks证书文件了。
android证书文件
由于我们这一次的双向认证实际需求并不是java客户端,而是安卓,而据说安卓里不识别jks、pfx等证书,因此只能把证书转换为安卓可以使用的。
经过多次调试,最终有一个可行的方案是,安卓根证书使用cer格式的根证书链文件,安卓通信证书则使用bks格式的证书文件。
因此实际操作也就是把上百年的pem根证书链转换为cer文件;把上边的client.jks转换为bks文件,cer转换可以使用如下命令:
openssl x509 -inform pem -in caroot.pem -outform der -out caroot.cer
而jks转换为bks似乎并不能直接用命令,最终是参考网上的文章借助一个工具进行转换,参考文章:
http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0831/3393.html
上边的文章不仅包含了bks的转换,也包含了安卓端okhttp双向认证的代码,我们安卓同事最终也是参考这个代码做的实现。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TVTI4o3I-1596082714592)(https://blog.tzxcode.cn/images/copyright.png)]
以上是关于CA双向认证补充:java客户端使用优化及证书链和Android证书的主要内容,如果未能解决你的问题,请参考以下文章
nginx和iis下的SSL双向认证教程【ca 自签 ssl证书】