关于密码学:FIDO-U2F设备的HID协议

33次阅读

共计 5320 个字符,预计需要花费 14 分钟才能阅读完成。

FIDO 概述

FIDO(Fast IDentity Online)联盟成立于 2012 年,FIDO 联盟通过定义出一套凋谢、可扩大、可协同的技术规范,来扭转现有在线认证形式,缩小认证用户时对明码(password)的依赖。FIDO 有两套标准:U2F 和 UAF。

无明码的 UAF(Universal Authentication Framework)

  • 用户携带含有 UAF 的客户设施
  • 用户出示一个本地的生物辨认特色或者 PIN
  • 网站额能够抉择是否保留明码

用户抉择一个本地的认证计划(例如按一下指纹、看一下摄像头、对麦克谈话,输出一个 PIN 等)把他的设施注册到在线服务下来。只须要一次注册,之后用户再须要去认证时,就能够简略的反复一个认证动作即可。用户在进行身份认证时,不在须要输出他们的明码了。UAF 也容许组合多种认证计划,比方指纹 +PIN。

第二因子的 U2F(Universal 2nd Factor)

  • 用户携带 U2F 设施,浏览器反对这个设施
  • 用户出示 U2F 设施
  • 网站能够应用简略的明码(比方 4 个数字的 PIN)

FIDO U2F 认证,国内的文章个别翻译成 FIDO 两步认证。U2F 是在现有的用户名 + 明码认证的根底之上,减少一个更平安的认证因子用于登录认证。用户能够像以前一样通过用户名和明码登录服务,服务会提醒用户出示一个第二因子设施来进行认证。U2F 能够应用简略的明码(比方 4 个数字的 PIN)而不就义安全性。

U2F 出示第二因子的模式个别是按一下 USB 设施上的按键或者放入 NFC。

U2F HID 协定

UAF 先放一边,U2F 的工作流程比较简单,具体的能够看 FIDO 联盟官网。上面次要说下 U2F HID 协定。

首先要明确一下 U2FHID 协定不是 U2F 的应用层协定,是形容 U2F 的音讯如何通过 HID 传输的底层协定,U2F 应用层的协定在 U2F Raw Message 中定义。U2FHID 协定能够反对在大多数平台上间接应用而不须要装置驱动,能够反对多利用并发拜访设施。

并发和通道

U2FHID 设施解决多客户端,比方多个利用通过 HID 栈拜访单个资源,每个客户端都能够和 U2FHID 设施通过一个逻辑通道(logical channel)进行通信,每个客户端都应用一个惟一的 32bit 通道 ID 来判断用处。通道 ID 由 U2F 设施来进行调配,确保惟一。产生通道 ID 的算法由 U2FHID 设施的厂商标准定义,FIDO 的 U2FHID 协定中不进行定义。

通道 ID 0 是保留的,0xFFFFFFFF 也是保留给播送命令的。

音讯和包构造

包 (Packets) 分为两类,初始化包(initialization packets)和附加包(continuation packets)。就像 initialization packets 这个名字一样,每个应用层音讯的第一包都是 initialization packet, 也是一个事物的开始。如果整个应用层音讯不能通过一个包下发,就须要一个或者多个 continuation packet 来发送了,直到把所有音讯发完。

一个应用层音讯从主机发送到设施叫做申请(request),从设施返回给主机的叫做响应(response)。申请和响应音讯是雷同的构造,一个事勿从一个申请的 initialization packet 开始,截止于一个响应的最初一个包。包的长度永远是固定的大小,一个包中的有的字节并没有被应用到,没有应用的字节须要设置为 0。

初始化包的定义

偏移 长度 名称 形容
0 4 CID 通道 ID
4 1 CMD 命令 ID
5 1 BCNTH 发送数据长度的高位
6 1 BCNTL 发送数据长度的低位
7 (s-7) DATA 发送数据(s 等于包的固定长度)

附加包的定义

