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。

初始化包的定义

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

附加包的定义

偏移长度名称形容
04CID通道ID
41SEQ包的程序,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_rcu2fh_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应用层的协定。