Closed30

CTAP の仕様を眺める

inabajunmrinabajunmr

1. Introduction

ユーザーが PC とかで RP(roaming authenticator とのインタラクションを促す)とやり取りするようなシナリオを想定

1.1. Relationship to Other Specifications

CTAP2 authenticators SHOULD also implement CTAP1/U2F.

1.2. Data Elements Referenced by Other Specifications

  • aaguid
    • 値が同じなら認証器のモデルが一緒
  • RP ID
    • 値が同じならRP が一緒
  • credentialID
    • 値が同じならクレデンシャルが一緒
  • up / uv
    • 認証器によって up / uv が行われたかどうか
inabajunmrinabajunmr

3. Protocol Structure

  • Authenticator API
    • 認証器を操作するための API たち
  • Message Encoding
    • 認証器とやり取りするためのエンコード
  • Transport-specific Binding
    • トランスポート(USB, NFC, Bluetooth)固有の話
inabajunmrinabajunmr

4. Protocol Overview

  1. 登録、認証の文脈で、Web アプリなら navigator.credentials.get / create を呼ぶ
    a. ネイティブアプリなら同等の API を呼ぶ
    b. クレデンシャル管理とかの場合はプラットフォーム自身で処理を開始する
  2. RP に渡されたやつとか、他の情報を使って使える認証器とのコネクションを確立する
  3. authenticatorGetInfo でプラットフォームが認証器の情報を取得する
    a. この情報で認証器何できんの?がわかる
  4. どういう操作するの?とかこれまでに取得した情報に従って後続の Authenticator API 呼び出しを行う
inabajunmrinabajunmr

5. Terminology

  • Built-in User Verification method
    • 認証器組み込みの uv
      • PIN のあれはこれじゃない
  • Evidence of user interaction
    • Platform-mediated user interactions such as clientPin may provide user verification but are not considered to assert user presence

    • NFC
      • NFC 認証器をリーダーに置く、が up の根拠になる場合がある、みたいな点で差があるっぽい
    • All other transports
  • pre-flight
    • プラットフォームが認証器に credentialID にマッチするクレデンシャルが登録されてるかチェックするために up=false で credential の作成とか assertion の取得の API をコールするっぽい
      • credential 作られたら困るような
      • pinUvAuthToken を呼ぶのはよくわからん
        • pin が要求されるので credential を作成せずに存在チェックだけできる?
  • Protected by some form of User Verification
    • uv が組み込みと client の pin それぞれ何をサポートしてるかは authenticatorGetInfo でわかる
  • Some form of User Verification
  • User action timeout
    • 認証器がユーザーのインタラクションを待つときのタイムアウト
      • 30 秒位がよいらしい
inabajunmrinabajunmr

6. Authenticator API

  • 各 API 呼び出しは独立していて、非同期で行われる
  • めっちゃ呼ばれてリソースが枯渇した場合認証器は busy status を返す
  • コマンドの結果、認証器が状態を持つものがある
    • ある状態を期待するコマンドを stateful commands、その状態を作るコマンドを state initializing command という
      • authenticatorGetNextAssertion, with state initialized by authenticatorGetAssertion.

6.1. authenticatorMakeCredential (0x01)

クライアントの作成をリクエストする。

  • clientDataHash
  • rp
    • PublicKeyCredentialRpEntity
  • user
    • PublicKeyCredentialUserEntity
    • contains an RP-specific user account identifier など
    • 非推奨だが empty account identifier is valid
  • pubKeyCredParams (0x04)
    • Array of PublicKeyCredentialParameters
    • サポートしてるアルゴリズムのリスト
  • excludeList (0x05)
    • Array of PublicKeyCredentialDescriptor
    • ここに指定されているクレデンシャルを認証器がすでに持ってたら新規作成を弾く
    • 同じユーザーが同じ認証器に複数のクレデンシャルを作成する、を防げる
  • extensions (0x06)
    • CBOR map of extension identifier → authenticator extension input values
  • options (0x07)
    • rk / up / uv の利用有無
  • pinUvAuthParam (0x08)
    • pin で uv する場合、事前に uv した結果をここに渡すっぽい
      • よくわからん
  • pinUvAuthProtocol (0x09)
    • PIN/UV protocol version chosen by the platform
  • enterpriseAttestation (0x0A)
    • Unsigned Integer
  • attestationFormatsPreference (0x0B)
    • クライアント / RP が要求するアステーションの種類
    • none の場合無しが許容される