偏移 长度 名称 形容
0 4 CID 通道 ID
4 1 SEQ 包的程序,0x00..0x7F
5 (s-5) DATA 发送数据(s 等于包的固定长度)

如果一个应用层音讯长度小于等于 s -7,那么一个包就能够发完,如果一个比拟大的应用层音讯,须要拆分成一个或者多个附加包,第一个附加包的 SEQ 为 0,每次附加包的 SEQ 值减少 1,最大到 0xFF。USB 全速设施的包长度的最大值是 64 字节,这样最大的一个应用层音讯能够发送的长度就是,64-7+128*(64-5)=7609 字节。

上面是一个初始化包和附加包的 C 的定义

typedef struct {
  uint32_t cid;                        // Channel identifier
  union {
    uint8_t type;                      // Frame type - b7 defines type
    struct {
      uint8_t cmd;                     // Command - b7 set
      uint8_t bcnth;                   // Message byte count - high part
      uint8_t bcntl;                   // Message byte count - low part
      uint8_t data[HID_RPT_SIZE - 7];  // Data payload
    } init;
    struct {
      uint8_t seq;                     // Sequence number - b7 cleared
      uint8_t data[HID_RPT_SIZE - 5];  // Data payload
    } cont;
  };
} U2FHID_FRAME;

同步

搞 USBKEY 就不能不说到同步,U2FHID 也一样,一个事务永远分为三个阶段:音讯从主机发送到设施,设施解决音讯,响应从设施返回到主机,U2FHID 的事务必须是原子的,一个事务一旦开始,不能被其余利用中断。

U2FHID 命令

还是要明确一下,上面介绍的是 U2FHID 协定中的命令,不是 U2F 应用层的命令,U2F 应用层的命令在 U2F Raw Message 中定义,并应用 U2F_MSG 命令发送。

1.U2FHID_MSG

这个指令是用来发送 U2F 应用层音讯的到 FIDO 设施的指令,数据域 data 中的值就是 U2F 的应用层指令。

2.U2FHID_INIT

这个指令是利用申请 FIDO 设施调配一个惟一的 32bit 的 CID,这个 CID 将会在利用剩下的工夫中应用到。申请 FIDO 设施调配一个新的逻辑通道,申请的利用须要应用播送通道 U2FHID_BROADCAST_CID, 设施会应用播送通道在响应中从新返回一个通道 ID。

3.U2FHID_PING

用来调试用的,发送一个事务到设施。

4.U2FHID_ERROR

只用于响应音讯。

5.U2FHID_WINK

这个指令是可选的,如果设施有 LED 灯,能够让灯闪一下或者其余相似的行为,次要是用于提醒用户。

能够看进去,U2FHID 协定最根本的就是应用下面的 U2FHID_FRAME 这个构造来和 FIDO 设施进行通信,上面这个函数就是把不同的 U2FHID 指令和数据域封装成 U2FHID_FRAME 的格局下发到 USB 设施中。

