https自簽名證書雙向認證


前言

本篇文章主要介紹的是OpenSSL生成自簽名證書,實現https雙向認證。

一、在linux中使用OpenSSL生成CA證書、客戶端證書、服務端證書

查看 OpenSSL版本號 openssl version -a
如果不存在,需要安裝OpenSSL
下載地址:www.openssl.org/source/openssl-1.0.2p.tar.gz

tar -zxv openssl-1.0.2p.tar.gz
cd openssl-1.0.2p/
./config
make && make install
./config shared 
make clean
make  && make install
1.在nginx目錄下創建ssl目錄,進入ssl,運行下面這個腳本,最后輸入客戶端密碼
#!/bin/bash
# function: 創建 nginx https ,雙向認證證書
#
# BEGIN
#
# 網站域名
# 在簽發服務(客戶)端證書的時候
# 這個域名必須跟 subj 中的 CN 對應
# 否則瀏覽器會報不安全的鏈接
# 查找所有javatest.hqxapp.com替換成域名運行即可
domain="javatest.hqxapp.com"
#
# ---------- CA ----------
#
# 准備CA密鑰
echo "創建CA密鑰.."
openssl genrsa -out $domain.CA.key 2048
# 生成CA證書請求
# 證書請求都是根據私鑰來生成的
echo "生成CA證書請求.."
openssl req -new -key $domain.CA.key -out $domain.CA.csr -days 365 -subj /C=CN/ST=GuangDong/L=GuangZhou/O=javatest.hqxapp.com/OU=javatest.hqxapp.com/CN=opcenter/emailAddress=javatest.hqxapp.com -utf8
 
# 簽名CA證書請求
# 使用自己的私鑰來給這個CA證書請求簽名
# 經過多次測試得知: 這個時間如何設置的太長,如 3650(10年) 
# chrome瀏覽器會報, 該網站使用的安全設置已過期
# 所以https不會顯示是綠色, 而是帶一個黃色三角形的圖標
# 奇怪的是: 如果設成1年,也就是365天,不會提示該網站使用的安全設置已過期
# 而且也是綠色的標識
# 但如果是2年, 也是綠色的標識,但是會提示該網站使用的安全設置已過期
# 這個應該chrome瀏覽器的問題
echo "創建CA證書.."
openssl x509 -req -in $domain.CA.csr -signkey $domain.CA.key -out $domain.CA.crt -days 365
 
# CA證書轉換為DER格式,
# DER格式似乎更加通用
openssl x509 -in $domain.CA.crt -out $domain.CA.der -outform DER
# 現在, 終於拿到了自己做 CA 需要的幾個文件了, 
# 密鑰: $domain.CA.key
# 證書: $domain.CA.crt
# 系統使用的: $domain.CA.der
# 接下來, 要創建一個網站, 就需要讓 CA 給他簽名一個證書了
 
#
 # --------- SERVER ----------
 #
 # 准備網站密鑰
 echo "創建網站(服務端)密鑰.."
 openssl genrsa -out $domain.server.key 2048
 # 生成網站證書請求
 # CN 一定要是網站的域名, 否則會通不過安全驗證
 echo "生成網站(服務端)證書請求.."
 openssl req -new -key $domain.server.key -out $domain.server.csr -days 365 -subj /C=CN/ST=GuangDong/L=GuangZhou/O=javatest.hqxapp.com/OU=javatest.hqxapp.com/CN=$domain/emailAddress=javatest.hqxapp.com -utf8
  
  # CA簽名網站證書請求
  # 不是拿到 CA 的證書了就可以說自己是 CA 的, 最重要的是, 簽名需要有 CA 密鑰
  # 如果客戶端(個人瀏覽器)信任 CA 的證書的話, 那么他也就會信任由 CA 簽名的網站證書
  # 因此讓瀏覽器信任 CA 的證書之后, 客戶端就自然信任服務端了, 只要做單向認證的話, 到這一步證書這一類材料就已經准備好了
  # 但是雙向認證就還要給客戶端(個人的瀏覽器)准備一份證書
  # 讓服務端可以知道客戶端也是合法的。
  # 假如讓服務端也信任 CA 的證書
  # 那 CA 簽名的客戶端證書也就能被信任了。
  echo "通過CA證書簽名, 創建網站(服務端)證書.."
  openssl x509 -req -in $domain.server.csr -out $domain.server.crt -CA $domain.CA.crt -CAkey $domain.CA.key -CAcreateserial -days 365
   
