TPM2でLinux Desktopのハードニング

はじめに

Debian 11 Bullseyeが2021/08にリリースされました。 個人的にはTPM2周りのパッケージがシンプルに導入可能になり、嬉しい次第です。

今回私がクライアント証明書やSSHの秘密鍵の安全な格納場所としてTPM2を選び、諸々の動作確認を実施しました。 ただし、TPM2をPKCS11で扱うことであり、深淵には一切手を出していません。#詳しくないので。

OSのインストールが終わった状態からであれば手順は大きく3手順で済みます。意外と手軽に利用可能になります。

  • パッケージインストール と 設定
  • 秘密鍵生成 と 証明書関連の操作
  • 各種プログラムの設定

なお、一部環境では上手くいったりうまくいかなかったりします。Firefoxで証明書が送れなかったり・・・といった感じです。 自身で頑張ってください。応援します。私も同じ罠に掛かっている端末もあり、苦しんでいます。

私のプライベートの端末では完全に動作し、会社の検証用の端末では一部が正常に使えなかった・・・といった経験もあります。 とりあえず沼にハマるだけハマってみてください。

攻撃手法とそれらに対する対策

機密な情報へのアクセス・機密なログインを防ぐという観点で防げる・防げないを記載します。 攻撃ベクターに突然自身の画面を盗み見る、概念上の飼い猫様に登場してもらいました。 猫様の攻撃は基本防ぎ難いです。あやつめ

以下のセッションとは、SSH/https以外にも、例えばログインの状態やsudoの状態など広く拡大解釈しています。

防げない

  • セッションを貼りっぱなしのタイミング && 以下のどれか
    • マルウェア
    • ロックし忘れなどによるセッションハイジャック ※社内ではロックし忘れると悲惨な目にあったり合わなかったり。
    • 飼い猫に作業を盗み見られている
  • セッションを貼っていないが、PINが漏れているタイミング && 以下のどれか
    • HWが物理的に盗まれている
    • 操作されている
    • マルウェア

防げる

  • セッションを貼っていなく、PINが漏れていない && 以下のどれか
    • マルウェア(秘匿化され、なんらかのタイミングでPINを入力したらアウトだが。)
    • HWが盗まれた(TPMの試行回数に制限あり)
    • 飼い猫に端末をおもちゃにされた
  • 機密な情報へのアクセス情報の奪取(passwordではないため。)
    • 証明書ファイルは盗めても、秘密鍵は取り出せない

環境

  • Linuxを入れる都合で全ての環境での動作保証は不可能
  • Dual BootによるBitLockerのパスワード封入などは壊れる可能性あり

などから一台余っているPCで実施を激しく推奨。 なお、TPM2を使う以上、あまりに古いPCには搭載されていない可能性が高い。もしくはTPM1.2(規格が違うのと、検証したことがない)が搭載されている可能性も。 またデスクトップではTPM2自体が後付けになる可能性が高い。この辺りはスペック等を確認。

昨今では仮想化ソフト側でTPM2を使えるようになっているため、そちらを使っても良い・・・かもしれません。

OSインストール

Debian11を適当にインストールください。 SecureBootできるとカッコいいですが、今回はその辺りを省きます。 過去の叡智などを参考に頑張りましょう。

パッケージインストール と 設定

ここから本格的にTPM2に対してアクセスし、諸々が使えるようにしていきます。

aptで以下のパッケージをインストールください。TPM2を操作するコマンド以外にもTPM2をpkcs11で取り扱うライブラリも入れます。 ここの作業はroot権限を持った手段で実施が必要です。

libpam-pkcs11 libtpm2-pkcs11-1-dev python3-tpm2-pkcs11-tools opensc libengine-pkcs11-openssl

# apt update
# apt install -y libpam-pkcs11 libtpm2-pkcs11* python3-tpm2-pkcs11-tools

これだけでは一般ユーザがTPM2デバイスを操作できないので権限を付与します。ユーザ名はusr1とします。 また合わせてTPM2内に入れるべき証明書を管理するディレクトリを作成します。

# 雰囲気では、どうやらTPM2内に直接証明書を入れるのではなく、ファイルシステムに証明書を設置、libtpm2-pkcs11が呼び出す際に証明書とセットで応答するというような動作のようです。

USERNAME=usr1
usermod -aG tss $USERNAME
mkdir -p /etc/tpm2_pkcs11
chown $USERNAME:$USERNAME /etc/tpm2_pkcs11

再ログインして、ターミナルでidコマンドで、tssグループに属していることを確認してください。ダメなら再起動というスーパーソリューション。

秘密鍵生成

