FIDO U2F HID协议

原文链接:http://blog.sina.com.cn/s/blog_625033800102vzag.html


FIDO(Fast IDentityOnline)联盟成立于2012年,FIDO联盟通过定义出一套开放、可扩展、可协同的技术规范,来改变现有在线认证方式,减少认证用户时对密码(password)的依赖。FIDO有两套规范:U2F和UAF。

无密码的UAF(UniversalAuthentication Framework

  • 用户携带含有UAF的客户设备
  • 用户出示一个本地的生物识别特征或者PIN
  • 网站额可以选择是否保存密码

用户选择一个本地的认证方案(例如按一下指纹、看一下摄像头、对麦克说话,输入一个PIN等)把他的设备注册到在线服务上去。只需要一次注册,之后用户再需要去认证时,就可以简单的重复一个认证动作即可。用户在进行身份认证时,不在需要输入他们的密码了。UAF也允许组合多种认证方案,比如指纹+PIN。


第二因子的U2F(Universal 2ndFactor) 

  • 用户携带U2F设备,浏览器支持这个设备
  • 用户出示U2F设备
  • 网站可以使用简单的密码(比如4个数字的PIN)

FIDOU2F认证,国内的文章一般翻译成FIDO两步认证。U2F是在现有的用户名+密码认证的基础之上,增加一个更安全的认证因子用于登录认证。用户可以像以前一样通过用户名和密码登录服务,服务会提示用户出示一个第二因子设备来进行认证。U2F可以使用简单的密码(比如4个数字的PIN)而不牺牲安全性。

U2F出示第二因子的形式一般是按一下USB设备上的按键或者放入NFC。


UAF先放一边,U2F的工作流程比较简单,具体的可以看FIDO联盟官网。下面主要说下U2F HID协议。

首先要明确一下U2FHID协议不是U2F的应用层协议,是描述U2F的消息如何通过HID传输的底层协议,U2F应用层的协议在U2FRaw Message中定义。U2FHID协议可以支持在大多数平台上直接使用而不需要安装驱动,可以支持多应用并发访问设备。

并发和通道

U2FHID设备处理多客户端,比如多个应用通过HID栈访问单个资源,每个客户端都可以和U2FHID设备通过一个逻辑通道(logicalchannel)进行通讯,每个客户端都使用一个唯一的32bit通道ID来判断用途通道ID由U2F设备来进行分配,确保唯一产生通道ID的算法由U2FHID设备的厂商规范定义,FIDO的U2FHID协议中不进行定义

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

消息和包结构

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

一个应用层消息从主机发送到设备叫做请求(request),从设备返回给主机的叫做响应(response)。请求和响应消息是相同的结构,一个事勿从一个请求的initializationpacket开始,截止于一个响应的最后一个包。包的长度永远是固定的大小,一个包中的有的字节并没有被使用到,没有使用的字节需要设置为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_tcid;                     // Channelidentifier

  union{

   uint8_t type;                  // Frame type - b7 defines type

   struct {

     uint8_t cmd;                 // Command - b7 set

     uint8_t bcnth;                // Messagebyte count - high part

     uint8_t bcntl;                // Messagebyte count - low part

     uint8_t data[HID_RPT_SIZE -7];  // Data payload

   } init;

   struct {

     uint8_t seq;                 // Sequence number - b7cleared

     uint8_t data[HID_RPT_SIZE -5];  // Data payload

   } cont;

 };

}U2FHID_FRAME;

同步

搞USBKEY就不能不说到同步,U2FHID也一样,一个事务永远分为三个阶段:消息从主机发送到设备,设备处理消息,响应从设备返回到主机,U2FHID的事务必须是原子的,一个事务一旦开始,不能被其他应用中断。

U2FHID 命令

还是要明确一下,下面介绍的是U2FHID协议中的命令,不是U2F应用层的命令,U2F应用层的命令在U2F RawMessage中定义,并使用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)

{

  intdatasent = 0;

  intsequence = 0;

  structu2fdevice *dev;


  if(index >= devs->num_devices ||!devs->devs[index].is_alive)

   {

     returnU2FH_NO_U2F_DEVICE;

   }


  dev =&devs->devs[index];


  while(sendlen > datasent)

   {

     U2FHID_FRAME frame = { 0};

     {

int len = sendlen -datasent;

intmaxlen;

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)

 returnU2FH_TRANSPORT_ERROR;

if (sizeof (U2FHID_FRAME) +1 != len)

 returnU2FH_TRANSPORT_ERROR;

     }

   }


 {

   U2FHID_FRAME frame;

   unsigned chardata[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 readrc read %d\n", len);

if (rc >0)

 {

  fprintf (stderr, "USB recv: ");

  dumpHex (data, 0, rc);

 }

     }

   if (rc < 0)

     {

returnU2FH_TRANSPORT_ERROR;

     }


   memcpy (&frame, data,HID_RPT_SIZE);

   if (frame.cid != dev->cid || frame.init.cmd!= cmd)

     {

returnU2FH_TRANSPORT_ERROR;

     }

   datalen = frame.init.bcnth << 8 |frame.init.bcntl;

   if (datalen + datalen % HID_RPT_SIZE >maxlen)

     {

returnU2FH_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, "nowtrying 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;

 }

  returnU2FH_OK;

}

这个函数搞定,基本上U2FHID协议中定义的命令就都可以搞定了。下一篇暂定写U2F RawMessage中定义的U2F应用层的协议。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
FIDO2协议中的通信协议格式主要包含两部分:CTAP (Client-to-Authenticator Protocol) 和 WebAuthn (Web Authentication)。 1. CTAP (Client-to-Authenticator Protocol) 协议格式 CTAP是一种用于在Web浏览器和安全密钥之间进行通信的协议,它定义了一系列的指令和响应格式。其中常用的指令有: - CTAP1_GET_VERSION:获取设备支持的CTAP版本号 - CTAP1_GET_RANDOM:获取设备生成的随机数 - CTAP1_REGISTER:用于在设备上注册新密钥 - CTAP1_AUTHENTICATE:用于在设备上进行身份验证 CTAP协议通信格式包含以下几个部分: - 指令代码:用于标识要执行的指令,例如CTAP1_REGISTER或CTAP1_AUTHENTICATE。 - 参数:用于传递指令所需的参数,例如注册新密钥时需要传递的公钥。 - 响应:设备返回的响应数据,例如注册新密钥成功后返回的证书和签名数据。 2. WebAuthn (Web Authentication) 协议格式 WebAuthn是一种在Web浏览器中使用的身份验证标准,它通过使用公共密钥加密技术,将密码替换为更安全的生物识别技术,例如指纹或面部识别等。 WebAuthn协议通信格式包含以下几个部分: - 认证器信息:包含了设备的元数据和验证策略,例如设备的厂商标识和支持的加密算法。 - 公钥:用于加密和签名数据,以及验证设备的真实性。 - 挑战:用于验证设备的真实性和防止重放攻击。 - 认证器响应:设备返回的响应数据,例如验证身份成功后返回的签名数据。 总之,FIDO2协议的通信协议格式主要包含了CTAP和WebAuthn两部分,它们共同构成了一种更加安全、便捷和私密的身份验证方式。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值