V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
hxndg
V2EX  ›  SSL

PSK_binder, PSK AND TICKET。

  •  
  •   hxndg · 2018-07-17 14:53:05 +08:00 · 1832 次点击
    这是一个创建于 2356 天前的主题,其中的信息可能已经有所发展或是发生改变。

    客户端和服务端通过(EC)DHE 的方式建立了连接,当握手完成之后,也就是服务端收到客户端的 FINISHED 报文之后,服务端可以发送多条 NST 报文,该消息将一个 ticket 键值和从重用主密钥导出的 PSK 建立了关联。

    NST 报文可以一次发送多条,也可以捕获特定事件之后再发送,比方说服务端发起一条 post-handshake 认证报文。   看一下 NST 报文的格式:

    struct {
          uint32 ticket_lifetime;
          uint32 ticket_age_add;
          opaque ticket_nonce<0..255>;
          opaque ticket<1..2^16-1>;
          Extension extensions<0..2^16-2>;
      } NewSessionTicket;
    

    ticket_lifetime 用 32bit 网络字节序 unsigned 整型表示。服务端绝对不能使该值大于 604800,也就是7天。该值为0,表示应当立刻丢弃。客户端不应当缓存 lifetime 超过七天的 ticket,服务端对该 ticket 的有效期应当小于 ticket_lifetime。

    ticket_age_add  一个满足随机性,安全生成的 32bit 数值,该数值用于混淆客户端发送过来的 ticket_age。客户端将 ticket_age 加上该值,然后对 2^32 取余。服务端对于每个 ticket 必须重新生成一个新的 ticket_age_add。

    ticket_nonce  独一无二的 ticket_nonce。

    extensions  目前 NST 报文只允许使用一种拓展,即 max_early_data_size,即发送 0-RTT 数据时,客户端可以发送的最大数量应用报文。只考虑应用数据,也就是明文但是不包含 padding 和报文种类等多余的数据。 ticket   ticket 的键值会被使用为 PSK-identity,ticket 本身是一个含混的标签(这一点很容易理解,对客户端而言他是一个身份),它可以是数据库查找的键值,或者是一个自加密-签名的数值。RFC5077 提供了一种推荐的 ticket 构建方法: 服务端使用两种不同的 key:128bit 的 AES-CBC 加密,和 256bit 的 HMAC-SHA-256

    struct {
          opaque key_name[16];
          opaque iv[16];
          opaque encrypted_state<0..2^16-1>;
          opaque mac[32];
      } ticket;
    

    key_name 用于鉴别不同的 key,因此服务端可以轻松鉴别自己签发的不同的 ticket。key_name 应当能够避免碰撞攻击。encrypted_state 使用 AES-CBC 模式同 IV 进行加密,MAC 码使用 HMAC-SHA256 对 key_name 和 IV 进行计算。

    struct {
          ProtocolVersion protocol_version;
          CipherSuite cipher_suite;
          CompressionMethod compression_method;
          opaque master_secret[48];
          ClientIdentity client_identity;
          uint32 timestamp;
      } StatePlaintext;
    

    StatePlaintext 存储着包含 master_secret 的 TLS 会话状态,服务端利用时间戳来判断会话状态的过期,client_identity 字段包含认证方式和秘钥交换算法,具体实现的时候可以根据需求来选择代码进行实现。我个人考虑 TLS1.3 不需要提供 certificate_based 的 session 存储,我们只需要提供 psk 的存储即可。值得注意的是,下面的 psk_identity 并不是 TLS1.3 的 psk_identity。

    enum {
          anonymous(0),
          certificate_based(1),
          psk(2)
    } ClientAuthenticationType;
    
      struct {
          ClientAuthenticationType client_authentication_type;
          select (ClientAuthenticationType) {
              case anonymous: struct {};
              case certificate_based:
                  ASN.1Cert certificate_list<0..2^24-1>;
              case psk:
                  opaque psk_identity<0..2^16-1>;   /* from [RFC4279] */
          };
       } ClientIdentity;
    

    同当前 ticket 相关联的 PSK 计算方式如下: HKDF-Expand-Label(resumption_master_secret, "resumption", ticket_nonce, Hash.length) ticket_nonce 的独特性保证了 PSK 的独特性。

    服务端通过 NST 报文,将 ticket 发送过来。现在客户端进行第二次连接,客户端需要发送 CH 报文来发送自己的 PSK 信息,CH 报文必须包含 pre_shared_key 拓展,该拓展的格式如下: struct { opaque identity<1..2^16-1>; uint32 obfuscated_ticket_age; } PskIdentity;

      opaque PskBinderEntry<32..255>;
    
      struct {
          PskIdentity identities<7..2^16-1>;
          PskBinderEntry binders<33..2^16-1>;
      } OfferedPsks;
    
      struct {
          select (Handshake.msg_type) {
              case client_hello: OfferedPsks;
              case server_hello: uint16 selected_identity;
          };
      } PreSharedKeyExtension;
    

    identity 对应于我们上文定义的 ticket,也可以是外部方式建立的一个标签。    obfuscated_ticket_age 对应于上文中我们说的加了混淆之后的 ticket_age,对应的元素可以在服务端发送的 NST 报文当中找到。如果该 PSK 身份不是通过 NST 报文建立的,那么这个字段必须为 0。    psk_binder 是关键信息,用来将一个 PSK 和当前的握手信息,与上一次通过 NST 报文建立的握手信息创建关联。使用 HMAC 对部分 CLIENTHELLO 报文- CLIENTHELLO 报文从头到 PreSharedKeyExtension.identities (即不包含 binders 字段,因此我们知道 PreSharedKeyExtension 必须是最后的拓展)进行计算,得出 binder。我在这里对于 HMAC 输出的长度存疑,我看到 PskBinderEntry 的长度为 49×8 位,但是我知道的 SHA-384、SHA-256、SHA-1 都没有这么长的输出长度。

    binder_entry_key =
       HKDF-Expand-Label(Binder_Key, "finished", "", Hash.length)
     verify_data =
          HMAC(binder_entry_key,
               Transcript-Hash(Truncate(ClientHello1)))
    

    另外一点需要注意的是如果服务端和客户端之间发送过 HelloRetryEquest,那么第二次计算的内容应当为: Transcript-Hash(ClientHello1, HelloRetryRequest, Truncate(ClientHello2))

    对于服务端,回复的 PSK 拓展,为从 0 开始的选择的客户端的 PSK 序号。
    
    目前尚无回复
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2782 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 23ms · UTC 09:25 · PVG 17:25 · LAX 01:25 · JFK 04:25
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.