天天看點

Go代碼打通HTTPs

TL;DR 手工建立CA憑證鍊,手寫代碼打通HTTPs的兩端

HTTPs最近是一個重要的話題,同時也是一個有點難懂的話題。是以網上有大量的HTTPs/TLS/SSL的教程。關于這些的原理,這裡不做講解,有興趣的可以自行搜尋。

本文介紹一個自己建立證書,并編寫 Go 代碼實作 client/server 兩端的過程。從實踐的角度幫助了解。

建構 CA 證書鍊

我們首先要建立 client/server 使用的證書。建立證書的方法有很多種:有不怕麻煩,直接通過 openssl

建立的,有通過 cfssl 建立的。這裡要介紹的是我認為最簡單的一種:tls-gen

tls-gen是一個用 Python 編寫的、非常易用的工具。它定義了三種 profile。這裡我們選擇最簡單的一種:一個根證書和一組證書、私鑰對。

在 shell 裡面執行一下的指令:

  1. git clone https://github.com/michaelklishin/tls-gen
  2. cd tls-gen/basic
  3. make CN=www.mytestdomain.io

    就這樣,我們就為域名 www.mytestdomain.io 建立了一套證書。觀察一下目前路徑的内容,我們會發現兩個新的目錄:testca 和 server。前者裡面存放了剛剛建立的根證書 (root CA),後者裡面存放了我們之後的服務程式要用的的證書和私鑰。

testca/
  cacert.pemserver/
  cert.pem
  key.pem           

複制

編寫服務

接下來開始寫代碼。Go 對 TLS 的支援還是比較完備的,也比較簡單。以下是伺服器端的代碼 (server.go):

func HelloServer(w http.ResponseWriter, req *http.Request) {
    w.Header().Set("Content-Type", "text/plain")
    w.Write([]byte("This is an example server.\n"))
}
func main() {
    http.HandleFunc("/hello", HelloServer)
    err := http.ListenAndServeTLS(":1443", "server/cert.pem", "server/key.pem", nil)    
   if err != nil {
        log.Fatal("ListenAndServe: ", err)
    }
}           

複制

可以看到我們建立了一個 HTTP 服務,這個服務監聽 1443 端口并且隻處理一個路徑 /hello。然後調用了下面這個函數來監聽 1443 端口。注意我們給出了之前建立的服務的證書和私鑰 - 這樣就保證了HTTP會用加密的方式來傳輸。

ListenAndServeTLS(addr, certFile, keyFile string, handler Handler)           

複制

運作服務程式:

go run server.go           

複制

通路HTTPs服務

假定我們的服務程式是運作在本地的。我們先改一下 /etc/hosts 來配置域名解析:

# echo 127.0.0.1 www.mytestdomain.io >> /etc/hosts           

複制

我們用以下的代碼 (client.go) 來通路服務:

func main() {    client := &http.Client{}
    resp, err := client.Get("https://www.mytestdomain.io:1443/hello")    
    if err != nil {
        panic("failed to connect: " + err.Error())
    }
    content, _ := ioutil.ReadAll(resp.Body)
    s := strings.TrimSpace(string(content))

    fmt.Println(s)
}           

複制

運作 go run client.go,隻能得到這樣的錯誤:

panic: failed to connect: Get https://www.mytestdomain.io:1443/hello: x509: certificate signed by unknown authorit           

複制

這是因為系統不知道如何來處理這個 self signed 證書。

各個 OS 添加根證書的方法是不同的。對于 Linux 系統 (以 Ubuntu 為例) 來說,把證書檔案放到相應的目錄即可:

# sudo cp testca/cacert.pem /etc/ssl/certs           

複制

如果是 macOS,可以用一下的指令:

# sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain  testca/cacert.pem           

複制

上面的方法會把我們手工建立的 root CA

添加到系統所已知的清單裡面。這樣一來,所有用該 root CA

建立的證書都可以被認證了。

現在我們再次運作剛才那個程就會成功的獲得服務端的響應了:

This is an example server.           

複制

另一種通路方法

假如隻是一個普通的使用者,沒有 root/sudo 權限,不就無法做上面的操作了嗎?這種情況下還有另外一種做法: 把 root CA 放置在代碼裡面。

在上面的 client.go 裡面添加這麼幾行代碼:

func main() {
    roots := x509.NewCertPool()
    ok := roots.AppendCertsFromPEM([]byte(rootPEM))    
    if !ok {        panic("failed to parse root certificate")
    }

    tr := &http.Transport{
        TLSClientConfig: &tls.Config{RootCAs: roots},
    }

    client := &http.Client{Transport: tr}    // ...           

複制

其中的 rootPEM 就是 testca/cacert.pem 的内容

var rootPEM = `-----BEGIN CERTIFICATE-----MIIDAjCCAeqgAwIBA
gIJAL2faqa73yLvMA0GCSqGSIb3DQEBCwUAMDExIDAeBgNVBAMMF1RMU0dl
blNlbGZTaWduZWR0Um9vdENBMQ0wCwYDVQQHDAQkJCQkMB4XDTE4MDIwNTA
5Mzc0NVoXDTI4MDIwMzA5Mzc0NVowMTEgMB4GA1UEAwwXVExTR2VuU2VsZl
NpZ25lZHRSb290Q0ExDTALBgNVBAcMBCQkJCQwggEiMA0GCSqGSIb3DQEBA
QUAA4IBDwAwggEKAoIBAQC9eO6Tam4XFDUbK9FAStAg29teYeKtt8WEJvK
GB50xMfXO2pD0StsXhKrspXBYck0FwKIBsTLr97w7dSqa64z3U2V2Borog
FzoEE4JH2sydYGAQqNAqezGx8VZnQVRyZEBifRPebR4WVD5GtXYe+MnSkH
PIgsG0QG0SaiSfMl05dSJHoE9T9Kly9fH6yED88++OYjZZRGKOf2THpQlX
JjF3iwCDLkwz9Z/kjmpK/rR0SEhtanf7bOgGs3OoFmX4DvmFJXoriVUC9jc
j0Z4oX3Ld81XXyd4FJkpKvdKDhYkqcugFgERqdBeRDM+MA38YooKHZh0klL
2EThNXJxM0r1vAgMBAAGjHTAbMAwGA1UdEwQFMAMBAf8wCwYDVR0PBAQDAgE
GMA0GCSqGSIb3DQEBCwUAA4IBAQBEqp0ON1A/pCKFztfKuzdW+9pauE8dl6Ij
3++dt6AqW5QYFLOEFQwoMBOkGChGQDxHkakyaA0DfGe5JntMH0yYyZnr4kfs+
AcY6P+2PfgrgVBqadhR6uAGOBaXDW7dlllqIJJ8NRInA/fTDYXMxBJbFrcj2c
GIYVPvAbrosZ5L/YdAdVM76V8uuk8Hmmy5zRQj+gWt/jDkYWFrp0b6k3FBXvM7
+nhqAIdyMjLioAdYwFpPglGj3xHXS5neWjyUDlAYISNe+PKMERSeDrptyDE+ljz
l77hvvfZD9OPhXbDkAeVU/NaDwHG/G5HDVdNbg/FZ6ueevF34Xuzejm3lrdJm-----E
ND CERTIFICATE-----`           

複制

也就是說,我們用準備好的 root CA 的内容産生了一個新的 http transport。

運作一下 go run client.go。成功!

This is an example server.           

複制

總結
一對 HTTPs client/server 程式中需要一個共同的 root CA。伺服器端需要該 root CA
建立的 CA/私鑰對。

這裡用的是 Go 語言來實作,其它的語言過程也類似。           

複制