PHP实现OpenSSL双向认证
SSL:Secure Socket Layer,安全套接字层,它位于TCP层与Application层之间。提供对Application数据的加密保护(密文),完整性保护(不被篡改)等安全服务,它缺省工作在TCP 443 端口,一般对HTTP加密,即俗称的HTTPS。
SSL双向认证具体过程
① 浏览器发送一个连接请求给安全服务器。
② 服务器将自己的证书,以及同证书相关的信息发送给客户浏览器。
③ 客户浏览器检查服务器送过来的证书是否是由自己信赖的CA中心(如沃通CA)所签发的。如果是,就继续执行协议;如果不是,客户浏览器就给客户一个警告消息:警告客户这个证书不是可以信赖的,询问客户是否需要继续。
④ 接着客户浏览器比较证书里的消息,例如域名和公钥,与服务器刚刚发送的相关消息是否一致,如果是一致的,客户浏览器认可这个服务器的合法身份。
⑤ 服务器要求客户发送客户自己的证书。收到后,服务器验证客户的证书,如果没有通过验证,拒绝连接;如果通过验证,服务器获得用户的公钥。
⑥ 客户浏览器告诉服务器自己所能够支持的通讯对称密码方案。
⑦ 服务器从客户发送过来的密码方案中,选择一种加密程度最高的密码方案,用客户的公钥加过密后通知浏览器。
⑧ 浏览器针对这个密码方案,选择一个通话密钥,接着用服务器的公钥加过密后发送给服务器。
⑨ 服务器接收到浏览器送过来的消息,用自己的私钥解密,获得通话密钥。
⑩ 服务器、浏览器接下来的通讯都是用对称密码方案,对称密钥是加过密的。
一、修改ssl配置文件
切换到tls目录下 cd /etc/pki/tls
修改openssl.conf文件[CA_default]下的dir根证书所在目录为自己定义的根证书目录
如:将dir=/etc/pki/CA 改为 dir = /www/php/handlers/certificates/CA
二、类封装
<?php namespace com\cerfiticates\fba; include_once realpath(__DIR__ . '/../../autoload.php'); function executeRequest() { $handler = new CreateCertificate(); $handler->run(); } class CreateCertificate { #证书所在根目录 private $dir; #根证书key所在目录 private $private_key_dir; #服务端证书所在目录 private $server_dir; #客户端证书所在目录 private $client_dir; #撤销客户端证书所在目录 private $revoke_dir; #请求的域名 private $url; #客户端证书有效期 private $client_expire_time; #接收itcode private $itcode; #openssl.conf private $ssl_conf; #下载 private $zip; #根证书密码 private $ca_pass; #客户端用户密码所在文件 private $client_user_pass; #加密字符串 private $crypt_key; const CERTROOT = __DIR__ . '/CA/'; public function __construct() { $this -> dir = self::CERTROOT; $this -> private_key_dir = self::CERTROOT . 'private/'; $this -> server_dir = self::CERTROOT . 'server/'; $this -> client_dir = self::CERTROOT . 'client/'; $this -> revoke_dir = self::CERTROOT . 'newcerts/'; $this -> zip = self::CERTROOT . 'zip/'; $this -> url = 'test.ssl.com'; $this -> client_expire_time = 3650; $this -> ssl_conf = '/etc/pki/tls/openssl.cnf'; $this -> ca_pass = 'root12345'; $this -> crypt_key = 'codata.lenovo.com'; } public function run() { $action = filter_input(INPUT_GET, 'action'); switch ($action) { case 'init': $this -> initialize(); break; case 'ca': $this -> GenerateCACert(); break; case 'server': $this -> GenerateServerCert(); break; case 'client': $this -> GenerateClientCert(); break; case 'revoke': $this -> RevokeCert(); case 'download': $this -> DownloadClientCer(); default: break; } } #初始化 private function initialize() { //创建CA根目录 if(!$this -> checkFile($this->dir)) { if(!$this -> createDir($this->dir)) { outputjson(array('code'=>-1,'msg'=>'make ca dir faild')); } } //创建CA key所在目录 if(!$this -> checkFile( $this->private_key_dir)) { if(!$this -> createDir($this->private_key_dir)) { outputjson(array('code'=>-1,'msg'=>'make private dir faild')); } } //创建服务端证书目录 if(!$this -> checkFile($this -> server_dir)) { if(!$this -> createDir($this -> server_dir)) { outputjson(array('code'=>-1,'msg'=>'make server dir faild')); } } //创建客户端证书目录 if(!$this -> checkFile($this -> client_dir)) { if(!$this -> createDir($this -> client_dir)) { outputjson(array('code'=>-1,'msg'=>'make client dir faild')); } } //创建客户端serial_number.pem所在根目录 if(!$this -> checkFile($this->revoke_dir)) { if(!$this -> createDir($this->revoke_dir)) { outputjson(array('code'=>-1,'msg'=>'make newcerts dir faild')); } } //创建zip下载目录 if(!$this -> checkFile($this->zip)) { if(!$this -> createDir($this->zip)) { outputjson(array('code'=>-1,'msg'=>'make zip dir faild')); } } //创建zip目录下的下载文件 if(!$this -> checkFile($this->zip . 'downloadcer.zip')) { $mk_zip = 'touch ' . $this->dir . 'downloadcer.zip'; exec($mk_zip, $result, $zip); if($zip != 0) { outputjson(array('code'=>-1,'msg'=>'create zip file faild')); } } //创建记录证书信息的index文件 if(!$this -> checkFile($this->dir . 'index.txt')) { $mk_index = 'touch ' . $this->dir . 'index.txt'; exec($mk_index, $result, $index); if($index != 0) { outputjson(array('code'=>-1,'msg'=>'create index text faild')); } } //创建证书序列号 if(!$this -> checkFile($this->dir . 'serial')) { $mk_serial = 'echo 01 > ' . $this->dir . 'serial'; exec($mk_serial, $result, $serial); if($serial != 0) { outputjson(array('code'=>-1,'msg'=>'create serial faild')); } } //创建被撤销的证书编号 if(!$this -> checkFile($this->dir . 'crlnumber')) { $mk_crlnumber = 'echo 01 > ' . $this->dir . 'crlnumber'; exec($mk_crlnumber, $result, $crlnumber); if($crlnumber != 0) { outputjson(array('code'=>-1,'msg'=>'create crlnumber faild')); } } //echo "unique_subject=no" > index.txt.attr if(!$this -> checkFile( $this->dir . 'index.txt.attr')) { $mk_index_attr = 'echo "unique_subject=no" > ' . $this->dir . 'index.txt.attr'; exec($mk_index_attr, $result, $attr); if($attr != 0) { outputjson(array('code'=>-1,'msg'=>'create index.txt.attr faild')); } } } //创建根证书 private function GenerateCACert() { //创建根证书密钥 $ret = $this -> createRootCertKey(); //创建根证书 $this -> createRootCert(); outputjson(array('code'=>0,'msg'=>'create CA certificate success')); } //创建服务端证书 private function GenerateServerCert() { //创建服务端key $this -> createServerKey(); //创建服务端证书请求文件 $this -> createServerCsr(); //根据服务端证书请求文件生成服务端证书 $this -> createServerCert(); //创建被吊销证书列表 $this -> createCrl(); outputjson(array('code'=>0,'msg'=>'create server certificate success')); } //创建客户端证书 private function GenerateClientCert() { $itcode = filter_input(INPUT_GET, 'itcode'); if(!empty($itcode)) { $ldap = new LDAPUtils(); //if (empty($ldap->getSingleUser($itcode))) outputjson(array('code'=>-1,'msg'=>'Please input correct itcode')); //$this -> itcode = $itcode; }else { outputjson(array('code'=>-1,'msg'=>'Please input correct itcode')); } //创建目录 $client_dir = sprintf("%s%s",$this->client_dir, $itcode); //创建当前用户证书文件夹 if(!$this->checkFile($client_dir)) { if( !$this->createDir($client_dir) ) { outputjson(array('code'=>-1,'msg'=>'create current client dir faild')); } } //创建客户端密码 $pass_file = sprintf("%s/clientpass.txt",$client_dir); if(file_exists($pass_file) == false) { $file = 'touch ' . $pass_file; exec($file, $result, $fle); if($fle != 0) { outputjson(array('code'=>-1,'msg'=>'create client pass file faild')); } } if(strlen($this->_readFile($pass_file)) == 0) { if(!$this -> createClientPass($itcode,$pass_file)) { outputjson(array('code'=>-1,'msg'=>'create client pass faild')); } } //创建客户端key $this -> createClientKey($client_dir, $pass_file, $itcode); //创建客户端证书请求文件 $this -> createClientCsr($client_dir, $pass_file, $itcode); //创建客户端证书 $this -> createClientCert($client_dir, $pass_file, $itcode); outputjson(array('code'=>0,'msg'=>'create client certificate success')); } //撤销证书 private function RevokeCert() { $itcode = filter_input(INPUT_GET, 'itcode'); if(!empty($itcode)) { $ldap = new LDAPUtils(); //if (empty($ldap->getSingleUser($itcode))) outputjson(array('code'=>-1,'msg'=>'Please input correct itcode')); //$this -> itcode = $itcode; }else { outputjson(array('code'=>-1,'msg'=>'Please input correct itcode')); } //目录文件 $file = $this->client_dir . $itcode . '/' . $itcode . '_cert.crt'; //查看是否有当前用户的证书 if (!file_exists($file)) { outputjson(array('code'=>-1,'msg'=>'file is not exit')); } $this -> revokeClientCert($this->client_dir, $this->revoke_dir, $this->server_dir, $itcode,$file); } //撤销客户端证书 private function revokeClientCert($client_dir, $revoke_dir, $server_dir, $itcode, $file) { if (!$cert_store = file_get_contents($file)) { outputjson(array('code'=>-1,'msg'=>'Unable to read the cert file')); } if (!$cert_store = openssl_x509_parse($cert_store)) { outputjson(array('code'=>-1,'msg'=>'Unable to read client certificate information')); } $serial_number = $cert_store['serialNumber']; if($serial_number < 10) { $serial_number = '0' . $serial_number; } $revoke = 'openssl ca -revoke ' . $revoke_dir . $serial_number . '.pem -config ' .$this->ssl_conf . ' -passin pass:' . $this->ca_pass; exec($revoke, $res, $r); if($r != 0) { outputjson(array('code'=>-1,'msg'=>'revoke client cert faild')); }else { $upd_ca_crl = 'openssl ca -gencrl -config ' . $this->ssl_conf . ' -out ' . $server_dir . 'ca.crl -passin pass:' . $this->ca_pass; exec($upd_ca_crl, $result, $crl); if($crl != 0) { outputjson(array('code'=>-1,'msg'=>'update ca crl faild')); } } outputjson(array('code'=>0,'msg'=>'revoke certificate success')); } //创建根证书密钥 private function createRootCertKey() { $file = $this -> private_key_dir . 'cakey.pem'; if($this -> checkFile($file)) { outputjson(array('code'=>-1,'msg'=>'root key file has exist')); } $create_root_key = 'openssl genrsa -des3 -passout pass:'.$this->ca_pass.' -out ' . $this->private_key_dir . 'cakey.pem 2048'; exec($create_root_key, $result, $key); if($key != 0) { outputjson(array('code'=>-1,'msg'=>'create root key faild')); } return $key; } private function createRootCert() { //根据密钥创建自签名根证书 $file = $this->dir . 'cacert.pem'; if($this -> checkFile($file)) { outputjson(array('code'=>-1,'msg'=>'root cert file has exist')); } $create_root_cer = 'openssl req -sha256 -new -x509 -key ' . $this->private_key_dir . 'cakey.pem -subj "/C=CN/ST=Beijing/L=Beijing/O=Lenovo (Beijing) Limited/OU=ITS/CN=LenovoSHA2ROOTCERT" -out ' . $this->dir . 'cacert.pem -days 7300 -passin pass:' . $this->ca_pass; exec($create_root_cer, $result, $root_cer); if($root_cer != 0) { outputjson(array('code'=>-1,'msg'=>'create root certificate faild')); } } //创建服务端证书密钥 private function createServerKey() { $file = $this->server_dir . 'serverkey.key'; if(!$this -> checkFile($file)) { $server_key = 'openssl genrsa -out ' . $this->server_dir . 'serverkey.key 2048'; exec($server_key, $result, $key); if($key != 0) { outputjson(array('code'=>-1,'msg'=>'create server key faild')); } } } //创建服务端证书请求文件 private function createServerCsr() { $file = $this->server_dir . 'server.csr'; if(!$this -> checkFile($file)) { $server_csr = 'openssl req -new -key ' . $this->server_dir . 'serverkey.key -subj "/C=CN/ST=Beijing/L=Beijing/O=Lenovo (Beijing) Limited/OU=ITS/CN=' . $this->url . '" -out ' . $this->server_dir . 'server.csr'; exec($server_csr, $result, $csr); if($csr != 0) { outputjson(array('code'=>-1,'msg'=>'create server csr faild')); } } } //根据服务端证书请求文件生成服务端证书 private function createServerCert() { $file = $this->server_dir . 'servercert.crt'; if(!$this -> checkFile($file)) { $server_cert = 'openssl ca -batch -in ' . $this->server_dir . 'server.csr -md sha256 -config ' . $this->ssl_conf . ' -out ' . $this->server_dir . 'servercert.crt -days 7200 -passin pass:'. $this->ca_pass; exec($server_cert, $result, $cert); if($cert != 0) { outputjson(array('code'=>-1,'msg'=>'create server cert faild')); } } } //创建被吊销证书列表 private function createCrl() { //创建被吊销证书列表 $mk_ca_crl = 'openssl ca -gencrl -config '.$this->ssl_conf. ' -keyfile '. $this->private_key_dir .'cakey.pem -cert '. $this->dir .'cacert.pem -out ' . $this->server_dir . 'ca.crl -passin pass:' . $this->ca_pass; exec($mk_ca_crl, $result, $crl); if($crl != 0) { outputjson(array('code'=>-1,'msg'=>'create ca crl faild')); } } #创建客户端证书秘钥 private function createClientKey($client_dir, $pass_file, $itcode) { $client_pass = $this -> _readFile($pass_file); if(!$client_pass) { outputjson(array('code'=>-1,'msg'=>'read client pass faild')); } $client_key = 'openssl genrsa -des3 -passout pass:'. $client_pass . ' -out ' . $client_dir . '/' . $itcode . '_key.key 2048'; exec($client_key, $result, $key); if($key != 0) { outputjson(array('code'=>-1,'msg'=>'create current client key faild')); } } //创建客户端证书请求文件 private function createClientCsr($client_dir, $pass_file, $itcode) { $client_pass = $this -> _readFile($pass_file); if(!$client_pass) { outputjson(array('code'=>-1,'msg'=>'read client pass faild')); } //创建目录 $client_csr = 'openssl req -new -key ' . $client_dir . '/' . $itcode . '_key.key -subj "/C=CN/ST=Beijing/L=Beijing/O=Lenovo (Beijing) Limited/OU=' . $itcode . '/CN=' . $this->url . '" -out ' . $client_dir . '/' . $itcode . '.csr -passin pass:'. $client_pass; exec($client_csr, $result, $csr); if($csr != 0) { outputjson(array('code'=>-1,'msg'=>'create client csr faild')); } } //创建客户端证书 private function createClientCert($client_dir, $pass_file, $itcode) { $client_pass = $this -> _readFile($pass_file); if(!$client_pass) { outputjson(array('code'=>-1,'msg'=>'read client pass faild')); } //$client_cert = 'openssl ca -batch -in ' . $client_dir . '/' . $this->itcode . '.csr -config ' . $this->ssl_conf . ' -out ' . $client_dir . '/' . $this->itcode . '_cert.crt -days ' . $this->client_expire_time; $client_cert = 'openssl ca -batch -in ' . $client_dir . '/' . $itcode . '.csr' .' -md sha256 -config ' . $this->ssl_conf . ' -out ' . $client_dir . '/' . $itcode . '_cert.crt -days ' . $this->client_expire_time . ' -passin pass:'. $this->ca_pass; exec($client_cert, $result, $cert); if($cert != 0) { outputjson(array('code'=>-1,'msg'=>'create client cert faild')); }else { $cert_pfx = 'openssl pkcs12 -export -clcerts -in ' . $client_dir . '/' . $itcode . '_cert.crt -inkey '. $client_dir . '/' . $itcode . '_key.key -out '. $client_dir . '/' . $itcode . '_cert.pfx -passin pass:'.$client_pass . ' -passout pass:'; //echo $cert_pfx;die; exec($cert_pfx, $res, $pfx); if($pfx != 0) { outputjson(array('code'=>-1,'msg'=>'change client cert into pfx faild')); } } } //检查文件是否存在 private function checkFile($file) { //如果由 filename 指定的文件或目录存在则返回 TRUE,否则返回 FALSE。 return file_exists($file); } //创建目录 private function createDir($path) { return mkdir($path,0777,TRUE); /*$path = 'sudo mkdir ' . $path; exec($path, $result, $p); if($p != 0) { outputjson(array('code'=>-1,'msg'=>'make dir faild')); }*/ } //修改文件权限 private function chmodFile($file) { chmod($file, 0777); } /** * DownloadClientCer: download certificate by client type * @param string $itcode * @return zip return zip if succeeded or faild. */ private function DownloadClientCer() { $itcode = filter_input(INPUT_GET, 'itcode'); if(!empty($itcode)) { $ldap = new LDAPUtils(); //if (empty($ldap->getSingleUser($itcode))) outputjson(array('code'=>-1,'msg'=>'Please input correct itcode')); }else { outputjson(array('code'=>-1,'msg'=>'Please input correct itcode')); } //查看客户端证书文件是否存在 $client_file_pfx = sprintf("%s%s/%s_cert.pfx", $this->client_dir, $itcode, $itcode); if(!$this -> checkFile($client_file_pfx)) { outputjson(array('code'=>-1,'msg'=>'can not find current client certificate')); } $filename = $this -> zip . 'downloadcer.zip'; $datalist = array($client_file_pfx); if(file_exists($filename)){ $zip = new \ZipArchive(); if ($zip->open($filename, \ZipArchive::CREATE)==TRUE) { foreach( $datalist as $val){ if(file_exists($val)){ $zip->addFile($val, basename($val)); } } $zip->close(); } } header("Cache-Control: public"); header("Content-Description: File Transfer"); header('Content-disposition: attachment; filename='.basename($filename)); header("Content-Type: application/zip"); header("Content-Transfer-Encoding: binary"); header('Content-Length: '. filesize($filename)); readfile($filename); $handle =fopen($filename,'w+'); fclose($handele); } private function checkCer() { if (!isset($_SERVER['SSL_CLIENT_M_SERIAL']) //The serial of the client certificate || !isset($_SERVER['SSL_CLIENT_V_END']) //Validity of client's certificate (end time) || !isset($_SERVER['SSL_CLIENT_VERIFY']) || $_SERVER['SSL_CLIENT_VERIFY'] !== 'SUCCESS' //NONE, SUCCESS, GENEROUS or FAILED:reason || !isset($_SERVER['SSL_CLIENT_I_DN']) //Issuer DN of client's certificate ) { outputjson(array('code'=>-1,'msg'=>'check client certificate faild')); } $dal = new DAL(); //check whether client certificate is expired if ($_SERVER['SSL_CLIENT_V_REMAIN'] <= 0) { outputjson(array('code'=>-1,'msg'=>'certificate has expired')); } outputjson(array('code'=>0,'msg'=>'client certificate is legal')); } private function encrypt($txt) { srand((double)microtime()*1000000); $encrypt_key = md5(rand(0,32000)); $ctr = 0; $tmp = ''; for($i=0; $i<strlen($txt); $i++) { $ctr = ($ctr == strlen($encrypt_key)) ? 0 : $ctr; $tmp .= $encrypt_key[$ctr].($txt[$i]^$encrypt_key[$ctr++]); } $str = base64_encode(self::__key($tmp, $this->crypt_key)); $str = str_replace('+', '!', $str); $str = str_replace('/', '-', $str); return $str; } private function decrypt($txt) { $txt = str_replace('!', '+', $txt); $txt = str_replace('-', '/', $txt); $txt = self::__key(base64_decode($txt), $this->crypt_key); $tmp = ''; for($i=0; $i<strlen($txt); $i++) { $md5 = $txt[$i]; $tmp .= $txt[++$i]^$md5; } return $tmp; } private function __key($txt, $encrypt_key) { $encrypt_key = md5($encrypt_key); $ctr = 0; $tmp = ''; for($i=0; $i<strlen($txt); $i++) { $ctr = ($ctr == strlen($encrypt_key) ? 0 : $ctr); $tmp .= $txt[$i]^$encrypt_key[$ctr++]; } return $tmp; } private function createClientPass($itcode,$pass_file) { //$pass = $this -> encrypt($itcode); $strs="QWERTYUIOPASDFGHJKLZXCVBNM1234567890qwertyuiopasdfghjklzxcvbnm"; $pass=substr(str_shuffle($strs),mt_rand(0,strlen($strs)-11),10); return $this -> _writeFile($pass_file,$pass); } //读文件 private function _readFile($filename) { return file_get_contents($filename); } //写文件 private function _writeFile($filename,$content) { return file_put_contents($filename, $content); } public function __destruct() { $this->crypt_key = ""; } }
//附apache配置 <VirtualHost *:443> DocumentRoot "/data0/ssltest" ServerName test.ssl.com ServerAlias ssl.com <Directory "/data0/ssltest"> Options FollowSymLinks ExecCGI AllowOverride All Order allow,deny Allow from all Require all granted </Directory> SSLEngine on SSLCARevocationPath "/data0/www/certificates/CA/newcerts" SSLCARevocationFile "/data0/www/certificates/CA/server/ca.crl" SSLCertificateFile "/data0/www/certificates/CA/server/servercert.crt" SSLCertificateKeyFile "/data0/www/certificates/CA//server/serverkey.key" SSLCARevocationCheck chain SSLCACertificateFile "/data0/www/certificates/CA/cacert.pem" #optional require SSLVerifyClient require SSLVerifyDepth 1 SSLOptions +StdEnvVars #SSLVerifyClient optional_no_ca </VirtualHost>