TLS协议分析 (五) handshake协议 证书与密钥交换

5.4. handshake — Server Certificate

当服务器确定了CipherSuite后,根据CipherSuite里面的认证算法,如果需要发送证书给客户端,那么就发送 Server Certificate消息给客户端。Server Certificate总是在ServerHello之后立即发送,所以在同一个RTT里。

Server Certificate里面包含了服务器的证书链。

消息结构:

opaque ASN.1Cert<1..2^24-1>;struct {

   ASN.1Cert certificate_list<0..2^24-1>;

} Certificate;

certificate_list:    证书列表,发送者的证书必须是第一个,后续的每一个证书都必须是前一个的签署证书。根证书可以省略

证书申请的时候,一般会收到好几个证书,有的需要自己按照这个格式来拼接成证书链。

如果服务器要认证客户端的身份,那么服务器会发送Certificate Request消息,客户端应该也以 这条Server Certificate消息的格式回复。

服务器发送的证书必须:

证书类型必须是 X.509v3。除非明确地协商成别的了(比较少见,rfc里提到了例如 [OpenPGP格式]  链接 https://tools.ietf.org/html/rfc5081 )。

服务器证书的公钥,必须和选择的密钥交换算法配套。

密钥交换+认证算法配套的证书中公钥类型

RSA  /  RSA_PSKRSA 公钥;证书中必须允许私钥用于加密 (即如果使用了X509V3规定的key usage扩展, keyEncipherment比特位必须置位) 这种用法没有前向安全性,因此在 TLS 1.3中被废弃了

DHE_RSA /  ECDHE_RSARSA 公钥;证书中必须允许私钥用于签名(即如果使用了X509V3规定的key usage扩展, digitalSignature比特位必须置位),并且允许server key exchange消息将要使用的签名模式(例如 PKCS1_V1.5 ,OAEP等)和hash算法(例如sha1, sha256等)

DHE_DSSDSA 公钥; 历史遗留产物,从来没有被大规模用过,安全性差,废弃状态。证书必须允许私钥用于签名,必须允许server key exchange消息中使用的hash算法。

DH_DSS  / DH_RSADiffie-Hellman 公钥; 要求key usage里面的keyAgreement比特位必须置位。 这种用法没有前向安全性,因此在 TLS 1.3中被废弃了

ECDH_ECDSA  / ECDH_RSA能做 ECDH 用途的公钥;公钥必须使用 客户端支持的ec曲线和点格式。这种用法没有前向安全性,因此在 TLS 1.3中被废弃了

ECDHE_ECDSAECDSA用途的公钥;证书必须运输私钥用作签名,必须允许server key exchange消息里面要用到的hash算法。公钥必须使用客户端支持的ec曲线和点格式。

“server_name” 和 “trusted_ca_keys” 扩展用于引导证书选择。

其中有5种是ECC密钥交换算法:ECDH_ECDSA, ECDHE_ECDSA, ECDH_RSA, ECDHE_RSA, ECDH_anon。ECC(椭圆曲线)体制相比RSA,由于公钥更小,性能更高,所以在移动互联网环境下越发重要。以上ECC的5种算法都用ECDH来计算premaster  secret, 仅仅是ECDH密钥的生命周期和认证算法不同。其中只有 ECDHE_ECDSA 和 ECDHE_RSA 是前向安全的。

如果客户端在ClientHello里提供了 “signature_algorithms” 扩展,那么服务器提供的所有证书必须用 “signature_algoritms”中提供的 hash/signature算法对 之一签署。要注意的是,这意味着,一个包含某种签名算法密钥的证书,可能被另一种签名算法签署(例如,一个RSA公钥可能被一个ECDSA公钥签署)。(这在TLS1.2和TLS1.1中是不一样的,TLS1.1要求所有的算法都相同。)注意这也意味着DH_DSS,DH_RSA,ECDH_ECDSA,和ECDH_RSA 密钥交换不限制签署证书的算法。固定DH证书可能使用”signature_algorithms”扩展列表中的 hash/签名算法对 中的某一个签署。名字 DH_DSS, DH_RSA, ECDH_ECDSA, 和 ECDH_RSA 只是历史原因,这几个名字的后半部分中指定的算法,并不会被使用,即DH_DSS中的DSS并不会被使用,DH_RSA中并不会使用RSA做签名,ECDH_ECDSA并不会使用ECDSA算法。。。如果服务器有多个证书,就必须从中选择一个,一般根据服务器的外网ip地址,SNI中指定的hostname,服务器配置来做选择。如果服务器只有一个证书,那么要确保这一个证书符合这些条件。要注意的是,存在一些证书使用了TLS目前不支持的 算法组合。例如,使用 RSASSA-PSS签名公钥的证书(即证书的SubjectPublicKeyInfo字段是id-RSASSA-PSS)。由于TLS没有给这些算法定义对应的签名算法,这些证书不能在TLS中使用。如果一个CipherSuite指定了新的TLS密钥交换算法,也会指定证书格式和要求的密钥编码方法。

5.5. handshake  — Server Key Exchange

服务器会在 server Certificate 消息之后,立即发送 Server Key Exchange消息。(如果协商出的CipherSuite不需要做认证,即anonymous negotiation,会在ServerHello之后立即发送Server Key Exchange消息)

只有在server Certificate 消息没有足够的信息,不能让客户端完成premaster的密钥交换时,服务器才发送 server Key Exchange, 主要是对前向安全的几种密钥协商算法,列表如下:

DHE_DSS

DHE_RSA

DH_anon

ECDHE_ECDSA

ECDHE_RSA

ECDH_anon

对下面几种密钥交换方法,发送ServerKeyExchange消息是非法的:

RSA

DH_DSS

DH_RSA

ECDH_ECDSA

ECDH_RSA

需要注意的是,ECDH和ECDSA公钥的数据结构是一样的。所以,CA在签署一个证书的时候,可能要使用 X.509 v3 的 keyUsage 和 extendedKeyUsage 扩展来限定ECC公钥的使用方式。

ServerKeyExchange传递足够的信息给客户端,来让客户端交换premaster secret。一般要传递的是:一个 Diffie-Hellman 公钥,或者一个其他算法(例如RSA)的公钥。

在TLS实际部署中,我们一般只使用这4种:ECDHE_RSA, DHE_RSA,  ECDHE_ECDSA,RSA

其中RSA密钥协商(也可以叫密钥传输)算法,由于没有前向安全性,在TLS 1.3里面已经被废除了。参见: [Confirming Consensus on removing RSA key Transport from TLS  1.3 ] 链接 http://www.ietf.org/mail-archive/web/tls/current/msg12266.html

消息格式:

enum { dhe_dss, dhe_rsa, dh_anon, rsa, dh_dss, dh_rsa,    ec_diffie_hellman

    } KeyExchangeAlgorithm;struct {

   opaque dh_p<1..2^16-1>;

   opaque dh_g<1..2^16-1>;

   opaque dh_Ys<1..2^16-1>;

} ServerDHParams;     /* Ephemeral DH parameters */dh_p

  Diffie-Hellman密钥协商计算的大质数模数。

dh_g

  Diffie-Hellman 的生成元,

dh_Ys

  服务器的Diffie-Hellman公钥 (g^X mod p).  struct {

     opaque point <1..2^8-1>;

  } ECPoint;  enum { explicit_prime (1), explicit_char2 (2),

        named_curve (3), reserved(248..255) } ECCurveType;  struct {

     ECCurveType    curve_type;

     select (curve_type) {          case named_curve:

             NamedCurve namedcurve;

     };

  } ECParameters;            struct {

     ECParameters    curve_params;

     ECPoint         public; //ECDH的公钥

  } ServerECDHParams;struct {

   select (KeyExchangeAlgorithm) {        case dh_anon:

           ServerDHParams params;        case dhe_dss:        case dhe_rsa:

           ServerDHParams params;

           digitally-signed struct {

               opaque client_random[32];

               opaque server_random[32];

               ServerDHParams params;

           } signed_params;        case ec_diffie_hellman:

           ServerECDHParams    params;

           Signature           signed_params;        case rsa:        case dh_dss:        case dh_rsa:              struct {} ;        /* message is omitted for rsa, dh_dss, and dh_rsa */

       /* may be extended, e.g., for ECDH -- see [TLSECC] */

   };

} ServerKeyExchange;

params

  服务器的密钥交换参数。

signed_params

  对需要认证的(即非anonymous的)密钥交换,对服务器的密钥交换参数的数字签名。

ECParameters 结构比较麻烦,其中ECCurveType是支持3种曲线类型的,可以自行指定椭圆曲线的多项式系数,基点等参数。但是,我们基本不会用到这种功能,因为一般部署都是使用 NamedCurve,即参数已经预先选定,各种密码学库普遍都支持的一组曲线,其中目前用的最广的是 secp256r1 (还被称为 P256,或 prime256v1)

NamedCurve 列表中比较重要的曲线(在TLS1.3中,只保留了这几条曲线。),定义如下:

enum {

   ...

   secp256r1 (23), secp384r1 (24), secp521r1 (25),

   reserved (0xFE00..0xFEFF),

   (0xFFFF)

} NamedCurve;

ECDHE_RSA 密钥交换算法的 SignatureAlgorithm 是 rsa 。ECDHE_RSA 密钥交换算法的 SignatureAlgorithm 是 ecdsa。

如果客户端提供了 “signature_algorithms” 扩展, 则签名算法和hash算法必须是列在扩展中的算法。要注意的是,这个地方可能有不一致,例如客户端可能提供了 DHE_DSS 密钥交换,但是 “signature_algorithms”扩展中没有DSA算法,在这类情况下,为了正确地协商,服务器必须确保满足自己选择的CipherSuite满足 “signature_algorithms” 的限制。这不优雅,但是是为了把对原来的CipherSuite协商的设计的改动减到最小,而做的妥协。

并且,hash和签名算法,必须和服务器的证书里面的公钥兼容。

5.6. handshake  — Certificate Request

TLS规定了一个可选功能:服务器可以认证客户端的身份,这通过服务器要求客户端发送一个证书实现,服务器应该在ServerKeyExchange之后立即发送CertificateRequest消息。

消息结构:

enum {

   rsa_sign(1), dss_sign(2), rsa_fixed_dh(3),dss_fixed_dh(4),

   rsa_ephemeral_dh_RESERVED(5),dss_ephemeral_dh_RESERVED(6),

   fortezza_dms_RESERVED(20),

   ecdsa_sign(64), rsa_fixed_ecdh(65),

   ecdsa_fixed_ecdh(66),

   (255)

} ClientCertificateType;

opaque DistinguishedName<1..2^16-1>;struct {

   ClientCertificateType certificate_types<1..2^8-1>;

   SignatureAndHashAlgorithm

     supported_signature_algorithms<2^16-1>;

   DistinguishedName certificate_authorities<0..2^16-1>;

} CertificateRequest;

certificate_types:  客户端可以提供的证书类型。

rsa_sign    包含RSA公钥的证书。

dss_sign    包含DSA公钥的证书。

rsa_fixed_dh  包含静态DH公钥的证书。

dss_fixed_dh  包含静态DH公钥的证书。

supported_signature_algorithms:    服务器支持的  hash/signature 算法的列表。

certificate_authorities:    服务器可以接受的CA(certificate_authorities)的 distinguished names 的列表 DER编码格式.

这些 distinguished names 可能为root CA或者次级CA指定了想要的 distinguished name ,因此,这个消息可以用来描述已知的root,或者希望的授权空间。如果 certificate_authorities 列表是空的,那么客户端可以发送任何适当的 ClientCertificateType 类型的证书,如果没有别的限制的话。

certificate_types 和 supported_signature_algorithms 字段的交叉选择很复杂。 certificate_types 这个字段从SSLv3时代就定义了,但是一直都没有详细定义,其大多数功能都被 supported_signature_algorithms 代替了。 有如下规则:

客户端提供的任何证书,必须用一个supported_signature_algorithms 中出现过的  hash/signature 算法对 签名.

客户端提供的末端证书必须提供一个和 certificate_types 兼容的key。 如果这个key是一个签名key,那必须能和 supported_signature_algorithms 中提供的某个 hash/signature 算法对配合使用。

由于历史原因,某些客户端证书类型的名字,包含了证书的签名算法,例如,早期版本的TLS中, rsa_fixed_dh 意思是一个被RSA算法签署,并且包含一个固定DH密钥的证书。在TLS1.2中,这个功能被 supported_signature_algorithms 淘汰,并且证书类型不再限制用来签署证书的算法。例如,如果服务器发送了 dss_fixed_dh 证书类型,和 { {sha1, dsa}, {sha1,rsa} } 签名类型,客户端可以回复一个 包含静态DH密钥,用RSA-sha1签署的证书。

如果协商出来的是匿名CipherSuite,服务器不能要求客户端认证。

5.7. handshake  — Server Hello Done

在 ServerHello和相关消息已经处理结束后,服务器发送ServerHelloDone。在发送ServerHelloDone后,服务器开始等待客户端的响应。

ServerHelloDone消息表示,服务器已经发送完了密钥协商需要的消息,并且客户端可以开始客户端的密钥协商处理了。

收到ServerHelloDone后,客户端应该确认服务器提供了合法的证书,并且确认服务器的ServerHello消息里面的参数是可以接受的。

消息格式:

  struct { } ServerHelloDone;

5.8. handshake  — Client Certificate

ClientCertificate消息是客户端收到ServerHelloDone后,可以发送的第一条消息。仅当服务器要求了一个证书的情况下,客户端才发送ClientCertificate消息,如果没有可用的合适证书,客户端必须发送一条不包含任何证书的ClientCertificate消息(即 certificate_list 结构长度为0)。

如果客户端没有发送任何证书,服务器自行决定,可以放弃要求客户端认证,继续握手;或者发送一条 fatal handshake_failure的alert消息,断开连接。并且,如果证书链的某些方面是不能接受的(比如证书没有被可信任的CA签署),服务器可以自行决定,是继续握手(放弃要求客户端认证),或者发送一条fatal的alert。

客户端证书使用和ServerCertificate相同的结构发送。

ClientCertificate把客户端的证书链发送给服务器。服务器会使用证书链来验证CertificateVerify 消息(如果使用基于签名的客户端认证),或者来计算premaster secret(对于非短暂的 DH)。证书必须和协商出来的CipherSuite的密钥交换算法配套,并和任何协商的扩展配套。

尤其是:

证书必须是X.509v3 类型的。

客户端的末级证书的公钥必须和CertificateRequest里列出的证书类型兼容。

客户端证书类型证书公钥类型

rsa_signRSA公钥;证书必须允许公钥用于certificateVerify消息中的数字签名和hash算法

dss_signDSA 公钥;证书必须允许密钥使用CertificateVerify中的hahs函数做签名;

ecdsa_sign可以用作 ECDSA 的公钥;证书必须允许 公钥用 CertificateVerify中的hash函数做签名;公钥必须使用服务器支持的曲线,和点格式;

rsa_fixed_dh / dss_fixed_dhDiffie-Hellman 公钥; 必须使用和服务器key相同的参数。

rsa_fixed_ecdh  /  ecdsa_fixed_ecdh可以用作 ECDH 的公钥。必须和服务器的公钥使用同样的曲线,同样的点格式

如果 certificate_authorities 列表不是空的,客户端证书链中的某一个证书必须是CA中的某一个签署的。

证书必须使用 服务器可以接受的 hash/signature 算法对。

类似于Server Certificate,有一些证书目前无法在TLS中使用。

5.9. handshake — Client Key Exchange

客户端必须在客户端的Certificate消息之后,立即发送ClientKeyExchange消息。或者必须在ServerHelloDone后立即发送ClientKeyExchange消息。

ClientKeyExchange消息中,会设置premaster secret,通过发送 RSA公钥加密premaster secret的密文,或者发送允许双方得出相同的premaster secret的Diffie-Hellman参数。

当客户端使用短暂的 Diffie-Hellman 密钥对时,ClientKeyExchange包含客户端的 Diffie-Hellman 公钥。如果客户端发送一个包含静态 Diffie-Hellman 指数的证书(比如,在使用固定DH的客户端认证),那么这条消息必须被发送,并且必须为空。

消息结构:消息的选择取决于选择的密钥交换算法。

struct {

   select (KeyExchangeAlgorithm) {        case rsa:

           EncryptedPreMasterSecret;        case dhe_dss:        case dhe_rsa:        case dh_dss:        case dh_rsa:        case dh_anon:

           ClientDiffieHellmanPublic;        case ec_diffie_hellman:

           ClientECDiffieHellmanPublic;

   } exchange_keys;

} ClientKeyExchange;

5.9.(1).  RSA 加密的 Premaster Secret 消息

如果用RSA做密钥协商和认证,客户端生成 48字节的 premaster secret,使用服务器证书里面的公钥加密,然后把密文EncryptedPreMasterSecret发送给服务器,结构定义如下:

struct {

   ProtocolVersion client_version;

   opaque random[46];

} PreMasterSecret;

client_version

  客户端支持的最新协议版本号,这个字段用来检测中间人版本回退攻击。T

random   46 字节的,安全生成的随机值。struct {        public-key-encrypted PreMasterSecret pre_master_secret;

} EncryptedPreMasterSecret;

pre_master_secret

  这个随机值由客户端生成,用于生成master secret。

注:PreMasterSecret里面的 client_version 是 ClientHello.client_version,而不是协商的到的版本号,这个特性用来阻止版本回退攻击。不幸的是,有些不正确的老的代码使用了协商得到的版本号,导致检查client_version字段的时候,和正确的实现无法互通。

客户端实现必须在PreMasterSecret中发送正确的版本号。如果 ClientHello.client_version 的版本号是 TLS 1.1 或者更高,服务器实现必须如下检查版本号。如果版本号是 TLS 1.0 或者更早,服务器必须检查版本号,但是可以通过配置项关闭检查。

要注意的是,如果版本号检查失败了,PreMasterSecret 应该像下面描述的那样填充成随机数。

TLS中的RSA使用的是 PKCS1-V1.5 填充( PKCS1-V1.5也是openssl库RSA的默认填充方式)。Bleichenbacher 在1998年发表了一种针对 PKCS1-V1.5 的选择密文攻击, Klima在2003年发现  PKCS1-V1.5 中 PreMasterSecret 版本号检查的一个侧通道攻击。只要TLS 服务器暴露一条特定的消息是否符合PKCS1-V1.5格式,或暴露PreMasterSecret解密后结构是否合法,或版本号是否合法,就可以用上面2种方法攻击。

Klima 还提出了完全避免这类攻击的方法:对格式不正确的消息,版本号不符的情况,要做出和完全正确的RSA块一样的响应,要让客户端区分不出这3种情况。具体地说,要如下:

生成 46 字节的密码学安全随机值 R

解密消息,获得明文 M

如果 PKCS#1 填充不正确,或者 PreMasterSecret 消息的长度不是48字节,则pre_master_secret = ClientHello.client_version || R或者如果 ClientHello.client_version <= TLS 1.0,并且明确禁止了版本号检查,则pre_master_secret = ClientHello.client_version || M[2..47]

注意:明确地用 ClientHello.client_version 构造 pre_master_secret 时,当客户端在原来的 pre_master_secret 中发送了错误的 客户端版本值时,会产生一个不合法的 master_secret 。

另一种解决问题的方法是,把版本号不符,当成 PKCS-1 格式错误来对待,并且完全随机填充 premaster secret。

生成 48 字节的密码学安全随机值 R

解密 PreMasterSecret 恢复出明文 M

如果 PKCS#1 填充不正确,或者消息的长度不是48字节,则pre_master_secret = R或者如果 ClientHello.client_version <= TLS 1.0,并且 明确禁止了版本号检查,则pre_master_secret = M或者如果 M[0..1] != CleintHello.client_versionpre_master_secret = R或者pre_master_secret = M

尽管实践中,还没有发现针对这种结构的攻击,Klima 在论文中描述了几种理论上的攻击方式,因此推荐上述的第一种结构。

在任何情况下,一个 TLS 服务器绝对不能在:1. 处理 RSA 加密的 premaster 消息失败, 2.或者版本号检查失败 时产生alert消息。当遇到这两种情况时,服务器必须用随机生成的 premaster 值继续握手。服务器可以把造成失败的真实原因log下来,用于调查问题,但是必须小心确保不能把这种信息泄漏给攻击者(比如通过时间侧通道,log文件,或者其它通道等泄漏)。

RSAES-OAEP 加密体制,更能抵抗 Bleichenbacher 发表的攻击,然而,为了和早期的TLS版本最大程度保持兼容,TLS 仍然规定使用  RSAES-PKCS1-v1_5 体制。只要遵守了上面列出的建议,目前还没有 Bleichenbacher 的变化形式能攻破 TLS 。

实现的时候要注意:公钥加密的数据用 字节数组 <0..2^16-1> 的形式表示。因此,ClientKeyExchange中的 RSA加密的PreMasterSecret 前面有2个字节用来表示长度。这2个字节在使用RSA做密钥协商时,是冗余的,因为此时 EncryptedPreMasterSecret 是 ClientKeyExchange 中的唯一字段,因此可以无歧义地得出 EncryptedPreMasterSecret 的长度。因此更早的 SSLv3 规范没有明确规定 public-key-encrypted 数据的编码格式,因此有一些SSLv3的实现没有包含 长度字段,这些实现直接把 RSA 加密的数据放入了 ClientKeyExchange消息里面。TLS规范要求 EncryptedPreMasterSecret 字段包含长度字段。因此得出的结果会和一些 SSLv3 的实现不兼容。实现者从 SSLv3 升级到 TLS 时,必须修改自己的实现,以接受并且生成带长度的格式。如果一个实现要同时兼容 SSLv3 和 TLS,那就应该根据协议版本确定自己的行为。

注意:根据 Boneh 等在2003年USENIX Security Symposium上发表的论文 “Remote timing attacks are practical”,针对 TLS RSA密钥交换的远程时间侧通道攻击,是实际可行的,起码当客户端和服务器在同一个LAN里时是可行的。因此,使用静态 RSA 密钥的实现,必须使用 RSA blinding,或者Boneh论文中提到的,其他抵抗时间侧通道攻击的技术。

openssl中的RSA blinding,参见:http://linux.die.net/man/3/rsa_blinding_on

5.9.(2).  客户端 Diffie-Hellman 公钥

这条消息把客户端的 Diffie-Hellman 公钥 ( Yc ) 发送给服务器。

Yc的编码方式由 PublicValueEncoding 决定。

消息的结构:

enum { implicit, explicit } PublicValueEncoding;

implicit

  如果客户端已经发送了一个包含合适的 DH 公钥的证书(即 fixed_dh 客户端认证方式),那么Yc已经隐式包含了,不需要再发送。这种情况下,ClientKeyExchange消息必须发送,并且必须是空的。explicit

  表示Yc需要发送。struct {

   select (PublicValueEncoding) {        case implicit:           struct { };         case explicit:            opaque dh_Yc<1..2^16-1>;

   } dh_public;

} ClientDiffieHellmanPublic;

dh_Yc

  客户端的 Diffie-Hellman 公钥 Yc.

5.9.(3).  客户端 EC Diffie-Hellman 公钥

struct {

select (PublicValueEncoding) {        case implicit:          struct { };      case explicit:          ECPoint ecdh_Yc;

} ecdh_public;

} ClientECDiffieHellmanPublic;

Diffie-Hellman 推广到椭圆曲线群上,就是 EC Diffie-Hellman ,简称 ECDH,其它的计算,和一般的 DH 计算类似。

ECDH 是目前最重要的密钥协商算法 http://vincent.bernat.im/en/blog/2011-ssl-perfect-forward-secrecy.html

5.10. handshake  — Cerificate Verify

当需要做客户端认证时,客户端发送CertificateVerify消息,来证明自己确实拥有客户端证书的私钥。这条消息仅仅在客户端证书有签名能力的情况下发送(就是说,除了含有固定 Diffie-Hellman 参数的证书以外的证书)。CertificateVerify必须紧跟在ClientKeyExchange之后发送。

消息结构: Structure of this message:

struct {

digitally-signed struct {

opaque handshake_messages[handshake_messages_length];

}

} CertificateVerify;

此处, handshake_messages 表示所有发送或者接收的握手消息,从client hello开始,一直到CertificateVerify之前的所有消息,包括handshake消息的type和length字段,这是之前所有握手结构体的拼接。要注意,这要求双方在握手过程中,都得缓存所有消息,或者在握手过程中,用每一种可能的hash算法计算到CeritificateVerify为止的hash值。

signature中用的hash和签名算法必须是 CertificateRequest 的 supported_signature_algorithms 中的某一种。另外,hash和签名算法必须和客户端的证书的算法兼容。RSA公钥可能被用于任何允许的hash函数,只要遵循证书中的限制。

                                                                本文转自微信后台团队,如有侵犯,请联系我们立即删除

OpenIMgithub开源地址:

https://github.com/OpenIMSDK/Open-IM-Server

OpenIM官网 : https://www.rentsoft.cn

OpenIM官方论坛: https://forum.rentsoft.cn/

更多技术文章:

开源OpenIM:高性能、可伸缩、易扩展的即时通讯架构https://forum.rentsoft.cn/thread/3

【OpenIM原创】简单轻松入门 一文讲解WebRTC实现1对1音视频通信原理https://forum.rentsoft.cn/thread/4

【OpenIM原创】开源OpenIM:轻量、高效、实时、可靠、低成本的消息模型https://forum.rentsoft.cn/thread/1

你可能感兴趣的:(TLS协议分析 (五) handshake协议 证书与密钥交换)