下の図に webauthn api との対応がのってて便利

wireshark で試してたときは options とかなんもなかったけどこれは up だけ、rk なし、uv なし、で結果デフォルトだからだったっぽい
https://zenn.dev/inabajunmr/scraps/62cb613c9e8c02#comment-0c95eb0e1e74f1

6.1.1. Platform Actions for authenticatorMakeCredential (non-normative)

  1. プラットフォームがリクエストパラメーターを作成する
    1. 認証器が UV で保護されてたり、RP が UV を要求した場合
      1. プラットフォームが pinUvAuthParam を作成済みなら authenticatorMakeCredential / authenticatorGetAssertion の呼び出しでパラメーターと合わせて渡す。authenticatorGetAssertion は例えば excludeList が渡されたときに、そのクレデンシャルが作成済みかチェックするのに使う
      2. そうでなければ、authenticatorGetInfo 内の option IDs を見て次どうするか決める
        1. uv option ID が true の場合
          1. pinUvAuthToken option ID が true なら、getPinUvAuthTokenUsingUvWithPermissions で pinUvAuthToken を取得することを selected operation とする(??)
          2. そうでなければ、authenticatorMakeCredential 呼び出しの uv option を true にする
        2. uv option ID が true でない場合
          1. pinUvAuthToken option ID が true なら、
            1. clientPin option ID が true であることとし、getPinUvAuthTokenUsingUvWithPermissions で pinUvAuthToken を取得することを selected operation とする
          2. pinUvAuthToken option ID が true でないなら
            1. clientPin option ID が true であることとし、getPinToken で pinUvAuthToken を取得することを selected operation とする
      3. pinUvAuthToken を取得するため、プラットフォームは
        2. shared secret を取得し
        3. pinUvAuthProtocol に shared secret 取得時に取得した値を入れる
      4. selected operation によってプラットフォームが pinUvAuthToken(mc と、場合によっては ga permission を持っている)を取得する
      5. pinUvAuthToken の取得に成功したら
        6. プラットフォームは calling authenticate(pinUvAuthToken, clientDataHash) の呼び出しによって pinUvAuthParam を取得し、1.1.1 へ
      6. さもなくば
        8. エラーが特定のやつだった場合、selected operation を getPinUvAuthTokenUsingUvWithPermissions にしてプラットフォームは PIN による認証にフォールバックして 1.1.2.2.1 へ
    2. NOT 認証器が UV で保護されてたり、RP が UV を要求した場合、uv なしで authenticatorGetAssertion を呼ぶ
inabajunmrinabajunmr

6.1.2. authenticatorMakeCredential Algorithm

条件によって uv したりだとか pinUvAuthToken の verify したり credentialID に対して認証器の作成状況をチェックしたりその他のパラメーター(rk とか)を見ながら適切なクレデンシャルを作って authenticatorMakeCredential response をかえす

structure は最後にかいてある

inabajunmrinabajunmr

6.2. authenticatorGetAssertion (0x02)

6.2.1. Platform Actions for authenticatorGetAssertion (non-normative)

inabajunmrinabajunmr

6.5. authenticatorClientPIN (0x06)

平文の PIN が認証器に送られないようにするためのコマンド。PIN/UV auth protocol(pinUvAuthProtocol)は、PIN が認証器に送られる間暗号化され、それから後続のコマンドを認証するために提供される pinUvAuthToken と交換されることを担保する。
さらに built-in user verification methods をサポートしている認証器は user verification にあわせて pinUvAuthToken を提供できる。

pinUvAuthToken はランダムに生成される推測できないのに十分なサイズのバイト配列である。

このプロトコルは 2 つのプロトコルが定義されている。

  • § 6.5.6 PIN/UV Auth Protocol One
  • § 6.5.7 PIN/UV Auth Protocol Two

6.5.1. PIN Composition Requirements

PIN の要件

6.5.2. PIN/UV Auth Protocol Global State

6.5.2.1. pinUvAuthToken State

inabajunmrinabajunmr

pinUvAuthToken
認証とか登録のタイミングで getPinUvAuthTokenUsingUvWithPermissions でとってくるやつ
getPinUvAuthTokenUsingUvWithPermissions は authenticatorClientPIN のサブコマンド
サブコマンドの説明はこのへん
https://fidoalliance.org/specs/fido-v2.2-rd-20230321/fido-client-to-authenticator-protocol-v2.2-rd-20230321.html#getPinUvAuthTokenUsingUvWithPermissions

