golang tls的使用

3,335 阅读7分钟

tls握手协议

一些概念:

Cipher suite(加密套件):这是一个由一组加密算法的名称组成的唯一标识符,在上图第 1 步中,用于客户端向服务端询问 TLS 通信采用的加密方案

每种Cipher的名字里包含了四部分信息,分别是:

1.密钥交换算法,用于决定客户端与服务器之间在握手的过程中如何认证,用到的算法包括RSA,Diffie-Hellman,ECDH,PSK等

2.加密算法,用于加密消息流,该名称后通常会带有两个数字,分别表示密钥的长度和初始向量的长度,比如DES 56/56, RC2 56/128, RC4 128/128, AES 128/128, AES 256/256

3.报文认证信息码(MAC)算法,用于创建报文摘要,确保消息的完整性(没有被篡改),算法包括MD5,SHA等。

4.PRF(伪随机数函数),用于生成“master secret”。

TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,从其名字可知,它是

1.基于TLS协议的;

2.使用ECDHE、RSA作为密钥交换算法;

3.加密算法是AES(密钥和初始向量的长度都是256);

4.MAC算法(这里就是哈希算法)是SHA。

1.密钥交换算法,一般是一个非对称加密算法,用于图中客户端使用公钥加密预主密钥(Premaster secret),服务端使用私钥解密的过程

2.加密算法,一般是一个对称加密算法,以握手生成的 Session key 为公钥,加密来自应用层的消息体

3.报文认证信息码(MAC)算法,一般是一个散列(Hash)函数,所以通常又称为 HMAC,用于对内容进行数字签名(digital signature)即验证消息完整性,以及进行对端的身份验证。

client random:这是一个由客户端生成的 32 字节序列,在每次连接中都是唯一的,一般应该以 4 位的时间戳 + 28 位的随机字节组成

server random:由服务端生成

Pre-Master(预主密钥):这是一个 48 字节的 blob 数据,一般是结合客户端随机数与服务端随机数,使用伪随机函数(Pseudorandom Function)生成的

Session key(会话密钥):这是 TLS 握手的最终目的,在握手的最后,客户端与服务端都能够拥有这个 Session key,因此可以将其作为对称加密的密钥,对双方的信息进行加解密

握手的具体过程:

1.Client Hello:客户端发送一条 Hello 信息发起握手,消息包含客户端要使用的 TLS 协议版本、Client random、Cipher suite、以及一些其他的必要信息

2.Server Hello:服务端收到客户端的 Hello 信息,根据信息中提供的加密套件列表,决定最终的握手类型。最后返回给客户端一条 Server Hello 消息,消息包含服务端随机数、最终确定使用的加密套件列表、以及服务端的证书(如 HTTPS 证书,证书中包含 RSA 生成的公钥和服务器的域名)

3.客户端验证证书,生成预主密钥:Server Hello 送来的证书经验证证书可信且站点归属正确后,客户端会将 Client random 与 Server random 结合,使用伪随机函数(Pseudorandom Function)生成 Pre-master secret,并使用证书中的公钥加密预主密钥,将其发送到服务端

4.服务端使用私钥解密,获取预主密钥:服务端接收到加密后的 Pre-master secret,使用之前 RSA 生成的与公钥配对的私钥进行解密,获得与客户端相同的 Pre-master secret

5.生成 Session key:此时客户端与服务端都通过密钥交换的过程,得到了相同的 Client random、Server random 与 Pre-master secret,客户端与服务端便可以各自推导出相同的 Session key,后续的通信内容则会使用这个 Session key 作为 AES_128_GCM 的密钥进行加解密

6.客户端与服务端交换加密的握手结束消息,并进行 HMAC 验证:生成会话密钥(Session key)之后,客户端与服务端会使用 Session key 加密消息体,交换一次 Finish 消息,表示握手正式完成,确认双方都能正常的使用 Session key 进行加密的数据传输,同时在 Finish 消息中使用 HMAC 进行数字签名验证以验证握手阶段的完整性

1.png 参考:juejin.cn/post/693943… github.com/halfrost/Ha…

证书的签发(Signing)和认证(Verification)的过程:

签发证书的步骤:

Signing阶段,首先撰写证书的元信息:签发人(Issuer)、地址、签发时间、过期失效等;当然,这些信息中还包含证书持有者(owner)的基本信息,例如owner的DN(DNS Name,即证书生效的域名),owner的公钥等基本信息。 通过Issuer(CA)的证书指定的Hash算法将信息摘要提取出来; Hash摘要通过Issuer(CA)私钥进行非对称加密,生成一个签名密文; 将签名密文attach到文件证书上,使之变成一个签名过的证书。

验证证书的步骤:

Verification阶段,浏览器获得之前签发的证书; 将其解压后分别获得“元数据”和“签名密文”; 将Issuer(CA)的证书指定的Hash算法应用到“元数据”获取摘要A; 将"签名密文"通过Issuer(CA)的公钥解密获得摘要B。 比对摘要A和摘要B,如果匹配,则说明这个证书是被CA验证过合法证书,里面的公钥等信息是可信的。 在Verification阶段,解密Signature获得摘要需要通过签发者(Issuer)的公钥,又该如何获得这个公钥,同时确保这个公钥是有效的呢?就是下面的证书链的内容

