TLS(以前称为SSL)最著名的功能是启用HTTPS,HTTP的安全版本。然而,正如TLS的名字(传输层安全)所暗示的那样,它实际上比HTTP更深入。TLS被认为是TCP的安全版本;换句话说,只要是通过套接字来实现通信的协议都可以使用TLS来实现安全通信,比如gRPC也可以基于TLS来实现安全通信。
在之前的一篇文章中,我们已经看到了如何使用TLS在Go中设置HTTPS服务器和客户端。本文,将展示如何创建加密的套接字服务器,它可以作为其他网络协议的基础。这篇文章的所有代码都可以在GitHub上找到。
TLS 套接字服务器端
下面是一个使用TLS的简单socket服务器:
func main() {
port := flag.String("port", "4040", "listening port")
certFile := flag.String("cert", "cert.pem", "certificate PEM file")
keyFile := flag.String("key", "key.pem", "key PEM file")
flag.Parse()
cert, err := tls.LoadX509KeyPair(*certFile, *keyFile)
if err != nil {
log.Fatal(err)
}
config := &tls.Config{Certificates: []tls.Certificate{cert}}
log.Printf("listening on port %s\n", *port)
l, err := tls.Listen("tcp", ":"+*port, config)
if err != nil {
log.Fatal(err)
}
defer l.Close()
for {
conn, err := l.Accept()
if err != nil {
log.Fatal(err)
}
log.Printf("accepted connection from %s\n", conn.RemoteAddr())
go func(c net.Conn) {
io.Copy(c, c)
c.Close()
log.Printf("closing connection from %s\n", conn.RemoteAddr())
}(conn)
}
}
上面的代码接收来自客户端的多个(并发)连接,并返回客户端发送的所有数据,直到客户端套接字关闭。它与非tls服务器的代码非常相似,除了net.Listen被tls.Listen取代了,后者需要tls.Config(已经在前一篇文章中遇到过)。可以使用前文中描述的证书生成工具,或者mkcert工具为这个服务器生成一个证书/密钥对。
TLS套接字客户端
func main() {
port := flag.String("port", "4040", "port to connect")
certFile := flag.String("certfile", "cert.pem", "trusted CA certificate")
flag.Parse()
cert, err := os.ReadFile(*certFile)
if err != nil {
log.Fatal(err)
}
certPool := x509.NewCertPool()
if ok := certPool.AppendCertsFromPEM(cert); !ok {
log.Fatalf("unable to parse cert from %s", *certFile)
}
config := &tls.Config{RootCAs: certPool}
conn, err := tls.Dial("tcp", "localhost:"+*port, config)
if err != nil {
log.Fatal(err)
}
_, err = io.WriteString(conn, "Hello simple secure Server\n")
if err != nil {
log.Fatal("client write error:", err)
}
if err = conn.CloseWrite(); err != nil {
log.Fatal(err)
}
buf := make([]byte, 256)
n, err := conn.Read(buf)
if err != nil && err != io.EOF {
log.Fatal(err)
}
fmt.Println("client read:", string(buf[:n]))
conn.Close()
}
同样,与非tls客户端的区别只是将net.Dial替换成tls.Dial,并附带tls.Config配置,添加客户端可以信任的证书(这可以是服务器自己的证书,也可以是权威机构签署的CA证书等等)。
查看服务器证书
下面是一个简单的程序,可以用来检查任何服务器的证书:
func main() {
addr := flag.String("addr", "localhost:4040", "dial address")
flag.Parse()
cfg := tls.Config{}
conn, err := tls.Dial("tcp", *addr, &cfg)
if err != nil {
log.Fatal("TLS connection failed: " + err.Error())
}
defer conn.Close()
certChain := conn.ConnectionState().PeerCertificates
for i, cert := range certChain {
fmt.Println(i)
fmt.Println("Issuer:", cert.Issuer)
fmt.Println("Subject:", cert.Subject)
fmt.Println("Version:", cert.Version)
fmt.Println("NotAfter:", cert.NotAfter)
fmt.Println("DNS names:", cert.DNSNames)
fmt.Println("")
}
}
给定一个IP地址,该程序与对应服务端建立TLS连接,并打印它使用的证书。我们可以先在自己的TLS套接字服务器上试试;注意,该程序没有任何预信任证书,因此它将拒绝自签名证书。但是,如果我们使用mkcert为服务器生成证书,它就可以工作了。
打开终端,使用mkcert生成的证书来运行TLS套接字服务器:
$ mkcert localhost
Created a new certificate valid for the following names ?
- "localhost"
The certificate is at "./localhost.pem" and the key at "./localhost-key.pem" ✅
It will expire on 7 July 2023 ?
$ go run tls-socket-server.go -cert localhost.pem -key localhost-key.pem
2021/04/07 06:27:20 listening on port 4040
在另一个终端,运行前面的代码:
$ go run tls-dial-port.go -addr localhost:4040
0
Issuer: CN=mkcert eliben@salami (Eli Bendersky),OU=eliben@salami (Eli Bendersky),O=mkcert development CA
Subject: OU=eliben@salami (Eli Bendersky),O=mkcert development certificate
Version: 3
NotAfter: 2023-07-07 13:27:12 +0000 UTC
DNS names: [localhost]
可以看到生成的证书mkcert。由于mkcert将此证书添加到系统根存储,因此tls.Dial默认使用该证书。
可以将该程序访问其他公共网站的443端口(HTTPS的默认端口);例如:
➜ go run tls-dial-port.g -addr baidu.com:443
0
Issuer: CN=DigiCert Secure Site Pro CN CA G3,O=DigiCert Inc,C=US
Subject: CN=www.baidu.cn,O=BeiJing Baidu Netcom Science Technology Co.\, Ltd,ST=北京市,C=CN
Version: 3
NotAfter: 2022-02-24 23:59:59 +0000 UTC
DNS names: [www.baidu.cn baidu.cn baidu.com baidu.com.cn w.baidu.com ww.baidu.com www.baidu.com.cn www.baidu.com.hk www.baidu.hk www.baidu.net.au www.baidu.net.ph www.baidu.net.tw www.baidu.net.vn wwww.baidu.com wwww.baidu.com.cn]
1
Issuer: CN=DigiCert Global Root CA,OU=www.digicert.com,O=DigiCert Inc,C=US
Subject: CN=DigiCert Secure Site Pro CN CA G3,O=DigiCert Inc,C=US
Version: 3
NotAfter: 2030-03-13 12:00:48 +0000 UTC
DNS names: []