U2F Signature and Verification

背景

U2F 协议被广泛使用和适配,硬件低廉,厂商众多(Yubi、Google),开发友好,被各种语言广泛支持,SDK 成熟,已经在行业中被证明。

硬件厂商:

  • Yubikey
  • U2FZero
  • Thetis
  • Feitan
  • Nitrokey FIDO U2F
  • Trezor

U2F Attestations

首先我们了解下 U2F 的请求结构体

webauthn

注册完成获取注册的公钥

根据上图中的 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")
    }