使用TLS传输层安全协议
TLS位于TCP传输层和应用层之间的安全传输层协议,它支持通过证书验证服务端身份,还能够实现客户端和服务器端之间的加密TCP通信,并保证数据在传输过程中不被篡改。TLS的应用极为广泛,像HTTPS协议其实就是基于TLS实现的。有关TLS协议的历史和细节这里就不再赘述了,具体可以参考信息安全相关章节,这篇笔记我们继续介绍如何在Go语言中使用TLS实现安全的TCP通信,我们将在之前的TCP服务器和客户端代码基础上将其改造为基于TLS协议安全传输数据。
生成自签名证书
使用TLS协议的前提是拥有X.509证书。一般来说,在公网使用的证书是需要CA来签发的,我们个人也可以通过Let's Encrypt等平台免费申请。不过实际开发中,如果我们开发的是纯内网应用或者只是用于测试目的,我们也可以使用自签名证书。
下面命令中,我们使用openssl工具生成了自签名证书。
openssl req -x509 -newkey rsa:2048 -nodes -keyout server.key -out server.crt -days 365
运行该命令会生成两个文件,其中server.crt是证书,server.key是证书的私钥。
TLS服务端代码实现
下面例子实现了TLS服务端,代码和TCP服务端非常类似,只是我们使用了crypto/tls来创建套接字的监听,并增加了加载证书的代码。
package main
import (
"bufio"
"crypto/tls"
"io"
"log/slog"
"net"
)
func handleConn(conn net.Conn) {
// 处理完成关闭TCP连接
defer func(conn net.Conn) {
if err := conn.Close(); err != nil {
slog.Error(err.Error())
return
}
}(conn)
reader := bufio.NewReader(conn)
writer := bufio.NewWriter(conn)
buffer := make([]byte, 1024)
for {
// 从连接中读取数据
n, err := reader.Read(buffer)
if err != nil {
if err == io.EOF {
slog.Info("Client EOF disconnected")
} else {
slog.Error(err.Error())
}
return
}
data := string(buffer[:n])
slog.Info("Recv: " + data)
// 向连接写入数据
if _, err := writer.Write([]byte("Echo: " + data)); err != nil {
slog.Error(err.Error())
return
}
if err = writer.Flush(); err != nil {
slog.Error(err.Error())
return
}
}
}
func main() {
// 加载服务器证书和私钥
cert, err := tls.LoadX509KeyPair("server.crt", "server.key")
if err != nil {
slog.Error(err.Error())
return
}
// 配置TLS
config := &tls.Config{Certificates: []tls.Certificate{cert}}
// 启动TCP服务端监听localhost:8000
listen, err := tls.Listen("tcp", "192.168.1.100:8000", config)
if err != nil {
slog.Error(err.Error())
return
}
slog.Info("TCP server listening on 192.168.1.100:8000...")
for {
conn, err := listen.Accept()
if err != nil {
slog.Error(err.Error())
continue
}
slog.Info("Connected from " + conn.RemoteAddr().String())
// 启动新协程处理连接
go handleConn(conn)
}
}
代码中,我们首先使用了LoadX509KeyPair()函数来加载X.509证书,TLS协议要求必须有证书,我们可以使用CA颁发的正式证书或是自签名证书,后面的config是一个TLS的配置对象,监听套接字时需要该结构体。
TLS客户端代码实现
下面代码实现了TLS的客户端。
package main
import (
"bufio"
"crypto/tls"
"io"
"log/slog"
"net"
"os"
)
func printText(conn net.Conn) {
reader := bufio.NewReader(conn)
buffer := make([]byte, 1024)
for {
// 从连接中读取数据
n, err := reader.Read(buffer)
if err != nil {
if err == io.EOF {
slog.Info("Server EOF disconnected")
} else {
slog.Error(err.Error())
}
return
}
slog.Info(string(buffer[:n]))
}
}
func main() {
// 配置 TLS
config := &tls.Config{
InsecureSkipVerify: true, // 允许忽略证书验证(用于内网自签名证书)
}
// 创建TCP连接
conn, err := tls.Dial("tcp", "192.168.1.100:8000", config)
if err != nil {
slog.Error(err.Error())
return
}
// 运行结束后关闭连接
defer func(conn net.Conn) {
if err := conn.Close(); err != nil {
slog.Error(err.Error())
return
}
}(conn)
// 启动新协程用于接收消息
go printText(conn)
reader := bufio.NewReader(os.Stdin)
writer := bufio.NewWriter(conn)
for {
// 从标准输入读取用户输入的内容
line, err := reader.ReadString('\n')
if err != nil {
slog.Error(err.Error())
return
}
// 写数据到连接中
if _, err = writer.Write([]byte(line)); err != nil {
slog.Error(err.Error())
return
}
if err = writer.Flush(); err != nil {
slog.Error(err.Error())
return
}
}
}
代码中,我们配置了InsecureSkipVerify: true,该配置用于忽略证书验证(即允许自签名证书),因为我们是在内网中测试所以将其设为true,如果你的客户端需要连接并验证拥有正式证书的服务端,则需要去掉该配置或将其配置为false。此时如果仍使用自签名证书就会报错tls: failed to verify certificate: x509: certificate signed by unknown authority。