DTLS--基于UDP的无链接TLS

来源:互联网 发布:淘宝直通车创意 编辑:程序博客网 时间:2024/05/21 09:12

翻出来以前做的笔记,关于实现DTLS需要注意的问题。


TLS结合非可靠连接面对的两个问题:
1.TLS的加密层不允许独立处理某个指定的记录层数据包,例如第N个包没有收到,第N+1个记录包不能解密。
2.TLS握手协议假设在可靠的有序信道上完成,如果某个消息丢失,握手过程将退出。


解决方法:
*TLS传输加密层不能独立出来主要有两个方面的限制:
1.加密算法的模式(CBC模式和流模式加密时)记录报文前后关联。
2.防重放攻击和消息记录保护依靠MAC中的序列号来提供,但是序列号包含在记录报文中。


~DTLS借用IPSEC ESP的实现机制,在记录报文中添加明确的序列号和CBC状态。


*实现可靠的握手
TLS握手是一个指定次序的加密握手过程,数据包必须按照一定的顺序进行收发,其他方式发送都是错误的,这个过程中不能重新排序和丢包。还有一个问题是发送的消息可能会比数据包的长度更大(发送证书的时候),因此需要处理碎片问题。DTLS需要处理这两个问题。
  ~ DTLS使用一个简单的超时重传来处理包丢失问题。
  ~ 每个握手消息都分配一个特别的序列号,当一方收到握手消息时,很快都可以判断出是不是自己希望收到的包,如果是,就处理他。如果不是,就用存起来,等前面的消息收到之后再一起处理。
  ~每个DTLS握手消息可以拆分成多个DTLS记录,每个DTLS消息结构中包含了片偏移和片长度。因此接收方在对这些包进行重组成非分片包


防止重放攻击:
采用和IPsec ESP 相同的技术,维护一个接收记录的位图窗口,如果记录在包在位图窗口中找到非常老的记录,并且之前已经接收过则拒绝。






DTLS和TLS1.1的差别:
1.记录层添加了一个明确的序列号。记录头如下:
struct {
         ContentType type;
         ProtocolVersion version;
         uint16 epoch;                                    // New field
         uint48 sequence_number;                          // New field
         uint16 length;
         opaque fragment[DTLSPlaintext.length];
       } DTLSPlaintext;
epoch是一个计数器 每一个加密状态改变时加1。主要是用来区分在一个多次重新协商的情况,多个记录包文可能会具有相同的序列号,因此再用这个域来区分,接收者可以用来区分不同的包。epoch初始值为0,每发送一个ChangeCipherSpec消息后都加1.
sequence_number 记录层的序列号,在每一个ChangeCipherSpec消息发送之后,sequence_number都设置为0.
其他选项和TLS1.1一样。


2.尽量避免DTLS数据在发送过程中分片,大小不超过PMTU。
   PMTU发现策略,在最开始的时候使用一个较为保守的MTU值,在握手或实际的应用数据传输过程中逐步更新。一开始采用网卡接口的MTU,DTLS在实现的时候允许应用程序控制DF bit的状态。


3.计算MAC ,TLS中用来计算MAC的64位序列号使用记录头中的epoch和sequence_number来替代。epoch+sequence_number的长度也是64bit。
计算MAC的协议版本号也需要用DTLS的{254,255}
在TLS中如果计算MAC出错,则会关闭连接。但是在DTLS中,如果计算出错,会简单丢弃出错包并继续维持连接。


4.DTLS可以使用空算法套件,但是不能是支持RC4流算法,因为RC4不能随机使用。
5.DTLS和TLS使用相同的对称算法套件。
6.DTLS使用序列号来提供重放攻击保护,序列号的验证需要采用滑动窗口机制来实现。引用rfc2402的3.4.3,最小的滑动窗口大小为32,但是64是首选,初始化的时候设置成64。






$握手协议
DTLS和TLS协议使用相同的消息,但是有如下三处差别:
  1.添加一个无状态的cookie exchange消息,保护拒绝服务器攻击
  2.修改握手报头来处理消息丢失、重组和分片。
  3.使用超时重传机制来处理包丢失。