その後プラットフォームは authenticate(pinUvAuthToken, clientDataHash) で pinUvAuthParam と引き換える

これを authenticatorGetAssertion とかの呼び出しで渡す

inabajunmrinabajunmr

これが UV しましたよ、の証になる、という理解で良い気がする

inabajunmrinabajunmr

getPinUvAuthTokenUsingPinWithPermissions だったらこのタイミングで認証器に PIN を渡して検証してもらうし、UsingUv の方だったら performBuiltInUv で認証器側の組み込み uv をすると

inabajunmrinabajunmr

で、このときに PIN を平文でやりとりしたくないので前段で鍵交換したりなんだりする

inabajunmrinabajunmr

pinUvAuthToken も暗号化するので組み込みの uv だけの場合でも鍵交換したりする必要はある、と

inabajunmrinabajunmr

11.5. Hybrid transports

Hybrid transports はクライアントプラットフォームが物理的に認証器に近いことの照明を、クライアントプラットフォームと認証器間の CTAP2 メッセージの送信から分離する。ここで定義される Hybrid Transport はカメラ付き認証器(一般的には電話)ををクライアントプラットフォームに接続することを意図している。これには tunnel service というサービスを介したネットワーク通信および近接性を表すための BLE 通信含んでいる。tunnel service は利用する認証器がドメイン名を知っている高可用性のあるネットワークである。

inabajunmrinabajunmr

11.5.1. QR-initiated Transactions

クライアントプラットフォームが hybrid authenticator と通信を望む場合、プラットフォームは公開鍵と共有秘密鍵が含まれる QR コードを表示する。公開鍵はクライアントプラットフォームを接続されている認証器に対して認証し、秘密鍵は認証器をプラットフォームに対して認証する。

QR コードのコンテンツは FIDO:/ で始まり数字でエンコードされた URI である。QR コードにおいて効率的なのでスキームは大文字である。

QR コード、使える文字列の制限によってモードが違うらしい
https://gigazine.net/news/20211013-qr-code/

コロンの前に 1 つスラッシュがあるのは、デバイスがこの QR コードのコンテンツを URI だと認識する必要があるからだが、ダブルスラッシュは authority を表すためこの URI スキームでは使わない。

エンコードされたデータは CBOR で、数値のキーをキー固有の値にマッピングする。CBOR は canoonical form でなければならない。

キーは

  • 0: 33byte, P-256, X9.62 公開鍵
  • 1: 16byte, ランダム QR シークレット
  • 2: この実装が知っているアサインされたトンネルサーバーのドメインのインデックス
    • あんまわかってないけど google のやつだったら 0、apple のやつだったら 1 みたいになってて、その中で使えるやつが全部入ってるんだと思う
  • 3: エポック秒の現在時刻
  • 4: QR コードを表示するデバイスが state-assisted transactions を利用できる場合は true(?)
  • 5: このあと getAssertion が続くか ga、makeCredential が続くか mc のヒント
    • 不明な値は ga として取り扱う
    • このフィールドはユーザーが QR コードをスキャンしてすぐ、CTAP メッセージを受け取るよりも前にガイダンスをするためにある
    • ヒントはできるだけ正確であるべきだが、プラットフォームが送信する後続の CTAP メッセージを制限するものではない

認証器は、今後キーが追加される可能性があるためこの情報をパースするために CBOR パーサーを使わなければいけない。

このエンコーディングは QR コードの表示時に効率的であるように設計されている。7 バイトのチャンクはリトルエンディアンで解釈され、基数 10 の 17 桁でエンコードされる。残りのバイトもある値のバイト数が必要とする最小の桁数を使ってエンコードされる。具体的には、残りが 1,2, 3,4,5,6 のバイト長であれば、それは 3,5,8,10,13,15 桁でそれぞれエンコードされる。

一度クライアントプラットフォームに QR コードが表示されたら、認証器からの接続の試みを待つ。この通信は攻撃を防ぐために近接性の証明を要求する。したがって、接続試行の通知が BLE advertisement の形式で来る。(近接性の証明がない場合、 Web サイトで QR コードを表示してユーザーにそれをユーザーの認証器でスキャンさせようとできる。認証器がクライアントプラットフォームが BLE advert の受信を証明することを要求することで、このような攻撃者は被害者の近くで Bluetooth 無線をコントロールしなければならなくなる)

