使用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

作者:Gacfox
版权声明:本网站为非盈利性质,文章如非特殊说明均为原创,版权遵循知识共享协议CC BY-NC-ND 4.0进行授权,转载必须署名,禁止用于商业目的或演绎修改后转载。