產品介紹

HSM 之上:資產金鑰治理的四個設計取捨

私鑰放進 HSM 只解決金鑰不被拿走。誰能叫 HSM 簽章、要不要第二個人在場、事後能否證明記錄沒被改,需要另一層來回答。本文以 RWA KMS 為例,談雙人覆核、拆分知識、金鑰身分與稽核鏈的設計取捨。

作者:雲杉科技
#RWA#KMS#HSM#Maker-Checker#金鑰治理#黃金代幣
HSM 之上:資產金鑰治理的四個設計取捨

資產上鏈之後,私鑰就是資產本身。多數團隊的第一步是把私鑰放進 HSM,這一步沒有錯,但它只回答了一個問題:金鑰會不會被拿走。

HSM 不回答的問題更多。誰有權叫它簽章?簽的是什麼?需不需要第二個人在場?半年後稽核問起,怎麼證明記錄沒被改過?這些問題的答案不在硬體裡,要由 HSM 之上的一層來負責。

我們把這一層做成了 RWA KMS。這篇文章不介紹功能,只談做的過程中四個比較難的取捨,以及我們選了什麼、放棄了什麼。

頁首的圖是整體分工:呼叫端、治理層、金鑰策略層、HSM。治理層與金鑰策略層是我們自己的部分。底下的 HSM 是 Luna Network HSM,私鑰在此產生、使用與保存,上層只拿得到 handle。至於資產上鏈時電文與錢包金鑰如何分工,可以先讀〈黃金代幣參加機構的 KMS 實作參考〉。

取捨一:雙人覆核,該擋哪些,不該擋哪些

Maker-Checker 的直覺做法是「全部都擋」。我們沒有這樣做,因為擋太多,覆核人會變成按核准的機器,覆核就失去意義。

判準只有一條:這個操作會不會創造或動用簽章能力。

  • 建立錢包、對錢包簽章、刪除錢包、建立 HD 錢包、HD 錢包簽章:要覆核。
  • 衍生位址、匯出 xpub:不用覆核。兩者只回傳公開資料,核准與否都不改變誰能簽章。
  • 建立與停用使用者:刻意不覆核。系統第一次啟動時沒有任何使用者,若建立第一位管理員也要第二人核准,就是先有雞還是先有蛋的死結。

需要覆核的操作,呼叫後不會立刻執行,而是回 202:

{"action_id": "…", "status": "pending_approval"}

操作進入待覆核清單,發起人無法核准自己的請求,逾時未處理的請求自動作廢。

第二位 KeyManager 在現場輸入帳密核准或拒絕

有兩個細節值得一提。

第一,簽章結果只會出現在核准的那一次回應裡。所以所有能核准簽章的畫面,都必須把結果顯示出來,否則覆核人按下核准,簽章就憑空消失了。

核准後,簽章結果顯示在待覆核頁上方

第二,待覆核清單給覆核人看的是一份白名單摘要,不是原始請求的全部參數。漏寫某個動作的摘要,頂多是摘要含糊;把整包參數洩漏給覆核人,則是另一等級的錯誤。我們寧可前者。

服務帳號是例外。後端系統的批次簽章不可能等人按核准,所以持有對應 scope 的服務帳號直接執行。這個取捨把風險從「每次操作的人工把關」移到「scope 的最小化」:服務帳號一建立就要勾選 scope,沒有 scope 的 token 什麼都不能做,停用後所有 token 立即失效。

角色邊界也是同樣的考量。SystemAdmin 管人員,不碰金鑰;Auditor 看稽核,不碰金鑰,連看似唯讀的 verify 與 encrypt 都不行,因為它們一樣用到 HSM 金鑰。三種角色都掛在同一個判準上,由測試從 HTTP 層逐項驗證,避免各個端點各寫各的授權判斷,日久漂移。

取捨二:拆分知識,讓伺服器強制

MK 匯入儀式是另一種雙人機制:主金鑰拆成 A、B 兩個分量,由兩位保管人各自持有,任何一人都無法單獨還原金鑰。

操作手冊很容易寫「A 碼與 B 碼必須由不同人輸入」。問題是手冊不會擋人。同一個人用同一個登入狀態連輸兩次,系統若不拒絕,拆分知識就只是個形式。

我們的做法是讓伺服器記錄每個分量的提交者,同一人提交第二份時直接拒絕。這個決定帶來一個操作上的後果:B 碼保管人必須用自己的帳密,所以儀式對話框多了一組 B 保管人的帳密欄位。操作上多一步,換來限制真正成立。

MK 匯入儀式:A 碼、B 碼由不同人提交,第二人核准後匯入

儀式的最後一道檢查是 KCV(Key Check Value),也就是合成後主金鑰的檢查碼。保管人用它確認合成出來的是預期的那把金鑰。我們一度只在結果畫面顯示 KEY ID,卻在手冊裡要求操作人員核對 KCV,兩者湊不起來。現在結果卡會顯示 KCV。

KCV 在瀏覽器端用自行實作的 AES 計算,沒有用瀏覽器內建的 Web Crypto。原因很實際:Web Crypto 只在安全環境(HTTPS 或 localhost)下存在,而這類管理主控台常常放在機房內網,用 IP 加 http 開啟。

