社内PKIを支えるYubiKeyのAttestation

はじめに。

はじめまして。技術部のishizukaです。社内でYubiKeyを使って遊ぶ機会に恵まれ、クライアント証明書の発行のフローと遊んでいる最中に気づいた面白いことをまとめていきます。基本CLIベースな記事なので画像がほとんどないです。

概要

CyberDefense社内では、YubiKeyに入れた証明書を利用した個人認証を行っています。これによりパスワードからの脱却を目指しております(多少誇張)。一方で、ICカード等の物理デバイスの運用は、PINの誤入力によるロック、物理的な紛失といった別のリスクが存在します。

これらのリスクに対して社内では証明書のリモート発行を準備することで、誤ってロックアウトされてしまった際でも出社することなく証明書を再発行する運用ができるように準備を整えました。この証明書発行フローを簡略化して説明していきます。

m-ishizuka_20200526123123

yubikey_farm

CDI社内で、YubiKeyにより認証している箇所

社員はWindows/Mac/Linuxのいずれかを利用し以下のリソースをYubiKeyの証明書で利用しています。

  • VPN
  • 802.1X(有線・無線の双方)
  • 一部Webサービス
  • Kerberos
  • メール
  • SSH(実際は証明書ではないですが)
  • 一部OSのログイン(U2Fの場合も)

YubiKeyのAttestation

公式のAttestationに記載の通り、秘密鍵がYubiKey内部で生成された際に、Attestationが生成できます。このAttestationの機能のおかげで、その秘密鍵が、外部で生成され、importされたものではないことが保証できます。これにより、秘密鍵がマルウェア等により作成されているリスクや、ファイルベースでコピーできるリスクなどをほぼ無視できます。リモート証明書発行にはこのAttestationの機能を利用します。

Attestationスロット(F9)にそのYubiKeyの中間CA証明書と秘密鍵が入っており、各種スロット(PIV Authentication, Card Authentication, Key Management, Digital Signature)のAttestationを生成する・・・ようです。(ブラックボックスのため推測)

今回の内容はYubiKey5をベースに記載してますが、openssl1.0を利用するか、本家のPythonスクリプトを利用することでYubiKey4でも確認が可能です。

確認環境

ベースOS

