zeerd's blog         Search     Categories     Tags     Feed

闲来生雅趣,无事乐逍遥。对窗相望雪,一盏茶香飘。

TLS技术简介

#OpenSSL #TLS @Program


交互时序(规范)

TLS v1.2

详情可以参照官方文档 RFC 5246

在 RFC 5246 的第 36 页中, 给出了 TLS v1.2 的握手流程图。如下图所示:

Client -> Server : ClientHello
note left
  客户端发送所支持的 SSL/TLS 最高协议版本号
  和所支持的加密算法集合
  及压缩方法集合等信息
  给服务器端。
end note
Server -> Client : ServerHello
note right
  选定双方都能够支持的 SSL/TLS 协议版本
  和加密方法及压缩方法,
  返回给客户端
end note
Server -\ Client : Certificate
note right
  服务器端发送服务端证书给客户端
end note
opt 仅需要时
  Server -\ Client : ServerKeyExchange
  note right
    仅当服务器证书消息(如果已发送)不包含足够
    的数据以允许客户端交换预主密钥时,服务器才
    会发送 ServerKeyExchange 消息。
    通常,只有使用 DHE_DSS 、 DHE_RSA 、
    DH_anon 时会发生。
  end note
end
opt 双向验证
  Server -\ Client : CertificateRequest
  note right
    服务器端向客户端请求客户端证书。
  end note
end
Server -> Client : ServerHelloDone
note right
  服务器端通知客户端初始协商结束
end note
opt 双向验证
  Client -\ Server : Certificate
  note left
    客户端向服务器端发送客户端证书。
  end note
end
Client -> Server : ClientKeyExchange
note left
  客户端使用服务器端的公钥,对客户端公钥和密钥
  种子进行加密,再发送给服务器端。
end note
opt 双向验证
  Client -\ Server : CertificateVerify
  note left
    客户端用本地私钥生成数字签名,
    并发送给服务器端,让其通过收到的客户端公钥
    进行身份验证。
  end note
end
Client -> Client : ChangeCipherSpec
note left
  客户端通知服务器端已将通讯方式切换到加密模式
end note
Client -> Server : Finished
note left
  客户端做好加密通讯的准备。
end note
Server -> Server : ChangeCipherSpec
note right
  服务器端通知客户端已将
  通讯方式切换到加密模式。
end note
Server -> Client : Finished
note right
   服务器做好加密通讯的准备。
end note
Client <-> Server : Application Data
  • 半箭头表示可选发送的消息或者根据实际境况决定是否发送的消息

TLS v1.3

详情可以参照官方文档 RFC 8446

在 RFC 8446 的第 11 页中, 给出了 TLS v1.2 的握手流程图。如下图所示:

Client -> Server : ClientHello
note left
  包含的信息:
    密钥交换:
      //key_share// :
        终端的加密参数
      //signature_algorithms// :
        希望服务器通过证书验证自己的客户端必须
        发送“signature_algorithms”
      //psk_key_exchange_modes// :
        使用预共享密钥时,必须发送
      //pre_shared_key// :
        用于协商预共享密钥的身份,以用于与 PSK
        密钥建立相关的给定握手。
end note
Server -> Client : ServerHello
note right
  包含的信息:
    密钥交换:
      //key_share// :
        终端的加密参数
      //pre_shared_key// :
        用于协商预共享密钥的身份,以用于与 PSK
        密钥建立相关的给定握手。
    服务端参数:
      EncryptedExtensions :
        用来发送加密扩展信息。
      //CertificateRequest// :
        当客户端需要验证时发送
    验证:
      //Certificate// :
        用于身份验证的证书
      //CertificateVerify// :
        对信息的签名
end note
Client -> Server : 验证
note left
  包含的信息:
    //Certificate// :
      用于身份验证的证书
    //CertificateVerify// :
      对信息的签名
end note
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() 接口来完成。

activate Client
Client --> Server : connect
note left : POSIX connect
Client -> Client : SSL_new
Client -> Client : SSL_set_fd
Client -> Server : SSL_connect
note left : SSL connect
deactivate Client

服务端

在通用的 Socket Accept 动作之后,需要再进行一次基于 TLS 的 Accept。 这个功能使用 SSL_accept() 接口来完成。

activate Client
Client --> Server : connect
note left : POSIX connect
activate Server
Server --> Server : accept()
deactivate Server
note right : POSIX accept
Client --> Client : SSL_new
Client --> Client : SSL_set_fd
Client --> Server : SSL_connect
note left : SSL connect
activate Server
Server -> Server : SSL_new
Server -> Server : SSL_set_fd
Server -> Server : SSL_accept
note right : 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

参照