TLS技术简介
Charles Chan @ 2022-09-10 #OpenSSL #TLS @Program
交互时序(规范)
TLS v1.2
详情可以参照官方文档 RFC 5246 。
在 RFC 5246 的第 36 页中, 给出了 TLS v1.2 的握手流程图。如下图所示:
sequenceDiagram
participant Client
participant Server
Client->>Server: ClientHello
Note left of Client: 客户端发送所支持的 SSL/TLS 最高协议版本号<br/>和所支持的加密算法集合<br/>及压缩方法集合等信息<br/>给服务器端。
Server->>Client: ServerHello
Note right of Server: 选定双方都能够支持的 SSL/TLS 协议版本<br/>和加密方法及压缩方法,<br/>返回给客户端
Server-)Client: Certificate
Note right of Server: 服务器端发送服务端证书给客户端
opt 仅需要时
Server-)Client: ServerKeyExchange
Note right of Server: 仅当服务器证书消息(如果已发送)不包含足够<br/>的数据以允许客户端交换预主密钥时,服务器才<br/>会发送 ServerKeyExchange 消息。<br/>通常,只有使用 DHE_DSS 、 DHE_RSA 、<br/>DH_anon 时会发生。
end
opt 双向验证
Server-)Client: CertificateRequest
Note right of Server: 服务器端向客户端请求客户端证书。
end
Server->>Client: ServerHelloDone
Note right of Server: 服务器端通知客户端初始协商结束
opt 双向验证
Client-)Server: Certificate
Note left of Client: 客户端向服务器端发送客户端证书。
end
Client->>Server: ClientKeyExchange
Note left of Client: 客户端使用服务器端的公钥,对客户端公钥和密钥<br/>种子进行加密,再发送给服务器端。
opt 双向验证
Client-)Server: CertificateVerify
Note left of Client: 客户端用本地私钥生成数字签名,<br/>并发送给服务器端,让其通过收到的客户端公钥<br/>进行身份验证。
end
Client->>Client: ChangeCipherSpec
Note left of Client: 客户端通知服务器端已将通讯方式切换到加密模式
Client->>Server: Finished
Note left of Client: 客户端做好加密通讯的准备。
Server->>Server: ChangeCipherSpec
Note right of Server: 服务器端通知客户端已将<br/>通讯方式切换到加密模式。
Server->>Client: Finished
Note right of Server: 服务器做好加密通讯的准备。
Client<<->>Server: Application Data
- 开箭头(如
-))表示可选发送的消息,或者根据实际境况决定是否发送的消息
TLS v1.3
详情可以参照官方文档 RFC 8446 。
在 RFC 8446 的第 11 页中, 给出了 TLS v1.2 的握手流程图。如下图所示:
sequenceDiagram
participant Client
participant Server
Client->>Server: ClientHello
Note left of Client: 包含的信息:<br/> 密钥交换:<br/> <i>key_share</i>:<br/> 终端的加密参数<br/> <i>signature_algorithms</i>:<br/> 希望服务器通过证书验证自己的客户端必须<br/> 发送“signature_algorithms”<br/> <i>psk_key_exchange_modes</i>:<br/> 使用预共享密钥时,必须发送<br/> <i>pre_shared_key</i>:<br/> 用于协商预共享密钥的身份,以用于与 PSK<br/> 密钥建立相关的给定握手。
Server->>Client: ServerHello
Note right of Server: 包含的信息:<br/> 密钥交换:<br/> <i>key_share</i>:<br/> 终端的加密参数<br/> <i>pre_shared_key</i>:<br/> 用于协商预共享密钥的身份,以用于与 PSK<br/> 密钥建立相关的给定握手。<br/> 服务端参数:<br/> EncryptedExtensions:<br/> 用来发送加密扩展信息。<br/> <i>CertificateRequest</i>:<br/> 当客户端需要验证时发送<br/> 验证:<br/> <i>Certificate</i>:<br/> 用于身份验证的证书<br/> <i>CertificateVerify</i>:<br/> 对信息的签名
Client->>Server: 验证
Note left of Client: 包含的信息:<br/> <i>Certificate</i>:<br/> 用于身份验证的证书<br/> <i>CertificateVerify</i>:<br/> 对信息的签名
Client<<->>Server: Application Data
- 斜体字表示根据情况可能使用也可能不使用的内容。 事实上,除了“ EncryptedExtensions ”,全都是斜体字。
- 可以注意到, v1.3 中没有提压缩算法,应该是删除了。
开发(基于OpenSSL)
初始化
通过 SSL_CTX_set_cipher_list() (v1.2)或者 SSL_CTX_set_ciphersuites() (v1.3)确定并设置加密算法。
通过 SSL_CTX_set_min_proto_version() 和 SSL_CTX_set_max_proto_version() 确定支持的版本范围。
服务端和客户端都通过 SSL_CTX_use_certificate_file() 加载各自的证书文件。 如果涉及到证书链,则改为通过 SSL_CTX_use_certificate_chain_file() 加载。 事实上,可以无脑使用后者。
服务端和客户端都通过 SSL_CTX_use_PrivateKey_file() 加载各自的私钥文件。
如果涉及到根证书,则通过 SSL_CTX_load_verify_locations() 加载。
如果证书有密码,使用 SSL_CTX_set_default_passwd_cb_userdata() 进行提供。
如果需要 Diffie-Hellman , OpenSSL 3.0之前,可以在服务端通过 SSL_CTX_set_tmp_dh() 加载 DH 文件。 OpenSSL 3.0以后,使用 SSL_CTX_set0_tmp_dh_pkey() 加载 EVP_PKEY-DH 。
双向验证
可以通过对 SSL_CTX_set_verify() 的使用来决定是否开启双向验证。
| 端 | 参数 | 解释 |
|---|---|---|
| 客户端 | SSL_VERIFY_NONE | 客户端不验证服务器证书, 但是服务器必须提供证书 |
| ^ | SSL_VERIFY_PEER | 客户端验证服务器证书 |
| 服务器 | SSL_VERIFY_NONE | 服务器不验证客户端证书, 客户端可以不提供证书 |
| ^ | SSL_VERIFY_PEER | 服务器验证客户端证书(如果有) |
| ^ | SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT | 客户端必须提供证书 |
这里的情况会比较复杂,需要设计合理的接口来配置,或者干脆分模型编译。
客户端
在通用的 Socket 连接动作之后,需要再进行一次基于 TLS 的连接。 这个功能使用 SSL_connect() 接口来完成。
sequenceDiagram
participant Client
participant Server
activate Client
Client->>Server: connect
Note left of Client: POSIX connect
Client->>Client: SSL_new
Client->>Client: SSL_set_fd
Client->>Server: SSL_connect
Note left of Client: SSL connect
deactivate Client
服务端
在通用的 Socket Accept 动作之后,需要再进行一次基于 TLS 的 Accept。 这个功能使用 SSL_accept() 接口来完成。
sequenceDiagram
participant Client
participant Server
activate Client
Client->>Server: connect
Note left of Client: POSIX connect
activate Server
Server->>Server: accept()
deactivate Server
Note right of Server: POSIX accept
Client->>Client: SSL_new
Client->>Client: SSL_set_fd
Client->>Server: SSL_connect
Note left of Client: SSL connect
activate Server
Server->>Server: SSL_new
Server->>Server: SSL_set_fd
Server->>Server: SSL_accept
Note right of Server: SSL accept
deactivate Server
deactivate Client
释放资源
整个过程中会创建两类资源,分别是“ SSL_CTX* ”和“ SSL* ”。 前者需要使用 SSL_CTX_free() 来释放; 后者需要使用 SSL_shutdown() 和 SSL_free() 来释放。
客户端比较简单,只有一条线。 服务端需要注意对 SSL_accept() 产生的各个 Session 进行释放。
证书的做成
我用下面的方法做出了可用的证书。例子中使用了“ abcd ”作为密码。
openssl genrsa -aes256 -passout pass:abcd -out server.key 2048
openssl req -new -passin pass:abcd -key server.key -out server.csr \
-subj "/C=CN/ST=LN/L=DL/O=MyCompany/OU=MyUnit/CN=SVR"
openssl genrsa -aes256 -passout pass:abcd -out client.key 2048
openssl req -new -passin pass:abcd -key client.key -out client.csr \
-subj "/C=CN/ST=LN/L=DL/O=MyCompany/OU=MyUnit/CN=CLI"
openssl genrsa -aes256 -passout pass:abcd -out ca.key 2048
openssl req -new -passin pass:abcd -x509 -key ca.key -out ca.crt \
-subj "/C=CN/ST=LN/L=DL/O=MyCompany/OU=MyUnit/CN=CA"
openssl x509 -req -passin pass:abcd -in server.csr -CA ca.crt -CAkey ca.key \
-CAcreateserial -out server.crt -days 20000
openssl x509 -req -passin pass:abcd -in client.csr -CA ca.crt -CAkey ca.key \
-CAcreateserial -out client.crt -days 20000
openssl dhparam -out dh2048.pem 2048
其中:
- ca.crt : 对应CA证书。即 SSL_CTX_load_verify_locations() 。
- client.key/server.key : 对应私钥。即 SSL_CTX_use_PrivateKey_file() 。
- client.crt/server.crt : 对应证书。即 SSL_CTX_use_certificate_chain_file() 。
验证
原则上来说,通讯成功我们就认为验证通过了。 不过还是有一些特殊的内容需要额外验证其是否生效了。
缺陷检查
有一个叫做 testssl.sh 的工具。 可以对TLS功能进行一些检查。
Diffie-Hellman
TLS v1.2的情况比较好验证,只要通过wireshark来检查是否存在如下字段:
Transport Layer Security
TLSv1.2 Record Layer: Handshake Protocol: Server Key Exchange
Handshake Protocol: Server Key Exchange
EC Diffie-Hellman Server Params
另外,注意cipher的选择。比如确定要使用 DH 功能,就要选择带有 DHE 字样的cipher。 比如“ECDHE-RSA-AES256-GCM-SHA384”。
TLS v1.3的比较麻烦。从 协议标准 来看,是支持DH的。但是,目前没有找到好的办法验证DH功能到底生效了没有。
扩展
对照 IANA 的 扩展列表 和 RFC8446 。可以看到,后者中并没有覆盖所有的扩展。 可能正是基于这个原因,OpenSSL也没有实现所有的扩展。
如果确实需要,则可以通过“SSL_CTX_add_custom_ext”接口来添加。具体的使用方法可以参照OpenSSL提供的测试程序“sslapitest.c”