UUID 0000fff9-0000-1000-8000-00805f9b34fb を advert に含めなければならず、クライアントプラットフォームは候補デバイスがこの UUID を advertising していることを要求しなkればならない。この UUID は表示された QR コードがマッチすることを確認するため試しに復号される 20 byte サービスデータペイロードを持っている必要がある。

試しに BLE adverts を復号するのに必要な鍵を導出するため、以下の鍵導出が利用される。特定の目的で鍵が必要とされるときはいつも、ドメイン分離を保証するために親の鍵から導出される。この導出は RFC5869(HKDF) を SHA-256 とともに利用する。ここでは入力キーマテリアルが親鍵、ソルトは optional の入力、info value はリトルエンディアンで 32 bit の purpose identifier である。(?)

adverts を復号するのに使われる鍵は QR シークレットから keyPurposeEIDKey とともに導出される 64 byte の値である。term "EID" は歴史的なあれで何かを表しているわけではない。

adverts を復号するとき、EID 鍵は 256 bit のキーのペアであり、この 64 バイトのうち最初の 32 バイトは AES 鍵で次の 32 バイトは HMAC-SHA256 鍵である。候補の BLE advert は最後の 4 バイトが 他の 16 バイトの正しい HMAC tag である場合有効であり、最初の 16 バイトは AES ブロックとして扱われ、AES 鍵で復号される。

これは wide-block modes の代替であるが、 wide-block modes は非標準である。BLE advert にはもう余地がないので nonce を含めない。2 つの認証器が同じ QR コードをスキャンし、同じ鍵を基にブロードキャストすることができる可能性があるため、 keystream と 平文を XOR するモードを避けることで潜在的な複雑さを防ぐことができる。(2 Authenticator が QR コード経由で同じ鍵を受け取ってからそれをもとに作った keystream と平文を XOR して暗号を作ると、Authenticator の通信に関連が生まれてしまって暗号的によくない、みたいな話か?)

認証と復号が成功したら BLE advert は 16 bytes の平文を生む。この 16 bytes は以下を含む。

  • flags byte
    • 今は zero
  • 80 bits の接続 nonce
  • 24 bit のルーティング ID
  • 16 bit の tunnel service identifier

nonce は BLE advert の所有したがって認証器への近接性を示す値である。

tunnel service はメッセージを認証器へ、もしくは認証器からリレーする。クライアントプラットフォームが認証器とリンクされているとき、認証器はリクエストに対して応答できるため、これは認証器のプロパティである。(tunnel service identifier を認証器がプラットフォームに送って、その tunnel service を通じてやりとりをする、みたいな話だと思うんだけど、identifier があんまわかってない)

認証器と tunnel service 間のプロトコルと、後にサービスが認証器に接続する方法の詳細は認証器の実証の private な詳細である。

エンコードされた tunnel service identifier は uint16 である。0 から 255 の値がアサインされ、256 以上の値はハッシュによってドメイン名に変換される。CNAME レコードの利用を許可するため "cabel" ラベルがハッシュされたドメイン名の先頭に追加される。

ドメインはシーケンシャルにアサインされ、アサインされたドメインのインデックスは QR コードに含まれる。これによって認証器はピアがアサインされたドメインを認識しているかどうかを知ることができ、潜在的に互換性のために hashed domain にフォールバックする事ができる。(ドメインに数字でインデックスが付いてるから数字だけで認証器は「俺が使いたいこの tunnel service をプラットフォームが使えるか判断する」ができて、更にそこで判定できない場合直接ドメインを送るような形にフォールバックできる、ということだと思う)


一旦整理

  • QR コードには「platform はこの tunnel service を知ってるぜ」が入っている
  • Authenticator が「platform が知っている tunnel service を利用する」場合、その tunnel service はインデックスだけで BLE 経由で通知する
    • platform が知らない tunnel service を使いたい場合、BLE でドメイン自体を送信する

ドメインは任意の値を送れるわけではないんだけど、コード例だと cable.6wxjc64fxol5c.com をサーブして id として BLE から 500 を送るとプラットフォームは cable.6wxjc64fxol5c.com に接続してくれる気がする。
ハッシュなので任意のドメインをとって指定させる、みたいなことはできなそうだけど、自分でサーブする、はできるのか?

var assignedTunnelServerDomains = []string{"cable.ua5v.com", "cable.auth.com"}