さてここまできたら一般ユーザに戻って(勿論rootでも可・・・だと思われる)TPM2で秘密鍵を生成します。RSAで生成してますが、ECDSAでもできます。できますが、私が実施した限り後の行程でバグに遭遇したので泣く泣くRSAにした苦い思い出があります。

read -p "sopin: " -s tpmsopin
read -p "pin: " -s tpmpin
tpm2_ptool init
tpm2_ptool addtoken --pid=1 --label=tpm --sopin=$tpmsopin --userpin=$tpmpin
tpm2_ptool addkey --algorithm=rsa2048 --label=tpm --userpin=$tpmpin

秘密鍵のCKA_IDを取得します。ここのCKA_IDは今後何回か出てきます。

tpm2_ptool listobjects --label=tpm

秘密鍵を生成しただけでは何もできないので、ひとまずは自己署名の証明書を生成してみます。

cat << EOF > engine.conf
openssl_conf = openssl_init
[openssl_init]
engines = engine_section

[engine_section]
pkcs11 = pkcs11_section

[pkcs11_section]
engine_id = pkcs11
MODULE_PATH = /usr/lib/x86_64-linux-gnu/libtpm2_pkcs11.so
init = 0

[ req ]
distinguished_name = req_distinguished_name

[ req_distinguished_name ]
CN=tpm-test
OU=Develop
O=CDI
ST=Tokyo
C=JP

EOF

SUBJECT="/CN=tpm-test/OU=Develop/O=CDI/ST=Tokyo/C=JP/"
CKA_ID=36303134346634376431623331303933
OUTCERT="tpm.crt"

openssl req -x509 -new -subj "$SUBJECT" -sha256 -config \
        engine.conf -engine pkcs11 -keyform engine -key $CKA_ID \
        -out $OUTCERT

これで自己署名の証明書が発行されました。本来はopenssl req -new ... にして、csrを生成し、適切なCAに署名をもらう・・・という流れになりますが、今回は所謂オレオレ証明書というかオレオレRootCAになっています。

これをtpm2-pkcs11にimportすれば利用可能になります。

tpm2_ptool addcert --label=tpm --key-id $CKA_ID $OUTCERT

証明書の確認

pkcs11-tool --module /usr/lib/x86_64-linux-gnu/libtpm2_pkcs11.so --type cert --id $CKA_ID -r | openssl x509 -in - -inform der

秘密鍵や証明書の利用

さて、証明書の発行や秘密鍵の生成だけでは面白さが一切ないので、それを使う方法に移ります。

  • pam
    • ログインの多要素認証の一つとして
  • ssh
    • 秘密鍵として
  • web
    • client certificateとして

さてそれぞれの設定をまとめていきます。

pam

設定を失敗すると締め出しされるので、慎重にかつ抜け道を用意しておくことをお勧めします。 BitLockerも記憶ではTPMに鍵を置くのと、TPMが破損とうした場合に備えたパスワード鍵による復号をサポートしていたはずです。 私の場合は パスワード認証に通過 かつ (YubikeyのU2F or TPMの証明書認証) にすることで、最悪時はYubikeyを指すことでログインができるようにしております。

他にもTPMの証明書認証は失敗してもログインに影響をしないようにするのも一つの手段かもしれません。