#
# --------- CLIENT ----------
#
# 准備客戶端私鑰
echo "創建瀏覽器(客戶端)密鑰.."
openssl genrsa -out $domain.client.key 2048
# 生成客戶端證書請求
echo "生成瀏覽器(客戶端)證書請求.."
openssl req -new -key $domain.client.key -out $domain.client.csr -days 3650 -subj /C=CN/ST=GuangDong/L=GuangZhou/O=javatest.hqxapp.com/OU=javatest.hqxapp.com/CN=$domain/emailAddress=javatest.hqxapp.com -utf8
# CA簽名客戶端證書請求
echo "通過CA證書簽名, 創建瀏覽器(客戶端)證書.."
openssl x509 -req -in $domain.client.csr -out $domain.client.crt -CA $domain.CA.crt -CAkey $domain.CA.key -CAcreateserial -days 365 
# 客戶端證書轉換為DER格式
openssl x509 -in $domain.client.crt -out $domain.client.der -outform DER
# 客戶端證書轉換為 PKCS, 即12格式
# 全稱應該叫做 Personal Information Exchange
# 通常以 p12 作為后綴
echo "轉換客戶端證書為p12格式.."
openssl pkcs12 -export -in $domain.client.crt -inkey $domain.client.key -out $domain.client.p12

運行該腳本前需要賦予權限:chmod 777 ssl.sh

生成證書:

2.在nginx中配置

vim nginx/conf/nginx.conf

server {
        listen       443;
        server_name  localhost;
        ssi on;
        ssi_silent_errors on;
        ssi_types text/shtml;
 
         ssl on;
         ssl_certificate ../ssl/javatest.hqxapp.com.server.crt;
         ssl_certificate_key  ../ssl/javatest.hqxapp.com.server.key;
         ssl_client_certificate ../ssl/javatest.hqxapp.com.CA.crt;
         ssl_session_timeout  5m;
         ssl_verify_client on;
         ssl_protocols  SSLv2 SSLv3 TLSv1;
         ssl_ciphers  RC4:HIGH:!aNULL:!MD5;
         ssl_prefer_server_ciphers   on;
        
     location / {
            index index.html index.htm;
            proxy_pass http://127.0.0.1:8080;
        }
    }

3.報錯nginx: [emerg] unknown directive “ssl” in /usr/local/nginx/conf/nginx.conf

原因:因為配置這個SSL證書需要引用到nginx的中SSL這模塊,然而一開始編譯的Nginx的時候並沒有把SSL模塊一起編譯進去,所以導致這個錯誤的出現。
解決方案:
1 ./configure --with-http_ssl_module //重新添加這個ssl模塊
2 執行make命令,但是不要執行make install,因為make是用來編譯的,而make install是安裝,然整個nginx會重新覆蓋的。
3 在我們執行完做命令后,我們可以查看到在nginx解壓目錄下,objs文件夾中多了一個nginx的文件,這個就是新版本的程序了。首先我們把之前的nginx先備份一下,然后把新的程序復制過去覆蓋之前的即可。(先停止nginx程序)
cp /usr/local/nginx/sbin/nginx /usr/local/nginx/sbin/nginx.bak
cp objs/nginx /usr/local/nginx/sbin/nginx
4 最后來到Nginx安裝目錄下,來查看是否有安裝ssl模塊成功。執行./sbin/nginx -V即可看到。
最后,進入nginx/sbin,./nginx –s reload ,重啟nginx。

二、 使用客戶端證書訪問https(證書有改動(生成新的證書),需要重啟nginx)

1. 瀏覽器

雙擊javatest.hqxapp.com.client.p12,安裝該證書,一直下一步直至完成,期間要輸入的密碼,是運行生成證書的腳本時,最后一步輸入的密碼。然后重啟瀏覽器即可。
不安裝該證書訪問,會報以下錯誤

2. postman

關閉驗證

添加秘鑰

請求接口

3. java代碼
1.獲取證書(server.crt轉換為server.bks)

keytool -importcert -v -trustcacerts -alias ttt -file 位置1 -keystore 位置2 -storetype BKS -providerclass org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath 位置3 -storepass 位置4

