前言
根据子安安的安利,入手了个canokey,直到我拿到手之前,不如说,在写这篇文章之前,我还不知道这是啥东西。
只知道这玩意是类似于什么将军令牌一样的东西,很酷,很炫,就是不会用。。
啊,既然入手了这个高端玩具,就得给自己创造需求了。
虽然说有bit那啥那啥密码管理工具能直接代替输入密码,但是登录的时候直接插入或者刷NFC就登录不是更帅?
所以想着自己造一个插件就整,不只typecho,以后看到啥就整啥。。
所以参考我英文看不懂的文档,canokey支持以下协议
- U2F / FIDO2
- OpenPGP Smart Card 3.4
- PIV (NIST SP 800-73-4)
- OATH
- NDEF
- WebUSB
其中,我们要作为认证的话可以使用OATH协议以及U2F/FIDO2协议。
首先,先从易到难糊一个先
最开始想到的念头就是TOTP,毕竟这玩意我之前在写业务的流程中也有遇到过,最经典的就是邮件验证以及session,排除你财大气粗使用redis的话,大部分session会话都是 通过 HASH(时间戳 左移三位 secret * 用户数据),这样用户输入了用户数据,只要服务器有secret,然后在时间戳范围内,本地HASH的会话和服务器HASH的会话是相同的,这样就能保证在不接触原始数据的情况下进行验证
TOTP算法也是一样,根据RFC使用 方式如下
- 生成一个任意字节的字符串密钥K,与客户端安全地共享。
- 基于T0的协商后,Unix时间从时间间隔(TI)开始计数时间步骤,TI则用于计算计数器C(默认情况下TI的数值是T0和30秒)的数值
- 协商加密哈希算法(默认为SHA-1)
- 协商密码长度(默认6位)
尽管RFC 6238标准允许使用不同的参数,Google开发的验证应用不允许不同于默认的T0、TI值、哈希方法和密码长度。RFC 3548也同时鼓励K密钥以base-32编码输入(或以QR码的形式提供)。[2]
一旦参数协商完毕,密码开始按照如下方法生成:
- 从T0开始已经过的时间,每个TI为一个单位,总数记为C。
- 使用C作为消息,K作为密钥,计算HMAC哈希值H(定义来自之前的HMAC算法,但是大部分加密算法库都有支持)。K应当保持原样
- 继续传递,C应当以64位的原始无符号的整形数值传递。
- 取H中有意义最后4位数的作为弥补,记为O。
- 取H当中的4位,从O字节MSB开始,丢弃最高有效位,将剩余位储存为(无符号)的32位整形数值I。
- 密码即为基数为10的最低N位数。如果结果位数少于N,从左边开始用0补全。
缺点也很明显,这玩意能被爆破+这玩意能被钓鱼+这玩意能通过大量数据爆破。
不过不管了,我们直接开整
TOTP
看了一晚上的官方文档,调试了半天结果APDU发送给我都是6700,整个人麻了,最后被告知直接用yubico-manager就行。
根据ykman的文档,我们可以很轻松的看出来,添加一个TOTP动态令牌直接使用
ykman -r Canokey oath accounts add test tteesstt -o TOTP
就能添加,然后使用
ykman -r Canokey oath accounts code test
test 847710
就能计算出test的动态令牌值啦
通过网页也能很轻松的看出来,既然流程可以做到,我们就是要开始造web的后端以及前端。
首先,我们得先查看ykman是如何实现生成TOTP的动态码的,我们要做的就是把这个动态码取出来。
根据ykman的源码,我们直接搜索generate,很轻松的可以看到generate的源码
其中,我们重点关注的是calculate_all 这个函数,这个函数会计算key内所有的oauth并返回相关code,我们只需要把它提取出来即可。
直接使用
ykman --log-level=debug -r Canokey oath accounts code test
来对apdu内容进行查看,然后根据打印输出内容
我们即可直到APDU发送的报文模式了.
这之中遇到了一个坑,其中之一就是如果不带上面那个AID.OATH直接发送genrate all的APDU是会返回6A86的。在我根据ASCII直接搜索代码之后才发现在class初始化的时候有一句
估计是作为select Mode使用,加上这个直接发送报文就成功了。。既然成功之后,我们接下来需要想的就是如何解析内容。
依旧是查看代码,主要代码在tlv这里
按照约定基本就是TAG + size + data 这个步骤来,比如我这个
710474657374760506259686dc71057465737432760506259686dc
其中第一个字段是71代表着NAME,然后04是后面data字段长度,然后74 65 73 74对应前面4个ASCII长度分别是test,基本就是这样解析,至于为什么是71,可以参考oath-define这里直接查看定义,了解了如何解析。接下来就是如何发送这些代码过去了。
在官方文档,提供了直接的链接代码,WebUSBl:l-CanoKeys
核心原理就是前端js选择了设备,之后发送计算CODE代码并提取,然后发送给后端进行验证,如果验证成功则登录
未完待续..正在写插件