RustでCTAPするクレートctap-hid-fido2を作った

# はじめに

以前 [Windows用で**CTAPcs**というCTAPライブラリを作りました](https://qiita.com/gebo/items/13fa8092f83ba92ba193)が、それのRust版を作りました。


この記事では**ctap-hid-fido2**の基本的な使い方を紹介したいと思います。



**crates.io ctap-hid-fido2** : 
https://crates.io/crates/ctap-hid-fido2



# 環境

macでもWindowsでも動きます。開発はmacメインでやっていますが、VSCODEなんでWindowsでもたいして変わりません。



- 動作確認済みFIDOキー
  - Yubikey Blue (Security Key Series)
  - Yubikey Black (YubiKey 5)
  - FEITIAN BioPass K27 USB Security Key
  - FEITIAN AllinPass FIDO2 K33
  - SoloKey
- Rust Version
  - cargo 1.45.1 (f242df6ed 2020-07-22)
  - rustc 1.45.2 (d3fb005a3 2020-07-31)
  - rustup 1.22.1 (b01adbbc3 2020-07-08)
- Mac環境
  - macOS Catalina 10.15.6
  - Visual Studio Code
- Windows環境
  - Windows10 1909
  - Visual Studio Code



# どんなものか

- YubikeyなどのFIDOキーをRustで使うためのクレートです。
- **FIDOキーはFIDO2対応しているものだけ使えます。U2Fは使えません。**
- JavaScriptのWebAuthnみたいなもので、似たようなことをRustアプリからできます。
- FIDOキーとの通信は[CTAPの仕様](https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html)で行っています。
  - HID(USB)に対応しています。NFC,BLEは未対応です。



# ソース

- [GitHub](https://github.com/gebogebogebo/ctap-hid-fido2)に公開しています。
  - **gebogebogebo/ctap-hid-fido2**
  - https://github.com/gebogebogebo/ctap-hid-fido2



# とりあえず使ってみる

### Windowsでの注意事項
**管理者として実行する必要があります。**
USBタイプのFIDOキーはHID（ヒューマンインタフェースデバイス）でWindowsに認識されるので、専用のドライバが不要ですべてのアプリから取り扱い可能、という点が良いところだったのですが、Windowsさんはいつの間にか（たぶん1903から）制限をかけるようになり、アクセスに**Administratorの権限が必要**になりました。これは手軽に使うことができなくなったという点で非常に残念ですが、Windows Helloなどセキュリティに関わる部分のポリシーに基づく対応なのかもしれません。この権限に関わる仕様変更について、Microsoftのドキュメント等の公式情報は見つけられていないのですが、そういう制限があるのでご注意ください。この時点で**Windows一般のアプリではFIDOキーはつかえねーもの化しちゃっている**のではと個人的には思います。



## プロジェクト作成

ターミナルから`cargo`コマンドでプロジェクトを作成します。<br>

```bash
$ cargo new gebo-ctap --bin
```

上記例の場合、カレントディレクトリに`gebo-ctap`ディレクトリが出来てその中にプロジェクトファイルができます。

```
gebo-ctap
├── Cargo.toml
└── src
    └── main.rs
```



VSCODEで`gebo-ctap`ディレクトリを開きます。



`Cargo.toml`にクレートの依存関係を書きます。

```toml
[package]
name = "gebo-ctap"
version = "0.1.0"
authors = ["gebo <35388172+gebogebogebo@users.noreply.github.com>"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
# 追加！バージョンは2020/9/27時点のものですが、もっと新しいものがあればそっちの方ががよいかも
ctap-hid-fido2 = "0.3.0"
```



**Examples**の`get_info()`の中身をそのままコピペします。

```Rust
use ctap_hid_fido2;

fn main() {
    println!("get_info()");
    let infos = match ctap_hid_fido2::get_info(&ctap_hid_fido2::HidParam::get_default_params()) {
        Ok(result) => result,
        Err(error) => {
            println!("error: {:?}", error);
            return;
        },
    };
    for (key, value) in infos {
        println!("- {} / {}", key, value);
    }
}
```



ビルドします。VSCODEのUIからビルドしてもいいですが、ターミナルから`cargo build`した方が早いですね。

```sh
$ cargo build

Finished dev [unoptimized + debuginfo] target(s) in 0.93s
```



実行します。

```sh
$ cargo run

get_info()
error: "Failed to open device"
```



おっと、FIDOキーが無いと怒られました。<br>FIDOキーをPCに挿して再度実行します。

```sh
$ cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.06s
     Running `target/debug/gebo-ctap`
get_info()
- versions / U2F_V2
- versions / FIDO_2_0
- extensions / hmac-secret
- aaguid / F8A011F38C0A4D15800617111F9EDC7D
- options-rk / true
- options-up / true
- options-plat / false
- options-clientPin / true
- max_msg_size / 1200
- pin_protocols / 1
```

FIDOキーの情報が出てくればOKです。<br>ここで<br>- options-clientPin / **true**<br>となっていることを確認しましょう。falseの場合はブラウザやFIDOキーの設定ツールでPINを設定しときましょう。



# とりあえず使ってみる２

登録と認証やってみます。

**Examples**の`make_credential() get_assertion()`をそのままコピペ

```rust

use ctap_hid_fido2;
use ctap_hid_fido2::util;

fn main() {
    println!("----- test-with-pin-non-rk start -----");

    // parameter
    let rpid = "test.com";
    let challenge = b"this is challenge".to_vec();
    let pin = "1234";

    println!("make_credential()");
    let cre_id = match ctap_hid_fido2::make_credential(
        &ctap_hid_fido2::HidParam::get_default_params(),
        rpid,
        &challenge,
        pin,
    ) {
        Ok(result) => result.credential_id,
        Err(err) => {
            println!("- Register Error {:?}", err);
            return;
        }
    };

    println!("- Register Success!!");
    println!(
        "- credential_id({:02}) = {:?}",
        cre_id.len(),
        util::to_hex_str(&cre_id)
    );

    println!("get_assertion_with_pin()");
    let att = match ctap_hid_fido2::get_assertion(
        &ctap_hid_fido2::HidParam::get_default_params(),
        rpid,
        &challenge,
        &cre_id,
        pin,
    ) {
        Ok(result) => result,
        Err(err) => {
            println!("- Authenticate Error {:?}", err);
            return;
        }
    };
    println!("- Authenticate Success!!");
    println!("- sign_count = {:?}", att.sign_count);
    println!(
        "- signature({:02}) = {:?}",
        att.signature.len(),
        util::to_hex_str(&att.signature)
    );

    println!("----- test-with-pin-non-rk end -----");
}
```



**pinだけ自分のFIDOキーのPINに書き換えましょう。**<br>この行ね。

```
let pin = "xxxx";
```



すかさず実行します！

ターミナルに**- touch fido key**と出たらFIDOキーがピカピカしているはずなのでをタッチしてください。<br>登録に1回タッチ、認証に1回タッチして正常終了すればOKです。

```sh
$ cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.05s
     Running `target/debug/gebo-ctap`
----- test-with-pin-non-rk start -----
make_credential()
- touch fido key
- Register Success!!
- credential_id(64) = "FFD89C9318BF48FD9288A7921D6F6DB51B04A55E20059C26584F2218A24ADDA3B0B6CE49C1F8BE9797A061A260601E25DDC34B14EA58721D98F154974A056DC3"
get_assertion_with_pin()
- touch fido key
- Authenticate Success!!
- sign_count = 275
- signature(71) = "304502203BFD72B5D76BF2A27CF4F872ABBEF9FBE71CA9BF47B4F751F0C5138A4C75F3E3022100C480B0E3DA7FCADED72982F5E1F984304DB3CDF2D08AA71EB4ED91E7312C8EFA"
----- test-with-pin-non-rk end -----
```



Visual Studioのアプリと比べるとRustはめっちゃ簡単です！



# API解説

## get_info() - FIDOキーの情報を取得する

```rust
pub fn get_info(
    hid_params: &[HidParam]
) -> Result<Vec<(String, String)>, &'static str>
```

- hid_paramは`ctap_hid_fido2::HidParam::get_default_params()`の戻り値の[HidParam](https://docs.rs/ctap-hid-fido2/0.3.0/ctap_hid_fido2/struct.HidParam.html)配列を指定してもいいんですが、FIDOキーの種類によっては取れない場合があります。ので、その場合はFIDOキーのVendorID,ProductIDを調べてHidParam配列に追加してください。
- APIが成功すると(String,String)のタプル配列が帰ってきて、エラーの場合はエラーメッセージが返ってきます。



## get_fidokey_devices() - FIDOキーのHID情報を取得する

```Rust
pub fn get_fidokey_devices() -> Vec<(String, HidParam)>
```

FIDOキーのVendorID,ProductIDが分からん！というときはこれで調べましょう。



## make_credential() - 登録

```rust
pub fn make_credential(
    hid_params: &[HidParam], 
    rpid: &str, 
    challenge: &[u8], 
    pin: &str
) -> Result<Attestation, String>
```

FIDOキーにユーザー情報を登録するようなイメージです。戻り値で登録結果(Attestation)が返ってきます。

- Attestationに含まれる署名を検証する必要があるのですが、ここではやってません。**ちゃんとしたアプリを作る場合はちゃんと署名を検証しましょう。**
- Attestationの`credential_id`が生成したクレデンシャルの情報で、認証(get_assertion)で使います。
- このAPIをコールするとFIDOキーが光りますんでタッチしてあげましょう。タッチしないとAPIから返ってきません。
  - **FIDO的にはこの行為(User Presence)がユーザーの存在確認と同意を意味します。**



## get_assertion() - 認証

```rust
pub fn get_assertion(
    hid_params: &[HidParam], 
    rpid: &str, 
    challenge: &[u8], 
    credential_id: &[u8], 
    pin: &str
) -> Result<Assertion, String>
```

make_credential()で生成したcredential_idの正当性を検証します、つまり認証ってことですね。

- 別のFIDOキーで生成したcredential_idとかrpidが違っているとエラーになります。
- 戻り値で認証結果(Assertion)が返ってきます。
  - Assertionに含まれる署名を検証する必要があるのですが、ここではやってません。**ちゃんとしたアプリを作る場合はちゃんと署名を検証しましょう。**



## get_pin_retries() - PINリトライ回数を調べる

```rust
pub fn get_pin_retries(hid_params: &[HidParam]) -> Result<i32, &'static str>
```

PINを何度も間違うとロックがかかります。ロックがかかるという事はすなわち**詰み**です。FIDOキーをリセットして出荷状態に戻さないと使えません。
このAPIでPINリトライカウンタを調べることができます。

- リトライカウンタはPINを間違えると減り、PINが通ると初期値に戻ります。
- ゼロになるとロック状態になります。



## wink() - FIDOキーのLEDを光らせる

```rust
pub fn wink(hid_params: &[HidParam]) -> Result<(), &'static str>
```

どうでもいい機能として、FIDOキーのLEDを光らせることができます。**ただ光らせるだけです。**



# おつかれさまでした

crates.ioのことをずっと クリエイターズ・アイオー って言ってた。