這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
傳輸層安全性通訊協定(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、 產生伺服器端的私密金鑰
1 |
openssl genrsa -out server.key 2048 |
2、 產生伺服器端認證
1 |
openssl req -new -x509 -key server.key -out server.pem -days 3650 |
用戶端的認證產生
除了"服務端認證",在某些場合中還會涉及到"用戶端認證"。所謂的"用戶端認證"就是用來證明用戶端訪問者的身份。
比如在某些金融公司的內網,你的電腦上必須部署"用戶端認證",才能開啟重要伺服器的頁面。
我會在後面的例子中示範"用戶端認證"的使用。
3、 產生用戶端的私密金鑰
1 |
openssl genrsa -out client.key 2048 |
4、 產生用戶端的認證
1 |
openssl req -new -x509 -key client.key -out client.pem -days 3650 |
或者使用下面的指令碼:
12345678910 |
#!/bin/bash# call this script with an email address (valid or not).# like:# ./makecert.sh demo@random.commkdir certsrm 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提供了認證管理的相關操作。
伺服器憑證的使用
本節代碼提供了伺服器使用認證的例子。
下面的代碼是伺服器的例子:
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253 |
package mainimport ("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程式一樣。
然後,我們看看用戶端是如何?的:
12345678910111213141516171819202122232425262728293031323334 |
package mainimport ("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測試這個例子。
用戶端認證的使用
在有的情況下,需要雙向認證,伺服器也需要驗證用戶端的真實性。在這種情況下,我們需要伺服器和用戶端進行一點額外的配置。
伺服器端:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869 |
package mainimport ("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}}} |
因為需要驗證用戶端,我們需要額外配置下面兩個欄位:
12 |
ClientAuth: tls.RequireAndVerifyClientCert,ClientCAs: clientCertPool, |
然後用戶端也配置這個clientCertPool:
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455 |
package mainimport ("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,不能正常通訊,因為前面的用戶端並沒有提供用戶端認證。
參考文檔和代碼
- https://zh.wikipedia.org/wiki/%E5%82%B3%E8%BC%B8%E5%B1%A4%E5%AE%89%E5%85%A8%E5%8D%94%E8%AD%B0
- http://drops.wooyun.org/tips/6002
- http://www.levigross.com/2015/11/21/mutual-tls-authentication-in-go/
- https://github.com/nareix/blog/blob/master/posts/golang-tls-guide.md
- http://www.ruanyifeng.com/blog/2014/02/ssl_tls.html
- https://gist.github.com/spikebike/2232102
- https://github.com/nareix/tls-example
- http://seanlook.com/2015/01/07/tls-ssl/
- https://golang.org/pkg/crypto/tls/