参考:blog.csdn.net/huzhenv5/ar…

创建ssl证书

自签名:

1.生成私钥

$ openssl genrsa -out server.key 2048

2.生成 CSR (Certificate Signing Request)

$ openssl req \
    -subj "/C=CN/ST=Tianjin/L=Tianjin/O=Mocha/OU=Mocha Software/CN=test1.sslpoc.com/emailAddress=test@mochasoft.com.cn" \
    -new \
    -key server.key \
    -out server.csr

3.生成自签名证书

$ openssl x509 \
    -req \
    -days 3650 \
    -in server.csr \
    -signkey server.key \
    -out server.crt

私有 CA 签名:

1.创建 CA 私钥

$ openssl genrsa -out ca.key 2048

2.生成 CA 的自签名证书

$ openssl req \
    -subj "/C=CN/ST=Tianjin/L=Tianjin/O=Mocha/OU=Mocha Software/CN=Server CA/emailAddress=test@mochasoft.com.cn" \
    -new \
    -x509 \
    -days 3650 \
    -key ca.key \
    -out ca.crt

3.生成需要颁发证书的私钥

$ openssl genrsa -out server.key 2048

4.生成要颁发证书的证书签名请求,证书签名请求当中的 Common Name 必须区别于 CA 的证书里面的 Common Name

$ openssl req \
    -subj "/C=CN/ST=Tianjin/L=Tianjin/O=Mocha/OU=Mocha Software/CN=test2.sslpoc.com/emailAddress=test@mochasoft.com.cn" \
    -new \
    -key server.key \
    -out server.csr

5.用 2 创建的 CA 证书给 4 生成的 签名请求 进行签名

$ openssl x509 \
    -req \
    -days 3650 \
    -in server.csr \
    -CA ca.crt \
    -CAkey ca.key \
    -set_serial 01 \
    -out server.crt

链接:www.jianshu.com/p/e5f46dcf4…

demo

先创建证书

server.go:

package main

import (
	"bufio"
	"crypto/tls"
	"crypto/x509"
	"io/ioutil"
	"log"
	"net"
)

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
		}
	}
}

func main() {
	cert, err := tls.LoadX509KeyPair("server.crt", "server.key")
	if err != nil {
		log.Println(err)
		return
	}

	certBytes, err := ioutil.ReadFile("../client/client.crt")
	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.VerifyClientCertIfGiven,
		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)
	}
}

client.go

package main

import (
	"crypto/tls"
	"crypto/x509"
	"io/ioutil"
	"log"
)

func main() {
	cert, err := tls.LoadX509KeyPair("client.crt", "client.key")
	if err != nil {
		log.Println(err)
		return
	}

	certBytes, err := ioutil.ReadFile("../server/server.crt")
	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{
		ServerName:   "17.chl.com",
		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 server.go
$ go run client.go

tls.Config

server:
&tls.Config{
	ClientAuth:   tls.VerifyClientCertIfGiven,
	Certificates: []tls.Certificate{cert},
	ClientCAs:    certPool,
}

client:
&tls.Config{
	ServerName:         "17.chl.com",
	RootCAs:            clientCertPool,
	Certificates:       []tls.Certificate{cert},
	InsecureSkipVerify: true,
}

Certificates:

Certificates 包含一个或多个证书链以呈现给连接的另一端。 自动选择与对等方要求兼容的第一个证书。

ClientAuth:

1.NoClientCert 表示在握手期间不应请求客户端证书,并且如果发送任何证书,则不会对其进行验证。

2.RequestClientCert 表示在握手期间应请求客户端证书,但不要求客户端发送任何证书。

3.RequireAnyClientCert 表示在握手过程中需要请求客户端证书,并且至少需要客户端发送一个证书,但不需要该证书是有效的。

4.VerifyClientCertIfGiven 表示在握手期间应请求客户端证书,但不要求客户端发送证书。 如果客户端确实发送了证书,则它必须是有效的。

5.RequireAndVerifyClientCert 表示握手时需要申请客户端证书,并且要求客户端至少发送一个有效证书。

ClientCAs:

ClientCAs 定义了服务器设置的一组根证书颁发机构,如果需要通过 ClientAuth 中的策略验证客户端证书。

RootCA:

RootCA 定义了客户端在验证服务器证书时使用的一组根证书颁发机构。 如果 RootCAs 为零,则 TLS 使用主机的根 CA 集。

InsecureSkipVerify:

InsecureSkipVerify 控制客户端是否验证服务器的证书链和主机名。如果 InsecureSkipVerify 为 true,则 TLS 接受服务器提供的任何证书以及该证书中的任何主机名。 在这种模式下,TLS 容易受到中间人攻击。 这应该仅用于测试。