基于Java-Tomcat 实现HTTPS双向验证
1.使用JDK自带的keytool生成各种证书
使用key_tool_test.bat,利用JDK自带的keytool文件生成各种证书,包括CA证书,使用CA签发的server证书,client证书。
在windows环境下跑key_tool_test.bat之前,需要保证当前系统环境变量PATH中能找到JDK自带的keytool工具
@echo off echo= echo ------------------------------------------------------------------------------------------------ echo generate self-signed certificate, make sure you have the keytool command in your current PATH echo ------------------------------------------------------------------------------------------------ echo= FOR /F "delims=" %%I IN ("keytool.exe") DO (if exist %%~$PATH:I (echo keytool is ok, let's go) else ( echo you don't have keytool in you PATH, please install JRE. & goto end)) echo= set certs_path=.\test_certs if exist %certs_path% rd /s/q %certs_path% mkdir %certs_path% cd %certs_path% :: 1. create CA, self-signed, keep an eye on the CN in the -dname file keytool -genkeypair -keyalg RSA -keysize 2048 -sigalg SHA1withRSA -validity 36000 -alias test_alias -keystore CA.keystore -dname "CN=TestRootCA,OU=MyCompany,O=MyGroup,L=PuDong,ST=ShangHai,C=China" -storepass 123456 -keypass 123456 ::create a server certificate, also self-signed keytool -genkeypair -keyalg RSA -keysize 2048 -sigalg SHA1withRSA -validity 36000 -alias test_alias -keystore server.keystore -dname "CN=localhost,OU=MyCompany,O=MyGroup,L=PuDong,ST=ShangHai,C=China" -storepass 123456 -keypass 123456 ::create a client certificate, also self-signed keytool -genkeypair -keyalg RSA -keysize 2048 -sigalg SHA1withRSA -validity 36000 -alias test_alias -keystore client.keystore -dname "CN=personal_name,OU=MyCompany,O=MyGroup,L=PuDong,ST=ShangHai,C=China" -storepass 123456 -keypass 123456 goto comment :: 2. check keys in keystore using alias keytool -list -rfc -alias test_alias -keystore server.keystore -storepass 123456 keytool -list -v -alias test_alias -keystore server.keystore -storepass 123456 keytool -list -v -keystore server.keystore -storepass 123456 keytool -list -v -keystore CA.keystore -storepass 123456 keytool -list -v -keystore client.keystore -storepass 123456 :comment :: 3. exprot the self-signed certificate(keytool generate a self-signed certificate for the public key after the generation of a keypair) ::export CA certfication keytool -exportcert -alias test_alias -keystore CA.keystore -file CA.cer -storepass 123456 ::export server certificate keytool -exportcert -alias test_alias -keystore server.keystore -file server.cer -storepass 123456 ::export client certificate keytool -exportcert -alias test_alias -keystore client.keystore -file client.cer -storepass 123456 ::uncomment this to check the certificate of server ::keytool -printcert -file server.cer -rfc :: 4. generate certificate request file(.csr) ::for server keytool -certreq -alias test_alias -keystore server.keystore -file server.csr -storepass 123456 -v ::for client keytool -certreq -alias test_alias -keystore client.keystore -file client.csr -storepass 123456 -v :: 5. using CA to issue the server certificate :: using the private key of the CA to issue the request server public key and distinguished name, output format is binary keytool -gencert -alias test_alias -keystore CA.keystore -storepass 123456 -infile server.csr -outfile server-CA-signed.cer :: for client keytool -gencert -alias test_alias -keystore CA.keystore -storepass 123456 -infile client.csr -outfile client-CA-signed.cer :: If -rfc is specified, output format is BASE64-encoded PEM; otherwise, a binary DER is created. ::keytool -gencert -alias test_alias -keystore CA.keystore -storepass 123456 -infile server.csr -outfile server-CA-signed.cer -rfc :: for client ::keytool -gencert -alias test_alias -keystore CA.keystore -storepass 123456 -infile client.csr -outfile client-CA-signed.cer -rfc ::delete csr file, after issue rm /s/f server.csr rm /s/f client.csr ::6. import certificate ::server - import CA cert firstly, because the CA(Issuer) cert is not trusted keytool -importcert -trustcacerts -alias test_tursted_ca -file CA.cer -keystore server.keystore -storepass 123456 -v ::server - replace the self-signed certificate with the CA signed certificate keytool -importcert -trustcacerts -alias test_alias -file server-CA-signed.cer -keystore server.keystore -storepass 123456 -v ::client - import CA cert firstly, because the CA(Issuer) cert is not trusted keytool -importcert -trustcacerts -alias test_tursted_ca -file CA.cer -keystore client.keystore -storepass 123456 -v ::client - replace the self-signed certificate with the CA signed certificate keytool -importcert -trustcacerts -alias test_alias -file client-CA-signed.cer -keystore client.keystore -storepass 123456 -v ::7. build the truststore ::server trust CA and the client-CA-signed.cer keytool -importcert -trustcacerts -alias test_tursted_ca -file CA.cer -keystore server-trust.keystore -storepass 123456 -v keytool -importcert -trustcacerts -alias test_tursted_client -file client-CA-signed.cer -keystore server-trust.keystore -storepass 123456 -v ::client trust CA and the server-CA-signed.cer keytool -importcert -trustcacerts -alias test_tursted_ca -file CA.cer -keystore client-trust.keystore -storepass 123456 -v keytool -importcert -trustcacerts -alias test_tursted_server -file server-CA-signed.cer -keystore client-trust.keystore -storepass 123456 -v ::8. covert .jks to pcks12 if needed ::server ::keytool -importkeystore -srckeystore server.keystore -destkeystore server.p12 -deststoretype PKCS12 ::client keytool -importkeystore -srckeystore client.keystore -destkeystore client.p12 -deststoretype PKCS12 cd .. :end
2. 跑上述BAT脚本,生成证书
可以在任意目录下跑key_tool_test.bat,命令执行过程中,需要选择yes/no的全部输入yes, 需要输入密码的全部输入123456。
3. 证书生成的文件说明
执行脚本之后,在当前目录下会生成test_certs目录,该目录下包含如下文件:
CA.cer — | CA 根证书,可以和由此CA签发的证书组成证书链,如果浏览器信任CA根证书 |
CA.keystore — | CA的keystore,包含了CA根证书和CA的私钥,CA用这个私钥给其他公钥签名完成证书签发 |
client-CA-signed.cer — | client证书,该证书时由CA签名的 |
client-trust.keystore — | 如果你信任client,就把client-trust.keystore里的证书(就是上面的client-CA-signed.cer) 导入了你自己的truststore中,JRE中该文件一般为jdk1.8.0_65\jre\lib\security\cacerts |
client.cer — | client证书, 自签名的 |
client.keystore — | client的keystore,保存有client的私钥和证书 |
client.p12 — | client的keystore, 使用的是p12格式的,因为一般客户端keystore通常保存成.p12文件,双向认证时需要提供这个文件给浏览器 |
server-CA-signed.cer — | 服务端证书,由CA签名了的 |
server-trust.keystore — | 如果你信任server,就把这个server-trust.keystore里的证书(就是server-CA-signed.cer) 导入到你自己的truststore中,JRE中该文件一般为jdk1.8.0_65\jre\lib\security\cacerts |
server.cer — | 服务器证书,自签名的 |
server.keystore — | server的keystore,包含私钥和服务器证书 |
server.p12 — | serverkeystore的另一种存储形式. |
4. Java 代码导入证书
使用HttpClient等客户端时,不信任自签名证书,报如下异常:
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
可以使用如下程序将证书导入JRE受信任的证书库。 比如可以将在你的程序的clapass根目录下新建一个self-singed-certs目录,将上面的
client-CA-signed.cer 以及 CA.cer 都拷贝到这个目录下面,跑下SelfSignedCertHandler,即可把这两个证书导入到JRE的truststore中
jdk1.8.0_65\jre\lib\security\cacerts。
import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.security.KeyStore; import java.security.cert.Certificate; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; public class SelfSignedCertHandler { static { handleSelfSignedCert(); } private static final String KEY_STORE_PASSWORD = "changeit"; private SelfSignedCertHandler() { } public static void main(String[] args) { } private static void handleSelfSignedCert(){ try { String trustStorePath = System.getProperty("java.home") + "/lib/security/cacerts"; System.out.println(trustStorePath); KeyStore ks = KeyStore.getInstance("jks"); FileInputStream is = new FileInputStream(new File(trustStorePath)); ks.load(is,KEY_STORE_PASSWORD.toCharArray()); is.close(); String certsPath = SelfSignedCertHandler.class.getClassLoader().getResource("self-singed-certs").getPath(); File file = new File(certsPath); File[] files = file.listFiles(); int i=0; for(File f:files) { BufferedInputStream bis = new BufferedInputStream(new FileInputStream(f)); CertificateFactory cf = CertificateFactory.getInstance("X.509"); Certificate cert = cf.generateCertificate(bis); X509Certificate cert509 = (X509Certificate) cert; String alias ="##_self_signed_cert_" + i + "_##"; ks.deleteEntry(alias); ks.setCertificateEntry(alias, cert509); bis.close(); i++; } FileOutputStream fos = new FileOutputStream(trustStorePath); ks.store(fos,KEY_STORE_PASSWORD.toCharArray()); fos.close(); } catch (Exception e) { e.printStackTrace(); } } }
5. 配置tomcat 7.0.40 Https的双向认证
将上面生成的 server.keystore 和 client-trust.keystore 拷贝到tomcat的conf目录下面,修改conf目录下面的 server.xml文件, 注释掉 28 行 <Listener className=”org.apache.catalina.core.AprLifecycleListener” SSLEngine=”on” /> 添加如下的connector配置
<Connector port="8443" protocol="HTTP/1.1" SSLEnabled="true" maxThreads="150" scheme="https" secure="true" clientAuth="true" sslProtocol="TLS" keystoreType="JKS" keystoreFile="conf/server.keystore" keystorePass="123456" truststoreType="JKS" truststoreFile="conf/client-trust.keystore" truststorePass="123456" SSLVerifyClient="require" SSLEngine="on"/>
6. 验证https双向认证配置
创建一个简单的maven web工程, 添加一个servlet,并修改web.xml, 打成war包之后,部署到上面的tomcat 7中
import java.io.IOException; import java.io.PrintWriter; import java.security.cert.X509Certificate; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * Servlet implementation class TestHttps */ public class TestHttps extends HttpServlet { private static final String ATTR_CER = "javax.servlet.request.X509Certificate"; private static final String CONTENT_TYPE = "text/plain;charset=UTF-8"; private static final String DEFAULT_ENCODING = "UTF-8"; private static final String SCHEME_HTTPS = "https"; private static final long serialVersionUID = 1L; /** * Default constructor. */ public TestHttps() { // TODO Auto-generated constructor stub } /** * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response) */ protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType(CONTENT_TYPE); response.setCharacterEncoding(DEFAULT_ENCODING); PrintWriter out = response.getWriter(); X509Certificate[] certs = (X509Certificate[]) request.getAttribute(ATTR_CER); if (certs != null) { int count = certs.length; out.println("共检测到[" + count + "]个客户端证书"); for (int i = 0; i < count; i++) { out.println("客户端证书 [" + (++i) + "]: "); out.println("校验结果:" + verifyCertificate(certs[--i])); out.println("证书详细:\r" + certs[i].toString()); } } else { if (SCHEME_HTTPS.equalsIgnoreCase(request.getScheme())) { out.println("这是一个HTTPS请求,但是没有可用的客户端证书"); } else { out.println("这不是一个HTTPS请求,因此无法获得客户端证书列表 "); } } out.close(); } /** * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response) */ protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } /** * <p> * 校验证书是否有效 * </p> * * @param certificate * @return */ private boolean verifyCertificate(X509Certificate certificate) { boolean valid = true; try { certificate.checkValidity(); } catch (Exception e) { e.printStackTrace(); valid = false; } return valid; } }
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" > <web-app> <display-name>Archetype Created Web Application</display-name> <servlet> <servlet-name>TestHttps</servlet-name> <display-name>TestHttps</display-name> <description>Test Https</description> <servlet-class>com.cv.test.https.TestHttps</servlet-class> </servlet> <servlet-mapping> <servlet-name>TestHttps</servlet-name> <url-pattern>/TestHttps</url-pattern> </servlet-mapping> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> <!-- 强制SSL配置,即普通的请求也会重定向为SSL请求 --> <security-constraint> <web-resource-collection> <web-resource-name>SSL</web-resource-name> <url-pattern>/*</url-pattern><!-- 全站使用SSL --> </web-resource-collection> <user-data-constraint> <description>SSL required</description> <!-- CONFIDENTIAL: 要保证服务器和客户端之间传输的数据不能够被修改,且不能被第三方查看到 --> <!-- INTEGRAL: 要保证服务器和client之间传输的数据不能够被修改 --> <!-- NONE: 指示容器必须能够在任一的连接上提供数据。(即用HTTP或HTTPS,由客户端来决定)--> <transport-guarantee>CONFIDENTIAL</transport-guarantee> </user-data-constraint> </security-constraint> </web-app>
7. 验证
使用chrome浏览, http://localhost:8080/test-https/TestHttps 会自动充定向到https的8443端口:https://localhost:8443/test-https/TestHttps
此时无法访问,提示证书神马的问题,这时需要将上面的CA.cer导入到chrome的受信任的根证书里,将client.p12导入到个人证书中,再访问上面地址,
能正常显示了:
共检测到[1]个客户端证书
客户端证书 [1]:
校验结果:true
证书详细:
[
[
Version: V3
Subject: CN=py23623, OU=CitiVelocity, O=CitiGroup, L=Zhangjiang, ST=ShangHai, C=China
Signature Algorithm: SHA256withRSA, OID = 1.2.840.113549.1.1.11
Key: Sun RSA public key, 2048 bits
modulus: 20270938183404489411882679147615003708022771319791246766352755709421048993390671500814312047261305774227599254430607099829349791104494527438782444351723552021248311666035092682053082192614979870383532903088276288218686143364981910347409021588267407434306861766119216720923682563960686924926260163058355449806821628581885882185630150303516611545553672179304747064724826075353857812784111382426128732787287520687325029561702413055618299271507672491376470835987959975630333225755125837042275143982924505776655661203606940763040706487012472623085175598453078112781948036391572088916316169918247603496181577560559917860989
public exponent: 65537
Validity: [From: Mon Nov 20 15:13:47 CST 2017,
To: Sun Feb 18 15:13:47 CST 2018]
Issuer: CN=TestRootCA, OU=CitiVelocity, O=CitiGroup, L=Zhangjiang, ST=ShangHai, C=China
SerialNumber: [ 1806788c]
Certificate Extensions: 2
[1]: ObjectId: 2.5.29.35 Criticality=false
AuthorityKeyIdentifier [
KeyIdentifier [
0000: 43 64 B8 64 70 85 B6 9D FA CC F3 27 B2 B1 FA 4E Cd.dp……’…N
0010: 40 9E 1E B4 @…
]
]
[2]: ObjectId: 2.5.29.14 Criticality=false
SubjectKeyIdentifier [
KeyIdentifier [
0000: 89 F2 A4 90 E8 B9 C9 A4 C0 22 29 27 FB 29 36 8C ………”)’.)6.
0010: EE AE B5 0D ….
]
]
]
Algorithm: [SHA256withRSA]
Signature:
0000: 3D 73 46 83 5E 07 92 74 BD A1 44 B2 04 72 56 C0 =sF.^..t..D..rV.
0010: FE 3C 5B 12 EB F8 59 1F 3E C9 AD 49 F7 51 B3 FE .<[…Y.>..I.Q..
0020: 68 37 66 BB C9 F0 D0 84 8C 65 B8 B8 E8 F0 CB F6 h7f……e……
0030: 64 28 9E F5 56 7E A4 EA 57 AB 4F 3E 52 2D 5A DF d(..V…W.O>R-Z.
0040: DB 64 63 62 97 5E 12 69 67 61 53 37 43 1D 6F DF .dcb.^.igaS7C.o.
0050: 13 E2 72 B1 AF 78 FC A8 30 9D 89 55 41 B5 EC 6A ..r..x..0..UA..j
0060: 41 D6 B1 D9 77 43 A5 0C E5 86 48 A4 C7 57 E1 DC A…wC….H..W..
0070: F8 F3 20 30 A3 E7 30 4E A6 69 E0 DA BE 36 EE EC .. 0..0N.i…6..
0080: 7A C3 AD 16 AA 18 45 B8 88 75 BB 7C D5 5D 55 BE z…..E..u…]U.
0090: FD A1 83 4B 8C C4 41 6D 03 5D 8F E7 85 2D C4 13 …K..Am.]…-..
00A0: A0 5B 90 A6 5E 33 7D 59 40 C8 94 F6 9A B1 CA 5E .[..^3.Y@……^
00B0: 13 34 51 BD CC 40 24 90 73 36 F9 C3 D2 49 24 76 .4Q..@$.s6…I$v
00C0: D8 B0 22 8B 2E 18 FE D5 55 DB 5E 7E 82 74 27 CE ..”…..U.^..t’.
00D0: E5 B6 7D 26 E8 F3 9C 0D 76 4F 15 40 8C BE 4C 27 …&….vO.@..L’
00E0: FB FA D6 46 5E 85 DB D1 03 04 BE 01 79 C6 9C C9 …F^…….y…
00F0: 55 89 55 E2 95 C4 F5 B9 6F 04 0D 28 A9 06 0C 56 U.U…..o..(…V
]
赞 赏
微信赞赏 支付宝赞赏
本文固定链接: https://www.jack-yin.com/coding/distributed/2629.html | 边城网事