基础概念介绍:秘钥/证书/https握手/CA相关概念
crt证书: 只含有公钥
p12证书: 是包含证书(含公钥)和私钥
JKS(Java key store): 存放密钥的容器。.jks .keystore .truststore等
KeyStore: 服务器的密钥存储库,存服务器的公钥私钥证书
TrustStore: 服务器的信任密钥存储库,存CA公钥(也就是CA根证书)
- 单向认证需要文件 :
- tomcat:
root.crt: 客户端使用的CA根证书,用来验证服务器传来的证书
server.keystore 服务端证书存放的容器 - apache:
server.crt: 服务器端证书
server.key: 服务器端私钥
root.crt: CA根证书
- **双向认证需要文件 : **
tomcat:
root.crt : 客户端使用的CA根证书
client.p12 : 客户端证书包含私钥
root.truststore : CA公钥存放到受信赖的容器
server.keystore : 服务端证书存放的容器
apache:
server.crt 服务器端证书
server.key 服务器端私钥
一.环境说明:
系统:CentOS release 5.5 (Final)
OpenSSL: OpenSSL 1.1.0c
apache: Apache/2.2.23 (Unix)
tomcat: Apache Tomcat/7.0.69
客户端浏览器:Chrome 49.0.2623.110 (64-bit)
二.配置https总体流程
制作CA根证书 ( 代表第三方权威CA机构 )
制作服务器端证书 -> 使用CA根证书签名认证服务器端证书
制作客户端证书 -> 使用CA根证书签名认证客户端证书(
双向认证需要
)配置服务器端配置文件, apache(http.conf) tomcat(server.xml),开启ssl
在客户端浏览器证书管理->授权中信中导入CA根证书(本步骤是自己搭建CA认证需要的,如果证书是第三方认证机构颁发的则不需要配置该步骤,因为各大浏览器厂商已经默认导入了CA权威机构的根证书<
包含对应公钥
>);
6.在客户端浏览器证书管理->您的证书(个人)中导入客户端证书(双向认证需要
),该证书是服务器端配置了双向认证后,服务器端需要验证客户端的证书;
三.配置https详细流程
目录结构:
CA根证书信息目录:/home/lxf/ca
服务证书信息目录:/home/lxf/ca/server
客户端证书信息目录:/home/lxf/ca/client
1.制作CA根证书 ( 代表第三方权威CA机构 )
(1). 创建根证书密钥文件(自己做CA) root.key
cd /home/lxf/ca
openssl genrsa -des3 -out root.key 2048
输出内容为:
Generating RSA private key, 2048 bit long modulus
.....................................................................................................................+++
..........................+++
e is 65537 (0x010001)
Enter pass phrase for root.key: ← 输入一个新密码
Verifying – Enter pass phrase for root.key: ← 重新输入一遍密码
(2). 创建根证书的申请文件 root.csr
openssl req -new -key root.key -out root.csr
输出内容为:
Enter pass phrase for root.key: ← 输入前面创建的密码
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter ‘.’, the field will be left blank.
—–
Country Name (2 letter code) [AU]:CN ← 国家代号,中国输入CN
State or Province Name (full name) [Some-State]:BeiJing ← 省的全名,拼音
Locality Name (eg, city) []:BeiJing ← 市的全名,拼音
Organization Name (eg, company) [Internet Widgits Pty Ltd]:MyCompany Corp. ← 公司英文名
Organizational Unit Name (eg, section) []: ← 可以不输入
Common Name (eg, YOUR name) []: ← 此时不输入(因为根证书自己验证自己)
Email Address []:[email protected] ← 电子邮箱,可随意填
Please enter the following ‘extra’ attributes
to be sent with your certificate request
A challenge password []: ← 可以不输入
An optional company name []: ← 可以不输入
(3). 创建一个自当前日期起为期十年的根证书 root.crt
openssl x509 -req -days 3650 -sha256 -extensions v3_ca -signkey root.key -in root.csr -out root.crt
输出内容为:
Signature ok
subject=/C=CN/ST=BeiJing/L=BeiJing/O=MyCompany Corp./[email protected]
Getting Private key
Enter pass phrase for root.key: ← 输入前面创建的密码
(4).根据CA证书生成truststore JKS文件 root.truststore (存储秘钥库可以放很多个证书), 也就是将CA根证书root.crt导入到root.truststore库中
注意:这一步只针对双向认证,单向不需要,需要配置在双向认证中的tomcat服务器server.xml中的Connector的truststoreFile="/home/lxf/ca/root.truststore" truststorePass="123456
keytool -keystore root.truststore -keypass 123456 -storepass 123456 -alias ca -import -trustcacerts -file root.crt
键入回事后,提示是否信息此证书,输入y, 则生成truststore成功
2. 制作service服务器端证书
(1).创建服务器证书密钥 server.key
openssl genrsa -des3 -out server/server.key 2048
输出内容为:
Generating RSA private key, 2048 bit long modulus
...........................+++
...............+++
e is 65537 (0x010001)
Enter pass phrase for server.key: ← 输入前面创建的密码
Verifying - Enter pass phrase for server.key: ← 重新输入一遍密码
运行时会提示输入密码,此密码用于加密key文件(参数des3便是指加密算法,当然也可以选用其他你认为安全的算法.),以后每当需读取此文件(通过openssl提供的命令或API)都需输入口令.如果觉得不方便,也可以去除这个口令,但一定要采取其他的保护措施!
去除key文件口令的命令:
openssl rsa -in server.key -out server.key
(2).创建服务器证书的申请文件 server.csr
openssl req -new -key server/server.key -out server/server.csr
输出内容为:
Enter pass phrase for server.key: ← 输入前面创建的密码
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter ‘.’, the field will be left blank.
—–
Country Name (2 letter code) [AU]:CN ← 国家名称,中国输入CN
State or Province Name (full name) [Some-State]:BeiJing-Server
Locality Name (eg, city) []:BeiJing-Server ← 市名,拼音
Organization Name (eg, company) [Internet Widgits Pty Ltd]:MyCompany Corp. ← 公司英文名
Organizational Unit Name (eg, section) []: ← 可以不输入
Common Name (eg, YOUR name) []:ljmis.develop ← 域名(或者IP),若填写不正确,浏览器会报告证书无效
Email Address []:[email protected] ← 电子邮箱,可随便填
Please enter the following ‘extra’ attributes
to be sent with your certificate request
A challenge password []: ← 可以不输入
An optional company name []: ← 可以不输入
(3).创建自当前日期起有效期为期十年的服务器证书 server.crt
openssl x509 -req -days 3650 -sha256 -extensions v3_req -CA root.crt -CAkey root.key -CAcreateserial -in server.csr -out server/server.crt
(4).将server.crt导出.p12文件 server.p12
openssl pkcs12 -export -in /tmp/ca/server.crt -inkey server/server.key -out server/server.p12 -name "server"
(5).将.p12 文件导入到keystore JKS文件 server.keystore ( tomcat服务器需要配置的 )
keytool -importkeystore -v -srckeystore server/server.p12 -srcstoretype pkcs12 -srcstorepass 123456 -destkeystore server/server.keystore -deststoretype jks -deststorepass 123456
这里srcstorepass后面的123456为server.p12的密码deststorepass后的123456为keyStore的密码
3. 制作client客户端证书
(1).创建客户端证书密钥文件 client.key
openssl genrsa -des3 -out client/client.key 2048
(2).创建客户端证书的申请文件 client.csr
openssl req -new -key client/client.key -out client.csr
输出内容为:
Enter pass phrase for client.key: ← 输入上一步中创建的密码
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter ‘.’, the field will be left blank.
—–
Country Name (2 letter code) [AU]:CN ← 国家名称,中国输入CN
State or Province Name (full name) [Some-State]:BeiJing-client ← 省名称,拼音
Locality Name (eg, city) []:BeiJing-client ← 市名称,拼音
Organization Name (eg, company) [Internet Widgits Pty Ltd]:MyCompany Corp. ← 公司英文名
Organizational Unit Name (eg, section) []: ← 可以不填
Common Name (eg, YOUR name) []:ljmis.develop ← 域名或自己的英文名
Email Address []:[email protected] ← 电子邮箱,可以随便填
Please enter the following ‘extra’ attributes
to be sent with your certificate request
A challenge password []: ← 可以不填
An optional company name []: ← 可以不填
(3)创建一个自当前日期起有效期为十年的客户端证书 client.crt
openssl x509 -req -days 3650 -sha256 -extensions v3_req -CA root.crt -CAkey root.key -CAcreateserial -in client/client.csr -out client/client.crt
(4).将client.crt导出.p12文件 client.p12
openssl pkcs12 -export -in /client/client.crt -inkey /client/client.key -out /client/client.p12 -name "client"
根据命令提示,输入client.key密码,创建p12密码。
4. 配置服务器端配置文件
- tomcat 单向认证 server.xml
- tomcat 双向认证 server.xml
- apache 配置
(1)vim /usr/local/apache/conf/https.conf 开启mod_ssl.so模块
LoadModule ssl_module modules/mod_ssl.so
Include conf/extra/httpd-ssl.conf
SSLRandomSeed startup builtin
SSLRandomSeed connect builtin
(2)编辑/usr/local/apache/conf/extra/httpd-ssl.conf文件
DocumentRoot "/home/ljmis_develop/html"
ServerAlias ljmis.develop
#开启ssl
SSLEngine on
#单向认证需要的文件
SSLCertificateFile "/home/lxf/ca/server/server.crt"
SSLCertificateKeyFile "/home/lxf/ca/server/server.key"
#双向认证需要的配置
SSLCACertificateFile "/home/lxf/ca/root.crt"
SSLVerifyClient require
SSLVerifyDepth 10
#日志配置
ErrorLog "/usr/local/apache/logs/ljmis-ssl-error_log"
TransferLog "/usr/local/apache/logs/ljmis-ssl-access_log"
如果apache没有mod_ssl.so模块, 两种方式
第一种:重新编译安装apache 添加ssl模块
./configure --prefix=/usr/local/apache --enable-ssl=shared --with-ssl=/usr/local/openssl
make && make install
第二种: 动态添加模块
进入到安装apache源码目录
cd /usr/local/src/httpd-2.4.12/modules/ssl/
/usr/local/apache2/bin/apxs -a -i -DHAVE_OPENSSL=1 -I/usr/include/openssl -L/usr/lib64/openssl -c *.c -lcrypto -lssl -ldl
查看/usr/local/apache2/modules/ 目录是否动态生成了mod_ssl.so
[root@version test-svn]# cd /usr/local/apache/modules/
[root@version modules]# ll | grep mod_ssl.so
-rwxr-xr-x 1 root root 713042 May 16 12:03 mod_ssl.so
5.客户端浏览器导入A根证书
在证书管理 -> 授权中信中导入CA根证书root.crt(如果证书经过第三方CA权威颁发,则不需要配置该步骤
)
6.客户端浏览器导入客户端证书(双向认证需要的配置
)
在证书管理 -> 您的证书中导入client.p12客户端证书
四.访问测试
在制作证书中域名输入的是ljmis.develop,则需要配置hosts
192.168.9.224 ljmis.develop
-
访问apache:https://ljmis.develop (apache会默认访问443端口)
浏览器会提示使用导入的client.p12进行访问(双向认证),选择刚刚导入的client.12证书点击确定即可;
出现以下图片情况代表配置成功,即有绿锁图标
访问tomcat: https://ljmis.develop:8443 (访问指定8443端口)
因为测试实验在一台服务器做的,apache和tomcat不能同时使用443一个端口,所所为要为tomcat特意指定8443端口;
五.使用java请求测试:
@Test
public void httpsAndClientGet() throws Exception
{
//加载客户端秘钥库(将客户端证书client.crt导入到该秘钥库)
KeyStore keyStore =KeyStore.getInstance(KeyStore.getDefaultType());
FileInputStream instream =new FileInputStream(new File("/home/lxf/ca/client/client.keystore"));
String pass = "123456";
keyStore.load(instream,pass.toCharArray());
instream.close();
//加载根证书秘钥库(将CA根证书root.crt导入到该秘钥库)
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
//FileInputStream instream1=new FileInputStream(new File("/usr/lib/jvm/java-7-openjdk-amd64/jre/lib/security/cacerts.bak.lxf"));
FileInputStream instream1=new FileInputStream(new File("/home/lxfca/root.truststore"));
trustStore.load(instream1,pass.toCharArray());
instream1.close();
// Trust own CA and allself-signed certs
SSLContext sslcontext= SSLContexts.custom()
.loadKeyMaterial(keyStore,pass.toCharArray())
.loadTrustMaterial(trustStore,new TrustSelfSignedStrategy())
.build();
// Allow TLSv1 protocol only
SSLConnectionSocketFactory sslsf =
new SSLConnectionSocketFactory(sslcontext,
new String[] {"TLSv1" },
null,
SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
CloseableHttpClient httpclient =HttpClients.custom()
.setSSLSocketFactory(sslsf)
.build();
String url = "https://localhost:8443/firstServlet/servlet/HelloServlet";
HttpGet httpget = new HttpGet(url);
System.out.println("ExecutingRequest:" + httpget.getRequestLine());
CloseableHttpResponse response = httpclient.execute(httpget);
HttpEntity entity =response.getEntity();
System.out.println("-------------------------------------");
System.out.println(response.getStatusLine());
System.out.println(EntityUtils.toString(entity));
EntityUtils.consume(entity);
response.close();
httpclient.close();
}
请求的servle(https://localhost:8443/firstServlet/servlet/HelloServlet)t打印客户端证书信息
package lxf.servlet;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.SortedMap;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.security.cert.X509Certificate;
import javax.servlet.annotation.WebServlet;
//继承于HttpServlet
public class HelloServlet extends HttpServlet {
private static final long serialVersionUID = 1601507150278487538L;
private static final String REQUEST_ATTR_CERT = "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";
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
//super.doGet(request, response);
request.setCharacterEncoding("utf-8");
request.setCharacterEncoding("utf-8");
System.out.println("处理GET()请求");
//获取浏览器输出对象
PrintWriter out = response.getWriter();
//用out对象给浏览器输出hello servlet
response.setContentType("text/html;charset=utf-8");
out.println("I am GET hello servlet");
response.setContentType(CONTENT_TYPE);
response.setCharacterEncoding(DEFAULT_ENCODING);
PrintWriter out1 = response.getWriter();
X509Certificate[] certs = (X509Certificate[]) request.getAttribute(REQUEST_ATTR_CERT);
if (certs != null) {
int count = certs.length;
out1.println("client's cert total = [" + count + "]");
for (int i = 0; i < count; i++) {
X509Certificate cert = certs[i];
out1.println("client cert [" + cert.getSubjectDN() + "]: ");
out1.println("client cert's valid date:" + (verifyCertificate(cert) ? "是" : "否"));
out1.println("client cert's detail info:\r" + cert.toString());
}
} else {
if (SCHEME_HTTPS.equalsIgnoreCase(request.getScheme())) {
//out1.println("this a HTTPS requeest,但是没有可用的客户端证书");
out1.println("this is a HTTPS requeest,But not usable cert");
} else {
out1.println("这不是一个HTTPS请求,因此无法获得客户端证书列表 ");
out1.println("this is not a HTTPS requeest ");
}
}
out1.close();
/*
try {
String res = CorefireHttpPost.connect("https://localhost:8443", null);
System.out.println(res.toString());
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
*/
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
System.out.println("处理POST()请求");
//获取浏览器输出对象
PrintWriter out = response.getWriter();
//用out对象给浏览器输出hello servlet
response.setContentType("text/html;charset=utf-8");
out.println("I am POST hello servlet");
}
/**
*
* 校验证书是否过期
*
*
* @param certificate
* @return
*/
private boolean verifyCertificate(X509Certificate certificate) {
boolean valid = true;
try {
certificate.checkValidity();
} catch (Exception e) {
e.printStackTrace();
valid = false;
}
return valid;
}
}
六.使用php请求测试
/************************curl双向认证常量配置start**************************/
//根证书路径
define('HTTPS_CAINFO', '/home/lxf/ca/root.crt');
//client.pem文件路径,可以从crt转换为pem,或使用命令生成pem证书
define('HTTPS_SSLCERT', '/home/lxf/ca/client/client.pem');
//私钥文件路径
define('HTTPS_SSLKEY', '/home/lxf/ca/client/client.key');
//私钥密码
define('HTTPS_SSLKEYPASSWD', '123456');
/************************curl双向认证常量配置end**************************/
/**
* [方法描述] CURL模拟post请求,执行https双向认证
* @param [string] $url 请求路径
* @param [array] $fields 请求参数 array( 'data' => '111' );
* @param [array ] $extraheader [header头部的重写]
* @param [const] 常量定义 HTTPS_CAINFO 根证书 例:/home/lxf/ca/root.crt
* @param [const] 常量定义 HTTPS_SSLCERT client.pem client端证书
* @param [const] 常量定义 HTTPS_SSLCERTPASSWD client证书密码
* @param [const] 常量定义 HTTPS_SSLKEY 私钥文件路径
* @param [const] 常量定义 HTTPS_SSLKEYPASSWD 私钥密码
* @return 接口返回的数据
*/
function do_Post($url, $fields, $extraheader = array()){
$fields = http_build_query($fields); //将数据进行URL-encode转换
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_PORT, 8443);//指定端口
curl_setopt($ch, CURLOPT_POSTFIELDS, $fields ); //post参数
curl_setopt($ch, CURLOPT_HTTPHEADER, $extraheader); //设置一个header中传输内容的数组。
curl_setopt($ch, CURLOPT_SSLVERSION, 1);//传递一个包含SSL版本的长参数。默认PHP将被它自己努力的确定,在更多的安全中你必须手工设置
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); //不信任任何证书
curl_setopt($ch, CURLOPT_CAINFO, HTTPS_CAINFO); //根证书路径
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 1); // 检查证书中是否设置域名,0不验证
curl_setopt($ch, CURLOPT_VERBOSE, 1); //debug模式
curl_setopt($ch,CURLOPT_SSLCERTTYPE,'PEM');
curl_setopt($ch, CURLOPT_SSLCERT, HTTPS_SSLCERT); //client.pem文件路径
// curl_setopt($ch, CURLOPT_SSLCERTPASSWD, HTTPS_SSLCERTPASSWD); //client证书密码
curl_setopt($ch, CURLOPT_SSLKEY, HTTPS_SSLKEY);//私钥文件路径
curl_setopt($ch, CURLOPT_SSLKEYPASSWD, HTTPS_SSLKEYPASSWD);//私钥密码
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // 如果成功只将结果返回,不自动输出任何内容。
$output = curl_exec($ch);
if(curl_errno($ch) != 0) $output = 'Curl error: ' . curl_error($ch);//curl错误信息
curl_close($ch);
return $output;
}
五.常用命令
- 查看证书信息
openssl x509 -in cert.pem -noout -text
- 查看秘钥仓库信息
keytool -list -keystore client.keystore
- PEM 转为 DER
openssl x509 -in xxx.pem -outform der -out xxx.der
- DER 转为 PEM
openssl x509 -in xxx.der -inform der -outform pem -out xxx.pem
- OpenSSL命令详解
参考站点:
linux下Tomcat+OpenSSL配置单向&双向认证(自制证书)
Linux下Tomcat配置使用SSL双向认证(使用openssl生成证书)
Java 和 HTTP 的那些事(四) HTTPS 和 证书
Apache 2 配置 SSL