TL;DR 手工創(chuàng)建CA證書鏈湿痢,手寫代碼打通HTTPs的兩端
HTTPs最近是一個重要的話題,同時也是一個有點難懂的話題扑庞。所以網(wǎng)上有大量的HTTPs/TLS/SSL的教程譬重。關(guān)于這些的原理,這里不做講解罐氨,有興趣的可以自行搜索臀规。
本文介紹一個自己創(chuàng)建證書,并編寫 Go 代碼實現(xiàn) client/server 兩端的過程栅隐。從實踐的角度幫助理解塔嬉。
構(gòu)建 CA 證書鏈
我們首先要創(chuàng)建 client/server 使用的證書。創(chuàng)建證書的方法有很多種:有不怕麻煩租悄,直接通過 openssl
創(chuàng)建的谨究,有通過 cfssl 創(chuàng)建的。這里要介紹的是我認為最簡單的一種:tls-gen
tls-gen
是一個用 Python 編寫的泣棋、非常易用的工具胶哲。它定義了三種 profile。這里我們選擇最簡單的一種:一個根證書和一組證書外傅、私鑰對纪吮。
在 shell 里面執(zhí)行一下的命令:
git clone https://github.com/michaelklishin/tls-gen
cd tls-gen/basic
make CN=www.mytestdomain.io
就這樣,我們就為域名 www.mytestdomain.io
創(chuàng)建了一套證書萎胰。觀察一下當前路徑的內(nèi)容碾盟,我們會發(fā)現(xiàn)兩個新的目錄:testca
和 server
。前者里面存放了剛剛創(chuàng)建的根證書 (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)
}
}
可以看到我們創(chuàng)建了一個 HTTP 服務,這個服務監(jiān)聽 1443 端口并且只處理一個路徑 /hello
搓扯。然后調(diào)用了下面這個函數(shù)來監(jiān)聽 1443 端口检痰。注意我們給出了之前創(chuàng)建的服務的證書和私鑰 - 這樣就保證了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
這是因為系統(tǒng)不知道如何來處理這個 self signed 證書公壤。
各個 OS 添加根證書的方法是不同的。對于 Linux 系統(tǒng) (以 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
上面的方法會把我們手工創(chuàng)建的 root CA
添加到系統(tǒng)所已知的列表里面。這樣一來慨飘,所有用該 root CA
創(chuàng)建的證書都可以被認證了确憨。
現(xiàn)在我們再次運行剛才那個程就會成功的獲得服務端的響應了:
This is an example server.
另一種訪問方法
假如只是一個普通的用戶,沒有 root/sudo 權(quán)限瓤的,不就無法做上面的操作了嗎休弃?這種情況下還有另外一種做法: 把 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
的內(nèi)容
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 的內(nèi)容產(chǎn)生了一個新的 http transport玫芦。
運行一下 go run client.go
。成功本辐!
This is an example server.
總結(jié)
一對 HTTPs client/server 程序中需要一個共同的 root CA桥帆。服務器端需要該 root CA
創(chuàng)建的 CA/私鑰對。
這里用的是 Go 語言來實現(xiàn)慎皱,其它的語言過程也類似老虫。