DOS攻击   
        dos攻击会导致以下两个问题:
        1。攻击者可以消耗服务器的很多资源,在服务器的处理初始化握手消息时需要设置很多状态和执行代价很高的加密相关操作。
        2.攻击者伪造一个源地址,发送请求,服务器会对其发送大量数据,可能会造成洪水攻击。(发送一个client hello,服务器回证书等信息)。
   为了避免拒绝服务攻击,DTLS采用和IKE一样的无状态cookie技术。当客户端发送client hello消息后,服务器发送HelloVerifyRequest消息,这个消息包含了无状态cookie。客户端收到之后必须重传添加上了cookie的clienthello
         The exchange is shown below:


         Client                                   Server
         ------                                   ------
         ClientHello           ------>


                               <----- HelloVerifyRequest
                                      (contains cookie)


         ClientHello           ------>
         (with cookie)


DTLS中带有cookie的ClientHello的结构如下:


struct {
        ProtocolVersion client_version;
        Random random;
        SessionID session_id;
        opaque cookie<0..32>;                             // New field
        CipherSuite cipher_suites<2..2^16-1>;
        CompressionMethod compression_methods<1..2^8-1>;
      } ClientHello;
发送第一个clienthello时,cookie为空,长度为0,两个hello的其他值都一样;


HelloVerifyRequest的结构如下:
  struct {
        ProtocolVersion server_version;
        opaque cookie<0..32>;
      } HelloVerifyRequest;


cookie的生成方法:
        Cookie = HMAC(Secret, Client-IP, Client-Parameters)
        secret为随机数。


**如果使用了HelloVerifyRequest机制,在计算finished的校验消息MAC值的时候,第一个clienthello和HelloVerifyRequest不参与计算。


握手消息格式:
        为了支持丢包、重组和分片,DTLS修改了TLS1.1的协商报头:
         struct {
        HandshakeType msg_type;
        uint24 length;
        uint16 message_seq;                               // New field
        uint24 fragment_offset;                           // New field
        uint24 fragment_length;                           // New field
        select (HandshakeType) {
          case hello_request: HelloRequest;
          case client_hello:  ClientHello;
          case hello_verify_request: HelloVerifyRequest;  // New type
          case server_hello:  ServerHello;
          case certificate:Certificate;
          case server_key_exchange: ServerKeyExchange;
          case certificate_request: CertificateRequest;
          case server_hello_done:ServerHelloDone;
          case certificate_verify:  CertificateVerify;
          case client_key_exchange: ClientKeyExchange;
          case finished:Finished;
        } body;
      } Handshake;
双方发送的第一个包 的message_seq都为0.每当消息生成时,这个值都加1,如果消息重传,使用相同的message_seq.
例子:
Client                             Server
      ------                             ------
      ClientHello (seq=0)  ------>


                              X<-- HelloVerifyRequest (seq=0)
                                              (lost)


      [Timer Expires]


      ClientHello (seq=0)  ------>
      (retransmit)


                           <------ HelloVerifyRequest (seq=0)


      ClientHello (seq=1)  ------>
      (with cookie)


                           <------        ServerHello (seq=1)
                           <------        Certificate (seq=2)
                           <------    ServerHelloDone (seq=3)


      [Rest of handshake]


消息分片和重组:
没有分片的源数据包的 fragment_offset=0  fragment_length=length
如果分片,每个包使用相同的message_seq,每一个新消息通过fragment_offset来区分,这个值包含以前分片的长度。fragment_length等于本分片的数据长度。接收方必须要实现数据包的缓存和重组。


超时和重传:


将协商流程划分成不同的阶段,对每一个阶段进行超时和重传控制。具体划分如下图:
完整协商流程:
 Client                                          Server
    ------                                          ------


    ClientHello             -------->                           Flight 1


                            <-------    HelloVerifyRequest      Flight 2


   ClientHello              -------->                           Flight 3


                                               ServerHello    \
                                              Certificate*     \
                                        ServerKeyExchange*      Flight 4
                                       CertificateRequest*     /
                            <--------      ServerHelloDone    /


    Certificate*                                              \
    ClientKeyExchange                                          \
    CertificateVerify*                                          Flight 5
    [ChangeCipherSpec]                                         /
    Finished                -------->                         /


                                        [ChangeCipherSpec]    \ Flight 6
                            <--------             Finished    /


          Figure 1. Message flights for full handshake


快速协商流程:
  Client                                           Server
    ------                                           ------


    ClientHello             -------->                          Flight 1


                                               ServerHello    \
                                        [ChangeCipherSpec]     Flight 2
                             <--------             Finished    /


    [ChangeCipherSpec]                                         \Flight 3
    Finished                 -------->                         /


   Figure 2. Message flights for session-resuming handshake


