您的位置:首页 > 其它

如何在不重启 JVM 的情况下重新加载证书文件?

2018-10-15 16:52 218 查看
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/defonds/article/details/83061232

某种情况下需要动态下载证书文件后使用 SSL 访问某服务,但由于 JVM 使用默认的证书访问新服务,该证书在 JVM 启动的时候加载,那时还没有新服务的证书,所以会报 ValidatorException,详情:

16:44:27,338 ERROR [HttpModelLoader] sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target at sun.security.ssl.Alerts.getSSLException(Unknown Source) at sun.security.ssl.SSLSocketImpl.fatal(Unknown Source) at sun.security.ssl.Handshaker.fatalSE(Unknown Source) at sun.security.ssl.Handshaker.fatalSE(Unknown Source) at sun.security.ssl.ClientHandshaker.serverCertificate(Unknown Source) at sun.security.ssl.ClientHandshaker.processMessage(Unknown Source) at sun.security.ssl.Handshaker.processLoop(Unknown Source) at sun.security.ssl.Handshaker.process_record(Unknown Source) at sun.security.ssl.SSLSocketImpl.readRecord(Unknown Source) at sun.security.ssl.SSLSocketImpl.performInitialHandshake(Unknown Source) at sun.security.ssl.SSLSocketImpl.startHandshake(Unknown Source) at sun.security.ssl.SSLSocketImpl.startHandshake(Unknown Source) at org.apache.http.conn.ssl.SSLConnectionSocketFactory.createLayeredSocket(SSLConnectionSocketFactory.java:394) at org.apache.http.conn.ssl.SSLConnectionSocketFactory.connectSocket(SSLConnectionSocketFactory.java:353) at org.apache.http.impl.conn.DefaultHttpClientConnectionOperator.connect(DefaultHttpClientConnectionOperator.java:141) at org.apache.http.impl.conn.PoolingHttpClientConnectionManager.connect(PoolingHttpClientConnectionManager.java:353) at org.apache.http.impl.execchain.MainClientExec.establishRoute(MainClientExec.java:380) at org.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:236) at org.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:184) at org.apache.http.impl.execchain.RetryExec.execute(RetryExec.java:88) at org.apache.http.impl.execchain.RedirectExec.execute(RedirectExec.java:110) at org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:184) at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:82) at com.defonds.als.gui.model.HttpModelLoader.requestByHttpClient4(HttpModelLoader.java:172) at com.defonds.als.gui.model.HttpModelLoader.doBusinessAction(HttpModelLoader.java:94) at com.defonds.als.gui.model.EJBModelLoader.doBusinessEvent(EJBModelLoader.java:69) at com.defonds.als.gui.model.ModelLoader.doBusiness(ModelLoader.java:193) at com.defonds.als.gui.model.ModelLoader.doBusiness(ModelLoader.java:96) at com.defonds.als.gui.model.BusinessEventHelper.callBizService(BusinessEventHelper.java:302) at com.defonds.als.gui.model.BusinessEventHelper.doBusiness(BusinessEventHelper.java:186) at com.defonds.als.gui.model.BusinessEventHelper.doBusiness(BusinessEventHelper.java:166) at com.defonds.als.gui.model.BusinessEventHelper.doBusiness(BusinessEventHelper.java:160) at com.defonds.als.gui.model.BusinessEventHelper.doBusiness(BusinessEventHelper.java:155) at com.defonds.als.gui.model.BusinessEventHelper.doBusiness(BusinessEventHelper.java:363) at com.defonds.als.gui.base.Login.init(Login.java:188) at com.defonds.als.gui.base.Login.(Login.java:122) at com.defonds.als.gui.base.Login.getInstance(Login.java:130) at com.defonds.als.Startup.(Startup.java:114) at com.defonds.als.Startup.main(Startup.java:287) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source) at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) at java.lang.reflect.Method.invoke(Unknown Source) at jbolt.deployment.smartyclient.JBoltSmartyClientStartup.main(JBoltSmartyClientStartup.java:121) Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target at sun.security.validator.PKIXValidator.doBuild(Unknown Source) at sun.security.validator.PKIXValidator.engineValidate(Unknown Source) at sun.security.validator.Validator.validate(Unknown Source) at sun.security.ssl.X509TrustManagerImpl.validate(Unknown Source) at sun.security.ssl.X509TrustManagerImpl.checkTrusted(Unknown Source) at sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(Unknown Source) ... 40 more Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target at sun.security.provider.certpath.SunCertPathBuilder.engineBuild(Unknown Source) at java.security.cert.CertPathBuilder.build(Unknown Source) ... 46 more
必须重启 JVM,然后才可以访问新的服务。那么有没有办法在不重启 JVM 的情况下重新加载新的证书并访问新的服务? 解决办法是自行创建一个 SSLSocket 工厂,并在访问新服务之前将其赋给 HttpsURLConnection 对象。就这么简单。示例代码如下:
URL localURL = new URL(urlPath);
URLConnection connection = localURL.openConnection();
HttpURLConnection httpURLConnection = (HttpURLConnection) connection;
if (connection instanceof HttpsURLConnection) {
SSLSocketFactory sslSocketFactory = getSSLFactoryInstance();
((HttpsURLConnection) httpURLConnection).setSSLSocketFactory(sslSocketFactory);
}
httpURLConnection.setDoOutput(true);
httpURLConnection.setRequestMethod("POST");
httpURLConnection.setRequestProperty("Content-Type", "application/octet-stream");
httpURLConnection.setRequestProperty("Accept-Encoding", "chunck");
httpURLConnection.setConnectTimeout(3000);
//httpURLConnection.setReadTimeout(18000);

