一、前言: 关于如何使用Tomcat服务器实现双向SSL认证的文章很早就有了, 比较实用的文章可以看看 IBM developerWorks 中国网站 2002年5月 配置Tomcat 4使用双向SSL(http://www-900.ibm.com/developerWorks/cn/security/se-tcssl/index.shtml )以及 配置Tomcat 5使用双向SSL (http://thinkbase.net/w/main/Wiki?Tomcat5SSL_ServerAndClient)。 关于原理方面不是本文的重点,故下面只讲解实际操作的过程以及每个过程的说明:
二、需要的软件包: Tomcat 5.0.25 用途:Web Server。 下载: http://jakarta.apache.org/builds/jakarta-tomcat-5.0/release/v5.0.25/bin/ JSSE 1.0,2 用途:用来产生Tocmcat使用的秘钥对(keystore)。 在 JDK 1.4以上版本 中已经自带了这个工具, 因此, 只需要安装 JDK1.4.x 即可 下载: http://java.sun.com/products/jsse/ Openssl 0.9.9.6 用途:用来产生CA证书、签名并生成IE可导入的PKCS#12格式私钥。 下载: http://www.openssl.org/ 以上工具的安装过程可以参考自带的帮助,本文就不再详细描述了。
三、证书环境设置: export CA_DN=/C=CN/ST=GuangDong/L=GuangZhou/O=boss ssl/OU=Boss CACenter/CN=Boss CA Root/emailAddress=winsonhrh@gmail.com
export SERVER_DNAME=CN=21cn.com,OU=Boss Server,O=boss ssl,L=GuangZhou,ST=GuangDong,C=CN export SERVER_KEYPASS=openssl export SERVER_STOREPASS=openssl export SERVER_ALIAS=boss-alias export SERVER_KEYSTORE=boss-alias.keystore
export CLIENT_DN_OU=W21cn-boss-client export CLIENT_DN_CN=Huronghua export CLIENT_DN=/C=CN/O=W21cn Boss/OU=$CLIENT_DN_OU/CN=$CLIENT_DN_CN export SSL_JAVA_HOME=/home/uud/software/jdk1.5.0_06 export JDK_KEYSTORE=$SSL_JAVA_HOME/jre/lib/security/cacerts #JSSE 信任的CA根证书的存储密码, 似乎不能改变 export JDK_STOREPASS=changeit export OPENSSL_SRL_FILE=./ca-cert.srl export CA_ROOT_ALIAS=Boss-CA-ROOT
四、生成CA私钥以及自签名根证书, 最后得到PKCS12格式的CA根证书:
说明: 该命令生成CA私钥以及自签名根证书, 最后得到PKCS12格式的CA根证书(PKCS12格式的CA根证书受密码保护, 因此有比较好的安全性); 生成的PKCS12格式的CA根证书保存在 dist\ca-cert 目录下, 在正式使用的系统中, 这个证书文件(*.pfx)需要妥善保存, 因为以后的服务器证书和客户端证书都需要依赖这个证书, 尤其是客户端证书, 如果丢失了CA根证书, 就无法发布新的客户端证书了, 而重新生成CA根证书, 则需要重新生成服务器证书和客户端证书, 在客户端用户较多的情况下, 重新发布所有的客户端证书是有相当大的工作量; 在执行这个命令的过程中, 会提示输入证书保护密码, 请注意不要遗忘或者泄漏这个密码, 否则根证书的安全会受到威胁.
mkdir ca mkdir dist cd dist mkdir ca-cert cd .. # genrsa [产生密钥命令] -out[密钥文件输出路径] 1024 [密钥位数] openssl genrsa -out ca/ca-key.pem 1024 # req[产生证书命令] -new[新生成] -out[证书文件输出路径] -key[私钥文件路径]
openssl req -new -out ca/ca-req.csr -key ca/ca-key.pem -subj $CA_DN # x509[签发x509证书命令] -req[输入待签发证书] -in[输入待签发证书文件路径] -out[产生x509证书文件输出路径] # -signkey[自签发密钥文件路径] -days[证书有效期] openssl x509 -req -in ca/ca-req.csr -out ca/ca-cert.pem -signkey ca/ca-key.pem -days 365 # 生成CA证书: ca/ca-cert.pfx, 注意一定要记住导出密码:默认为 ssl # pkcs12[生成PKCS12格式证书命令] -export[导出文件] -clerts[仅导出client证书] -password[导出密码] # -in[输入的client证书文件路径] -inkey[client证书密钥文件路径] -out[导出PKS12格式文件路径] openssl pkcs12 -export -clcerts -in ca/ca-cert.pem -inkey ca/ca-key.pem -out ca/ca-cert.pfx # 将生成的证书保存起来(以后需要用到) cp ca/ca-cert.pfx dist/ca-cert/ca-cert-200606.pfx |
五、从PKCS12格式的CA根证书导出 CA私钥和未加密CA根证书
说明: 该命令用于从PKCS12格式的CA根证书中导出CA私钥和未加密CA根证书; 由于安全原因, CA根证书平时以密码保护的PKCS12格式文件(*.pfx)存放, 那么如果需要使用CA根证书来发布服务器证书或者客户端证书时, 首先需要执行这个命令得到CA根证书的未加密形式; 在执行这个命令的过程中, 会出现两次提示输入根证书的保护密码, 如果密码不正确, 这个命令将不能执行成功, 也就无法进行下面两步的发布证书的操作了.
mkdir decrypt_ca # 从PKCS12格式的CA证书导出不加密的CA证书: ca\ca-cert.pem openssl pkcs12 -in dist/ca-cert/ca-cert-20060621.pfx -clcerts -nodes -nokeys -out decrypt_ca/ca-cert.pem # 将得到的证书文件的开头四行(Bag Attributes)去掉 # 因为step2-server.bat中keytool import时会认为含有Bag Attributes的文件"不是一个 X.509 认证" vi ca-cert.pem 先按 4,再按 dd 最后按 x! 退出文件编辑 # 从PKCS12格式的CA证书导出CA私钥: ca\ca-key.pem openssl pkcs12 -in dist/ca-cert/ca-cert-20060621.pfx -clcerts -nodes -out decrypt_ca/file.pem openssl rsa -in decrypt_ca/file.pem -out decrypt_ca/ca-key.pem |
六、生成服务器端证书
说明: 该命令用于生成服务器端证书(keystore文件); 注意: CA根证书同时也会被导入到证书的keystore文件中, 这样在将这个证书应用到Tomcat Web服务器上的时候就不需要将CA根证书 导入到JSSE的默认位置了; 这个命令必须在执行 (五、从PKCS12格式的CA根证书导出 CA私钥和未加密CA根证书) 之后才能正常运行.
生成KeyPair mkdir server # -genkey[产生密钥对] -alias[密钥对别名] -validity[密钥有效期] -keyalg[密钥算法参数] -keysize[密钥位数] # -keypass[密钥保护密码]- storepass[存储密码] # -dname[别名相关附加信息,其中cn是服务器的名字一定要与WEB服务器中设置的一样] -keystore[密钥存储文件路径] keytool -genkey -alias $SERVER_ALIAS -validity 720 -keyalg RSA -keysize 1024 -keypass $SERVER_KEYPASS -storepass $SERVER_STOREPASS -dname $SERVER_DNAME -keystore server/$SERVER_KEYSTORE 生成待签名证书 server/server.csr # -certreq[产生待签名证书] -alias[证书别名] -sigalg[证书算法参数] -file [产生文件输出路径] # -keypass[密钥保护密码] -keystore[存储文件路径] -storepass[存储密码] keytool -certreq -alias $SERVER_ALIAS -sigalg MD5withRSA -file server/server.csr -keypass $SERVER_KEYPASS -keystore server/$SERVER_KEYSTORE -storepass $SERVER_STOREPASS 用CA私钥进行签名, 产生x509证书文件 # x509[签发x509证书命令] -req[输入待签发证书] -in[输入待签发证书文件路径] -out[产生x509证书文件输出路径] # -CA[签发根证书] -CAkey[根证书密钥文件] -days[证书有效期] -CAserial[CA序列号文件] openssl x509 -req -in server/server.csr -out server/server-cert.pem -CA decrypt_ca/ca-cert.pem -CAkey decrypt_ca/ca-key.pem -days 365 -CAserial $OPENSSL_SRL_FILE -sha1 -trustout 导入(替换)信任的CA根证书到JSSE的默认位置(%JDK_KEYSTORE%) # 导入前首先删除(如果原来已经导入过) # JSSE 信任的CA根证书的密钥存储文件路径 keytool -delete -v -alias $CA_ROOT_ALIAS -storepass $JDK_STOREPASS -keystore $JDK_KEYSTORE # -import[导入命令] -v trustcacerts[导入信任证书] -storepass[存储密码] -alias[CA根证书的别名] # -file[证书文件路径] -keystore[导入文件路径] -noprompt[不提示"信任这个认证?"] keytool -import -v -trustcacerts -storepass $JDK_STOREPASS -alias $CA_ROOT_ALIAS -file decrypt_ca/ca-cert.pem -keystore $JDK_KEYSTORE 把CA签名后的server端证书导入keystore: server/%SERVER_KEYSTORE% # -import[导入命令] -v trustcacerts[导入信任证书] -storepass[存储密码] -keypass[密钥保护密码] # -alias[服务器证书的别名] -file[证书文件路径] -keystore[导入文件路径] keytool -import -v -trustcacerts -storepass $SERVER_STOREPASS -keypass $SERVER_KEYPASS -alias $SERVER_ALIAS -file server/server-cert.pem -keystore server/$SERVER_KEYSTORE 把CA根证书导入keystore: server/%SERVER_KEYSTORE% # -import[导入命令] -v trustcacerts[导入信任证书] -storepass[存储密码] -keypass[密钥保护密码] # -alias[服务器证书的别名] -file[证书文件路径] -keystore[导入文件路径] -noprompt[不提示"您仍然想要将它添加到自己的keystore 吗?"] keytool -import -v -trustcacerts -storepass $SERVER_STOREPASS -keypass $SERVER_KEYPASS -alias $CA_ROOT_ALIAS -file decrypt_ca/ca-cert.pem -keystore server/$SERVER_KEYSTORE 查看JSSE CA根证书 keytool -list -storepass $JDK_STOREPASS -keystore $JDK_KEYSTORE -alias $CA_ROOT_ALIAS -v 删除导入到JSSE的默认位置的CA根证书(使JDK恢复原状) keytool -delete -v -alias $CA_ROOT_ALIAS -storepass $JDK_STOREPASS -keystore $JDK_KEYSTORE 将生成的证书保存起来(供服务器使用) cd dist mkdir server cd .. cp server/$SERVER_KEYSTORE dist/server/$SERVER_KEYSTORE 查看server端证书 keytool -list -storepass $SERVER_STOREPASS -keystore dist/server/$SERVER_KEYSTORE -v |
七、发布客户端证书:
说明: 这个命令用于发布客户端证书; 为了便于批量生成客户端证书, 这个命令支持命令行参数, 第1到3个参数依次为: 客户端证书的名称(Common Name) 客户端证书所属的组织(Organizational Unit Name) 产生的PKS12格式客户端证书的导入密码, 这个密码可以保护证书只能被知道密码的用户导入到浏览器 这个命令必须在执行 (五、从PKCS12格式的CA根证书导出 CA私钥和未加密CA根证书) 之后才能正常运行.
mkdir client cd dist mkdir client cd .. # 生成client私钥: client/client-key.pem # genrsa [产生密钥命令] -out[密钥文件输出路径] 1024 [密钥位数] openssl genrsa -out client/client-key.pem 1024 生成待签名证书: client/client-req.csr # req[产生证书命令] -new[新生成] -out[证书文件输出路径] -key[私钥文件路径] # -subj[request's subject, 这里放置Distinguished Name(DN)信息] openssl req -new -out client/client-req.csr -key client/client-key.pem -subj $CLIENT_DN 用CA私钥进行签名, 产生x509证书文件: client/client.crt # x509[签发x509证书命令] -req[输入待签发证书] -in[输入待签发证书文件路径] -out[产生x509证书文件输出路径] # -signkey [密钥文件路径] # -CA[签发根证书] -CAkey[根证书密钥文件] -days[证书有效期] -CAserial[CA序列号文件] openssl x509 -req -in client/client-req.csr -out client/client.crt -signkey client/client-key.pem -CA decrypt_ca/ca-cert.pem -CAkey decrypt_ca/ca-key.pem -days 365 -CAserial $OPENSSL_SRL_FILE 生成client端的个人证书: client/$_CLIENT_P12_FILE # pkcs12[生成PKS12格式证书命令] -export[导出文件] -clerts[仅导出client证书] -password[导出密码] # -in[输入的client证书文件路径] -inkey[client证书密钥文件路径] -out[导出PKS12格式文件路径] # -name[好记的名字] 密码是 clientssl openssl pkcs12 -export -clcerts -in client/client.crt -inkey client/client-key.pem -out client/$CLIENT_DN_OU-$CLIENT_DN_CN.p12 -name $CLIENT_DN_OU-$CLIENT_DN_CN 将生成的证书保存起来(发布给用户) cp client/$CLIENT_DN_OU-$CLIENT_DN_CN.p12 dist/client/$CLIENT_DN_OU-$CLIENT_DN_CN.p12 |
八、Tomcat 5 服务器配置:
参考配置方法如下: 将 "六、生成服务器端证书" 命令产生的 dist\server 目录下的 keystore 文件(如果使用默认配置, 这个文件叫做"boss-alias.keystore")复制到 Tomcat 安装目录的 conf 目录下; 修改 Tomcat 安装目录的 conf 目录下的 "server.xml" 文件, 修改 <Service name="Catalina"> 包含的 "Connector" 元素, 示例如下(仅供参考):
<Connector port="8443" maxHttpHeaderSize="8192" maxThreads="150" minSpareThreads="25" maxSpareThreads="75" enableLookups="false" disableUploadTimeout="true" acceptCount="100" scheme="https" secure="true" clientAuth="true" sslProtocol="TLS" URIEncoding="UTF-8" keystoreFile="%Tomcat%/conf/boss-alias.keystore" keystorePass="openssl" truststoreFile="%Tomcat%/conf/boss-alias.keystore" truststorePass="openssl"/> |
九、启用双向 SSL 时 Web 应用程序的配置 :
要启用双向 SSL 认证, 在 Web 应用程序的 web.xml 中需要如下增加一些配置: auth-method=CLIENT-CERT 说明是"以客户端数字证书来确认用户的身份", transport-guarantee=CONFIDENTIAL 表示应用程序要求数据必须在一种"防止其他实体看到传输的内容的方式中传送"..
<login-config> <!-- Authorization setting for SSL --> <auth-method>CLIENT-CERT</auth-method> <realm-name>Client Cert Users-only Area</realm-name> </login-config> <security-constraint> <!-- Authorization setting for SSL --> <web-resource-collection > <web-resource-name >SSL</web-resource-name> <url-pattern>/*</url-pattern> </web-resource-collection> <user-data-constraint> <transport-guarantee>CONFIDENTIAL</transport-guarantee> </user-data-constraint> </security-constraint> |
十、编写 JSP 页面显示 证书的内容:
经过以上的配置之后, 那么即使用户是通过 http 访问 Web 应用程序的, 浏览器也会自动切换到 https 方式并弹出选择客户端证书的对话框. 如何使用客户端证书进行用户验证
在 web 应用程序中, 可以通过 java 代码从 request 对象中获得, 根据 Servlet Specifications, 使用request.getAttribute("javax.servlet.request.X509Certificate") 就可以得到 https 请求的客户端证书链信息, 其中第一个元素就是客户端证书, 相应的示例代码如下:
<%@ page import="java.security.cert.X509Certificate"%> <% //response.sendRedirect("jsp/vpay/input.jsp"); String certSubject = null; java.security.cert.X509Certificate[] certChain= (java.security.cert.X509Certificate[]) request.getAttribute("javax.servlet.request.X509Certificate"); if (null!=certChain){ int len=certChain.length; if (len>0){ java.security.cert.X509Certificate cert = (java.security.cert.X509Certificate)certChain[0]; java.security.Principal pSubject = cert.getSubjectDN(); certSubject = pSubject.getName(); } } %> Subject = <%=certSubject%> |
十一、导入客户端证书并访问 HTTPS 应用:
导入客户端证书到浏览器中(双击客户端证书文件 "W21cn-boss-client-Huaronghu.p12" 即可导入 IE)。 导入完毕后, 启动 Tomcat, 可以看到 http://localhost:8080/index.jsp 内容的访问则会自动切换到 https://localhost:8443 上去了; 同时可以看到, 使用 HTTPS 方式访问时, 客户端证书的 Subject 可以被 jsp 页面获得。
试着在浏览器里面把导入的证书删除, 你会发现 网站的内容已经不能访问了:
十二、总结:
当Web服务器开始正式运行以后, "四、生成CA私钥以及自签名根证书, 最后得到PKCS12格式的CA根证书" 命令是不能再次执行的, 如果需要重新发布服务器证书, 或者发布新的客户端证书, 在执行 “六、生成服务器端证书” 和 “七、生成客户器端证书” 命令前, 可以通过 “五、从PKCS12格式的CA根证书导出 CA私钥和未加密CA根证书”重新从保存的PKCS12格式CA根证书中导出CA私钥和未加密CA根证书. |