# FIDO U2F、FIDO2 和 CTAP 的关系

## 背景

2012 年，Yubico 和 Google 设计了 U2F 协议，第二年 U2F 成为 FIDO 组织的标准，之后加入了 NFC 的支持。之后，FIDO2 作为替代 U2F 的新标准产生，原来的 U2F 以兼容的方式成为了 CTAP1，而采用 CBOR 封装格式的 CTAP(CTAP2) 则是 FIDO2 的主要协议。

## U2F

### 命令格式

U2F 定义了它的[命令格式](https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-raw-message-formats-v1.2-ps-20170411.pdf)，基于 ISO7816-4 APDU（short APDU） ：

| CLA    | INS    | P1     | P2     | Lc        | data            | Le        |
| ------ | ------ | ------ | ------ | --------- | --------------- | --------- |
| 1 byte | 1 byte | 1 byte | 1 byte | 0-1 bytes | variable length | 0-1 bytes |

比如 U2F_VERSION 就是：

| CLA | INS | P1  | P2  | Lc  | data  | Le  |
| --- | --- | --- | --- | --- | ----- | --- |
| 00  | 03  | 00  | 00  | 0   | empty | 00  |

返回的数据就是 `U2F_V2` 的 ASCII 加上 `9000` 的状态。

除此之外，它还有一种 extended length 格式的 APDU，和上面的是等价的不同表示。

### 传输方式

实际使用 U2F 的时候，又有三种情况，分别是 USB、Bluetooth 和 NFC。

#### USB

在 [U2FHID](https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-hid-protocol-v1.2-ps-20170411.pdf) 里面，为了让 U2F 的命令通过 HID 接口传输，它规定了两个 endpoint，分别是 Interrupt IN 和 Interrupt OUT，还有一个固定的 HID Report Descriptor。为了发 U2F 命令，首先会进行一次封装：

| CMD        | BCNT | DATA    |
| ---------- | ---- | ------- |
| U2FHID_MSG | 4..n | n bytes |

添加了一个头，表示载荷是一个 U2F 的 command（自然也是 APDU）。

在 cmd 之上，还会封装一层，为了解决 USB 的 packet size 限制等问题，定义了 init packet：

| CID     | CMD    | BCNTH  | BCNTL  | DATA            |
| ------- | ------ | ------ | ------ | --------------- |
| 4 bytes | 1 byte | 1 byte | 1 byte | variable length |

如果数据太长，就会拆分成一个 init 和 多个 continuation packet：

| CID     | SEQ    | DATA            |
| ------- | ------ | --------------- |
| 4 bytes | 1 byte | variable length |

把 init 和 continuation 里面的 data 组合起来，就是 U2F 的 message，message 里面可能又有 U2F raw command，也就是 APDU。

发送的时候，先 Interrupt OUT 发送请求，再 Interrupt IN 读取回应。

#### Bluetooth

在 [U2F/Bluetooth](https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-bt-protocol-v1.2-ps-20170411.pdf) 里面，也用了一个类似的封装格式，请求：

| CMD    | HLEN   | LLEN   | DATA            |
| ------ | ------ | ------ | --------------- |
| 1 byte | 1 byte | 1 byte | variable length |

这里的 DATA payload 就是 extended length 格式的 APDU

#### NFC

在 [U2F/NFC](https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-nfc-protocol-v1.2-ps-20170411.pdf) 里面，既然 ISO 7816-4 本来就是 NFC-native 的格式，就不要额外的封装了。只需要规定一个 Applet 的 AID 即可：`A0000006472F0001`

### 总结

总而言之，U2F raw commands 就是在 APDU 格式上定义了几个命令。在 USB 和 Bluetooth 上都加了几个小的 Header，而 NFC 上则是规定了一个 AID。这对应用程序来说很方便，核心的命令只有一套，需要的时候封装一下即可。

## FIDO2

在之后，[FIDO2](https://fidoalliance.org/specs/fido-v2.0-rd-20170927/fido-client-to-authenticator-protocol-v2.0-rd-20170927.html#message-encoding) 出现了，在保持 U2F 兼容的基础上添加了新的功能，并且出现了 WebAuthN 作为浏览器使用 FIDO2 的协议。U2F 就变成了第一代的 CTAP，称为 CTAP1，然后 CTAP 默认指的就是 CTAP2。

### 命令格式

FIDO2 里面，定义了一些 CTAP 命令，比如 authenticatorMakeCredential，对应 U2F 的 U2F_REGISTER 命令。然后，规定了一个 [CBOR](https://tools.ietf.org/html/rfc7049) 的格式，来表示命令附带的数据。CBOR 是 RFC 7049，所以也是借用过来的格式。

### 传输方式

FIDO2 定义了在 USB 和 NFC 上的传输格式。

#### USB

在 USB 上传输的时候，定义了 CTAPHID 的协议，与 U2FHID 基本是一样的，规定了 init packet 和 continuation packet，packet 里面也是 CTAPHID 的消息，这部分是兼容 U2F 的。并且，额外添加了 CTAPHID_CBOR 消息：

| CMD          | BCNT     | DATA         | DATA + 1             |
| ------------ | -------- | ------------ | -------------------- |
| CTAPHID_CBOR | 1..(n+1) | CTAP command | n bytes of CBOR data |

它的载荷就是 CBOR 格式的请求。

类似地，它也是通过 Interrupt OUT 发送请求，从 Interrupt IN 读取回应。

#### NFC

在 NFC 上传输的时候，因为内部的格式是 CBOR，不再是 APDU 了，所以需要一些封装。

首先，它也定义了一个 Applet ID：`A0000006472F0001`，和 U2F 一样。为了保持兼容，它都支持 U2F 定义的 APDU 命令。

那怎么区分设备是否支持 CTAP1/U2F 和 CTAP2 呢？使用前面提到的 U2F_VERSION 命令即可。如果得到 U2F_V2，说明是支持 CTAP1/U2F 的；如果得到是其他的，说明只支持 CTAP2，不支持 CTAP1/U2F。

如果要发 CTAP2 的命令，就要把 CTAP command 和 CBOR 格式的数据封装到 APDU 里面：

| CLA | INS | P1  | P2  | Data         | Le  |
| --- | --- | --- | --- | ------------ | --- |
| 80  | 10  | 00  | 00  | CTAP Command |     |

它规定，如果请求采用的是 extended length 的 APDU，那么响应也要是 extended length 的 APDU；如果请求是 short APDU，那么响应也要支持 short APDU 的 chaining。

### 兼容性

可以看到，CTAP2 设计的基本都考虑了兼容 U2F，允许用 U2F 的 API 操作 U2F 和 CTAP2 的设备；也允许用 CTAP2 的 API 操作 U2F（只支持部分命令）和 CTAP2 的设备。

## 总结

可以看到，这里有一堆套娃的过程：

U2F：

|     | USB HID        | Bluetooth | NFC                   |
| --- | -------------- | --------- | --------------------- |
| 4   | APDU           | APDU      | APDU                  |
| 3   | U2F message    |           |                       |
| 2   | USB HID packet |           |                       |
| 1   | USB            | Bluetooth | ISO 14443-4/ISO 18092 |

FIDO2:

|     | USB HID                  | NFC                      |
| --- | ------------------------ | ------------------------ |
| 4   | CTAP command + CBOR data | CTAP command + CBOR data |
| 3   | CTAP message             | APDU                     |
| 2   | USB HID packet           |                          |
| 1   | USB                      | ISO 14443-4/ISO 18092    |
