TLS完全指南(三):用Go語言寫HTTPS程式
這篇文字基本是Tony Bai的這篇部落格tony的翻版;只是使 內容和前兩篇介紹TLS原理的OpenSSL操作的文字銜接。
k8sp/tls單向驗證身份
一般的HTTPS服務都是隻需要客戶端驗證伺服器的身份就好了。比如我們想訪問 銀行的網站,我們得確認那個網站真是我們要訪問的銀行的網站,而不是一個界 面類似的用來誘騙我們輸入銀行賬號和密碼的釣魚網站。而銀行網站並不需要通 過TLS驗證我們的身份,因為我們會透過在網頁裡輸入賬號的密碼向伺服器展示 我們的使用者身份。
k8sp/tlsHTTPS
伺服器程式
上文中我們貼了一個用Go語言寫的HTTPS server程式[。/server。go](。/server。go):
package main
import (
“io”
“log”
“net/http”
)
func main() {
http。HandleFunc(“/”, func(w http。ResponseWriter, req *http。Request) {
io。WriteString(w, “hello, world!\n”)
})
if e := http。ListenAndServeTLS(“:443”, “
server。crt
”, “server。key”, nil); e != nil {
log。Fatal(“ListenAndServe: ”, e)
}
}
我們可以用[create_tls_asserts。bash](。/create_tls_asserts。bash)建立私 鑰 server。key和身份證server。crt,
openssl genrsa -out server。key 2048
openssl req -nodes -new -key server。key -subj “/CN=localhost” -out server。csr
openssl x509 -req -sha256 -days 365 -in server。csr -signkey server。key -out server。crt
並且啟動服務程式:
sudo go run server。go &
k8sp/tls不驗證伺服器身份的客戶端程式
上文中我們展示了可以給curl一個-k引數, 讓它不驗證伺服器身份即訪問。我們自己也寫一個類似curl -k的client程式[unsecure-client。go](。/unsecure-client。go)來堅持訪問一個不一定安全的 HTTPS server:
package main
import (
“crypto/tls”
“io”
“log”
“net/http”
“os”
)
func main() {
c := &http。Client{
Transport: &http。Transport{
TLSClientConfig: &tls。Config{InsecureSkipVerify: true},
}}
if resp, e := c。Get(“https://localhost”); e != nil {
log。Fatal(“http。Client。Get: ”, e)
} else {
defer resp。Body。Close()
io。Copy(os。Stdout, resp。Body)
}
}
用以下命令編譯和啟動這個
客戶端程式
:
$ go run unsecure-client。go
hello, world!
k8sp/tls用自簽署的身份證驗證伺服器身份
上文中我們還展示了可以把伺服器的身份證 server。crt透過——cacert引數傳給curl,讓curl用伺服器自己的身份證驗證 它自己。類似的,我們也可以寫一個類似curl ——cacert server。crt的Go程式 [secure-client。go](。/secure-client。go)來訪問HTTPS server。這個程式和 上一個的區別僅僅在於 TLSClientConfig 的配置方式:
c := &http。Client{
Transport: &http。Transport{
TLSClientConfig: &tls。Config{RootCAs: loadCA(“server。crt”)},
}}
其中 loadCA 的實現很簡單:
func loadCA(caFile string) *x509。CertPool {
pool := x509。NewCertPool()
if ca, e :=
ioutil。ReadFile
(caFile); e != nil {
log。Fatal(“ReadFile: ”, e)
} else {
pool。AppendCertsFromPEM(ca)
}
return pool
}
k8sp/tls雙方認證對方身份
有的時候,客戶端透過輸入賬號和密碼向伺服器端展示自己的身份的方式太過繁 瑣。尤其是在如果客戶端並不是一個人,而只是一個程式的時候。這時,我們希 望雙方都利用一個身份證(certificate)透過TLS協議向對方展示自己的身份。 比如這個關於Kubernetes的例子。
k8sp/tls建立CA並簽署server以及client的身份證
我們可以按照上文中例子展示的:讓通 信雙方互相交換身份證,這樣既可互相驗證。但是如果一個分散式系統裡有多方, 任意兩房都要交換身份證太麻煩了。我們通常建立一個 自簽署的根身份證,然後用它來簽署分散式系 統中各方的身份證。這樣每一方都只要有這個根身份證即可驗證所有其他通訊方。 這裡解釋了用OpenSSL生成根身份證和簽署其他身 份證的過程。針對我們的例子,具體過程如下:
建立我們自己CA的私鑰:
openssl genrsa -out ca。key 2048
建立我們自己CA的CSR,並且用自己的私鑰自簽署之,得到CA的身份證:
openssl req -x509 -new -nodes -key ca。key -days 10000 -out ca。crt -subj “/CN=we-as-ca”
建立server的私鑰,CSR,並且用CA的私鑰自簽署server的身份證:
openssl genrsa -out server。key 2048
openssl req -new -key server。key -out server。csr -subj “/CN=localhost”
openssl x509 -req -in server。csr -CA ca。crt -CAkey ca。key -CAcreateserial -out server。crt -days 365
建立client的私鑰,CSR,以及用ca。key簽署client的身份證:
openssl genrsa -out client。key 2048
openssl req -new -key client。key -out client。csr -subj “/CN=localhost”
openssl x509 -req -in client。csr -CA ca。crt -CAkey ca。key -CAcreateserial -out client。crt -days 365
k8sp/tlsServer
相對於上面的例子,server的原始碼 [。/bidirectional/server。go](。/bidirectional/server。go)稍作了一些修改: 增加了一個 http。Server 變數s,並且呼叫s。ListenAndServeTLS,而不 是像之前那樣直接呼叫http。ListenAndServeTLS了:
func main() {
s := &http。Server{
Addr: “:443”,
Handler: http。HandlerFunc(func(w http。ResponseWriter, r *http。Request) {
fmt。Fprintf(w, “Hello World!\n”)
}),
TLSConfig: &tls。Config{
ClientCAs: loadCA(“
ca。crt
”),
ClientAuth: tls。RequireAndVerifyClientCert,
},
}
e := s。ListenAndServeTLS(“server。crt”, “server。key”)
if e != nil {
log。Fatal(“ListenAndServeTLS: ”, e)
}
}
k8sp/tlsClient
客戶端程式[。/bidirectional/client。go](。/bidirectional/client。go)相對 於上面提到的unsecure-client。go和
secure-client
。go的變化主要在於
呼叫tls。LoadX509KeyPair讀取client。key和client。crt,並返回一個
tls。Certificate變數
,
把這個變數傳遞給http。
Client變數
,然後呼叫其Get函式。
func main() {
pair, e := tls。LoadX509KeyPair(“client。crt”, “client。key”)
if e != nil {
log。Fatal(“LoadX509KeyPair:”, e)
}
client := &http。Client{
Transport: &http。Transport{
TLSClientConfig: &tls。Config{
RootCAs: loadCA(“ca。crt”),
Certificates: []tls。Certificate{pair},
},
}}
resp, e :=
client。Get
(“https://localhost”)
if e != nil {
log。Fatal(“http。Client。Get: ”, e)
}
defer resp。Body。Close()
io。Copy(os。Stdout, resp。Body)
}
k8sp/tls執行和測試
cd bidirectional
。/create_tls_asserts。bash # 建立各種TLS資源
sudo go run 。/server。go & # 啟動伺服器
go run 。/client。go # 嘗試連線伺服器
應當看到螢幕上打印出來 Hello World!。
在這篇部落格tony中提到,需要建立一個client。ext檔案, 使得client的身份證裡包含ExtKeyUsage欄位。但是我並沒有這麼做,得到的 程式也可以執行。
k8sp/tls參考文獻
tony Go和HTTPS | Tony Bai