func decodeTunnelServerDomain(encoded uint16) (string, bool) {
	if encoded < 256 {
		if int(encoded) >= len(assignedTunnelServerDomains) {
			return "", false
		}
		return assignedTunnelServerDomains[encoded], true
	}

	shaInput := []byte{
		0x63, 0x61, 0x42, 0x4c, 0x45, 0x76, 0x32, 0x20,
		0x74, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x20, 0x73,
		0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x64, 0x6f,
		0x6d, 0x61, 0x69, 0x6e,
	}
	shaInput = append(shaInput, byte(encoded), byte(encoded>>8), 0)
	digest := sha256.Sum256(shaInput)

	v := binary.LittleEndian.Uint64(digest[:8])
	tldIndex := uint(v & 3)
	v >>= 2

	ret := "cable."
	const base32Chars = "abcdefghijklmnopqrstuvwxyz234567"
	for v != 0 {
		ret += string(base32Chars[v&31])
		v >>= 5
	}

	tlds := []string{".com", ".org", ".net", ".info"}
	ret += tlds[tldIndex&3]

	return ret, true
}

inabajunmrinabajunmr

routing ID は tunnel service に渡される opaque な値で、tunnel service の操作を補助する。

この時点でクライアントプラットフォームは認証器へのトンネルを確立するのに必要なすべてを保持している。最初のステップでやることは、tunnel ID を導出することである。tunnel ID は tunnel service が認識している、同時にすすめている他の交換から分離して交換を識別する。これは上記で説明している QR シークレットから導出される。 BLE advert からの nonce には依存していない。なぜならそれは tunnel service が tunnel ID から nonce をブルートフォースを試みることができることを意味するからである。(?)tunnel service は認証器に信頼されるが、必要以上に信用する必要はない。(この辺全然わからない)

tunnel ID を取得したら WebSockets 経由で tunnel service にコンタクトされる。与えられた tunnel ID に接続をリクエストするため、WebSockets URI のパスは /cable/connect/{routing id}/{tunnel id} となる。WebSocket 接続の subprotocol identifier は fido.cable でなければならない。

tunnel の確立とともに、メッセージは binary WebSocket frames で交換され、他のフレームタイプは許可されない。認証器とクライアントプラットフォームは前方秘匿性のある認証された接続を確立するため、最初にまず暗号学的なハンドシェイクを行う。このハンドシェイクは P-256, SHA-256, AES-256-GCM を利用した Noise KNpsk0 である。

The client platform speaks first to prove possession of the BLE advert...

inabajunmrinabajunmr

よく迷子になるのでここまでの流れを書いておく

  1. client platform が画面に QR コードを表示
  2. Authenticator が読み取る
  3. Authenticator が BLE advert して client platform に接続
  4. BLE advert 経由で利用する tunnel service を client platform に通知
  5. client platform / authenticator が QR コードに含まれる情報をもとに tunnel ID を決めて、tunnel service に接続
inabajunmrinabajunmr

クライアントプラットフォームは最初に BLE advert の所有を証明するために話す。認証器はこのためにクライアントプラットフォームのハンドシェイクメッセージを受け取ることだけが必要であり、ハンドシェイクを完了するために reply を送信する。KNpsk0 パターンはクライアントプラットフォームが事前に認証器と公開鍵を共有し、両者は共通鍵を持つことを要求する。事前に交換される公開鍵は QR コード経由で認証器に渡され、pre-shared symmetric key は QR シークレットと復号された BLE advert によって導出される。(完全な BLE advert が、advert format に今後つい明かされる機能が自動で認証されるために PSK 導出に含まれる)(?)

inabajunmrinabajunmr

上記の通りハンドシェイク自体は Noise NKpsk0 です。後に必要となるため以下の関数は NKpsk0 と KN psk0 を実装しています。Noise operations は Noise specification で指定されています。p256X962Length は uncompressed, X9.62, P-256 point の長さを byte であらわしたものです。(この手のハンドシェイクの手順をいろいろまとめたやつが Noise らしい)

一度ハンドシェイクが完了したら、Noise's split operation から得られる結果としての traffic-keys が クライアントプラットフォーム to 認証器 と 認証器 to クライアントプフラットフォームのフローにそれぞれアサインされます。tunnel のさらなるメッセージはパディングされ、AES-256-GCM で暗号化されます。パディングは平文の最後のバイトにパディングされる byte の数を加えることで行われます。パディングの bytes は任意の値ですが、zero が推奨されます。実装では 256 bytes までの粒度でパディングを行なえますが、32 bytes が推奨されます。nonce は方向ごとのカウンターで、Big Endian で 12 bytes にエンコードされます。各メッセージの additional data は空です。