位置1:server.crt證書的絕對路徑
位置2:要生成的server.bks文件的絕對路徑
位置3:轉換jar包的絕對路徑
位置4:要設置的證書密碼

舉例:keytool -importcert -v -trustcacerts -alias ttt -file C:\Users\20180030\Desktop\ssl\javatest.hqxapp.com.server.crt -keystore C:\Users\20180030\Desktop\ssl\javatest.hqxapp.com.server.bks -storetype BKS -providerclass org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath D:\SSL\bcprov-jdk15on-163.jar -storepass 密碼

2.server.bks證書轉換為server.jks證書

server.jks證書才是java最終要用到的服務端證書。
將兩個證書文件放到項目根目錄下創建的key文件夾下。打開portecle.jar,啟動該工具。

打開server.bks文件


輸入轉換bks命令時設置的密碼

轉換為jks格式

另存為,獲得jks證書

調用java代碼

package com.hqx.util;

import com.alibaba.fastjson.JSONObject;
import org.junit.Test;

import javax.net.ssl.*;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.*;
import java.security.cert.CertificateException;
import java.util.List;
import java.util.Map;


public class HttpsClient {
    public static String KEY_STORE_FILE="key/javatest.hqxapp.com.client.p12";
    public static String KEY_STORE_PASS="密碼";
    /**
     * server.crt轉換bks,再轉為jks格式
     */
    public static String TRUST_STORE_FILE="key/javatest.hqxapp.com.server.jks";
    public static String TRUST_STORE_PASS="密碼";


