TLS协议分析 与 现代加密通信协议设计(二)

发表于2015-09-10
评论0 1.9k浏览

3. handshake 协议外层结构

从消息格式来看,TLS Handshake Protocol 在 TLS Record Protocol 的上层. 这个协议用于协商一个session的安全参数。 Handshake 消息(例如ClientHello,ServerHello等) 被包装进 TLSPlaintext结构里面,传入TLS record层,根据当前session 状态做处理,然后传输。

如下:

  1.  enum {
  2. hello_request(0), client_hello(1), server_hello(2),
  3. certificate(11), server_key_exchange (12),
  4. certificate_request(13), server_hello_done(14),
  5. certificate_verify(15), client_key_exchange(16),
  6. finished(20), (255)
  7.  } HandshakeType;
  8.  struct {
  9.  HandshakeType msg_type; /* handshake type */
  10.  uint24 length; /* bytes in message */
  11. select (HandshakeType) {
  12.  case hello_request: HelloRequest;
  13.  case client_hello: ClientHello;
  14.  case server_hello: ServerHello;
  15.  case certificate: Certificate;
  16.  case server_key_exchange: ServerKeyExchange;
  17.  case certificate_request: CertificateRequest;
  18.  case server_hello_done: ServerHelloDone;
  19.  case certificate_verify: CertificateVerify;
  20.  case client_key_exchange: ClientKeyExchange;
  21.  case finished: Finished;
  22.  case session_ticket: NewSessionTicket; /* NEW */
  23.  } body;
  24.  } Handshake;

TLS协议规定,handshake 协议的消息必须按照规定的顺序发,收到不按顺序来的消息,当成fatal error处理。也就是说,TLS协议可以当成状态机来建模编码。

下面按照消息发送必须遵循的顺序,逐个解释每一条握手消息。

handshake协议的外层字段,见这个抓包:

4. handshake -- ClientHello,ServerHello,HelloRequest

Hello消息有3个:ClientHello, ServerHello,HellloRequest 
逐个说明:

4.1 Client Hello

当客户端第一次连接到服务器时,第一条message必须发送ClientHello。 
另外,rfc里规定,如果客户端和服务器支持重协商,在客户端收到服务器发来的HelloRequest后,也可以回一条ClientHello,在一条已经建立的连接上开始重协商。(重协商是个很少用到的特性。)

消息结构:

  1.  struct {
  2.  uint32 gmt_unix_time;
  3. opaque random_bytes[28];
  4.  } Random;
  5. opaque SessionID<0..32>;
  6.  uint8 CipherSuite[2];
  7.  enum { null(0), (255) } CompressionMethod;
  8.  struct {
  9.  ProtocolVersion client_version;
  10.  Random random;
  11.  SessionID session_id;
  12.  CipherSuite cipher_suites<2..2^16-2>;
  13.  CompressionMethod compression_methods<1..2^8-1>;
  14. select (extensions_present) {
  15.  case false:
  16.  struct {};
  17.  case true:
  18.  Extension extensions<0..2^16-1>;
  19.  };
  20.  } ClientHello;

Random 其中: 
gmt_unix_time 是 unix epoch时间戳。 
random_bytes 是 28字节的,用密码学安全随机数生成器 生成出来的随机数。


密码学安全的随机数生成,这是个很大的话题,也是一个大陷阱,目前最好的做法就是用 /dev/urandom,或者openssl库的 RAND_bytes()

历史上,恰好就在SSL的random_bytes这个字段,NetScape浏览器早期版本被爆出过随机数生成器漏洞。 
被爆菊的随机数生成器使用 pid + 时间戳 来初始化一个seed,并用MD5(seed)得出结果。 
见 http://www.cs.berkeley.edu/~daw/papers/ddj-netscape.html, 
建议读者检查一下自己的随机数生成器。


client_version
客户端支持的最高版本号。
random
客户端生成的random。

ClientHello.session_id 唯一标识一个session,用来做session cache。如果为空,表示不做复用,要求服务器生成新的session。 
session_id的来源有:

  1. 之前的协商好的连接的session_id
  2. 当前连接的session_id
  3. 当前也在使用中的另一条连接的session_id

其中第三种允许不做重新握手,就同时建立多条独立的安全连接。这些独立的连接可能顺序创建,也可以同时创建。一个SessionID当握手协商的Finished消息完成后,就合法可用了。存活直到太旧被移除,或者session 关联的某个连接发生fatal error。SessionID的内容由服务器端生成。

注:由于SessionID的传输是不加密,不做MAC保护的,服务器不允许把私密信息发在里面,不能允许伪造的SessionID在服务器造成安全问题。(握手过程中的数据,整体是受Finished消息的保护的)

