首頁 > 軟體

使用Go實現TLS伺服器和使用者端的範例

2021-12-08 16:00:09

傳輸層安全協定(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("worldn"))
        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("hellon"))
    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("worldn"))
        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("hellon"))
    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伺服器和使用者端內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com