1 ポイント 投稿者 GN⁺ 4 시간 전 | 1件のコメント | WhatsAppで共有
  • セキュリティトークンは秘密鍵をデバイスの外へ出さずにデバイス内で署名し、ユーザーの物理的な操作を要求することで、リモート攻撃者が任意の署名を作ることを難しくする
  • SSH認証、U2F、パスワードレスのローカルログイン、sudo、gitのコミット署名に使え、最新のノートPCやスマートフォンの内蔵セキュリティ機構がYubiKeyの代わりになりうる
  • ssh-keygen -t ed25519-skで作られる「秘密鍵」ファイルは実際の秘密鍵ではなく、トークン内の鍵を指すハンドルであり、同じトークンを使えば別のコンピューターでも同じSSH鍵ファイルを生成できる
  • MacBookではsecure elementをSSH鍵として設定し、Touch IDベースのSSHログインが可能で、gitのコミット署名ではファイルパスではなくssh-agentkey::形式のuser.signingKey設定が必要だった
  • セキュリティトークンは紛失すると秘密鍵を復旧できず、繰り返しタッチに慣れてしまうユーザビリティ上のリスクがあり、WindowsノートPCではWindows Helloが顔認証・指紋・PINでSSH鍵の使用を確認できた

セキュリティトークンの利点と限界

  • リモート攻撃を防ぐ中核構造

    • セキュリティトークンは秘密鍵/公開鍵のペアをデバイス内に保持し、公開鍵は簡単に取り出せる一方で、秘密鍵はデバイスの外に出ないようにする装置である
    • 署名するデータパケットをデバイスに送ると、秘密鍵でデバイス内署名が行われ、通常は点滅するタッチボタンを押すといった物理的なユーザー操作が必要になる
    • リモート攻撃者がコンピューターにアクセスできても、ユーザーが現実世界で操作しない限りセキュリティトークンは任意の署名を行わないため、~/.sshディレクトリにSSH秘密鍵/公開鍵の完全なペアをファイルとして置く方式より良さそうに見える
    • SoloKeysNitrokeysのように、FOSSファームウェアを望む選択肢もある
    • より高機能なセキュリティトークンは内蔵指紋リーダーのような生体認証機能を追加するが、中核は秘密鍵をデバイス外へ出さない構造にある
  • 使い勝手から生じるリスク

    • ユーザーがセキュリティトークンの点滅のたびに押すことに慣れると、悪意ある要求にも無意識に応答してしまう可能性がある
    • 連続した署名作業の最中にトークンを何度も押す状況では、もう一度点滅する要求に本当に気づくのは難しいことがある
    • AppleとMicrosoftはスマートフォンの認証アプリで、アクセス要求ごとにランダムな数字コードを表示してユーザーに入力させる方式を使っているが、これは煩雑で、TOTPを使うAuthyやGoogle Authenticatorアプリと比べたときのセキュリティトークンの使い勝手上の利点を弱める
  • 紛失とバックアップの問題

    • セキュリティトークンを失くすと、その秘密鍵は永久に失われ、バックアップする方法もない
    • 複数のアカウントから締め出されるリスクを避けるには、セキュリティトークンを買う際に少なくとも2個購入し、同じサービスに登録しておく必要がある
    • 代替として、BIP 39のように秘密鍵を人間が読める単語リストに変換して書き留めるバックアップ・復元方式がある
    • 秘密鍵がセキュアエンクレーブの外に出せる場合、ユーザーをだまして単語リストを不適切な場所に書かせるフィッシング攻撃も可能になる
    • すべてのセキュリティトークンを失う可能性が本当に心配なら、BIP 39の単語リストはシステムへのアクセスを取り戻すための最後の手段になりうる

SSHとgitでセキュリティトークンを使う

  • SSH秘密鍵をセキュリティトークンに置く

    • 通常、ssh-keygenを実行すると秘密鍵全体を含むファイルのペアが作られる
    • セキュリティトークンに秘密鍵を置くには、YubicoのFIDO/U2Fガイドに従ってlibfido2をインストールし、セキュリティトークンを挿した状態でssh-keygen -t ed25519-skを実行する
    • この場合もファイルのペアは生成されるが、「秘密鍵」ファイルは実際の秘密鍵ではなく、セキュリティトークン内にある秘密鍵を指すハンドルである
    • 同じセキュリティトークンでssh-keygen -t ed25519-skを再実行すれば、どのコンピューターでも同じ秘密鍵/公開鍵ファイルを作成できるため、SSHアクセス権は特定のコンピューター上の特定のファイルではなく、セキュリティトークンとともに移動する
  • git認証とコミット署名

    • セキュリティトークンを押す状況の約90%はgitの利用が理由である
    • git forgeはpushやpullのためのSSH認証を実装しており、上で生成したid_ed25519_sk.pubファイルをアップロードすれば、セキュリティトークンの鍵ペアを利用できる
    • gitはコミット署名にもSSH鍵をサポートしており、GitHubドキュメントのSSH鍵で署名鍵を設定するに従ったうえでgit config --global commit.gpgsign trueを実行すると、すべてのコミットが自動署名される
    • git forgeがコミットを本人の署名として認識するには公開鍵を再度アップロードする必要があり、このフィールドは通常SSH認証用のフィールドとは別である
  • コミット署名の不便さ

    • 長いコミット列をrebaseするときは、すべてのコミットに再署名しなければならない
    • 指紋リーダー付きのYubiKeyは、数十件のコミットを連続署名するには指紋認識の失敗率が高すぎて、使うのをやめることになった
    • gitの「rebase/amend中心」ラッパーであるjujutsuには、push時点でのみコミットに署名する方法がある
  • Linuxのローカルログインとsudo

    • Linuxシステムでは、Pluggable Authentication Module(PAM)により、セキュリティトークンをパスワードレスのローカルログインやsudoによる権限昇格に使える