u2fh_rc
u2fh_sendrecv (u2fh_devs * devs, unsigned index, uint8_t cmd,
           const unsigned char *send, uint16_t sendlen,
           unsigned char *recv, size_t * recvlen)
{
  int datasent = 0;
  int sequence = 0;
  struct u2fdevice *dev;
 
  if (index >= devs->num_devices || !devs->devs[index].is_alive)
    {return U2FH_NO_U2F_DEVICE;}
 
  dev = &devs->devs[index];
 
  while (sendlen > datasent)
    {U2FHID_FRAME frame = { 0};
      {
    int len = sendlen - datasent;
    int maxlen;
    unsigned char *data;
    frame.cid = dev->cid;
    if (datasent == 0)
      {
        frame.init.cmd = cmd;
        frame.init.bcnth = (sendlen >> 8) & 0xff;
        frame.init.bcntl = sendlen & 0xff;
        data = frame.init.data;
        maxlen = sizeof (frame.init.data);
      }
    else
      {
        frame.cont.seq = sequence++;
        data = frame.cont.data;
        maxlen = sizeof (frame.cont.data);
      }
    if (len > maxlen)
      {len = maxlen;}
    memcpy (data, send + datasent, len);
    datasent += len;
      }
 
      {unsigned char data[sizeof (U2FHID_FRAME) + 1];
    int len;
    data[0] = 0;
    memcpy (data + 1, &frame, sizeof (U2FHID_FRAME));
    if (debug)
      {fprintf (stderr, "USB send:");
        dumpHex (data, 0, sizeof (U2FHID_FRAME));
      }
 
    len = hid_write (dev->devh, data, sizeof (U2FHID_FRAME) + 1);
    if (debug)
      fprintf (stderr, "USB write returned %d\n", len);
    if (len < 0)
      return U2FH_TRANSPORT_ERROR;
    if (sizeof (U2FHID_FRAME) + 1 != len)
      return U2FH_TRANSPORT_ERROR;
      }
    }
 
  {
    U2FHID_FRAME frame;
    unsigned char data[HID_RPT_SIZE];
    int len = HID_RPT_SIZE;
    int maxlen = *recvlen;
    int recvddata = 0;
    short datalen;
    int timeout = HID_TIMEOUT;
    int rc = 0;
 
    while (rc == 0)
      {if (debug)
      {fprintf (stderr, "now trying with timeout %d\n", timeout);
      }
    rc = hid_read_timeout (dev->devh, data, len, timeout);
    timeout *= 2;
    if (timeout > HID_MAX_TIMEOUT)
      {
        rc = -2;
        break;
      }
      }
    sequence = 0;
 
    if (debug)
      {fprintf (stderr, "USB read rc read %d\n", len);
    if (rc > 0)
      {fprintf (stderr, "USB recv:");
        dumpHex (data, 0, rc);
      }
      }
    if (rc < 0)
      {return U2FH_TRANSPORT_ERROR;}
 
    memcpy (&frame, data, HID_RPT_SIZE);
    if (frame.cid != dev->cid || frame.init.cmd != cmd)
      {return U2FH_TRANSPORT_ERROR;}
    datalen = frame.init.bcnth << 8 | frame.init.bcntl;
    if (datalen + datalen % HID_RPT_SIZE > maxlen)
      {return U2FH_TRANSPORT_ERROR;}
    memcpy (recv, frame.init.data, sizeof (frame.init.data));
    recvddata = sizeof (frame.init.data);
 
    while (datalen > recvddata)
      {
    timeout = HID_TIMEOUT;
    rc = 0;
    while (rc == 0)
      {if (debug)
          {fprintf (stderr, "now trying with timeout %d\n", timeout);
          }
        rc = hid_read_timeout (dev->devh, data, len, timeout);
        timeout *= 2;
        if (timeout > HID_MAX_TIMEOUT)
          {
        rc = -2;
        break;
          }
      }
    if (debug)
      {fprintf (stderr, "USB read rc read %d\n", len);
        if (rc > 0)
          {fprintf (stderr, "USB recv:");
        dumpHex (data, 0, rc);
          }
      }
    if (rc < 0)
      {return U2FH_TRANSPORT_ERROR;}
 
    memcpy (&frame, data, HID_RPT_SIZE);
    if (frame.cid != dev->cid || frame.cont.seq != sequence++)
      {
        fprintf (stderr, "bar: %d %d %d %d\n", frame.cid, dev->cid,
             frame.cont.seq, sequence);
        return U2FH_TRANSPORT_ERROR;
      }
    memcpy (recv + recvddata, frame.cont.data, sizeof (frame.cont.data));
    recvddata += sizeof (frame.cont.data);
      }
    *recvlen = datalen;
  }
  return U2FH_OK;
}

这个函数搞定,基本上 U2FHID 协定中定义的命令就都能够搞定了。下一篇暂定写 U2F Raw Message 中定义的 U2F 应用层的协定。

正文完
 0