php实现https(tls/ssl)双向认证

php实现https(tls/ssl)双向认证

一般状况下,在部署https的时候,是基于ssl单向认证的,也就是说只要客户端认证服务器,而服务器不须要认证客户端。php

但在一些安全性较高的场景,如银行,金融等领域,一般会要求进行客户端认证。从而实现ssl的双向认证。nginx

因为nginx的ssl_client_certificate参数只能指定一个客户端公钥,若是增长一个客户端进行通讯就要从新配一个server。
n:1的模式是经过CA的级联证书模式实现的,首先本身生成一套CA根级证书,再借助其生成二级证书做为client证书。
此时client私钥签名不只能够经过对应的client公钥验证,还可经过根证书的公钥进行验证。windows

看到这里应该豁然开朗了吧,下面简单介绍下具体怎么操做:浏览器

1 准备工做

1.1 openssl目录准备

通常状况下openssl的配置文件都在这个目录/etc/pki/tls,so:安全

mkdir /etc/pki/ca_linvo
cd /etc/pki/ca_linvo
mkdir root server client newcerts
echo 01 > serial
echo 01 > crlnumber
touch index.txt

1.2 openssl配置准备

修改openssl配置服务器

vi /etc/pki/tls/openssl.cnf

找到这句注释掉,替换为下面那句curl

#default_ca      = CA_default
default_ca      = CA_linvo

把[ CA_default ]整个部分拷贝一份,改为上面的名字[ CA_linvo ]
修改里面的以下参数:函数

dir = /etc/pki/ca_linvo
certificate = $dir/root/ca.crt
private_key = $dir/root/ca.key

保存退出工具

2 建立CA根级证书

生成key:openssl genrsa -out /etc/pki/ca_linvo/root/ca.key
生成csr:openssl req -new -key /etc/pki/ca_linvo/root/ca.key -out /etc/pki/ca_linvo/root/ca.csr
生成crt:openssl x509 -req -days 3650 -in /etc/pki/ca_linvo/root/ca.csr -signkey /etc/pki/ca_linvo/root/ca.key -out /etc/pki/ca_linvo/root/ca.crt
生成crl:openssl ca -gencrl -out /etc/pki/ca_linvo/root/ca.crl -crldays 7

生成的根级证书文件都在/etc/pki/ca_linvo/root/目录下
注意:建立证书时,建议证书密码设置长度>=6位,由于Java的keytool工具貌似对它有要求。post

3 建立server证书

生成key:openssl genrsa -out /etc/pki/ca_linvo/server/server.key
生成csr:openssl req -new -key /etc/pki/ca_linvo/server/server.key -out /etc/pki/ca_linvo/server/server.csr
生成crt:openssl ca -in /etc/pki/ca_linvo/server/server.csr -cert /etc/pki/ca_linvo/root/ca.crt -keyfile /etc/pki/ca_linvo/root/ca.key -out /etc/pki/ca_linvo/server/server.crt -days 3650

说明:
一、这里生成的crt是刚才ca根级证书下的级联证书,其实server证书主要用于配置正常单向的https,因此不使用级联模式也能够:

openssl rsa -in /etc/pki/ca_linvo/server/server.key -out /etc/pki/ca_linvo/server/server.key
openssl x509 -req -in /etc/pki/ca_linvo/server/server.csr -signkey /etc/pki/ca_linvo/server/server.key -out /etc/pki/ca_linvo/server/server.crt -days 3650

二、-days 参数可根据须要设置证书的有效期,例如默认365天

4 建立client证书

生成key:openssl genrsa -des3 -out /etc/pki/ca_linvo/client/client.key 1024
生成csr:openssl req -new -key /etc/pki/ca_linvo/client/client.key -out /etc/pki/ca_linvo/client/client.csr
生成crt:openssl ca -in /etc/pki/ca_linvo/client/client.csr -cert /etc/pki/ca_linvo/root/ca.crt -keyfile /etc/pki/ca_linvo/root/ca.key -out /etc/pki/ca_linvo/client/client.crt -days 3650

说明:
一、这里就必须使用级联证书,而且能够重复该步骤,建立多套client证书
二、生成crt时可能会遇到以下报错:
openssl TXT_DB error number 2 failed to update database
可参照这里进行操做。
我使用的是方法一,即将index.txt.attr中unique_subject = no

5 配置nginx

这里只列出server段的关键部分:

ssl_certificate  /etc/pki/ca_linvo/server/server.crt;#server公钥
ssl_certificate_key  /etc/pki/ca_linvo/server/server.key;#server私钥
ssl_client_certificate   /etc/pki/ca_linvo/root/ca.crt;#根级证书公钥,用于验证各个二级client
ssl_verify_client on;

重启Nginx

6 测试

6.1 浏览器测试

因为是双向认证,直接经过浏览器访问https地址是被告知400 Bad Request(No required SSL certificate was sent)的,须要在本机安装client证书。
windows上安装的证书须要pfx格式,也叫p12格式,生成方式以下:

openssl pkcs12 -export -inkey /etc/pki/ca_linvo/client/client.key -in /etc/pki/ca_linvo/client/client.crt -out /etc/pki/ca_linvo/client/client.pfx

