U2F Signature and Verification
背景
U2F 协议被广泛使用和适配,硬件低廉,厂商众多(Yubi、Google),开发友好,被各种语言广泛支持,SDK 成熟,已经在行业中被证明。
- https://www.yubico.com/authentication-standards/fido-u2f/
- https://en.wikipedia.org/wiki/Universal_2nd_Factor
- https://fidoalliance.org/specs/u2f-specs-master/fido-u2f-overview.html
硬件厂商:
- Yubikey
- U2FZero
- Thetis
- Feitan
- Nitrokey FIDO U2F
- Trezor
U2F Attestations
首先我们了解下 U2F 的请求结构体
注册完成获取注册的公钥
根据上图中的 user reg response 可以看到前 65 个字节为用户的公钥,提取如下: 公钥其实位置在 buf[:65], ECC 曲线为 P256
x, y := elliptic.Unmarshal(elliptic.P256(), buf[:65])
if x == nil {
return nil, nil, ErrInvalidPublicKey
}
PublicKey.Curve = elliptic.P256()
PublicKey.X = x
PublicKey.Y = y
Auth 数据进行验签
在后续的 Authcate 过程中,可以获取签名数据,使用注册的 public key 进行验签,验签的原始数据如下:
var buf []byte
buf = append(buf, appHash[:]...)
buf = append(buf, response.RawResponse[:5]...)
buf = append(buf, challenge[:]...)
s = sha256.New()
if _, err := s.Write(buf); err != nil {
return err
}
h := s.Sum(nil)
验签数据为 appid 哈希值 raw response 的前 5 字节,这 5 个字节含义分别为:
res[0]: UserPresenceVerified
res[1:5]: Counter,计算方式为 uint32(res[1])<<24 | uint32(res[2])<<16 | uint32(res[3])<<8 | uint32(res[4])
另外还有 challenge 数据,这个为 client data 的 hash 真正的签名数据提取,数据其实位置在 RawResponse[5:], 使用 ASN1 来解析:
var sig ecdsaSig
rest, err := asn1.Unmarshal(response.RawResponse[5:], &sig)
if err != nil {
return err
}
if len(rest) != 0 {
return errors.New("trailing data after X.509 signature")
}
校验签名的部分比较标准,为:
if !ecdsa.Verify(&publicKey, h, sig.R, sig.S) {
return errors.New("invalid signature")
}