背景介绍
https不了解,对https双向认证更是一脸懵
客户方要求系统提供https的服务,一年前申请某网站的免费证书,下载后包含了各种web容器的证书,应用程序的web容器为tomcat8,最后选用了tomcat下的证书,为jks格式。
一年后免费服务到期,需要更换证书,客户申请了阿里云的证书,下载下来的格式为pfx,客户说只有这种格式的。坑已挖好。
操作记录
百度搜索结果说可以将pfx转换成jks
命令如下:
keytool -importkeystore -srckeystore xxx.pfx -destkeystore xxx.jks -srcstoretype PKCS12 -deststoretype JKS -srcstorepass xxx -deststorepass xxx -srcalias alias -destalias destalias
因为原来tomcat中配置了jks相关的信息,想着直接将新的jks的文件名和密码保持一致,直接替换原文件就可以了。结果被坑。
按原来的文件名、密码生成新证书后,启动tomcat,事实证明想的还是太简单。
报错信息如下:
15-Nov-2019 12:20:20.221 SEVERE [main] org.apache.coyote.AbstractProtocol.init Failed to initialize end point associated with ProtocolHandler ["http-nio-443"]
java.security.UnrecoverableKeyException: Cannot recover key
at sun.security.provider.KeyProtector.recover(Unknown Source)
at sun.security.provider.JavaKeyStore.engineGetKey(Unknown Source)
at sun.security.provider.JavaKeyStore$JKS.engineGetKey(Unknown Source)
at java.security.KeyStore.getKey(Unknown Source)
at sun.security.ssl.SunX509KeyManagerImpl.(Unknown Source)
at sun.security.ssl.KeyManagerFactoryImpl$SunX509.engineInit(Unknown Source)
at javax.net.ssl.KeyManagerFactory.init(Unknown Source)
at org.apache.tomcat.util.net.jsse.JSSESocketFactory.getKeyManagers(JSSESocketFactory.java:617)
at org.apache.tomcat.util.net.jsse.JSSESocketFactory.getKeyManagers(JSSESocketFactory.java:546)
.
.
.
Caused by: org.apache.catalina.LifecycleException: Protocol handler initialization failed
at org.apache.catalina.connector.Connector.initInternal(Connector.java:962)
at org.apache.catalina.util.LifecycleBase.init(LifecycleBase.java:102)
... 12 more
Caused by: java.security.UnrecoverableKeyException: Cannot recover key
at sun.security.provider.KeyProtector.recover(Unknown Source)
at sun.security.provider.JavaKeyStore.engineGetKey(Unknown Source)
处理办法:
将新的jks的密码和pfx的密码保持一致,修改tomcat配置文件中的密码。
其实tomcat可以直接配置pfx格式证书,需要指定 keystoreType="PKCS12"
keystoreFile="/证书路径/名称.pfx" keystoreType="PKCS12" keystorePass="证书密码"
但这次的客户有做https双向认证,之前的代码已经写死支持jks证书库,所以需要生成jks
遇到的异常说明
密码错误异常
15-Nov-2019 11:59:55.017 SEVERE [main] org.apache.coyote.AbstractProtocol.init Failed to initialize end point associated with ProtocolHandler ["http-nio-443"]
java.io.IOException: Keystore was tampered with, or password was incorrect
at sun.security.provider.JavaKeyStore.engineLoad(Unknown Source)
at sun.security.provider.JavaKeyStore$JKS.engineLoad(Unknown Source)
.
.
.
Caused by: java.security.UnrecoverableKeyException: Password verification failed
... 25 more
pfx和jks密码不匹配
异常信息如下:
15-Nov-2019 12:20:20.221 SEVERE [main] org.apache.coyote.AbstractProtocol.init Failed to initialize end point associated with ProtocolHandler ["http-nio-443"]
java.security.UnrecoverableKeyException: Cannot recover key
双向认证
示例代码
import javax.net.ssl.*;
import java.io.*;
import java.net.URL;
import java.nio.charset.Charset;
import java.security.KeyStore;
/**
* #2
* HTTPS 双向认证 - use truststore
* 原生方式
*
* @Author soft_xiang
* @Date 7/11/2017
*/
public class HttpsTruststoreNativeDemo {
// 客户端证书路径,用了本地绝对路径,需要修改 调用方证书
private final static String CLIENT_CERT_FILE = "D:\\xxx\\https\\xxx-ip.p12";
// 客户端证书密码
private final static String CLIENT_PWD = "pwd";
// 信任库路径 (被调用方证书合集)
private final static String TRUST_STRORE_FILE = "D:\\xxx\\https\\xxx.jks";
// 信任库密码
private final static String TRUST_STORE_PWD = "xxx";
private static String readResponseBody(InputStream inputStream) throws IOException {
try {
BufferedReader br = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8")));
StringBuffer sb = new StringBuffer();
String buff = null;
while ((buff = br.readLine()) != null) {
sb.append(buff + "\n");
}
return sb.toString();
} finally {
inputStream.close();
}
}
public static void httpsCall() throws Exception {
// 初始化密钥库
KeyManagerFactory keyManagerFactory = KeyManagerFactory
.getInstance("SunX509");
KeyStore keyStore = getKeyStore(CLIENT_CERT_FILE, CLIENT_PWD, "PKCS12");
keyManagerFactory.init(keyStore, CLIENT_PWD.toCharArray());
// 初始化信任库
TrustManagerFactory trustManagerFactory = TrustManagerFactory
.getInstance("SunX509");
KeyStore trustkeyStore = getKeyStore(TRUST_STRORE_FILE, TRUST_STORE_PWD, "JKS");
trustManagerFactory.init(trustkeyStore);
// 初始化SSL上下文
SSLContext ctx = SSLContext.getInstance("SSL");
ctx.init(keyManagerFactory.getKeyManagers(), trustManagerFactory
.getTrustManagers(), null);
SSLSocketFactory sf = ctx.getSocketFactory();
HttpsURLConnection.setDefaultSSLSocketFactory(sf);
String url = "post url";
URL urlObj = new URL(url);
HttpsURLConnection con = (HttpsURLConnection) urlObj.openConnection();
con.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36");
con.setRequestProperty("Accept-Language", "zh-CN;en-US,en;q=0.5");
con.setRequestProperty("Content-Type", "text/xml");
con.setRequestMethod("POST");
con.setRequestProperty("Content-Length", "0");
con.setDoInput(true);
con.setDoOutput(true);
DataOutputStream os = new DataOutputStream(con.getOutputStream());
os.write("".getBytes("UTF-8"), 0, 0);
os.flush();
os.close();
con.connect();
InputStream is = con.getInputStream();
Integer code = con.getResponseCode();
final String contentType = con.getContentType();
System.out.println(code + ":" + contentType);
String response = readResponseBody(is);
System.out.println(response);
}
/**
* 获得KeyStore
*
* @param keyStorePath
* @param password
* @return
* @throws Exception
*/
private static KeyStore getKeyStore(String keyStorePath, String password, String type)
throws Exception {
FileInputStream is = new FileInputStream(keyStorePath);
KeyStore ks = KeyStore.getInstance(type);
ks.load(is, password.toCharArray());
is.close();
return ks;
}
public static void main(String[] args) throws Exception {
httpsCall();
}
}
双向认证示例中可能碰到的问题
在允许调用的服务器,直接浏览器打开需要访问的地址,调用出现401,sap问题,协调sap解决
证书错误,确认sap加入信任库的证书和代码中调用的证书库中的证书是同一个
常用命令
keytool -import -alias xxx -file "xxx.der" -keystore xxx.jks -storepass pass
将证书导入信任库
keytool -export -alias xxx -keystore abc.jks -storepass pass -file xxx.cer
将证书库abc.jks中别名为xxx的证书导出为xxx.cer,cer客户端证书
keytool -import -alias xxx -file xxx.cer -keystore "%JAVA_HOME%/jre/lib/security/cacerts" -storepass changeit -trustcacerts
将客户端证书导入jdk的默认信任库(cacerts为jdk默认信任库,导入之前记得备份)