而后考到windows中双击便可进行安装,安装时会提示输入生成证书时设置的密码。
安装成功后,重启浏览器输入网址访问,浏览器可能会提示你选择证书,选择刚才安装的那个证书便可。
此时有些浏览器会提示用户该证书不受信任,地址不安全之类,这是由于咱们的server证书是咱们本身颁发的,而非真正的权威CA机构颁布(一般很贵哦~),忽略它既可。

6.2 php curl测试

这里只列出关键的须要设置的curl参数:

curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // 信任任何证书,不是CA机构颁布的也不要紧  
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 1); // 检查证书中是否设置域名,若是不想验证也可设为0  
curl_setopt($ch, CURLOPT_VERBOSE, '1'); //debug模式,方便出错调试  
curl_setopt($ch, CURLOPT_SSLCERT, CLIENT_CRT); //client.crt文件路径,这里我用常量代替  
curl_setopt($ch, CURLOPT_SSLCERTPASSWD, CRT_PWD); //client证书密码  
curl_setopt($ch, CURLOPT_SSLKEY, CLIENT_KEY); //client.key文件路径  

CURLOPT_TIMEOUT:超时时间
CURLOPT_RETURNTRANSFER:是否要求返回数据
CURLOPT_SSL_VERIFYPEER:是否检测服务器的证书是否由正规浏览器认证过的受权CA颁发的
CURLOPT_SSL_VERIFYHOST:是否检测服务器的域名与证书上的是否一致
CURLOPT_SSLCERTTYPE:证书类型,"PEM" (default), "DER", and"ENG".
CURLOPT_SSLCERT:证书存放路径
CURLOPT_SSLCERTPASSWD:证书密码,没有能够留空
CURLOPT_SSLKEYTYPE:私钥类型,"PEM" (default), "DER", and"ENG".
CURLOPT_SSLKEY:私钥存放路径


function curl_post_ssl($url, $vars, $second=30,$aHeader=array())
{
    $ch = curl_init();
    //curl_setopt($ch,CURLOPT_VERBOSE,'1');
    curl_setopt($ch,CURLOPT_TIMEOUT,$second);
    curl_setopt($ch,CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($ch,CURLOPT_URL,$url);
    curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,false);
    curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,false);
    curl_setopt($ch,CURLOPT_SSLCERTTYPE,'PEM');
    curl_setopt($ch,CURLOPT_SSLCERT,'/data/cert/php.pem');
    curl_setopt($ch,CURLOPT_SSLCERTPASSWD,'1234');
    curl_setopt($ch,CURLOPT_SSLKEYTYPE,'PEM');
    curl_setopt($ch,CURLOPT_SSLKEY,'/data/cert/php_private.pem');

    if( count($aHeader) >= 1 ){
            curl_setopt($ch, CURLOPT_HTTPHEADER, $aHeader);
    }

    curl_setopt($ch,CURLOPT_POST, 1);
    curl_setopt($ch,CURLOPT_POSTFIELDS,$vars);
    $data = curl_exec($ch);
    curl_close($ch);
    if($data)
            return $data;
    else   
            return false;
}

验证失败,nginx的错误日志中,会有以下信息

2017/06/05 17:45:07 [crit] 16084#0: *27458991 SSL_do_handshake() failed (SSL: error:04067084:rsa routines:RSA_EAY_PUBLIC_DECRYPT:data too large for modulus e
rror:1408807A:SSL routines:ssl3_get_cert_verify:bad rsa signature) while SSL handshaking, client: 116.255.208.194, server: 0.0.0.0:443

6.3 php soap测试

首先须要构建client的pem格式证书,经过openssl命令也能够,不过由于咱们已经有了crt和key,因此手动合并也很简单:
新建一个文件,把crt中-----BEGIN CERTIFICATE-----和-----END CERTIFICATE-----之间的base64内容(包括这两个分割线)拷贝进去,而后把key中-----BEGIN RSA PRIVATE KEY-----和-----END RSA PRIVATE KEY-----之间的内容也复制进去,而后保存为client.pem便可。
其实更省事的话能够以下命令,直接合并两个文件:

cat /etc/pki/ca_linvo/client/client.crt /etc/pki/ca_linvo/client/client.key > /etc/pki/ca_linvo/client/client.pem
有了pem文件,下面可使用php内置的SoapClient进行调用,构造函数须要设置第二个参数:
$header = array(          
    'local_cert' => CLIENT_PEM, //client.pem文件路径  
    'passphrase' => CRT_PWD //client证书密码  
    );  
$client = new SoapClient(FILE_WSDL, $header); //FILE_WSDL为要访问的https地址

上一篇博客里最后说到local_cert设置成远程路径的话会报错,好像是由于第一次获取wsdl时并无使用client证书的缘由,须要将wsdl保持成本地文件进行调用;
可是此次测试却没问题,不用另存为本地文件,直接远程获取便可。
原本认为是以前的证书有问题,可是使用以前的那套证书依然能够,非常诡异~~~~~

参考:http://blog.csdn.net/linvo/article/details/9173511

相关文章
相关标签/搜索