美文网首页我爱编程网络相关
tomcat/apache+https单&双向认证

tomcat/apache+https单&双向认证

作者: liangxifeng833 | 来源:发表于2017-05-18 15:59 被阅读1744次

    基础概念介绍:秘钥/证书/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总体流程

    1. 制作CA根证书 ( 代表第三方权威CA机构 )

    2. 制作服务器端证书 -> 使用CA根证书签名认证服务器端证书

    3. 制作客户端证书 -> 使用CA根证书签名认证客户端证书(双向认证需要

    4. 配置服务器端配置文件, apache(http.conf) tomcat(server.xml),开启ssl

    5. 在客户端浏览器证书管理->授权中信中导入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 []:admin@mycompany.com ← 电子邮箱,可随意填
    
    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./emailAddress=admin@mycompany.com
    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 []:admin@mycompany.com ← 电子邮箱,可随便填
    
    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 []:admin@mycompany.com ← 电子邮箱,可以随便填
    
    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
    <Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol" SSLEnabled="true"
                   maxThreads="150" scheme="https" secure="true"
                   clientAuth="false" sslProtocol="TLS" 
                   keystoreFile="/home/lxf/ca/server/server.keystore" keystorePass="123456" 
                  />
    
    • tomcat 双向认证 server.xml
    <Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol" SSLEnabled="true"
                   maxThreads="150" scheme="https" secure="true"
                   clientAuth="true" sslProtocol="TLS" 
                   keystoreFile="/home/lxf/ca/server/server.keystore" keystorePass="123456" 
                   truststoreFile="/hom/lxf/ca/root.truststore" truststorePass="123456"
                  />
    
    • 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
    <IfModule ssl_module>
    SSLRandomSeed startup builtin
    SSLRandomSeed connect builtin
    </IfModule>
    

    (2)编辑/usr/local/apache/conf/extra/httpd-ssl.conf文件

    <VirtualHost 192.168.9.224:443>
            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"
    </VirtualHost>
    

    如果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权威颁发,则不需要配置该步骤

    ssl-1.jpg

    6.客户端浏览器导入客户端证书(双向认证需要的配置

    在证书管理 -> 您的证书中导入client.p12客户端证书


    ssl-2.jpg

    四.访问测试

    在制作证书中域名输入的是ljmis.develop,则需要配置hosts

    192.168.9.224  ljmis.develop
    
    • 访问apache:https://ljmis.develop (apache会默认访问443端口)
      浏览器会提示使用导入的client.p12进行访问(双向认证),选择刚刚导入的client.12证书点击确定即可;

      ssl-3.jpg
      出现以下图片情况代表配置成功,即有绿锁图标
      ssl-4.jpg
    • 访问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("<strong>I am GET hello servlet</strong>");
            
            
            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("<strong>I am POST hello servlet</strong>");
        }   
        /**
         * 
         * 校验证书是否过期
         * 
         * 
         * @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
    

    参考站点:
    linux下Tomcat+OpenSSL配置单向&双向认证(自制证书)
    Linux下Tomcat配置使用SSL双向认证(使用openssl生成证书)
    Java 和 HTTP 的那些事(四) HTTPS 和 证书
    Apache 2 配置 SSL

    相关文章

      网友评论

        本文标题:tomcat/apache+https单&双向认证

        本文链接:https://www.haomeiwen.com/subject/hyqsxxtx.html