YubiKeyの技術検証を任されたので, 検証用のデモサーバを実装した話

# はじめに
この記事は, [マイナビAdvent Calendar 2020](https://qiita.com/advent-calendar/2020/mynavi)の12/05分の記事です。
昨日は@ekusiadadusさんの[強い将棋AIを作る](https://ekusiadadus.com/%e5%b0%86%e6%a3%8bai%e3%82%92%e4%bd%9c%e3%82%8b/)です。  

今回, 上司から「アドカレ書かない？」と誘われたのでこの記事を書く運びとなりました。
アルバイト業務はいくつか担当したのですが, その中でYubiKeyの技術検証があまり知られていない内容だと思ったので, これを記事にします。  

# YubiKeyとは？
一言でいえば, 2FAに用いられるハードウェアセキュリティキーです。  

YubiKeyの国内ディストリビューターである日本情報システム株式会社によると, 下記の通り説明されています。  
> YubiKeyはアメリカ・スウェーデンに本社を持つYubico社が製造、販売するハードウェアセキュリティキーです。USB、NFC、Lightningの各インターフェースで接続し、人の指でタッチし静電気によって認証を行います。

渡されたのは, [Security Key by Yubico](https://www.yubico.com/jp/product/security-key-by-yubico/)で, Amazonで1つ当たり4000円程度で購入できる廉価版です。

私に任されたのは, **このYubiKey1つ1つが誰のものかを特定できるようにすること**でした。

# YubiKeyを一意に特定するための調査とデモサーバの実装
## シリアルナンバー編
調査すると, 下記のような文が見つかりました。  
> The serial number, which is printed both literally and in 2D barcode form on every YubiKey is the definitive way to uniquely distinguish one YubiKey from another. Serial numbers are unique across all models of YubiKeys, with the exception of Security Keys, which do not have serial numbers. A YubiKey serial number cannot be changed or overwritten. 
ref: [Identifying Your YubiKey](https://support.yubico.com/hc/en-us/articles/360013642100-Identifying-Your-YubiKey)

ざっくりまとめると, 「全てのYubiKeyは全てのモデルにおいて一意なシリアルナンバーを持ち, このシリアルナンバーは変更や上書きできません。**ただし, シリアルナンバーを持たないSecurity Keysは除きます**。」  

シリアルナンバーは一意だったのですが, Security Key by Yubicoはシリアルナンバーを持たないようです[^1]。そのため, このYubiKeyだとシリアルナンバーを用いた所持者の特定はできませんでした。

しかし, [Yubico Demonstration Site](https://demo.yubico.com/)で確認すると, 1つのYubiKeyに対して同じ名前のKeyを1つしか登録できないことから, YubiKey所持者の特定はできると踏みました。

## 認証方法・プロトコル調査編
次はYubikeyの認証方法と2つのプロトコルを調査しました。  

このYubicoの認証は, YubiKey-ブラウザ間のCTAP(Client-to-Authenticator Protocol)およびブラウザ-RPサーバ(Relying Party)間のWebAuthnという2つのプロトコルから成るFIDO2が用いられています。言い換えれば, YubiKey-ブラウザ-RPサーバ間の全てをひっくるめたプロトコルがFIDO2です。  

ドキュメントは, [Web Authentication API(WebAuthn)](https://developer.mozilla.org/ja/docs/Web/API/Web_Authentication_API)と[Client to Authenticator Protocol(CTAP) - 3. Protocol Structure](https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#protocol-structure)をご参照ください。  

また, YubiKeyのu2f実装について参考にした記事も載せておきます。  
 - [YubiKeyの U2F 実装](https://qiita.com/watahani/items/b61ae2bcd52ed81297fe)
 - [いまさらながらのFIDO U2F](http://blog.toke.jp/2015/11/fido-u2f.html)

それと, 先日のアドカレでも分かりやすい記事があったので載せておきます。
 - [FIDOのPIN認証のフローを理解する](https://qiita.com/gebo/items/2c6e854fadebaaa45cc7)

調査していくうちに, サーバとクライアント間の認証では単純な公開鍵認証が用いられていることが分かったので, 各公開鍵にYubiKeyの所持者名を紐づけることで認証時に所有者を特定することが出来ると考えました。

## デモサーバ実装編
Security Key by Yubicoはシリアルナンバーこそ一意ではないものの, YubiKeyごとの秘密鍵(Device Secret)は一意です。そのため, サーバに保存されている全ての公開鍵で認証を行い, 認証できた公開鍵と紐づけられているユーザがそのYubiKeyの所有者ということになります。サーバに保存される公開鍵の数が多かった場合でも, 個々の認証にかかる時間が短いためこのような実装でも問題ないと判断しました。また, 仮にこの公開鍵が漏洩しても公開鍵が漏洩しただけではYubiKey本体側の秘密鍵のなりすましは出来ないので, リスク的にも問題ないと考えています。

デモ用の実装を一応おいておきます。デモ実装なので酷いコードですが, お許しください。  
[task4233/YubiKey-Discriminator](https://github.com/task4233/YubiKey-Discriminator)

# おわりに
今回の検証は調査が8割, 実装が2割という内訳で殆どが調査だったと考えています。そもそも検証を始める前はYubiKeyという名前を聞いたことがある程度でしたし, こういった検証系は, 何が正しいかが分からないため非常に苦労しました。しかし, 途中で挫折しかけながらも相談しながら進めることができたのでなんとか形になったと考えています。  

こういった検証系は, 知識を広げられる機会でもあるので面白いですね。やっているときは全然進まない時もありましたが, 何かを境に急に出来るようになる瞬間が特に良いです。

さて, 明日は@bake0937さんの[「CodeBuild に用意されている Docker イメージ」で AWS CodeBuild 上での docker-compose のインストール作業を省略する](https://bake0937.hatenablog.com/entry/2020/12/06/212648)です！お楽しみに。

# References
## YubiKeyに関するライブラリ・実装まとめ
 - [Webauthn.io](http://webauthn.io/) ([respository](https://github.com/duo-labs/webauthn.io))
   - WebAuthnのライブラリ、デモが含まれたWebページ
   - ページ中盤の「Using WebAuthn」にライブラリが一覧あり
 - [duo-lab/webauthn](https://github.com/duo-labs/webauthn) [hbolimovsky/webauthn-example](https://github.com/hbolimovsky/webauthn-example)
     - Golangで実装されたWebAuthnのライブラリとデモ
     - httpルータとしてgorilla/muxを使用
 - [koesie10/webauthn](https://github.com/koesie10/webauthn) [koesie10/webauthn-demo](https://github.com/koesie10/webauthn-demo)
     - Golangで実装されたWebAuthnのライブラリとデモです
     - httpルータとしてlabstack/echoを使用

## プロトコルのドキュメント
 - [FIDO Specification(FIDO2/UAF/FIDO-UAF/U2F)](https://fidoalliance.org/specifications/download/)
   - Client(Browser)とYubiKey(Authenticator)間のプロトコル
   - 重要度が高いと感じたのは下記の4つ(個人的な優先度順)
       - FIDO U2F Archtectural Overview(U2Fの概要)
       - FIDO U2F JavaScript API(JSで動くAPI)
       - FIDO U2F Raw Message Formats(データの返却形式等)
       - CTAP 2.0 Proposed Standard(CTAPのプロトコル詳細)
 - [Web Authentication: An API for accessing Public Key Credentials Level 1](https://www.w3.org/TR/webauthn/)
   - RP(Relying Party)とClient(Browser)間のプロトコル
 - [Web Authentication API - MDN Web Docs](https://developer.mozilla.org/ja/docs/Web/API/Web_Authentication_API)
   - ブラウザに入っている認証API

## 理解のためのページ
 - [いまさらながらのFIDO U2F](http://blog.toke.jp/2015/11/fido-u2f.html)
   - FIDO U2Fの流れについてまとめられているページ
 - [WebAuthnことはじめ](https://engineering.mercari.com/blog/entry/2019-06-04-120000/)
   - 具体的なデータが提示されており理解の助けになるページ
 - [How U2F Security Work](https://fastmail.blog/2016/07/23/how-u2f-security-keys-work/)
   - YubiKey鍵生成の分かりやすい図があるページ

[^1]: 一意でない, シリアルナンバーもどきはありました