当前位置: 首页 > 分布式架构 > 正文

基于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 | 边城网事

该日志由 边城网事 于2017年11月21日发表在 分布式架构 分类下, 你可以发表评论,并在保留原文地址及作者的情况下引用到你的网站或博客。
原创文章转载请注明: 基于Java-Tomcat 实现HTTPS双向验证 | 边城网事

基于Java-Tomcat 实现HTTPS双向验证 暂无评论

发表评论

快捷键:Ctrl+Enter