はじめに
以前 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は未対応です。
ソース
-
GitHubに公開しています。
- 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
コマンドでプロジェクトを作成します。
$ 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"
Examplesのget_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
登録と認証やってみます。
Examplesのmake_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のことをずっと クリエイターズ・アイオー って言ってた。