白話https之golang實現(xiàn)https雙向認證

一、什么是https?

日常開發(fā)中大家可能接觸最多的都是http協(xié)議,說到http協(xié)議也不得不提到TCP/IP協(xié)議以及計算機網(wǎng)絡(luò)中tcp/ip五層與OCI7層架構(gòu)模型有關(guān),http(超文本傳輸協(xié)議)用戶客戶端和服務(wù)端之間的通信,位于tcp/ip五層協(xié)議中最上層傳輸層,http傳輸過程中都是明文傳輸,易引起安全問題,所有誕生了https協(xié)議。

HTTPS協(xié)議 = HTTP協(xié)議 + SSL/TLS協(xié)議,在HTTPS數(shù)據(jù)傳輸?shù)倪^程中,需要用SSL/TLS對數(shù)據(jù)進行加密和解密,需要用HTTP對加密后的數(shù)據(jù)進行傳輸,由此可以看出HTTPS是由HTTP和SSL/TLS一起合作完成的。

SSL的全稱是Secure Sockets Layer,即安全套接層協(xié)議,是為網(wǎng)絡(luò)通信提供安全及數(shù)據(jù)完整性的一種安全協(xié)議。SSL協(xié)議在1994年被Netscape發(fā)明,后來各個瀏覽器均支持SSL,其最新的版本是3.0
https通信過程如下圖所示:


image.png

二、加密算法

2.1 TLS/SSL的功能主要依賴三類算法實現(xiàn):散列函數(shù) Hash、對稱加密和非對稱加密,其利用非對稱加密實現(xiàn)身份認證和密鑰協(xié)商,對稱加密算法采用協(xié)商的密鑰對數(shù)據(jù)加密,基于散列函數(shù)驗證信息的完整性(對于三種加密算法含義這里就不一一概述),而在TLS/SSL加密算法中還會涉及一下幾個關(guān)鍵概念:

  • 密鑰:改變密碼行為的數(shù)字化參數(shù)。
  • 對稱密鑰加密系統(tǒng):編 / 解碼使用相同密鑰的算法。
  • 不對稱密鑰加密系統(tǒng):編 / 解碼使用不同密鑰的算法。
  • 公開密鑰加密系統(tǒng):一種能夠使數(shù)百萬計算機便捷地發(fā)送機密報文的系統(tǒng)。
  • 數(shù)字簽名:用來驗證報文未被偽造或篡改的校驗和。
  • 數(shù)字證書:由一個可信的組織驗證和簽發(fā)的識別信息。
    2.2 TLS的基本工作方式是,客戶端使用非對稱加密與服務(wù)器進行通信,實現(xiàn)身份驗證并協(xié)商對稱加密使用的密鑰, 然后對稱加密算法采用協(xié)商密鑰對信息以及信息摘要進行加密通信,不同的節(jié)點之間采用的對稱密鑰不同,從而可以保證信息只能通信雙方獲取。
    2.3 https通信過程中數(shù)字證書概覽:


    image.png

    ca.pem 根證書,由根證書頒發(fā)客戶端和服務(wù)端證書
    client.pem 客戶端證書
    client-key.pem 客戶端秘鑰
    server.pem 服務(wù)端證書
    server-key.pem 服務(wù)端秘鑰

三、如何自簽名證書?

3.1 目前自頒發(fā)證書工具有兩類,一是使用openssl 插件生成證書,二是通過cfssl插件生成證書,本例采用cfssl 工具生成證書

3.2 cfssl是一款由golang編寫的證書生成工具,官網(wǎng)地址:https://github.com/cloudflare/cfssl
安裝步驟:

@tips: 該方法需要安裝golang環(huán)境,如果沒有g(shù)olang請參照二進制包安裝方式
go get -u github.com/cloudflare/cfssl/cmd/cfssl
go get -u github.com/cloudflare/cfssl/cmd/cfssljson
@tips windows環(huán)境需要將gopath下bin目錄添加path環(huán)境中方可執(zhí)行cfssl命令
查看安裝是否成功:
cfssl
image.png

3.3 創(chuàng)建CA證書

mkdir ssl & cd ssl
cfssl print-defaults config > ca-config.json
cfssl print-defaults csr > ca-csr.json
然后修改ca-config.json文件
vi ca-config.json
{
    "signing": {
        "default": {
            "expiry": "43800h"
        },
        "profiles": {
            "server": {
                "expiry": "43800h",
                "usages": [
                    "signing",
                    "key encipherment",
                    "server auth"
                ]
            },
            "client": {
                "expiry": "43800h",
                "usages": [
                    "signing",
                    "key encipherment",
                    "client auth"
                ]
            },
            "peer": {
                "expiry": "43800h",
                "usages": [
                    "signing",
                    "key encipherment",
                    "server auth",
                    "client auth"
                ]
            }
        }
    }
}
## 生成ca 證書
mkdir ca
cfssl gencert -initca ca-csr.json | cfssljson -bare ca/ca -