Mac or Linuxを推奨します。opensslとyubico-piv-tool(https://developers.yubico.com/yubico-piv-tool/Releases/) が手軽に利用できるからです。WSL2でも利用できるかもですが、非推奨というか利用方法がよくわからないです。

またDockerコンテナ内で作業もできますが、USBをコンテナ側から利用できるようにする必要がありますので、これも非推奨というかハードル高いです。 一見ハードルが高そうなVMのLinuxが一番ハードル低いかもです。

今回はUbuntu20.04がリリースされて間もないので、折角なのでVirtualBoxにこれを入れ使います。インストール手順は省略します。

プログラム

  • openssl
    • mac/ubuntu ともに初期で入っているはず。
  • yubico-piv-tool
    • ubuntuなら sudo apt install -y yubico-piv-tool で入ります。
    • macは適当にインストーラを使うなど。

手順

今回は分かりやすいようにリリースされている既存のツールのみを利用し、PIV authenticationのスロット(9a)のみ説明していきます。

なお、実際の会社では以下のようなフローになるかなと思います。

  1. 資産管理チーム
    1. シリアルと、利用者と紐付け、証明書発行チームに連絡
  2. 証明書発行チーム
    1. Attestationの中間CAを保存
    2. CSR生成
    3. Attestation 生成
    4. 証明書発行
    5. YubiKeyに証明書の導入、ユーザにYubiKeyを渡す
  3. ユーザ
    1. 利用

再発行時は、

  1. ユーザ
    1. CSR生成
    2. Attestation 生成
    3. 証明書発行チームにCSRとAttestationを送付・依頼
  2. 証明書発行チーム
    1. Attestationの検証
    2. CSRとAttestationの確認
    3. 証明書発行
    4. ユーザに証明書送付
  3. ユーザ
    1. YubiKeyに証明書の導入
    2. 利用

Attestationの中間CA証明書を保存

社員に証明書を発行する前にYubiKeyからAttestationの中間CA証明書を保存しておきます。これを利用し、そのYubiKeyが会社から支給されたYubiKeyか否かの判断材料になります。

$ yubico-piv-tool -a read-certificate -s f9 -o test-intca.crt
$ openssl x509 -in test-intca.crt -noout -issuer -subject
issuer=CN = Yubico PIV Root CA Serial 263751
subject=CN = Yubico PIV Attestation

このようにIssuerがYubico PIV Root CA Serial 263751の中間CA証明書が出力されます。先に記載したAttestationにRootCAの証明書があるので、検証できるか確認してみます。

$ wget -q https://developers.yubico.com/PIV/Introduction/piv-attestation-ca.pem
$ openssl verify -CAfile piv-attestation-ca.pem test-intca.crt 
test-intca.crt: OK

Verify OKになりました。これでこの中間CA証明書は適切に利用可能であることがわかりました。

CSR生成

この作業は利用者側で実行でも構わない箇所です。というのも在宅勤務でYubiKeyをロックさせてしまう可能性を考慮してです。なお私もこのコロナ禍の初期に一度YubiKeyの証明書を消失させてしまった経験があります。VPNが利用できず辛みしかありませんでした。

ECDSAのほうが秘密鍵作成時間が短いので、意図的にECDSAを利用しています。

PINは初期値の123456を利用しています。

$ yubico-piv-tool -a generate -s 9a -A ECCP256 -o 9a.pub
Successfully generated a new private key.
$ yubico-piv-tool -a verify-pin -a request-certificate -i 9a.pub -s 9a -S "/CN=test/" -o 9a.csr -P 123456
Successfully verified PIN.
Successfully generated a certificate request.

さてCSRが作成できました。合わせてCSRの中身を見てみます。

$ openssl req -in 9a.csr -noout -subject
subject=CN = test 

適切なPEMフォーマットのCSRだとわかります。

Attestation 生成

され、これだけだと誰がどのようにして作成したCSRかわからないため、Attestationを生成します。ついでに、以前に取得した中間CA証明書を利用して、検証できるか確認します。

$ yubico-piv-tool -a attest -s 9a -o 9a.attest.crt
$ openssl verify -CAfile <(cat piv-attestation-ca.pem test-intca.crt ) 9a.attest.crt 
9a.attest.crt: OK

Attestationの検証に成功しました。この9a.attest.crtと9a.csrを証明書発行チームに送ります。

証明書発行チーム

証明書発行チームが見る内容は以下です。

  1. Attestationの検証に通ること
  2. Attestationの中身の公開鍵と、CSRの公開鍵が一致すること
  3. Attestationの証明書内のYubiKeyのシリアルが配布したYubiKeyのシリアルであること

1により、秘密鍵がYubiKey内部で生成されており、複製が計算量的に非常に困難であることが保証できます。 2により、提出されたCSRの公開鍵がAttestationで保証できます。 3により他の人のYubiKeyではないことが確認できます。1

この2つを確認することで、提出されたCSRが会社支給のYubiKeyで生成された秘密鍵を利用して作成されたものであることがリモートから検証できます。

Attestationの検証

$ openssl verify -CAfile <(cat piv-attestation-ca.pem test-intca.crt ) 9a.attest.crt
9a.attest.crt: OK

Attestationの中身の公開鍵とCSRの公開鍵の比較

$ diff <(openssl req -in 9a.csr -noout -pubkey) <(openssl x509 -in 9a.attest.crt -noout -pubkey)

Attestationの証明書内のYubiKeyのシリアルの確認

Attestationの中身 を見る限り、単純なシリアルナンバーのはずだが、頭2バイトに不要な値が。

$ openssl asn1parse -in 9a.attest.crt | grep -A1 "1.3.6.1.4.1.41482.3.7"
  473:d=5  hl=2 l=  10 prim: OBJECT            :1.3.6.1.4.1.41482.3.7
  485:d=5  hl=2 l=   6 prim: OCTET STRING      [HEX DUMP]:020347B06E
$ printf "%d\n" 0x47B06E                                                                                                                                                   
4698222

検証に通ること、diffによる差がないことを持って提出されたCSRがYubiKey内部で生成されたことが保証されました。これをもってCSRに署名をします。ここは省略します。

YubiKeyへの証明書のimport

証明書発行チームから9a.crtというファイルが届いたという前提で後続作業をします。

以下の可能性を考慮し正当性を確認しましょう。

  • 証明書発行チームが悪意を持って違う証明書を送ってきた
  • 証明書発行チームのヒューマンエラーで違う証明書を送ってきた
  • 途中経路で改竄が発生し、異なる証明書になってしまった

証明書発行チームが行っているCSRとAttestationの比較を今度は送付された証明書で行います。自身のYubiKeyに入っているAttestationのPubkeyと送られてきた証明書のpubkeyを比較します。

$ diff <(yubico-piv-tool -a attest -s 9a | openssl x509 -in - -noout -pubkey) <(openssl x509 -in 9a.crt -noout -pubkey)

さて、これで送られてきた証明書が正しいものだと確認できました。証明書をyubikeyに入れ、最後に確認します。

$ yubico-piv-tool -a import-certificate -s 9a -i 9a.crt 
Successfully imported a new certificate.
$ yubico-piv-tool -a status

Attestation周りの確認

  • 外から鍵をimportするとAttestationは生成されない。というよりエラーが帰ってくる。
$ openssl genpkey -paramfile <(openssl ecparam -name prime256v1 ) -out 9a.key
$ yubico-piv-tool -a import-key -i 9a.key -s 9a
Successfully imported a new private key.
$ yubico-piv-tool -a attest -s 9a
Failed to attest data.

持っているYubiKeyの秘密鍵が外からimportされたものではないことを確認する意図なら、証明書が出力されればそれでOKと考えることもできます。

結び

YubiKey5を利用したオンライン証明書発行を支えるAttestationの機能の確認と実際の利用の仕方についてまとめました。次回はYubiKeyを破壊します。


  1. 確認した限り、60本のYubiKeyで同一の中間CA証明書でした。単位については不明(同一ロットでなのか、注文時のまとめ買い数なのか、Firmwareなのか) ↩︎

© 2016 - 2024 DARK MATTER / Built with Hugo / テーマ StackJimmy によって設計されています。