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>

 

posted @ 2018-03-27 11:27  SOARING-SUN  阅读(324)  评论(0编辑  收藏  举报