使用 Go 實現 TLS 服務器和客戶端
【導讀】TLS 安全協議是什麼,用 TLS 能達到什麼效果?本文結合實戰案例說明了 TLS 服務端客戶端實現方案。
傳輸層安全協議(Transport Layer Security,縮寫:TLS),及其前身安全套接層(Secure Sockets Layer,縮寫:SSL)是一種安全協議,目的是爲互聯網通信提供安全及數據完整性保障。
SSL 包含記錄層(Record Layer)和傳輸層,記錄層協議確定了傳輸層數據的封裝格式。傳輸層安全協議使用 X.509 認證,之後利用非對稱加密演算來對通信方做身份認證,之後交換對稱密鑰作爲會談密鑰(Session key)。這個會談密鑰是用來將通信兩方交換的數據做加密,保證兩個應用間通信的保密性和可靠性,使客戶與服務器應用之間的通信不被攻擊者竊聽。
本文並沒有提供一個 TLS 的深度教程,而是提供了兩個 Go 應用 TLS 的簡單例子,用來演示使用 Go 語言快速開發安全網絡傳輸的程序。
TLS 歷史:
-
1994 年早期,NetScape 公司設計了 SSL 協議(Secure Sockets Layer)的 1.0 版,但是未發佈。
-
1994 年 11 月,NetScape 公司發佈 SSL 2.0 版,很快發現有嚴重漏洞。
-
1996 年 11 月,SSL 3.0 版問世,得到大規模應用。
-
1999 年 1 月,互聯網標準化組織 ISOC 接替 NetScape 公司,發佈了 SSL 的升級版 TLS 1.0 版。
-
2006 年 4 月和 2008 年 8 月,TLS 進行了兩次升級,分別爲 TLS 1.1 版和 TLS 1.2 版。最新的變動是 2011 年 TLS 1.2 的修訂版。
-
現在正在制定 tls 1.3。
證書生成
首先我們創建私鑰和證書。
服務器端的證書生成
使用了 "服務端證書" 可以確保服務器不是假冒的。
1、 生成服務器端的私鑰
openssl genrsa -out server.key 2048
2、 生成服務器端證書
openssl req -new -x509 -key server.key -out server.pem -days 3650
或者
go run $GOROOT/src/crypto/tls/generate_cert.go --host localhost
客戶端的證書生成
除了 "服務端證書",在某些場合中還會涉及到 "客戶端證書"。所謂的 "客戶端證書" 就是用來證明客戶端訪問者的身份。比如在某些金融公司的內網,你的電腦上必須部署 "客戶端證書",才能打開重要服務器的頁面。我會在後面的例子中演示 "客戶端證書" 的使用。
3、 生成客戶端的私鑰
openssl genrsa -out client.key 2048
4、 生成客戶端的證書
openssl req -new -x509 -key client.key -out client.pem -days 3650
- 或者使用下面的腳本:
#!/bin/bash
# call this script with an email address (valid or not).
# like:
# ./makecert.sh demo@random.com
mkdir certs
rm certs/*
echo "make server cert"
openssl req -new -nodes -x509 -out certs/server.pem -keyout certs/server.key -days 3650 -subj "/C=DE/ST=NRW/L=Earth/O=Random Company/OU=IT/CN=www.random.com/emailAddress=$1"
echo "make client cert"
openssl req -new -nodes -x509 -out certs/client.pem -keyout certs/client.key -days 3650 -subj "/C=DE/ST=NRW/L=Earth/O=Random Company/OU=IT/CN=www.random.com/emailAddress=$1"
Golang 例子
Go Package tls 部分實現了 tls 1.2 的功能,可以滿足我們日常的應用。Package crypto/x509 提供了證書管理的相關操作。
服務器證書的使用
本節代碼提供了服務器使用證書的例子。下面的代碼是服務器的例子:
package main
import (
"bufio"
"crypto/tls"
"log"
"net"
)
func main() {
cert, err := tls.LoadX509KeyPair("server.pem", "server.key")
if err != nil {
log.Println(err)
return
}
config := &tls.Config{Certificates: []tls.Certificate{cert}}
ln, err := tls.Listen("tcp", ":443", config)
if err != nil {
log.Println(err)
return
}
defer ln.Close()
for {
conn, err := ln.Accept()
if err != nil {
log.Println(err)
continue
}
go handleConn(conn)
}
}
func handleConn(conn net.Conn) {
defer conn.Close()
r := bufio.NewReader(conn)
for {
msg, err := r.ReadString('\n')
if err != nil {
log.Println(err)
return
}
println(msg)
n, err := conn.Write([]byte("world\n"))
if err != nil {
log.Println(n, err)
return
}
}
}
首先從上面我們創建的服務器私鑰和 pem 文件中得到證書 cert,並且生成一個 tls.Config 對象。這個對象有多個字段可以設置,本例中我們使用它的默認值。
然後用 tls.Listen 開始監聽客戶端的連接,accept 後得到一個 net.Conn,後續處理和普通的 TCP 程序一樣。
我們再看看客戶端是如何實現的:
package main
import (
"crypto/tls"
"log"
)
func main() {
conf := &tls.Config{
InsecureSkipVerify: true,
}
conn, err := tls.Dial("tcp", "127.0.0.1:443", conf)
if err != nil {
log.Println(err)
return
}
defer conn.Close()
n, err := conn.Write([]byte("hello\n"))
if err != nil {
log.Println(n, err)
return
}
buf := make([]byte, 100)
n, err = conn.Read(buf)
if err != nil {
log.Println(n, err)
return
}
println(string(buf[:n]))
}
InsecureSkipVerify 用來控制客戶端是否證書和服務器主機名。如果設置爲 true, 則不會校驗證書以及證書中的主機名和服務器主機名是否一致。
因爲在我們的例子中使用自簽名的證書,所以設置它爲 true, 僅僅用於測試目的。
可以看到,整個的程序編寫和普通的 TCP 程序的編寫差不太多,只不過初始需要做一些 TLS 的配置。
你可以 go run server.go 和 go run client.go 測試這個例子。
客戶端證書的使用
在有的情況下,需要雙向認證,服務器也需要驗證客戶端的真實性。在這種情況下,我們需要服務器和客戶端進行一點額外的配置。
服務器端:
package main
import (
"bufio"
"crypto/tls"
"crypto/x509"
"io/ioutil"
"log"
"net"
)
func main() {
cert, err := tls.LoadX509KeyPair("server.pem", "server.key")
if err != nil {
log.Println(err)
return
}
certBytes, err := ioutil.ReadFile("client.pem")
if err != nil {
panic("Unable to read cert.pem")
}
clientCertPool := x509.NewCertPool()
ok := clientCertPool.AppendCertsFromPEM(certBytes)
if !ok {
panic("failed to parse root certificate")
}
config := &tls.Config{
Certificates: []tls.Certificate{cert},
ClientAuth: tls.RequireAndVerifyClientCert,
ClientCAs: clientCertPool,
}
ln, err := tls.Listen("tcp", ":443", config)
if err != nil {
log.Println(err)
return
}
defer ln.Close()
for {
conn, err := ln.Accept()
if err != nil {
log.Println(err)
continue
}
go handleConn(conn)
}
}
func handleConn(conn net.Conn) {
defer conn.Close()
r := bufio.NewReader(conn)
for {
msg, err := r.ReadString('\n')
if err != nil {
log.Println(err)
return
}
println(msg)
n, err := conn.Write([]byte("world\n"))
if err != nil {
log.Println(n, err)
return
}
}
}
因爲需要驗證客戶端,我們需要額外配置下面兩個字段:
ClientAuth: tls.RequireAndVerifyClientCert,
ClientCAs: clientCertPool,
然後客戶端也配置這個 clientCertPool:
package main
import (
"crypto/tls"
"crypto/x509"
"io/ioutil"
"log"
)
func main() {
cert, err := tls.LoadX509KeyPair("client.pem", "client.key")
if err != nil {
log.Println(err)
return
}
certBytes, err := ioutil.ReadFile("client.pem")
if err != nil {
panic("Unable to read cert.pem")
}
clientCertPool := x509.NewCertPool()
ok := clientCertPool.AppendCertsFromPEM(certBytes)
if !ok {
panic("failed to parse root certificate")
}
conf := &tls.Config{
RootCAs: clientCertPool,
Certificates: []tls.Certificate{cert},
InsecureSkipVerify: true,
}
conn, err := tls.Dial("tcp", "127.0.0.1:443", conf)
if err != nil {
log.Println(err)
return
}
defer conn.Close()
n, err := conn.Write([]byte("hello\n"))
if err != nil {
log.Println(n, err)
return
}
buf := make([]byte, 100)
n, err = conn.Read(buf)
if err != nil {
log.Println(n, err)
return
}
println(string(buf[:n]))
}
運行這兩個代碼 go run server2.go 和 go run client2.go, 可以看到兩者可以正常的通訊,如果用前面的客戶端 go run client.go,不能正常通訊,因爲前面的客戶端並沒有提供客戶端證書。
更正 使用自定義的 CA 的例子可以參考 https://github.com/golang/net/tree/master/http2/h2demo
Make CA:
$ openssl genrsa -out rootCA.key 2048
$ openssl req -x509 -new -nodes -key rootCA.key -days 1024 -out rootCA.pem
... install that to Firefox
Make cert:
$ openssl genrsa -out server.key 2048
$ openssl req -new -key server.key -out server.csr
$ openssl x509 -req -in server.csr -CA rootCA.pem -CAkey rootCA.key -CAcreateserial -out server.crt -days 500
轉自:VIL 凌霄
jianshu.com/p/4cf92c5a386d
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/EIS5scxk0Fdfc35Xr7x_yQ