【求助】java请求https双向认证出错Remote host closed connection during handshake

时光灬之书 2019-11-12 05:26:56
JDK版本:1.8_191
tomcat版本:8.0.53
证书生成方式:keytool
生成命令:

1、生成服务器证书库

keytool -validity 365 -genkey -v -alias server -keyalg RSA -keystore server.keystore -dname "CN=localhost,OU=icesoft,O=icesoft,L=Haidian,ST=Beijing,c=cn" -storepass ghwolf -keypass ghwolf


2、生成客户端证书库

keytool -validity 365 -genkeypair -v -alias client -keyalg RSA -storetype PKCS12 -keystore client.p12 -dname "CN=client,OU=icesoft,O=icesoft,L=Haidian,ST=Beijing,c=cn" -storepass ghwolf -keypass ghwolf


3、从客户端证书库中导出客户端证书

keytool -export -v -alias client -keystore client.p12 -storetype PKCS12 -storepass ghwolf -rfc -file client.cer


4、从服务器证书库中导出服务器证书

keytool -export -v -alias server -keystore server.keystore -storepass ghwolf -rfc -file server.cer


5、生成客户端信任证书库(由服务端证书生成的证书库)

keytool -import -v -alias server -file server.cer -keystore client.truststore -storepass ghwolf


6、将客户端证书导入到服务器证书库(使得服务器信任客户端证书)

keytool -import -v -alias client -file client.cer -keystore server.keystore -storepass ghwolf

给tomcat配置server.keystore之后,安装client.p12,浏览器可以正常进行双向认证访问,并且弹出了证书选择框。但是

通过java程序去请求,出现了一个错:



请求代码:

(这段代码是直接读取系统证书,然后请求,只需要修改PWD和REQUEST_URL常量,无需依赖任何其他jar包,直接javac编译就可以用java执行了)


import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.net.URL;
import java.security.Key;
import java.security.KeyStore;
import java.security.Security;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.text.SimpleDateFormat;
import java.util.Enumeration;

import javax.net.ssl.HostnameVerifier;
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;

import java.util.*;
import java.io.*;

import sun.security.mscapi.SunMSCAPI;

