Fedora 上使用硬體加密的 SSH 與 PGP

從一開始入手 Yubikey 4,後來換成 Yubikey 5 NFC,我已經很久沒有把 SSH 跟 PGP 的密鑰放在檔案系統裡過了。我最近新買了一隻 Yubikey 5 Nano,簡單設定後紀錄分享一下怎麼使用硬體加密的 SSH 和 PGP 簽章。

Yubikey 的原理是它有一個硬體的「安全元件 (secure element)」可以用來實做各種智慧卡 (smart card) 的功能,這裡我們關心的是它的 PIV (personal identity verification) 與 PGP 兩個智慧卡界面。

PIV 與 PKCS#11 與 PC/SC

PIV 是一種智慧卡的標準,定義了智慧卡需要支援的金鑰格式,演算法等等。Yubikey 的 PIV 支援標準的 9a, 9c, 9d, 9e 金鑰儲區,還有額外的 10 個以上的過期金鑰儲存位置。這些標準的金鑰儲存區分別用來「認證」、「簽章」、「加密」、「驗證」。雖然沒有強制規定這些金鑰要怎麼使用,通常 SSH 會設定為使用 9a 的「認證」用金鑰區來儲存 SSH 的私鑰。

SSH 不是直接使用 PIV 來使用智慧卡,而是支援另外一個標準 PKCS#11,這個標準定義了統一的抽象軟體界面來提供硬體的加密演算法功能。Yubico 有提供自己的 libykcs11.so 這個函式庫可以直接使用 Yubikey 上的 PIV 功能,而 Fedora, Debian 等系統則預設使用 OpenSC 的 opensc-pkcs11.so 這個函式庫。OpenSC 則仰賴 pcscd-lite 這個程式透過標準的 PC/SC 界面和各種不同的智慧卡溝通。

因為同一個系統上可以同時有多個不同的 PKCS#11 函式庫,Fedora 便用 p11-kit 的 p11-kit-proxy 來更簡單的使用不同的 PKCS#11 提供元。

PKCS#11 相關的軟體生態相當複雜,簡化為一張圖的話大概繪像這樣:

{{ figure(name=‘pkcs11-2023-03-05-1621-1.png’, ‘PKCS#11 生態練’) }}

實際上因為用了 p11-kit 的關係,所以設定起來其實非常簡單。首先先用 ykman 產生私鑰(預設是 RSA 2048 bit)跟公鑰(包裝為 X.509 憑證格式):

$ sudo dnf install yubikey-manager
$ ykman piv keys generate 9a public.pem
$ ykman piv certificates generate -s CN=SSH 9a public.pem

然後就可以使用了,可以用 ssh-keygen 把公鑰弄出來,然後設定 ssh 預設用 p11-kit-proxy 來存取私鑰:

$ ssh-keygen -D pkcs11: | tee ~/.ssh/id_rsa.pub
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC0tEUNb5QGKlGW8wcv+WYZCN1xKZE7NLFeCqj8QUbsfuobudTp8fZYCwtF8UwkwXpVTTg5RLdFOanp0V3ZDDVanyZEzopfhghcmZAmNJ5UoBZx+hOerlTHBmZNBJY0T8MzogQO60UluVYqOwcRynQfdV+F2oNrhqNph0BHIOgPTtlc5WO6R6VXcFBaHQROBDvB7hAi1MrIqZ5AoX9bI7thYbmzQuK6PYbFQEcSXrxyvnb4Xus7hmk6Vw7itn65/7JMKhgAz3jRR0HHAyRBB2pjqPxdNFOMDB3IieviiYOGvMlfZ2HaimQB8c6EvC8i+G3/KV/w5C8zP0VE+lfGhyHn PIV AUTH pubkey pkcs11:id=%01;object=PIV%20AUTH%20pubkey;token=SSH;manufacturer=piv_II?module-path=/usr/lib64/p11-kit-proxy.so

$ echo 'IdentityFile "pkcs11:"' > ~/.ssh/config
# 也可以把私鑰加到 ssh-agent 中
$ ssh-add -s pkcs11:

設定起來算是相當無腦(參考文章)。

GnuPG 與 scdaemon

我一開始用 Yubikey 主要就是為了它的硬體 PGP 智慧卡,搭配 GnuPG 可以實現簽章、加密、SSH 認證三個功能。這是一個常用的功能,而且網路上已經有許多的文件說明,這裡就不再重複。我想要紀錄的是如何讓 GnuPG 與 PIV 共存。

網路搜尋可以發現許多關於 gpg –card-status 不正常運作的問題:

$ gpg --card-status 
gpg: selecting openpgp failed: No such device
gpg: OpenPGP card not available: No such device

這是因為 GnuPG 跟 OpenSC / pcscd 都需要透過 CCID 這個 USB 通訊協定跟智慧卡溝通,但是 GnuPG 用來實現這個功能的工具程式 scdaemon 預設會開啟獨占模式,不允許其他的程式也使用系統上的智慧卡,理由是 scdaemon 會暫時快取一些狀態在記憶體裡面,如果其他程式也同時使用智慧卡的話,有可能會造成狀態不一致的錯誤。相關的討論中如 T6218T2440 可以看到 GnuPG 的開發者並不願意改變這個預設行為。

網路上有許多很久以前的解法,甚至 pcsc-lite 的作者都有一篇 blog 說明這個問題。我測試後現在可以暫時解決這個問題的方法是強制 scdaemon 以共享模式使用 pscsd 的資源:

# $HOME/.gnupg/scdaemon.conf
disable-ccid
pcsc-shared

這樣就可以讓 gpg 幾乎都可以正常運作。如果還是有問題,跑指令

$ gpgconf --kill all

也幾乎都可以解決。

基於現在 Debian 還是只能用 PGP 來認證上傳的套件,希望 GnuPG 可以改善 scdaemon 的預設行為,跟其他軟體和平相處。▞