実装では nonce のオーバーフローを防ぐため 24 bit を超える nonce の接続を終了してもよい。

復号は暗号化の逆からなります。

認証器からの最初のメッセージは "post handshake" メッセージです。このメッセージは往復を減らすために認証器の getInfo メッセージを含みます。このメッセージは CTAP2 canonical form の CBOR map で構成されている必要がある。

CBOR map は以下を含みます。

  • Key 0 : パディングのための zero bytes を含むバイト列
  • Key 1 : getInfo レスポンス

これで tunnel が完全に設定できたので、パーティはメッセージを交換できる。各メッセージはメッセージの type を表す byte で始まる。空のメッセージはエラーとなる。以下の type が定義されている。

  • 0: a shutdown message.
  • 1: a CTAP message.
  • 2: an update message.

shutdown message はクライアントから認証器に送信される。メッセージは type byte だけを含まなければならない。これはクライアントがこれ以上 CTAP コマンドを認証器に送らないことを意味する。認証器はこのメッセージの受信に対してコネクションを閉じる。もし state-assisted トランザクションをサポートする場合、クライアントは shutdown message を送ったあと少なくとも 2 分間の間認証器からのメッセージを受け入れるべきである。

CTAP メッセージは処理のための CTAP2 ペイロードを含む。例えば、クライアントから認証器にメッセージが送られたとき、後続する type byte は CTAP2 command となる。(8 章の Command の意味があんまりわかってないけど、Authenticator API のコールをできる、くらいの意味でいいのか?)

update メッセージはいつでも、いずれからも送られる。この type byte の後続の byte は CBOR map でエンコードされている必要がある。map の不明なキーは無視される。キーは向きごとに定義されていて、現時点でキーは認証器からクライアントの向きでのみ定義されている。

  • Key 0 : パディングのための zero bytes を含むバイト列
  • Key 1 : linking information を含む map

linking map は以下を含む。

  • Key 1: contact ID
    • 認証器を識別するために tunnel service に渡される opaque な値
    • Android の場合 FCM registration token
      • (プッシュ通知とかするときに使うやつと同じ?アプリごとに決まるみたいな話があって同じものなのかよくわからない)
  • Key2: link ID
    • 認証器へのリンクを識別する opaque な値
    • これはクライアントプラットフォームのために利用する鍵のセットを知るため、接続時に認証器に送り返されなければならない(?)
  • Key3: link secret
    • 共有秘密鍵
  • Key4: 認証機の公開鍵
    • X9.62 で非圧縮
    • 認証器を識別する
      • もし同じ認証器が何度も QR-initiated transaction で利用されると、この値でクライアントプラットフォームは linking information の重複を排除する
      • デスクトップは Chrome Sync のようなシステムを利用した linking information を同期でき、この公開鍵は linking information を持つクライアントプラットフォームから、認証器が他のクライアントプラットフォームへのなりすましを行うことを防ぐ。
  • Key5: authenticator's name
  • Key6: ハンドシェイクの署名

この linking data 署名における署名は、公開鍵の所持を証明するために送る。これは公開鍵が識別子であり、同じ公開鍵を所持する今後の linking message が古いものを更新するために必要である。これによって認証器は暗いなとプラットフォームの linking information を更新できるが、認証器は他の認証器のデータを更新できるべきではない。

ハンドシェイクのハッシュは Noise’s channel binding value であり、ハンドシェイクのメッセージをハッシュする。認証器の公開鍵は後の Noise ハンドシェイクの ECDH key として利用されるので、ECdSA 鍵としても負荷をかけたくない。(?)そのため linking message の署名は実際のところ、認証器の鍵とクライアントプラットフォームの QR コードの鍵の間での共通鍵で行われるハンドシェイクハッシュの HMAC である。

クライアントプラットフォームは認証器にアクションを行うことを指示するため、CTAP2 コマンドを送らなければいけない。一般的に CTAP2 交換においてそれは getInfo リクエストである。けれども、レスポンスは post-handshake メッセージですでに提供されているため、クライアントプラットフォームはすぐにもっと実質的なリクエストを送ることができる。例では余分な authenticatorGetInfo リクエストを送っている。

inabajunmrinabajunmr

トンネルでのやり取りが頭に全然入らないので一旦整理