MacBookのsecure elementをSSH鍵として使う

  • USB-Cポートにセキュリティトークンを挿しっぱなしにすると、誤って落としたりぶつけたりしたときにポートやトークンを壊しかねない小さなてこのように突き出した状態になる
  • 2020年モデルのM1 MacBook Airで、Arian van Puttenのガイドに従って内蔵セキュリティ要素をSSH鍵として設定した
sc_auth create-ctk-identity -l ssh -k p-256-ne -t bio
ssh-keygen -w /usr/lib/ssh-keychain.dylib -K -N ""
  • このコマンドはid_ecdsa_sk_rk秘密鍵/公開鍵ファイルのペアを作成し、それらのファイルを~/.sshディレクトリへ移動した
  • ここでも秘密鍵ファイルは実際の秘密鍵ではなく、デバイス内の鍵に対するハンドルなので、公開的に貼り付けられる形式である
  • ホームラボのサーバーに公開鍵をauthorized keyとして追加するには、次のように実行する
ssh-copy-id -i ~/.ssh/id_ecdsa_sk_rk.pub <server nickname>
  • その後、~/.ssh/configに次の設定を追加した
Host *
  IdentityFile ~/.ssh/id_ecdsa_sk_rk
  SecurityKeyProvider=/usr/lib/ssh-keychain.dylib
  • ssh <server nickname>を実行すると、ログイン前にmacOSが自動で指紋認証の要求を表示し、その後SSHログインが正常に進む

MacBookのsecure elementでgitコミットに署名する

  • git config --global user.signingKey /Users/ahelwer/.ssh/id_ecdsa_sk_rkを設定し、.ssh/allowed_signersファイルを更新しても、gitのコミット署名はすぐには動かなかった
  • gitはコミット署名に失敗し、device not found?のようなエラーを出力した
error: Signing file /var/folders/l5/5wqvq2l10p96wtdtfr6lvrvw0000gn/T//.git_signing_buffer_tmpc4uQgO
Confirm user presence for key ECDSA-SK SHA256:oQDA2SNYb2MoSQcxJVSmWyAeAWPqMp7rxliBRfi87as
Couldn't sign message: device not found?
Signing /var/folders/l5/5wqvq2l10p96wtdtfr6lvrvw0000gn/T//.git_signing_buffer_tmpc4uQgO failed: device not found?

fatal: failed to write commit object
  • 解決策は、~/.sshディレクトリ内のファイルを直接参照する代わりにssh-agentを使うことだった
  • 上記のチュートリアルに従って、次のコマンドで鍵ペアをssh-agentに登録した
ssh-add -K -S /usr/lib/ssh-keychain.dylib
  • その後、user.signingKeyにはファイルパスではなく、~/.ssh/id_ecdsa_sk_rk.pubの内容の先頭にkey::を付けた鍵そのものを~/.gitconfigに入れた
[user]
	name = Andrew Helwer
	signingKey = "key::sk-ecdsa-sha2-nistp256@openssh.com AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb20AAAAIbmlzdHAyNTYAAABBBGxFEdnIg6ppz+pQCdd1eisjOV4gxrjMv1Y4SbtdLoSm6CJCgPZ6q7lnNyuQQsdnS4/Tllsc656AQL7BO3OS47cAAAAEc3NoOg== ssh:"
  • この設定の後、MacBookのsecure element内の鍵でファイルに署名し、GitLab Pagesサイトへpushできた

WindowsとLinuxで試した結果

  • 会社支給のWindowsノートPCでも簡単な実験を行った
winget install Microsoft.OpenSSH.preview
ssh-keygen -t ecdsa-sk
  • このコマンドでも秘密鍵/公開鍵ファイルのペアが生成され、SSH接続時にはWindows Helloの標準ログインフローを通じて、顔認証・指紋・PINのいずれかで確認できた
  • Linuxでは、同様の実ユーザー存在確認の後ろにsecure elementがぶら下がっているノートPCへアクセスできず、デモはできなかった