ClientHello.cipher_suites字段,包含了客户端支持的CipherSuite的列表,按照客户端希望的优先级排序,每个CipherSuite有2个字节,每个CipherSuite由:一个密钥交换算法,一个大量数据加密算法(需要制定key length参数),一个MAC算法,一个PRF 构成。服务器会从客户端发过来的列表中选择一个;如果没有可以接受的选择,就返回一个 handshake failure 的 alert,并关闭连接。如果列表包含服务器不认识,不支持,或者禁用的CipherSuite,服务器必须忽略。 
如果SessionID不为空,则cipher_suites里面起码要包含客户端cache的session里面的那个CipherSuite

compression_methods,类似地,ClientHello里面包含压缩算法的列表,按照客户端优先级排序。当然,如前介绍,服务器一般禁用TLS的压缩。

compression_methods 后面可以跟一组扩展(extensions), extensions都是可选的,比较有用的扩展如: SNI, session ticket,ALPN,OCSP 等,后文介绍。

客户端发送了ClientHello后,服务器端必须回复ServerHello消息,回复其他消息都会导致 fatal error 关闭连接。

4.2 Server Hello

当收到客户端发来的ClientHello后,正常处理完后,服务器必须回复ServerHello。

消息结构:

  1.  struct {
  2.  ProtocolVersion server_version;
  3.  Random random;
  4.  SessionID session_id;
  5.  CipherSuite cipher_suite;
  6.  CompressionMethod compression_method;
  7. select (extensions_present) {
  8.  case false:
  9.  struct {};
  10.  case true:
  11.  Extension extensions<0..2^16-1>;
  12.  };
  13.  } ServerHello;
server_version
服务器选择 ClientHello.client_version 和 服务器支持的版本号 中的最小的。
random
服务器生成的random,必须确保和客户端生成的random没有关联。
session_id
服务器为本连接分配的SessionID。如果ClientHello.session_id不为空,服务器会在自己的本地做查找。
  • 如果找到了匹配,并且服务器决定复用找到的session建立连接,服务器应该把ClientHello.session_id同样的 session id填入ServerHello.session_id,这表示恢复了一个session,并且双方会立即发送Finished消息。
  • 否则,回复一个和ClientHello.random_id不同的Serverhello.session_id,来标识新session。服务器可以回复一个空的session_id,来告诉客户端这个session不要cache,不能恢复。 
    如果一个session 被恢复了,那必须恢复成之前协商的session里面的 CipherSuite。要注意的是,并不要求服务器一定要恢复session, 服务器可以不做恢复。

在实践中,session cache在服务器端要求key-value形式的存储,如果tls服务器不止一台的话,就有一个存储怎么共享的问题,要么存储同步到所有TLS服务器的内存里,要么专门搞服务来支持存储,并使用rpc访问, 
无论如何,都是很麻烦的事情,相比之下,后文要介绍的session ticket就简单多了,所以一般优先使用session ticket。

cipher_suite
服务器选定的一个CipherSuite。如果是恢复的session,那就是session里的CipherSuite。
compression_method
跟上面类似。
extensions
扩展列表。要注意的是,ServerHello.extensions 必须是 ClientHello.extensions的子集。

4.3 Hello Extensions

The extension 的格式是:

  1.  struct {
  2.  ExtensionType extension_type;
  3. opaque extension_data<0..2^16-1>;
  4.  } Extension;
  5.  enum {
  6. signature_algorithms(13), (65535)
  7.  } ExtensionType;

其中:

  • "extension_type" 标识是哪一个扩展类型。

  • "extension_data" 一坨二进制的buffer,扩展的数据体,各个扩展自己做解析。

extension_type 只能出现一次,ExtensionType之间不指定顺序。

extensions 可能在新连接创建时被发送,也可能在要求session恢复的时候被发送。所以各个extension都需要规定自己再完整握手和session恢复情况下的行为。 
这些情况比较琐碎而微妙,具体案例要具体分析。

4.4 Hello Request

服务器任何时候都可以发送 HelloRequest 消息。

HelloRequest的意思是,客户端应该开始协商过程。客户端应该在方便的时候发送ClientHello。服务器不应该在客户端刚创建好连接后,就发送HelloRequest,此时应该让客户端发送ClientHello。

客户端收到这个消息后,可以直接忽略这条消息。 
服务器发现客户端没有响应HelloRequest后,可以发送fatal error alert。

消息结构:

  1.  struct { } HelloRequest;

HelloRequest不包含在握手消息的hash计算范围内。

5. handshake -- Server Certificate

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

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