inabajunmrinabajunmr
<mxfile host="app.diagrams.net" modified="2023-12-06T16:36:02.288Z" agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36" etag="o83r-sQr2e-nDx-Js3Xj" version="22.1.6" type="google">
  <diagram name="ページ1" id="aLLbclV3DBHzHAjqIyvB">
    <mxGraphModel grid="1" page="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
      <root>
        <mxCell id="0" />
        <mxCell id="1" parent="0" />
        <mxCell id="vSpzwnX1Lpqdq4ApjiLk-1" value="" style="fontColor=#0066CC;verticalAlign=top;verticalLabelPosition=bottom;labelPosition=center;align=center;html=1;outlineConnect=0;fillColor=#CCCCCC;strokeColor=#6881B3;gradientColor=none;gradientDirection=north;strokeWidth=2;shape=mxgraph.networks.pc;" vertex="1" parent="1">
          <mxGeometry x="260" y="200" width="130" height="100" as="geometry" />
        </mxCell>
        <mxCell id="vSpzwnX1Lpqdq4ApjiLk-2" value="" style="verticalLabelPosition=bottom;html=1;verticalAlign=top;align=center;strokeColor=none;fillColor=#00BEF2;shape=mxgraph.azure.mobile;pointerEvents=1;" vertex="1" parent="1">
          <mxGeometry x="521.11" y="210" width="35" height="50" as="geometry" />
        </mxCell>
        <mxCell id="vSpzwnX1Lpqdq4ApjiLk-3" value="" style="sketch=0;aspect=fixed;pointerEvents=1;shadow=0;dashed=0;html=1;strokeColor=none;labelPosition=center;verticalLabelPosition=bottom;verticalAlign=top;align=center;fillColor=#00188D;shape=mxgraph.mscae.general.tunnel" vertex="1" parent="1">
          <mxGeometry x="380" y="110" width="111.11" height="20" as="geometry" />
        </mxCell>
        <mxCell id="vSpzwnX1Lpqdq4ApjiLk-6" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;" edge="1" parent="1" source="vSpzwnX1Lpqdq4ApjiLk-4" target="vSpzwnX1Lpqdq4ApjiLk-2">
          <mxGeometry relative="1" as="geometry" />
        </mxCell>
        <mxCell id="vSpzwnX1Lpqdq4ApjiLk-4" value="" style="shape=image;html=1;verticalAlign=top;verticalLabelPosition=bottom;labelBackgroundColor=#ffffff;imageAspect=0;aspect=fixed;image=https://cdn1.iconfinder.com/data/icons/ionicons-outline-vol-2/512/qr-code-outline-128.png" vertex="1" parent="1">
          <mxGeometry x="330" y="220" width="30" height="30" as="geometry" />
        </mxCell>
        <mxCell id="vSpzwnX1Lpqdq4ApjiLk-7" style="edgeStyle=none;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;entryPerimeter=0;startArrow=classic;startFill=1;" edge="1" parent="1" source="vSpzwnX1Lpqdq4ApjiLk-1" target="vSpzwnX1Lpqdq4ApjiLk-2">
          <mxGeometry relative="1" as="geometry">
            <Array as="points">
              <mxPoint x="450" y="340" />
            </Array>
          </mxGeometry>
        </mxCell>
        <mxCell id="vSpzwnX1Lpqdq4ApjiLk-9" style="edgeStyle=none;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;exitPerimeter=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;startArrow=classic;startFill=1;" edge="1" parent="1" source="vSpzwnX1Lpqdq4ApjiLk-2" target="vSpzwnX1Lpqdq4ApjiLk-3">
          <mxGeometry relative="1" as="geometry" />
        </mxCell>
        <mxCell id="vSpzwnX1Lpqdq4ApjiLk-10" style="edgeStyle=none;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=0.07;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;startArrow=classic;startFill=1;" edge="1" parent="1" source="vSpzwnX1Lpqdq4ApjiLk-1" target="vSpzwnX1Lpqdq4ApjiLk-3">
          <mxGeometry relative="1" as="geometry" />
        </mxCell>
        <mxCell id="vSpzwnX1Lpqdq4ApjiLk-11" value="1. scan QR code by Phone" style="text;strokeColor=none;align=center;fillColor=none;html=1;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
          <mxGeometry x="414" y="250" width="60" height="30" as="geometry" />
        </mxCell>
        <mxCell id="vSpzwnX1Lpqdq4ApjiLk-12" value="2. BLE connection" style="text;strokeColor=none;align=center;fillColor=none;html=1;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
          <mxGeometry x="384" y="340" width="60" height="30" as="geometry" />
        </mxCell>
        <mxCell id="vSpzwnX1Lpqdq4ApjiLk-13" value="3. WebSocket Connection&lt;br&gt;via Tunnel" style="text;strokeColor=none;align=center;fillColor=none;html=1;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
          <mxGeometry x="380" y="68" width="116" height="40" as="geometry" />
        </mxCell>
      </root>
    </mxGraphModel>
  </diagram>
