java SSLContext创建方式
作者:shuxiaohua
概述
HTTPS相对于HTTP多了SSL(security sock layer),应用层将数据丢给TCP时,需要经过SSL层的加密处理;TCP层的数据丢给应用层时,需要经过SSL层的解密处理。
因此网络中传输的都是加密后的数据,提高的通信的安全性。
HTTPS除了拥有传输加密的功能,还提供身份认证,防止对端伪造身份。这个是通过HTTPS证书实现。
注下图中对HTTPS握手过程进行简化,握手过程的关键步骤就是证书校验及对称加密秘钥的协商,另外客户端证书的校验是可选的。
问题
标准证书都是第三方机构颁发的,需要付费找第三方机构申请。有些出于节省成本或者省事的目的,一般会使用工具生成自签名的证书。
浏览器访问这种自签名证书的网址时就会弹出不安全的提示,如果是java代码访问这种网址时就会抛出异常。
解决这种问题有2个途径,一种是忽略证书认证,另一种就是将自签名的证书添加到受信证书库中。
SSLContext关键参数说明
SSLContext都是通过SSLContext.getInstance(String protocol)方法获取,获取后需要调用init方法初始化。
init方法签名如下:
public final void init(KeyManager[] km, TrustManager[] tm,SecureRandom random)
KeyManager
- 用于HTTPS双向认证时,客户端向服务端发送的认证信息(证书);与不同的服务端交互时,客户端可能用不同的身份(证书),所以需要KeyManager进行管理。
- 如果只是单向认证,KeyManager[]可以为空。
TrustManager
- 用于客户端检测服务端发送过来的证书。
KeyStore:只是单纯的存储证书、秘钥数据,不包含任何逻辑。
TrustManager,KeyManager可以使用KeyStore得到证书列表进行相应的业务处理。
忽略证书认证
有些语言比如Python是可以修改全局配置去忽略证书检查,但是Java好像没有提供这种功能,所以需要覆写证书认证的类X509TrustManager 。
忽略证书认证后,双方通信的时候仍然是加密的,只是无法对服务端的身份进行认证,有服务端伪造的风险。如果都是在内网,可以采用这种简洁的手法。
public SSLContext buildSSLContext() throws Exception { X509TrustManager x509TrustManager = new X509TrustManager() { @Override public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { } @Override public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { } @Override public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; } }; SSLContext sslContext = SSLContext.getInstance("TLSv1.2"); sslContext.init((KeyManager[]) null, new X509TrustManager[] {x509TrustManager}, (SecureRandom) null); return sslContext; }
将自签名证书放到受信证书库
将证书直接导入到JDK的证书库
待续
通过编码的方式将证书放到受信证书列表中
@Override public SSLContext buildSSLContext() throws Exception { KeyStore keyStore = buildKeyStore(); TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); tmf.init(keyStore); SSLContext sslContext = SSLContext.getInstance("TLSv1.2"); sslContext.init((KeyManager[]) null, tmf.getTrustManagers(), (SecureRandom) null); return sslContext; } private KeyStore buildKeyStore() throws Exception { // 获取自签名的证书 List<X509Certificate> certs = loadHttpsCerts(); KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); keyStore.load(null, null); TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance( TrustManagerFactory.getDefaultAlgorithm()); trustManagerFactory.init((KeyStore) null); // 获得jdk证书库中的证书 Arrays.stream(trustManagerFactory.getTrustManagers()) .filter(manager -> manager instanceof X509TrustManager) .findFirst() .ifPresent(manager -> { List<X509Certificate> jdkCerts = Arrays.asList(((X509TrustManager) manager).getAcceptedIssuers()); certs.addAll(jdkCerts); }); certs.stream().forEach(cert -> { keyStore.setCertificateEntry(cert.getSubjectDN().getName(), cert); }); return keyStore; } private List<X509Certificate> loadHttpsCerts() throws IOException, CertificateException { List<X509Certificate> certs = new ArrayList<>(32); PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); Resource[] resources = resolver.getResources(HTTPS_CERTS_PATH); for (Resource resource : resources) { InputStream inStream = resource.getInputStream(); CertificateFactory factory = CertificateFactory.getInstance("X.509"); X509Certificate cert = (X509Certificate) factory.generateCertificate(inStream); certs.add(cert); } return certs; }
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。