DTLS的超时重传机制使用如下的状态机,客户端从PREARING开始。服务器从WAITING状态开始,但是使用空的空间和没有超时重传。


               +-----------+
                   | PREPARING |
             +---> |           | <--------------------+
             |     |           |                      |
             |     +-----------+                      |
             |           |                            |
             |           |                            |
             |           | Buffer next flight         |
             |           |                            |
             |          \|/                           |
             |     +-----------+                      |
             |     |           |                      |
             |     |  SENDING  |<------------------+  |
             |     |           |                   |  | Send
             |     +-----------+                   |  | HelloRequest
     Receive |           |                         |  |
        next |           | Send flight             |  | or
      flight |  +--------+                         |  |
             |  |        | Set retransmit timer    |  | Receive
             |  |       \|/                        |  | HelloRequest
             |  |  +-----------+                   |  | Send
             |  |  |           |                   |  | ClientHello
             +--)--|  WAITING  |-------------------+  |
             |  |  |           |   Timer expires   |  |
             |  |  +-----------+                   |  |
             |  |         |                        |  |
             |  |         |                        |  |
             |  |         +------------------------+  |
             |  |                Read retransmit      |
     Receive |  |                                     |
        last |  |                                     |
      flight |  |                                     |
             |  |                                     |
            \|/\|/                                    |
                                                      |
         +-----------+                                |
         |           |                                |
         | FINISHED  | -------------------------------+
         |           |
         +-----------+


        Figure 3. DTLS timeout and retransmission state machine


状态机有三个基础状态:
        1.准备状态:在准备状态,为下一步的计算阶段准备做必要的准备,如初始化缓冲区。
        2.发送状态:在发送状态,传输并且缓存消息,一旦握手过程中的最后一个阶段的消息发送成功,就进入结束状态。如果希望接收更过的消息,就设置重传时间并且进入等待状态。
        3.等待状态:有三种退出等待状态的方式。
         *重传定时器到期。进入发送状态,在这里重新传输发送本阶段的消息,重新设置重传定时器,并且返回等待状态。
*接收到了对方发送的重传阶段消息。进入发送状态,在这里重新传输发送本阶段的消息,重新设置重传定时器,并且返回等待状态。这里因为对方的定时器超时了,建议重新发送。
*接收到了希望接收的下一个阶段的消息。如果这是最后一个阶段的消息,则进入到结束状态。如果需要发送下一个阶段的消息,则进入到准备状态。


      如果服务器想要重新协商,将从结束状态切换到准备状态发送hellorequest。当客户端收到hellorequest,将从结束状态切换到准备状态准备发送clienthello。




定时器的值:
定时器的值初始时设为1秒,每次重传就加倍,直到不超过最大值60秒。应当保存当前的定时器的值直到丢包,因为这个时候会重新设置定时器的值。重新协商时,当大量数据传输完之后,将定时器的值设置成初始值。






告警消息将不会做任何重传,即使在协商过程中也不会




为什么不用IPSEC来实现UDP的安全传输?
首先IPSEC是端到端的协议,不适用于C/S模型。主要原因是IPsec是网络层而非会话层或应用层的协议。
SP使用IP来标记用户策略,对域名的支持不好。
在内核层实现,但是有一些操作系统目前还不支持。




在协商的阶段采用TCP,传输阶段采用UDP。借鉴IPsec的两阶段处理方法。这个方案的优点是可以完成可靠的握手过程。但是应用程序必须管理两个套接字(一个TCP,一个UDP),同步这两个套接字是一个程序的主要问题,特别是重新协商的时候这个架构显得很复杂。如果密钥协商已经完成,TCP连接将关闭,重新协商的消息必须使用非可靠的通道,因此需要实现一个重传机制。
如果协商完成后,TCP连接保持打开,将会消耗很多不必要的系统资源,尤其是当一个进程打开了大量的套接字的时候。
采用这样的两阶段处理方式,在错误处理方面也会变得更加复杂,例如TCP连接重置了,意味着整个隧道将要关闭。




DTLS的设计需求:
要设计一个在用户空间的协议,使用单通道的协议。
1.在协商阶段和数据传输过程中均采用UDP通道。这样应用程序能较轻松的管理DTLS套接字。
2.可靠的会话机制。实现一种简单轻量级重传机制保证握手过程是可靠的。
3.安全服务。提供公正、保密的数据传输通道,提供处理重传数据的能力。
4.开发简单。
5.语义,TLS模仿了TCP。DTLS要模仿UDP。
6.微小的改动。


0 0
原创粉丝点击