消息结构:

  1. opaque ASN.1Cert<1..2^24-1>;
  2.  struct {
  3. ASN.1Cert certificate_list<0..2^24-1>;
  4.  } Certificate;
certificate_list
证书列表,发送者的证书必须是第一个,后续的每一个证书都必须是前一个的签署证书。根证书可以省略

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

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

服务器发送的证书必须:

  • 证书类型必须是 X.509v3。除非明确地协商成别的了(比较少见,rfc里提到了例如 OpenPGP格式)。

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

密钥交换+认证算法配套的证书中公钥类型
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密钥交换算法,也会指定证书格式和要求的密钥编码方法。

6. handshake -- Server Key Exchange

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

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

  1. DHE_DSS
  2. DHE_RSA
  3. DH_anon
  4. ECDHE_ECDSA
  5. ECDHE_RSA
  6. ECDH_anon

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

  1. RSA
  2. DH_DSS
  3. DH_RSA
  4. ECDH_ECDSA
  5. 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

消息格式:

  1.  enum { dhe_dss, dhe_rsa, dh_anon, rsa, dh_dss, dh_rsa,ec_diffie_hellman
  2.  } KeyExchangeAlgorithm;
  3.  struct {
  4. opaque dh_p<1..2^16-1>;
  5. opaque dh_g<1..2^16-1>;
  6. opaque dh_Ys<1..2^16-1>;
  7.  } ServerDHParams; /* Ephemeral DH parameters */
  8. dh_p
  9.  Diffie-Hellman密钥协商计算的大质数模数。
  10. dh_g
  11.  Diffie-Hellman 的生成元,
  12. dh_Ys
  13.  服务器的Diffie-Hellman公钥 (g^X mod p).
  14.  struct {
  15. opaque point <1..2^8-1>;
  16.  } ECPoint;
  17.  enum { explicit_prime (1), explicit_char2 (2),
  18. named_curve (3), reserved(248..255) } ECCurveType;
  19.  struct {
  20.  ECCurveType curve_type;
  21. select (curve_type) {
  22.  case named_curve:
  23.  NamedCurve namedcurve;
  24.  };
  25.  } ECParameters;
  26.  struct {
  27.  ECParameters curve_params;
  28.  ECPoint public; //ECDH的公钥
  29.  } ServerECDHParams;
  30.  struct {
  31. select (KeyExchangeAlgorithm) {
  32.  case dh_anon:
  33.  ServerDHParams params;
  34.  case dhe_dss:
  35.  case dhe_rsa:
  36.  ServerDHParams params;
  37. digitally-signed struct {
  38. opaque client_random[32];
  39. opaque server_random[32];
  40.  ServerDHParams params;
  41.  } signed_params;
  42.  case ec_diffie_hellman:
  43.  ServerECDHParams params;
  44.  Signature signed_params;
  45.  case rsa:
  46.  case dh_dss:
  47.  case dh_rsa:
  48.  struct {} ;
  49.  /* message is omitted for rsa, dh_dss, and dh_rsa */
  50.  /* may be extended, e.g., for ECDH -- see [TLSECC] */
  51.  };
  52.  } ServerKeyExchange;
  53. params
  54.  服务器的密钥交换参数。
  55. signed_params
  56.  对需要认证的(即非anonymous的)密钥交换,对服务器的密钥交换参数的数字签名。

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

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

  1.  enum {
  2.  ...
  3. secp256r1 (23), secp384r1 (24), secp521r1 (25),
  4. reserved (0xFE00..0xFEFF),
  5.  (0xFFFF)
  6.  } NamedCurve;

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

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

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

7. handshake -- Certificate Request

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

消息结构:

  1.  enum {
  2. rsa_sign(1), dss_sign(2), rsa_fixed_dh(3),dss_fixed_dh(4),
  3. rsa_ephemeral_dh_RESERVED(5),dss_ephemeral_dh_RESERVED(6),
  4. fortezza_dms_RESERVED(20),
  5. ecdsa_sign(64), rsa_fixed_ecdh(65),
  6. ecdsa_fixed_ecdh(66),
  7.  (255)
  8.  } ClientCertificateType;
  9. opaque DistinguishedName<1..2^16-1>;
  10.  struct {
  11.  ClientCertificateType certificate_types<1..2^8-1>;
  12.  SignatureAndHashAlgorithm
  13. supported_signature_algorithms<2^16-1>;
  14.  DistinguishedName certificate_authorities<0..2^16-1>;
  15.  } 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,服务器不能要求客户端认证。

8. handshake -- Server Hello Done

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

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

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

消息格式:

  1.  struct { } ServerHelloDone;


如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引