設定箇所は3つあり、

  • /etc/pam.d/*
    • lightdm の@include common-auth 直下に以下を追記しました
      • sudoやlock画面の場合も同様に実施すればOK
      • loginなど実施は別途調査してください。(consoleログイン等)
    • なお、この設定ではpam_u2f と pam_pkcs11 が失敗した場合、締め出されるのでご注意ください。
    • RHELなどでは手動編集は非推奨になっています
    • lightdm以外のDisplayManagerの場合はそれ相応の場所なので、その際も。
  • /etc/pam_pkcs11/tpm2_pkcs11.conf
    • サンプルを下記に載せました
  • $HOME/.eid/authorized_certificates
    • sshの公開鍵を設置するような雰囲気
    • PEM形式の証明書をベタ置きでOK

/etc/pam.d/* :

auth [success=2 default=ignore] pam_pkcs11.so config_file=/etc/pam_pkcs11/tpm2_pkcs11.conf
auth [success=1 default=ignore] pam_u2f.so
auth requisite pam_deny.so

ただし、ただ単純にTPM2のPINを必要とさせるなら、この1行を追記でもOK

auth required pam_pkcs11.so config_file=/etc/pam_pkcs11/tpm2_pkcs11.conf
cat << EOF > /etc/pam_pkcs11/tpm2_pkcs11.conf
#
# Configuration file for pam_pkcs11 module
#
# Version 0.4
# Author: Juan Antonio Martinez <jonsito@teleline.es>
#
pam_pkcs11 {
  # Allow empty passwords
  nullok = true;

  # Enable debugging support.
  debug = false;

  # Do not prompt the user for the passwords but take them from the
  # PAM_ items instead.
  use_first_pass = false;

  # Do not prompt the user for the passwords unless PAM_(OLD)AUTHTOK
  # is unset.
  try_first_pass = false;

  # Like try_first_pass, but fail if the new PAM_AUTHTOK has not been
  # previously set (intended for stacking password modules only).
  use_authtok = false;

  # Filename of the PKCS #11 module. The default value is "default"
  use_pkcs11_module = tpm2;

  # tpm2 pkcs11 module
  pkcs11_module tpm2 {
    module = /usr/lib/x86_64-linux-gnu/libtpm2_pkcs11.so;
    description = "TPM2 pkcs#11 module";
    slot_num = 0;
    support_threads = false;
    token_type = "TPM2";
  }

  use_mappers = opensc;

  # Search certificates from $HOME/.eid/authorized_certificates to match users
  mapper opensc {
        debug = false;
        module = /lib/x86_64-linux-gnu/pam_pkcs11/opensc_mapper.so;
  }
}
EOF

PAM動作確認

Desktopの画面や、ロック画面ではおおよそこのような形です。パスワード認証が成功すると、TPMのPINが聞かれます。

ターミナルでsudoではこんな感じ

usr1@localhost:~$ sudo su -
[sudo] password for usr1:
Smartcard authentication starts
TPM2 found.
Welcome tpm!
TPM2 PIN:
verifying certificate
root@localhost:~#

PINを間違えると。

1 usr1@localhost:~$ sudo su -
[sudo] password for usr1:
Smartcard authentication starts
TPM2 found.
Welcome tpm!
TPM2 PIN:
WARNING:esys:src/tss2-esys/api/Esys_Unseal.c:295:Esys_Unseal_Finish() Received TPM Error
ERROR:esys:src/tss2-esys/api/Esys_Unseal.c:98:Esys_Unseal() Esys Finish ErrorCode (0x0000098e)
ERROR: Esys_Unseal: tpm:session(1):the authorization HMAC check failed and DA counter incremented
ERROR: Error unsealing wrapping key
ERROR:pam_pkcs11.c:550: open_pkcs11_login() failed: C_Login() failed: 0x000000A0
Error 2320: Wrong smartcard PIN
Sorry, try again.
[sudo] password for usr1:

規定回数間違えると非常に困ったことになるのでご注意。

ssh

一番簡単です。

ssh-keygen -D /usr/lib/x86_64-linux-gnu/libtpm2_pkcs11.so
ssh -I /usr/lib/x86_64-linux-gnu/libtpm2-pkcs11.so hogehoge@example.com

ssh configに以下を入れておくと幸せかもしれません。

Host *
  PKCS11Provider /usr/lib/x86_64-linux-gnu/libtpm2_pkcs11.so

web

curl / firefoxで諸々遊びました。

OPENSSL_CONF=engine.conf curl -E 'pkcs11:' https://example.com

firefox: 以下で読み込むライブラリをlibtpm2-pkcs11.soにすればOK

https://github.com/OpenSC/OpenSC/wiki/Installing-OpenSC-PKCS%2311-Module-in-Firefox,-Step-by-Step

Chrome: modutil で入れればOKのハズだが、未確認

罠など遭遇した問題

  • tpm2_ptoolで生成した鍵は/etc/tmp2_pkcs11以下のsqlite3ファイルが消えるとアクセス困難
    • このsqlite3を消して再度初期化・・・等を繰り返した結果TPM2内に秘密鍵生成時に失敗するように
    • 検証用だったので、そのままUEFIからTPM2の初期化を実施、ことなきを得る
  • Firefox / Chromeで証明書が使えない場合がある
    • 良くわからない・・・
    • NSSの問題なのかlibtpm2_pkcs11の問題なのか
    • 私の端末1では動いた、2では動かなかった。
  • ECDSAでは動かないこともあったりなかったり
    • curlでは動くのでNSSの問題と思われる

結論

  • (ほぼ自己満足に近い)Linux Desktopのハードニングを実施
  • これで私の個人PCが盗まれてもSSHや証明書の秘密鍵は取り出せない・・・ハズ。
  • あ〜そういうことね、TPM完全に理解した
  • pkcs11を介さないことは何もしていない。
  • 困った時のArch wikiとかDebian user manual, RHELのドキュメント、やはりこれらはしっかりしてる
  • TPMはここにはかけないことを含めて本当に闇
© 2016 - 2024 DARK MATTER / Built with Hugo / テーマ StackJimmy によって設計されています。