outputStream = httpURLConnection.getOutputStream();
baos = new ByteArrayOutputStream(8196);
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(e);
oos.flush();
oos.close();
outputStream.write(baos.toByteArray());
outputStream.flush();

if (HttpStatus.SC_OK == httpURLConnection.getResponseCode()) {

调用到的 getSSLFactoryInstance 方法:

private static SSLSocketFactory selfSSLSocketFactoryInstance = null;

private static synchronized SSLSocketFactory getSSLFactoryInstance() throws ALSAppException {
if (null == selfSSLSocketFactoryInstance) {
KeyStore keyStore = null;
TrustManagerFactory tmf = null;
SSLContext ctx = null;
InputStream is = null;
String strJnlpHome = System.getProperty("jnlpx.home");
try {
keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
if (StringUtil.isNotEmpty(strJnlpHome)) {
is = fullStream(strJnlpHome + "/../lib/security/cacerts");
}
keyStore.load(is, "changeit".toCharArray());
tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(keyStore);
ctx = SSLContext.getInstance("TLSv1");
ctx.init(null, tmf.getTrustManagers(), null);
selfSSLSocketFactoryInstance = ctx.getSocketFactory();
} catch (Exception e) {
throw new ALSAppException(ExceptionKey.RUN_TIME_EXCEPTION, e);
}
}
return selfSSLSocketFactoryInstance;
}

调用到的 fullStream 方法:

private static InputStream fullStream(String fname) throws IOException {
FileInputStream fis = new FileInputStream(fname);
DataInputStream dis = new DataInputStream(fis);
byte[] bytes = new byte[dis.available()];
dis.readFully(bytes);
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
return bais;
}

还有一种解决方案是实现 X509TrustManager 接口,以下为互联网上流行的一种写法:

TrustManager tm = new X509TrustManager() {
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}

public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {

}

public X509Certificate[] getAcceptedIssuers() {
return null;
}
};

但是很明显以上写法忽略了任何 SSL 证书校验,虽然走的还是 https 协议即密文传输,但是在中间人劫持面前失去了 SSL 应有的保护作用,所以还是要谨慎使用。起码的 server 端校验、证书域名校验还是要有的,不能偷懒。

参考资料

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: