LoginSignup
4
2

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-09-27

はじめに

以前 Windows用でCTAPcsというCTAPライブラリを作りましたが、それの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の仕様で行っています。
    • HID(USB)に対応しています。NFC,BLEは未対応です。

ソース

とりあえず使ってみる

Windowsでの注意事項

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

プロジェクト作成

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

$ cargo new gebo-ctap --bin

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

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

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

Cargo.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"

Examplesget_info()の中身をそのままコピペします。

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した方が早いですね。

$ cargo build

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

実行します。

$ cargo run

get_info()
error: "Failed to open device"

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

$ 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です。
ここで
- options-clientPin / true
となっていることを確認しましょう。falseの場合はブラウザやFIDOキーの設定ツールでPINを設定しときましょう。

とりあえず使ってみる2

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

Examplesmake_credential() get_assertion()をそのままコピペ


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に書き換えましょう。
この行ね。

let pin = "xxxx";

すかさず実行します!

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

$ 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キーの情報を取得する

pub fn get_info(
    hid_params: &[HidParam]
) -> Result<Vec<(String, String)>, &'static str>
  • hid_paramはctap_hid_fido2::HidParam::get_default_params()の戻り値のHidParam配列を指定してもいいんですが、FIDOキーの種類によっては取れない場合があります。ので、その場合はFIDOキーのVendorID,ProductIDを調べてHidParam配列に追加してください。
  • APIが成功すると(String,String)のタプル配列が帰ってきて、エラーの場合はエラーメッセージが返ってきます。

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

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

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

make_credential() - 登録

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() - 認証

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リトライ回数を調べる

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

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

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

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

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

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

おつかれさまでした

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

4
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2