Go代碼打通HTTPs

TL;DR 手工建立CA證書鏈,手寫代碼打通HTTPs的兩端git

HTTPs最近是一個重要的話題,同時也是一個有點難懂的話題。因此網上有大量的HTTPs/TLS/SSL的教程。關於這些的原理,這裏不作講解,有興趣的能夠自行搜索。github

本文介紹一個本身建立證書,並編寫 Go 代碼實現 client/server 兩端的過程。從實踐的角度幫助理解。shell

構建 CA 證書鏈

咱們首先要建立 client/server 使用的證書。建立證書的方法有不少種:有不怕麻煩,直接經過 openssl
建立的,有經過 cfssl 建立的。這裏要介紹的是我認爲最簡單的一種:tls-gen服務器

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

在 shell 裏面執行一下的命令:函數

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

就這樣,咱們就爲域名 www.mytestdomain.io 建立了一套證書。觀察一下當前路徑的內容,咱們會發現兩個新的目錄:testcaserver。前者裏面存放了剛剛建立的根證書 (root CA),後者裏面存放了咱們以後的服務程序要用的的證書和私鑰。工具

testca/
  cacert.pem

server/
  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會用加密的方式來傳輸。code

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

運行服務程序:server

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-----
MIIDAjCCAeqgAwIBAgIJAL2faqa73yLvMA0GCSqGSIb3DQEBCwUAMDExIDAeBgNV
BAMMF1RMU0dlblNlbGZTaWduZWR0Um9vdENBMQ0wCwYDVQQHDAQkJCQkMB4XDTE4
MDIwNTA5Mzc0NVoXDTI4MDIwMzA5Mzc0NVowMTEgMB4GA1UEAwwXVExTR2VuU2Vs
ZlNpZ25lZHRSb290Q0ExDTALBgNVBAcMBCQkJCQwggEiMA0GCSqGSIb3DQEBAQUA
A4IBDwAwggEKAoIBAQC9eO6Tam4XFDUbK9FAStAg29teYeKtt8WEJvKGB50xMfXO
2pD0StsXhKrspXBYck0FwKIBsTLr97w7dSqa64z3U2V2BorogFzoEE4JH2sydYGA
QqNAqezGx8VZnQVRyZEBifRPebR4WVD5GtXYe+MnSkHPIgsG0QG0SaiSfMl05dSJ
HoE9T9Kly9fH6yED88++OYjZZRGKOf2THpQlXJjF3iwCDLkwz9Z/kjmpK/rR0SEh
tanf7bOgGs3OoFmX4DvmFJXoriVUC9jcj0Z4oX3Ld81XXyd4FJkpKvdKDhYkqcug
FgERqdBeRDM+MA38YooKHZh0klL2EThNXJxM0r1vAgMBAAGjHTAbMAwGA1UdEwQF
MAMBAf8wCwYDVR0PBAQDAgEGMA0GCSqGSIb3DQEBCwUAA4IBAQBEqp0ON1A/pCKF
ztfKuzdW+9pauE8dl6Ij3++dt6AqW5QYFLOEFQwoMBOkGChGQDxHkakyaA0DfGe5
JntMH0yYyZnr4kfs+AcY6P+2PfgrgVBqadhR6uAGOBaXDW7dlllqIJJ8NRInA/fT
DYXMxBJbFrcj2cGIYVPvAbrosZ5L/YdAdVM76V8uuk8Hmmy5zRQj+gWt/jDkYWFr
p0b6k3FBXvM7+nhqAIdyMjLioAdYwFpPglGj3xHXS5neWjyUDlAYISNe+PKMERSe
DrptyDE+ljzl77hvvfZD9OPhXbDkAeVU/NaDwHG/G5HDVdNbg/FZ6ueevF34Xuze
jm3lrdJm
-----END CERTIFICATE-----`

也就是說,咱們用準備好的 root CA 的內容產生了一個新的 http transport。

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

This is an example server.

總結

一對 HTTPs client/server 程序中須要一個共同的 root CA。服務器端須要該 root CA 建立的 CA/私鑰對。

這裏用的是 Go 語言來實現,其它的語言過程也相似。

相關文章
相關標籤/搜索