    private static SSLContext sslContext;
    /**
     * 向指定URL發送GET方法的請求 
     *
     * @param url
     *            發送請求的URL 
     * @param param
     *            請求參數,請求參數應該是 name1=value1&name2=value2 的形式。 
     * @return URL 所代表遠程資源的響應結果 
     *
     */
    public static String sendGet(String url, String param) {
        String result = "";
        BufferedReader in = null;
        try {
            String urlNameString = url + "?" + param;
            URL realUrl = new URL(urlNameString);
            // 打開和URL之間的連接  
            HttpURLConnection connection = (HttpURLConnection) realUrl.openConnection();
            // 打開和URL之間的連接  
            if(connection instanceof HttpsURLConnection){
                ((HttpsURLConnection)connection)
                        .setSSLSocketFactory(getSSLContext().getSocketFactory());
            }

            // 設置通用的請求屬性  
            connection.setRequestProperty("accept", "*/*");
            connection.setRequestProperty("connection", "Keep-Alive");
            connection.setRequestProperty("user-agent",
                    "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
            // 建立實際的連接  
            connection.connect();
            // 獲取所有響應頭字段  
            Map<String, List<String>> map = connection.getHeaderFields();
            // 遍歷所有的響應頭字段  
            for (String key : map.keySet()) {
                //System.out.println(key + "--->" + map.get(key));  
            }
            // 定義 BufferedReader輸入流來讀取URL的響應  

            if(connection.getResponseCode()==200){
                in = new BufferedReader(new InputStreamReader(
                        connection.getInputStream(),"utf8"));
            }else{
                in = new BufferedReader(new InputStreamReader(
                        connection.getErrorStream(),"utf8"));
            }
            String line;
            while ((line = in.readLine()) != null) {
                result += line;
            }

        } catch (Exception e) {
            System.out.println("發送GET請求出現異常!" + e);
            e.printStackTrace();
        }
        // 使用finally塊來關閉輸入流  
        finally {
            try {
                if (in != null) {
                    in.close();
                }
            } catch (Exception e2) {
                e2.printStackTrace();
            }
        }
        return result;
    }

    /**
     * 向指定 URL 發送POST方法的請求  
     *
     * @param url
     *            發送請求的 URL  
     * @param param
     *            請求參數,請求參數應該是 name1=value1&name2=value2 的形式。  
     * @return 所代表遠程資源的響應結果
     */
    public static String sendPost(String url, String param) {
        PrintWriter out = null;
        BufferedReader in = null;
        String result = "";
        try {
            URL realUrl = new URL(url);
            // 打開和URL之間的連接  
            HttpURLConnection conn = (HttpURLConnection) realUrl.openConnection();
            if(conn instanceof HttpsURLConnection){
                ((HttpsURLConnection)conn)
                        .setSSLSocketFactory(getSSLContext().getSocketFactory());
            }
            // 設置通用的請求屬性  
            conn.setRequestProperty("accept", "*/*");
            conn.setRequestProperty("connection", "Keep-Alive");
            conn.setRequestProperty("Content-Type", "application/json");
            conn.setRequestProperty("user-agent",
                    "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
            // 發送POST請求必須設置如下兩行  
            conn.setDoOutput(true);
            conn.setDoInput(true);
            // 獲取URLConnection對象對應的輸出流  
            out = new PrintWriter(conn.getOutputStream());
            // 發送請求參數  
            out.print(param);
            // flush輸出流的緩沖  
            out.flush();
            // 定義BufferedReader輸入流來讀取URL的響應  
            if(conn.getResponseCode()==200){
                in = new BufferedReader(
                        new InputStreamReader(conn.getInputStream(),"utf8"));
            }else{
                in = new BufferedReader(
                        new InputStreamReader(conn.getErrorStream(),"utf8"));
            }
            String line="";
            while ((line = in.readLine()) != null) {
                result += line;
            }
        } catch (Exception e) {
            System.out.println("發送 POST 請求出現異常!"+e);
            e.printStackTrace();
        }
        //使用finally塊來關閉輸出流、輸入流  
        finally{
            try{
                if(out!=null){
                    out.close();
                }
                if(in!=null){
                    in.close();
                }
            }catch(IOException ex){
                ex.printStackTrace();
            }
        }
        return result;
    }

    public static SSLContext getSSLContext(){
        long time1=System.currentTimeMillis();
        if(sslContext==null){
            try {
                KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
                kmf.init(getkeyStore(),KEY_STORE_PASS.toCharArray());
                KeyManager[] keyManagers = kmf.getKeyManagers();

                TrustManagerFactory trustManagerFactory=TrustManagerFactory.getInstance("SunX509");
                trustManagerFactory.init(getTrustStore());
                TrustManager[]  trustManagers= trustManagerFactory.getTrustManagers();

                sslContext = SSLContext.getInstance("TLS");
                sslContext.init(keyManagers, trustManagers, new SecureRandom());
                HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
                    @Override
                    public boolean verify(String hostname, SSLSession session) {
                        return true;
                    }
                });
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (NoSuchAlgorithmException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } catch (UnrecoverableKeyException e) {
                e.printStackTrace();
            } catch (KeyStoreException e) {
                e.printStackTrace();
            } catch (KeyManagementException e) {
                e.printStackTrace();
            }
        }
        long time2=System.currentTimeMillis();
        System.out.println("SSLContext 初始化時間:"+(time2-time1));
        return sslContext;
    }


    public static KeyStore getkeyStore(){
        KeyStore keySotre=null;
        try {
            keySotre = KeyStore.getInstance("PKCS12");
            File file = new File(KEY_STORE_FILE);
            System.out.println(file.getAbsolutePath());
            FileInputStream fis = new FileInputStream(new File(KEY_STORE_FILE));
            keySotre.load(fis, KEY_STORE_PASS.toCharArray());
            fis.close();
        } catch (KeyStoreException e) {
            e.printStackTrace();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (CertificateException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return keySotre;
    }
    public static KeyStore getTrustStore() throws IOException{
        KeyStore trustKeyStore=null;
        FileInputStream fis=null;
        try {
            trustKeyStore=KeyStore.getInstance("JKS");
            fis = new FileInputStream(new File(TRUST_STORE_FILE));
            trustKeyStore.load(fis, TRUST_STORE_PASS.toCharArray());
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (KeyStoreException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (CertificateException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally{
            fis.close();
        }
        return trustKeyStore;
    }

    @Test
    public static void main(String[] args) throws UnsupportedEncodingException {
        String result=sendGet("https://javatest.hqxapp.com/demo/", "");
        System.out.println(result);
    }

    @Test
    public void t() {
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("content", "111");
        String result=sendPost("https://javatest.hqxapp.com/demo/", jsonObject.toJSONString());
        System.out.println(result);
    }
}  

上面用到的jar包、腳本、代碼可在資源模塊中下載。

CSDN:https://blog.csdn.net/qq_27682773
簡書:https://www.jianshu.com/u/e99381e6886e
博客園:https://www.cnblogs.com/lixianguo
個人博客:https://www.lxgblog.com


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM