標籤:

TLS完全指南(三):用Go語言寫HTTPS程序

這篇文字基本是Tony Bai的這篇博客tony的翻版;只是使 內容和前兩篇介紹TLS原理的OpenSSL操作的文字銜接。

k8sp/tls單向驗證身份

一般的HTTPS服務都是只需要客戶端驗證伺服器的身份就好了。比如我們想訪問 銀行的網站,我們得確認那個網站真是我們要訪問的銀行的網站,而不是一個界 面類似的用來誘騙我們輸入銀行賬號和密碼的釣魚網站。而銀行網站並不需要通 過TLS驗證我們的身份,因為我們會通過在網頁里輸入賬號的密碼向伺服器展示 我們的用戶身份。

k8sp/tlsHTTPS伺服器程序

上文中我們貼了一個用Go語言寫的HTTPS server程序[./server.go](./server.go):

package mainnnimport (nt"io"nt"log"nt"net/http"n)nnfunc main() {nthttp.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {nttio.WriteString(w, "hello, world!n")nt})ntif e := http.ListenAndServeTLS(":443", "server.crt", "server.key", nil); e != nil {nttlog.Fatal("ListenAndServe: ", e)nt}n}n

我們可以用[create_tls_asserts.bash](./create_tls_asserts.bash)創建私 鑰 server.key和身份證server.crt,

openssl genrsa -out server.key 2048nopenssl req -nodes -new -key server.key -subj "/CN=localhost" -out server.csrnopenssl x509 -req -sha256 -days 365 -in server.csr -signkey server.key -out server.crtn

並且啟動服務程序:

sudo go run server.go &n

k8sp/tls不驗證伺服器身份的客戶端程序

上文中我們展示了可以給curl一個-k參數, 讓它不驗證伺服器身份即訪問。我們自己也寫一個類似curl -k的client程序[unsecure-client.go](./unsecure-client.go)來堅持訪問一個不一定安全的 HTTPS server:

package mainnnimport (nt"crypto/tls"nt"io"nt"log"nt"net/http"nt"os"n)nnfunc main() {ntc := &http.Client{nttTransport: &http.Transport{ntttTLSClientConfig: &tls.Config{InsecureSkipVerify: true},ntt}}nntif resp, e := c.Get("https://localhost"); e != nil {nttlog.Fatal("http.Client.Get: ", e)nt} else {nttdefer resp.Body.Close()nttio.Copy(os.Stdout, resp.Body)nt}n}n

用以下命令編譯和啟動這個客戶端程序:

$ go run unsecure-client.gonhello, world!n

k8sp/tls用自簽署的身份證驗證伺服器身份

上文中我們還展示了可以把伺服器的身份證 server.crt通過--cacert參數傳給curl,讓curl用伺服器自己的身份證驗證 它自己。類似的,我們也可以寫一個類似curl --cacert server.crt的Go程序 [secure-client.go](./secure-client.go)來訪問HTTPS server。這個程序和 上一個的區別僅僅在於 TLSClientConfig 的配置方式:

tc := &http.Client{nttTransport: &http.Transport{ntttTLSClientConfig: &tls.Config{RootCAs: loadCA("server.crt")},ntt}}n

其中 loadCA 的實現很簡單:

func loadCA(caFile string) *x509.CertPool {ntpool := x509.NewCertPool()nntif ca, e := ioutil.ReadFile(caFile); e != nil {nttlog.Fatal("ReadFile: ", e)nt} else {nttpool.AppendCertsFromPEM(ca)nt}ntreturn pooln}n

k8sp/tls雙方認證對方身份

有的時候,客戶端通過輸入賬號和密碼向伺服器端展示自己的身份的方式太過繁 瑣。尤其是在如果客戶端並不是一個人,而只是一個程序的時候。這時,我們希 望雙方都利用一個身份證(certificate)通過TLS協議向對方展示自己的身份。 比如這個關於Kubernetes的例子。

k8sp/tls創建CA並簽署server以及client的身份證

我們可以按照上文中例子展示的:讓通 信雙方互相交換身份證,這樣既可互相驗證。但是如果一個分散式系統里有多方, 任意兩房都要交換身份證太麻煩了。我們通常創建一個 自簽署的根身份證,然後用它來簽署分散式系 統中各方的身份證。這樣每一方都只要有這個根身份證即可驗證所有其他通信方。 這裡解釋了用OpenSSL生成根身份證和簽署其他身 份證的過程。針對我們的例子,具體過程如下:

  1. 創建我們自己CA的私鑰:

    openssl genrsa -out ca.key 2048n

    創建我們自己CA的CSR,並且用自己的私鑰自簽署之,得到CA的身份證:

    openssl req -x509 -new -nodes -key ca.key -days 10000 -out ca.crt -subj "/CN=we-as-ca"n

  2. 創建server的私鑰,CSR,並且用CA的私鑰自簽署server的身份證:

    openssl genrsa -out server.key 2048nopenssl req -new -key server.key -out server.csr -subj "/CN=localhost"nopenssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days 365n

  3. 創建client的私鑰,CSR,以及用ca.key簽署client的身份證:

    openssl genrsa -out client.key 2048nopenssl req -new -key client.key -out client.csr -subj "/CN=localhost"nopenssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out client.crt -days 365n

k8sp/tlsServer

相對於上面的例子,server的源碼 [./bidirectional/server.go](./bidirectional/server.go)稍作了一些修改: 增加了一個 http.Server 變數s,並且調用s.ListenAndServeTLS,而不 是像之前那樣直接調用http.ListenAndServeTLS了:

func main() {nts := &http.Server{nttAddr: ":443",nttHandler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {ntttfmt.Fprintf(w, "Hello World!n")ntt}),nttTLSConfig: &tls.Config{ntttClientCAs: loadCA("ca.crt"),ntttClientAuth: tls.RequireAndVerifyClientCert,ntt},nt}nnte := s.ListenAndServeTLS("server.crt", "server.key")ntif e != nil {nttlog.Fatal("ListenAndServeTLS: ", e)nt}n}n

k8sp/tlsClient

客戶端程序[./bidirectional/client.go](./bidirectional/client.go)相對 於上面提到的unsecure-client.go和secure-client.go的變化主要在於

  1. 調用tls.LoadX509KeyPair讀取client.key和client.crt,並返回一個 tls.Certificate變數,
  2. 把這個變數傳遞給http.Client變數,然後調用其Get函數。

func main() {ntpair, e := tls.LoadX509KeyPair("client.crt", "client.key")ntif e != nil {nttlog.Fatal("LoadX509KeyPair:", e)nt}nntclient := &http.Client{nttTransport: &http.Transport{ntttTLSClientConfig: &tls.Config{nttttRootCAs: loadCA("ca.crt"),nttttCertificates: []tls.Certificate{pair},nttt},ntt}}nntresp, e := client.Get("https://localhost")ntif e != nil {nttlog.Fatal("http.Client.Get: ", e)nt}ntdefer resp.Body.Close()ntio.Copy(os.Stdout, resp.Body)n}n

k8sp/tls運行和測試

cd bidirectionaln./create_tls_asserts.bash # 創建各種TLS資源nsudo go run ./server.go & # 啟動伺服器ngo run ./client.go # 嘗試連接伺服器n

應當看到屏幕上列印出來 Hello World!。

在這篇博客tony中提到,需要創建一個client.ext文件, 使得client的身份證里包含ExtKeyUsage欄位。但是我並沒有這麼做,得到的 程序也可以運行。

k8sp/tls參考文獻

  • tony Go和HTTPS | Tony Bai

推薦閱讀:

Let's Encrypt 使用教程,免費的SSL證書,讓你的網站擁抱 HTTPS
一步步教你把HTTP網站免費轉成HTTPS網站
為什麼 12306 不買 https 證書?
WEB加速,協議先行

TAG:SSL | HTTPS |