</mxfile>

inabajunmrinabajunmr

QR-initiated Transactions

  1. スマホが QR コードをスキャン
  2. 2 スマホが BLE アドバタイズ
    1. (アドバタイズでだけ情報を送ってるように見える)(図が双方向っぽくて変)
    2. QR コードで渡した鍵を使って PC がアドバタイズを復号
    3. アドバタイズの中に利用する tunnel service を識別する情報と routing ID が入っている
  3. tunnel service を通じて WebSocket で PC とスマホが接続
    1. 双方が QR コードの情報から tunnel ID を導出
    2. 双方が /cable/connect/{routing id}/{tunnel id}(ドメインはアドバタイズで受け取った情報から導出)にアクセス
      1. これで WebSocket でやり取りできるようになる
      2. このあと PC は usb とかの場合と同じノリで ctap2 の API を WebSocket 経由でコールして、クレデンシャルを作る命令を出したりする
inabajunmrinabajunmr

11.5.2. State-assisted Transactions

もしクライアントプラットフォームが以前の QR-initiated transaction からの認証器のための linking information をもっていたら、認証器と再度やりとりするために QR コードを表示する必要はない。/cable/contact に続く base64url-encoded した contact ID(tunnel 経由で受け取るやつと一緒?)でキャッシュした tunnel service に接続することで、 WebSocket コネクションを作成することによって、tunnel service は特定された認証器とトンネルを確立することを試みる。(プラットフォームが tunnel service につなぐと tunnel service が自分でスマホにつなぎに行く?)もし tunnel service が認証器が永続的に接続できないと判断したら(ユーザーがクライアントプラットフォームと認証器の接続を切ったため)tunnel server は HTTP 410 を返し、クライアントプラットフォームは link 情報を捨てる。

認証器は tunnel とのやりとりを始めるのに、どのクライアントプラットフォームが接続しているから知るための link ID とクライアントプラットフォームからの nonce の 2 つの値を必要とする。後者は BLE advert を暗号化する鍵を多様にし、受動的に listen している誰かが advert を link keys のセットと遡及的にリンクすることを防ぐ。(?)2 つの値は client payload と呼ばれ、X-caBLE-Client-Payload HTTP ヘッダに hex-encoded される。

認証器が user に UI を表示するのを助けるため、3 番目の値として以降のトランザクションが makeCredential か getAssertion かについてのヒントが client payload にエンコードされる。

tunnel が準備できたら認証器はハンドシェイクメッセージを送り、近接性の検証のため BLE advertise を開始する。この場合の BLE advert は 0 の初期フラグバイトを含み、残りの 15 bytes は nonce となる。一度 BLE advert を受信するとクライアントプラットフォームはハンドシェイク PSK を計算しレスポンスする。

この場合認証器は以前に公開鍵を共有しているので、ハンドシェイクは NKpsk0 となる。

クライアントペイロードは以下のフォーマットの CBOR でエンコードされている。

  • Key 1: the 8-byte link ID; a bytestring.
  • Key 2: a 16-byte nonce generated by the client platform; a bytestring.
  • Key 3: either the string “ga” to hint that a getAssertion will follow, or “mc” to hint that a makeCredential will follow.

この時点でコネクションは QR-initiated と同様に動く。認証器はもし linking information を更新したい場合、任意に post-handshake メッセージに link information を送信し、それから前回のように CTAP2 メッセージのやりとりをする。

inabajunmrinabajunmr

QR-initiated Transactions

  1. 以前の QR-initiated で取得した接続情報を使って tunnel service にアクセス
    1. /cable/contact/{contact id}
  2. contact ID によって tunnel service は認証器を特定できるので、tunnel service は認証器と通信
  3. 認証器は tunnel service 経由でハンドシェイクメッセージを送る
  4. 認証器は BLE advertise
  5. クライアントプラットフォームは BLE advertise で受け取った値を使ってハンドシェイクをレスポンスする
  6. ハンドシェイクが完了したらあとは QR-initiated と同じ
このスクラップは4ヶ月前にクローズされました