3.4 創(chuàng)建serverd端證書

mkdir server
cfssl print-defaults csr > server.json
## 修改server.json
{
    "CN": "Server",
    "hosts": [
            "localhost",
            "127.0.0.1"
       ],
    "key": {
        "algo": "ecdsa",
        "size": 256
    },
    "names": [
        {
            "C": "CN",
            "L": "SH",
            "ST": "SH"
        }
    ]
}
## 簽發(fā)server端證書
cfssl gencert -ca=ca/ca.pem -ca-key=ca/ca-key.pem -config=ca-config.json -profile=server server/server.json | cfssljson -bare server/server

3.5 創(chuàng)建客戶端證書:

mkdir client
cfssl print-defaults csr > client.json
## 修改client.json
{
    "CN": "Client",
    "hosts": [],
    "key": {
        "algo": "ecdsa",
        "size": 256
    },
    "names": [
        {
            "C": "CN",
            "L": "SH",
            "ST": "SH"
        }
    ]
}
## 生成客戶端證書和私鑰
cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=client client.json | cfssljson -bare client/client

3.6 檢驗生成的證書是否和配置相符合:

openssl x509 -in ca.pem -text -noout
openssl x509 -in server.pem -text -noout
openssl x509 -in client.pem -text -noout

四 golang實現(xiàn)https雙向認證

server端代碼實現(xiàn):

func handler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w,
        "Hi, This is an example of https service in golang!")
}

func main() {
  // ssl 雙向檢驗
    pool := x509.NewCertPool()
    crt, err := ioutil.ReadFile(cert.K8sAgentCrtPath)
    if err != nil {
        log.Fatalln("讀取證書失??!", err.Error())
    }
    pool.AppendCertsFromPEM(crt)
    http.HandleFunc("/", handler)
    s := &http.Server{
        Addr: ":8080",
        TLSConfig: &tls.Config{
            ClientCAs:  pool,
                        ClientAuth: tls.RequireAndVerifyClientCert,  // 檢驗客戶端證書
                      
        },
    }
    log.Fatal(s.ListenAndServeTLS(cert.K8sAgentServerCertPath, cert.K8sAgentKeyPath))
}

客戶端代碼實現(xiàn):

func main() {
    pool := x509.NewCertPool()
    caCrt, err := ioutil.ReadFile(cert.K8sAgentCrtPath)
    if err != nil {
        log.Fatal("read ca.crt file error:", err.Error())
    }
    pool.AppendCertsFromPEM(caCrt)
    cliCrt, err := tls.LoadX509KeyPair(cert.ClientCrt, cert.ClientKey)
    if err != nil {
        log.Fatalln("LoadX509KeyPair error:", err.Error())
    }
    tr := &http.Transport{
        TLSClientConfig: &tls.Config{
            RootCAs:      pool,
            Certificates: []tls.Certificate{cliCrt},
        },
    }
    client := &http.Client{Transport: tr}
    resp, err := client.Get("https://127.0.0.1:8080/")
    if err != nil {
        panic(err.Error())
    }
    defer resp.Body.Close()
    body, err := ioutil.ReadAll(resp.Body)
    fmt.Println(string(body))
}

跨語言調(diào)用,node 客戶端:

const https = require('https');
const fs = require('fs');

const options = {
    hostname: '127.0.0.1',
    port: 8080,
    method: 'get',
    key: fs.readFileSync('./cert/clent-key.pem'),
    cert: fs.readFileSync('./cert/clent-key.pem'),
    ca:[fs.readFileSync('./cert/clent-key.pem')],
    agent: false,
    rejectUnauthorized: true
};

const req = https.request(options, (res) => {
    const certificate = res.connection.getPeerCertificate();
    const rawDir = certificate.raw;
    console.log(rawDir);
    console.log('Client connected', res.connection.authorized ? 'authorized': 'unauthorized');
    console.log('狀態(tài)碼:', res.statusCode);
    console.log('請求頭:', res.headers);
    res.setEncoding('utf-8');
    res.on('data', (d) => {
        process.stdout.write(d);
    });
});

req.on('error', (e) =>{
    console.error(e);
});

req.end();
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容