public class Demo {

public static final String PWD = "ghwolf";
public static final String REQUEST_URL = "https://localhost:8443";

// 如果不设置这个,那么证书颁发给那个域名,就必须访问哪个域名,否则验证不通过。使用这个后无论证书颁发给谁,都可以使用任意ip或域名进行访问,但也会非常不安全。
static HostnameVerifier hv = new HostnameVerifier() {

public boolean verify(String urlHostName, SSLSession session) {
System.out.println("Warning: URL Host: " + urlHostName + " vs. " + session.getPeerHost());
return true;
}
};

public static void main(String args[]) throws Exception {
System.setProperty("https.protocols", "TLSv1,TLSv1.1,TLSv1.2,SSLv3");
SSLSocketFactory sslSocketFactory = getSslSocketFactory();
Scanner scan = new Scanner(System.in);
System.out.println("请输入指令,\n1:打印当前系统证书 。\n2:请求。\n ...");
while(scan.hasNextInt()) {
int i = scan.nextInt();
switch(i) {
case 1 : print(); break ;
case 2 : conn(sslSocketFactory) ; break ;
default : System.out.println("none ...");
}
}
}

static void conn(SSLSocketFactory sslSocketFactory) {
try {
String query = "name=value";
byte[] input = "Hello World.".getBytes();
URL url = new URL(REQUEST_URL);

System.out.println(url);
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
connection.setHostnameVerifier(hv);
// 如果用http注释掉这一行
connection.setSSLSocketFactory(sslSocketFactory);
connection.setRequestMethod("GET");
connection.setRequestProperty("connection", "Keep-Alive");
connection.setDoOutput(input != null);
// if (input != null) {
// OutputStream out = connection.getOutputStream();
// out.write(input);
// out.flush();
// out.close();
// }
ByteArrayOutputStream baos = new ByteArrayOutputStream();
InputStream in = connection.getInputStream();
copy(in, baos);
System.out.println("status:" + connection.getResponseCode());
System.out.println("data:" + baos.toString());
} catch (Exception e) {
e.printStackTrace();
}
}

// 信任所有证书
static class miTM implements TrustManager, X509TrustManager {

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

public boolean isServerTrusted(X509Certificate[] certs) {
return true;
}

public boolean isClientTrusted(X509Certificate[] certs) {
return true;
}

public void checkServerTrusted(X509Certificate[] certs, String authType) throws CertificateException {
return;
}

public void checkClientTrusted(X509Certificate[] certs, String authType) throws CertificateException {
return;
}
}

public static SSLSocketFactory getSslSocketFactory() throws Exception {
String clientKeyStoreFile = "E:\\User\\Ghwolf\\Desktop\\ssl\\client.p12";
String clientKeyStorePwd = PWD;
String clientKeyPwd = PWD;

// String clientTrustKeyStoreFile = "E:\\User\\Ghwolf\\Desktop\\ssl\\client.truststore";
// String clientTrustKeyStorePwd = PWD;

KeyStore clientKeyStore = KeyStore.getInstance("JKS");
clientKeyStore.load(new FileInputStream(clientKeyStoreFile), clientKeyStorePwd.toCharArray());
// KeyStore clientKeyStore = fun();
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(clientKeyStore, clientKeyPwd.toCharArray());

// KeyStore clientTrustKeyStore = KeyStore.getInstance("JKS");
// clientTrustKeyStore.load(new FileInputStream(clientTrustKeyStoreFile), clientTrustKeyStorePwd.toCharArray());
// TrustManagerFactory trustManagerFactory = TrustManagerFactory
// .getInstance(TrustManagerFactory.getDefaultAlgorithm());
// trustManagerFactory.init(clientTrustKeyStore);

SSLContext sslContext = SSLContext.getInstance("TLSv1.2");

TrustManager[] trustAllCerts = new TrustManager[1];
TrustManager tm = new miTM();
trustAllCerts[0] = tm;

sslContext.init(keyManagerFactory.getKeyManagers(), trustAllCerts, null);
// sslContext.init(keyManagerFactory.getKeyManagers(),trustManagerFactory.getTrustManagers(), null);

return sslContext.getSocketFactory();
}

public static void copy(InputStream in, OutputStream out) throws IOException {
byte[] buffer = new byte[512];
int n = -1;
while ((n = in.read(buffer)) != -1) {
out.write(buffer, 0, n);
}
in.close();
out.close();
}

public static KeyStore fun() {
SunMSCAPI providerMSCAPI = new SunMSCAPI();
Security.addProvider(providerMSCAPI);
KeyStore ks;
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
try {
ks = KeyStore.getInstance("Windows-MY");
ks.load(null, null);
// Enumeration aliases = ks.aliases();
// while (aliases.hasMoreElements()) {
// String alias = (String) aliases.nextElement();
// Certificate[] certs = ks.getCertificateChain(alias);
// if (certs != null) {
// System.out.println("证书链:" + alias + "':");
// for (int c = 0; c < certs.length; c ++) {
// if (certs[c] instanceof X509Certificate) {
// X509Certificate cert = (X509Certificate) certs[c];
// System.out.println(cert.getPublicKey());
// System.out.println(ks.getKey(alias, "123456".toCharArray()));
// System.out.println(" 顺序: " + (c + 1) + ":");
// System.out.println(" 主题: " + cert.getSubjectDN());
// System.out.println(" 签名算法: " + cert.getSigAlgName());
// System.out.println(" 签发时间: " + sdf.format(cert.getNotBefore()));
// System.out.println(" 到期时间: " + sdf.format(cert.getNotAfter()));
// System.out.println(" 签发者: " + cert.getIssuerDN());
// }
// }
// }
// }
} catch (Exception e) {
throw new RuntimeException(e);
}
return ks ;
}

public static void print() {
SunMSCAPI providerMSCAPI = new SunMSCAPI();
Security.addProvider(providerMSCAPI);
KeyStore ks;
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
try {
ks = KeyStore.getInstance("Windows-MY");
ks.load(null, null);
Enumeration aliases = ks.aliases();
while (aliases.hasMoreElements()) {
String alias = (String) aliases.nextElement();
Certificate[] certs = ks.getCertificateChain(alias);
if (certs != null) {
System.out.println("证书链:" + alias + "':");
for (int c = 0; c < certs.length; c ++) {
if (certs[c] instanceof X509Certificate) {
X509Certificate cert = (X509Certificate) certs[c];
System.out.println(cert.getPublicKey());
// System.out.println(ks.getKey(alias, PWD.toCharArray()));
System.out.println(" 顺序: " + (c + 1) + ":");
System.out.println(" 主题: " + cert.getSubjectDN());
System.out.println(" 签名算法: " + cert.getSigAlgName());
System.out.println(" 签发时间: " + sdf.format(cert.getNotBefore()));
System.out.println(" 到期时间: " + sdf.format(cert.getNotAfter()));
System.out.println(" 签发者: " + cert.getIssuerDN());
}
}
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}


之前在一个win7的电脑上实验是成功的。这次用的win10,出错了。也正因为如此,我将tomcat版本,jdk版本都换成和之前一样的,还是不行。也不清楚到底是哪里出现的问题。

对这种错误网上说法非常多,但是经过实验都不能解决我的问题。

最后进行抓包分析:


注意上面两个红圈,浏览器请求的抓包我也分析了(图就不上了),经过分析对比,发现就是在Server Hello之后,客户端向服务端发起Client Key Exchange请求有巨大的区别。正常在这一步,客户端会把客户端证书一部分信息发送给服务端,包大小至少在500字节,但是现在却只有218字节,查看包内容发现,Certificates Length为0,根本就没有传输证书信息,这是为什么?
也因为如此,第14行服务端返回了Bad Certificate,导致出错。

网上并没有相关解答,这个貌似也不是证书错误导致的,这个是怎么回事,有没有人知道啊。



...全文
1565 2 打赏 收藏 转发到动态 举报
写回复
用AI写文章
2 条回复
切换为时间正序
请发表友善的回复…
发表回复
tianfang 2020-01-08
  • 打赏
  • 举报
回复
很可能是用于jdk 的小版本升级中改变了 TLS支持的加密算法设置导致的,导致客户端不支持服务器的加密算法,所以不传送证书 变更记录: https://www.java.com/en/jre-jdk-cryptoroadmap.html 修改TLS支持的加密算法设置方法: https://www.java.com/en/configure_crypto.html
Revolution_lxx 2020-01-08
  • 打赏
  • 举报
回复
是否解决? 我这里也是这个问题,集中在 jdk 1.8.0_45 jdk 1.8.0_24

62,628

社区成员

发帖
与我相关
我的任务
社区描述
Java 2 Standard Edition
社区管理员
  • Java SE
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