另外兩個和 KEY ID 有關的決定,也出自同樣的現實考量。

  • KEY ID 由發送金鑰的一方指派,必填,原樣用作 HSM 標籤與 JWE 標頭的 kid。我們曾經自動編號,但自己編的 ID,對方根本不認得。
  • 同一個 KEY ID 重複匯入是合法操作,因為對方會用同一個 ID 重新交付。所以重複匯入要明確勾選覆寫,且覆寫這個選擇本身列入覆核的參數。相對地,JWS 金鑰的 kid 重複一律回 409,沒有例外。兩種行為看似矛盾,其實各自對應不同的來源:前者是對方交付的,後者是我們自己產生的。

取捨三:金鑰身分用 CKA_ID,不用標籤

HSM 裡的物件有 handle、標籤與 CKA_ID 三種「名字」,只有最後一種能當身分。

標籤可以重複,可以改名,甚至可以不存在。我們在 partition 上就遇過多組同標籤的金鑰對,要判斷該留哪一組,根本是擲硬幣。

handle 更危險。刪除物件後,handle 會被回收再發給別的物件。拿著過期的 handle 操作,HSM 不會回報錯誤,而是對別人的物件執行操作,並回傳成功。沒有錯誤可以接,就沒有東西能讓你發現弄錯了。

所以我們的做法是:

  • 四條建立金鑰的路徑,都向 HSM 要 16 個隨機位元組寫入 CKA_ID,由 HSM 自己的亂數產生。公私鑰成對共用同一個 ID。
  • 資料庫裡錢包的 ID 就是這串位元組的十六進位表示,不另外編號。
  • 刪除、匯出這類破壞性或外洩性的操作,先驗證身分。對不上時回 409 而不是 500,因為呼叫端看到的是過時的世界,這個狀況不會自己好轉。
  • 暫時性的 session 物件(如暫時的 KEK)刻意不給 CKA_ID,用完即毀。

有兩個容易寫反的地方。用「最多回傳一個」的查詢取第一筆,不等於「恰好一個」,必須用會拒絕歧義的查詢。把十六進位字串直接寫進 CKA_ID 也是錯的:字串 "0a1b" 的 ASCII 是四個位元組,寫入看起來成功了,存的卻不是你要的值。

這項遷移尚未完成。舊金鑰仍靠標籤定位,所以身分驗證保留了標籤的退路,等回填完成才能拿掉。

取捨四:稽核記不到,操作就不做

稽核有四條紀律,每一條都是用一個取捨換來的。

寫入失敗就讓請求失敗。 如果稽核寫不進去、操作仍然成功,系統就留下一筆沒有記錄的成功。我們選擇拒絕這筆請求。只有在請求本來就要失敗的路徑上,才允許稽核寫入失敗而不影響結果。

記動作,不記金鑰材料。 MK 分量、KCV、密碼、client secret 都不進稽核內容。JOSE 操作只留指紋,也就是簽章或密文的最後 8 個位元組,足以把別處發現的 JWS 對回產生它的那次呼叫,卻無法還原任何東西。

驗證類的記錄,狀態記的是結論。 驗章的稽核狀態寫的是「驗證通過或不通過」,不是「這次呼叫有沒有完成」。否則稽核人員看到一排 success,會誤以為驗章全部通過。

授權被拒也記,但只記人。 一個過濾器統一記錄 403,往後新增的端點自動涵蓋,不必靠誰記得去加。只記真人、不記服務帳號:設定錯誤的服務帳號會不斷重試被拒的呼叫,把訊號淹掉。401 因為沒有可歸屬的主體,也不記。

記錄之間以 HMAC 串成一條鏈,金鑰放在 HSM 裡。資料庫層以觸發器阻擋 UPDATE 與 DELETE。稽核人員可以呼叫驗證端點重算每一筆,並比對前後串接是否吻合。

Auditor 驗證雜湊鏈完整性,重算每筆 HMAC 並比對前後串接

驗證只在 API 內做是不夠的,API 說鏈完整,你得先相信 API。所以我們另附一支腳本,繞過 API 直接讀資料庫,連「觸發器真的擋得住修改」都一併檢查。

這些取捨的代價

治理不是免費的。以下是我們已知、也選擇保留的限制。

  • 業務資料與稽核記錄放在兩個獨立資料庫。 兩者之間沒有跨庫交易,業務操作與它的稽核記錄無法同時成功或同時失敗。我們用上述「稽核寫入失敗即請求失敗」補強一半,剩下的視窗是明知的取捨。
  • HD 錢包沒有備份與還原機制。 seed 在 HSM 內產生,派生出 master 之後即銷毀,所以沒有任何備份路徑可言。代價是 HSM partition 遺失時,整棵金鑰樹無法重建。使用者介面在建立前就以警語寫明。
  • 服務帳號沒有 secret 輪替,也不能重新啟用。 需要換 secret,只能停用舊帳號、建立新帳號。
  • HD 錢包目前沒有刪除端點。

結語

回到開頭的問題:私鑰在 HSM 裡,然後呢?

答案是還需要一層,讓每一次簽章都有第二個人在場,讓拆分知識由系統而不是手冊來保證,讓金鑰的身分不會因為標籤重複或 handle 回收而認錯,也讓事後的稽核可以由外部驗證。

想看實際的操作畫面與功能範圍,請參考 RWA KMS 產品頁。若您正在規劃資產上鏈後的金鑰保管,歡迎與我們聯絡。