使用Go實現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 [email protected] 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
到此這篇關於使用Go實現TLS服務器和客戶端的示例的文章就介紹到這瞭,更多相關Go TLS服務器和客戶端內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- Linux環境下生成openssl證書註意細節介紹
- Go語言實現UDP協議及TCP通訊
- Golang之defer 延遲調用操作
- golang判斷net.Conn 是否已關閉的操作
- 300行代碼實現go語言即時通訊聊天室