1件のコメント

 
GN⁺ 4 시간 전
Lobste.rsの意見
  • 素晴らしい記事で、こういうことが可能だという事実を示しているだけでも非常に有益
    個人的には、これを動かすのに適したライブラリのバージョンは見つけられなかったが、1Password 8 が SSH キーを安全に保存し、エージェントが生体認証でキーのロック解除をサポートしていることを知った
    なので今では、指を置くだけで git 作業もできるし、SSH ホストにもログインできる
    ガイド: https://developer.1password.com/docs/ssh/get-started/

  • これはMac 専用のように見える

    • 会社で最新の Windows ノート PC を使えるので、次のように OpenSSH がWindows Helloと連携するようにした
      winget install Microsoft.OpenSSH.preview  
      ssh-keygen -t ecdsa-sk  
      
      その後は従来どおり動作し、キーでどこかに SSH 接続するたびに標準の Windows Hello フローを経て、指紋認証、顔認証、PIN のいずれかを使えた
      そうしたセキュア要素を持つ Linux システムは試せておらず、自分の Linux ワークステーションには V1 TPM があるが、署名処理が実際のユーザー存在確認の後にのみ実行されることを保証する方法は特に分からない
      Framework のような Linux ノート PC を持っている人なら試せるかもしれない。Asahi でも実際に動く可能性はある
    • ほぼそうだと思う。Linux や Windows にTPM2 FIDO エミュレーション層があるのかはよく分からない
  • では、提供されたprivate key ファイルの中には正確には何が入っているのか?

    • @wrs が先に答えているが、ssh: の部分、つまりアプリケーションは passkey の origin に相当し、ホストやドメインごとのresident keyを作るときに役立つ
      例えば、同じ物理 YubiKey の中でも用途ごとにキーを分離するのに使っている
      flags はハードウェアがキーをどう扱うべきかを指定する [1]。エージェントが独自の制約を追加することもある
      技術的には、FIDO キーには別の blob や拡張も保存でき、前職では認証と一緒に X.509 公開鍵のような補助資格情報を渡すために使ったことがある。かなりクールな仕組みだ
      [1]
      #define SSH_SK_USER_PRESENCE_REQD  0x01  
      #define SSH_SK_USER_VERIFICATION_REQD  0x04  
      #define SSH_SK_FORCE_OPERATION    0x10  
      #define SSH_SK_RESIDENT_KEY    0x20  
      
    • Claude によれば、そして openssh_key_parser で検証したところ、構造は次のとおり
      外側のラッパーにはマジック値 openssh-key-v1\0cipher=nonekdf=none があり、暗号文ではない
      公開鍵 blob の 74 バイトには、キータイプ sk-ssh-ed25519@openssh.com、32 バイトの Ed25519 点 fdcce889…03e7852b、アプリケーション ssh: が入っており、この値が SSH と WebAuthn の FIDO 資格情報を名前空間で分離している
      秘密セクションは 248 バイトで、cipher=none なので平文。checkint1 == checkint2 == 0x46744267 のランダム値、繰り返されたキータイプと公開鍵、アプリケーション ssh:flags: 0x01 が入っている
      このフラグは USER_PRESENCE_REQUIRED を意味し、タッチは必要だが PIN / ユーザー検証はなく、非 resident key である
      key_handle は 128 バイトの不透明な資格情報 ID で、authenticatorGetAssertion に渡され、デバイスが内部的に解釈して Ed25519 シードを復元する
      そのほかに空の reserved、コメント ahelwer@ah-mbair.local、パディング 01 02 03 がある
    • base64 デコーダに入れると次のようになる

      openssh-key-v1����none���none����������J���sk-ssh-ed25519@openssh.com��� 盘˪<F$KW+���ssh:���FtBgFtBg���sk-ssh-ed25519@openssh.com��� 盘˪<F$KW+���ssh:���fІpF$D8"&0[X 'L=Ev ')BjM]$}rTv6Z+p9O8ݹ%V* f.|қ.%I{9 .W !D"8N ai*W�y53 �������ahelwer@ah-mbair.local
      ここにはキー標準バージョン v1、キータイプ sk-ssh-ed25519@openssh.com が含まれており、なぜか繰り返されていて、人間が読みやすいキー名 ahelwer@ah-mbair.local もある
      残りは OpenSSH のフラグ、たとえば PIN やユーザー存在確認が必要かどうか、そして OpenSSH が challenge とともに FIDO/U2F API に渡せるハンドル GUID だと思われる
      OpenSSH はキータイプ、とくに sk を見て、これが実際の秘密鍵ではなくセキュア要素を呼び出すべきものだと推論できる
      その後、セキュア要素と通信できるようにする動的ライブラリをどこから読み込むか、SecurityKeyProvider 設定または SSH_SK_PROVIDER 環境変数を確認する

  • この記事は SSH だけを扱っているようだが、自分のコンピュータのSecure Enclave や TPM を FIDO2 または U2F キーとして使う方法はあるのだろうか?

    • もちろん可能で、ほぼデフォルト設定のままで動くはず
      Passkey も、Web サイトごとに別個の秘密鍵を使うこうした方式の一形態だ
  • この事実を見ると、ハードウェアに紐付けられる非対称または HMAC API キーのサポートがもっと一般的でないのは不思議に感じる
    WebAuthn、DBSC(Device-Bound Session Credentials)、OAuth2 DPOP のように、この方向をさらに後押しする仕様が増えているのはうれしい