金鑰請求服務¶
金鑰請求服務是金鑰保留服務的一部分(參閱核心金鑰保留服務)。本文件更全面地解釋了請求演算法的工作原理。
該過程要麼始於核心透過呼叫request_key*()來請求服務
struct key *request_key(const struct key_type *type,
const char *description,
const char *callout_info);
或
struct key *request_key_tag(const struct key_type *type,
const char *description,
const struct key_tag *domain_tag,
const char *callout_info);
或
struct key *request_key_with_auxdata(const struct key_type *type,
const char *description,
const struct key_tag *domain_tag,
const char *callout_info,
size_t callout_len,
void *aux);
或
struct key *request_key_rcu(const struct key_type *type,
const char *description,
const struct key_tag *domain_tag);
要麼由使用者空間呼叫 request_key 系統呼叫
key_serial_t request_key(const char *type,
const char *description,
const char *callout_info,
key_serial_t dest_keyring);
這些訪問點之間的主要區別在於,核心介面不需要將金鑰連結到金鑰環以防止其立即被銷燬。核心介面直接返回指向金鑰的指標,銷燬金鑰的任務由呼叫者負責。
request_key_tag() 呼叫與核心中的request_key()類似,但它還接受一個域標籤,允許金鑰按名稱空間分離並作為組被銷燬。
request_key_with_auxdata() 呼叫與request_key_tag()呼叫類似,不同之處在於它們允許將輔助資料傳遞給上層呼叫者(預設為 NULL)。這僅對那些定義自己上層呼叫機制而非使用 /sbin/request-key 的金鑰型別有用。
request_key_rcu() 呼叫與request_key_tag()呼叫類似,但它不檢查正在構建的金鑰,也不嘗試構建缺失的金鑰。
使用者空間介面將金鑰連結到與程序關聯的金鑰環,以防止金鑰消失,並向呼叫者返回金鑰的序列號。
以下示例假定所涉及的金鑰型別沒有定義自己的上層呼叫機制。如果它們有,則應將這些機制替換為 /sbin/request-key 的 fork 和執行。
過程¶
請求按以下方式進行
程序 A 呼叫 request_key() [使用者空間系統呼叫會呼叫核心介面]。
request_key() 搜尋程序已訂閱的金鑰環,檢視是否存在合適的金鑰。如果存在,它將返回該金鑰。如果不存在,並且 callout_info 未設定,則返回錯誤。否則,過程進入下一步。
request_key() 看到程序 A 尚未擁有所需的金鑰,因此它建立兩樣東西
一個未例項化的金鑰 U,具有請求的型別和描述。
一個授權金鑰 V,它引用金鑰 U,並指出程序 A 是金鑰 U 應該被例項化和保護的上下文,並且可以從該上下文滿足相關的金鑰請求。
request_key() 然後 fork 並執行 /sbin/request-key,帶有一個包含指向授權金鑰 V 的連結的新會話金鑰環。
/sbin/request-key 假定與金鑰 U 關聯的許可權。
/sbin/request-key 執行一個合適的程式來執行實際的例項化。
該程式可能需要從 A 的上下文中訪問另一個金鑰(例如 Kerberos TGT 金鑰)。它只需請求相應的金鑰,金鑰環搜尋會注意到會話金鑰環在其底層包含授權金鑰 V。
這將允許它像程序 A 一樣,使用程序 A 的 UID、GID、組和安全資訊搜尋程序 A 的金鑰環,並找到金鑰 W。
然後程式執行必要的操作,以金鑰 W 為參考(或許它使用 TGT 聯絡 Kerberos 伺服器)獲取用於例項化金鑰 U 的資料,然後例項化金鑰 U。
在例項化金鑰 U 後,授權金鑰 V 會自動撤銷,因此不能再次使用。
然後程式以 0 退出,request_key() 刪除金鑰 V 並將金鑰 U 返回給呼叫者。
這也進一步擴充套件。如果金鑰 W(上述第 7 步)不存在,金鑰 W 將被建立為未例項化,另一個授權金鑰(X)將被建立(如第 3 步所示),並且 /sbin/request-key 的另一個副本將被生成(如第 4 步所示);但是授權金鑰 X 指定的上下文仍然是程序 A,就像授權金鑰 V 中一樣。
這是因為程序 A 的金鑰環不能簡單地附加到 /sbin/request-key 的適當位置,因為 (a) execve 會丟棄其中兩個,並且 (b) 它需要全程使用相同的 UID/GID/組。
負例項化與拒絕¶
除了例項化金鑰之外,授權金鑰的持有者還可以負例項化正在構建的金鑰。這是一個短期佔位符,如果被拒絕或指定錯誤,則任何在此期間重新請求金鑰的嘗試都將失敗並返回錯誤 ENOKEY。
提供此功能是為了防止對無法獲取的金鑰過度重複生成 /sbin/request-key 程序。
如果 /sbin/request-key 程序的退出程式碼非 0 或因訊號而終止,正在構建的金鑰將自動在短時間內被負例項化。
搜尋演算法¶
任何特定金鑰環的搜尋都按以下方式進行
當金鑰管理程式碼搜尋金鑰 (keyring_search_rcu) 時,它首先對其開始搜尋的金鑰環呼叫 key_permission(SEARCH)。如果此操作拒絕許可權,則不再進一步搜尋。
它考慮該金鑰環中的所有非金鑰環金鑰,如果任何金鑰符合指定條件,則對其呼叫 key_permission(SEARCH) 以檢視該金鑰是否允許被找到。如果允許,則返回該金鑰;否則,搜尋繼續,如果錯誤程式碼的優先順序高於當前設定的錯誤程式碼,則保留該錯誤程式碼。
然後,它考慮當前正在搜尋的金鑰環中的所有金鑰環型別金鑰。它對每個金鑰環呼叫 key_permission(SEARCH),如果此操作授予許可權,則遞迴,在該金鑰環上執行步驟 (2) 和 (3)。
一旦找到具有使用許可權的有效金鑰,該過程立即停止。任何來自先前匹配嘗試的錯誤都將被丟棄,並返回該金鑰。
當呼叫 request_key() 時,如果 CONFIG_KEYS_REQUEST_CACHE=y,則首先檢查每個任務的一個金鑰快取是否匹配。
當呼叫 search_process_keyrings() 時,它執行以下搜尋,直到其中一個成功
如果存在,搜尋程序的執行緒金鑰環。
如果存在,搜尋程序的程序金鑰環。
搜尋程序的會話金鑰環。
如果程序已獲得與 request_key() 授權金鑰關聯的許可權,則
如果存在,搜尋呼叫程序的執行緒金鑰環。
如果存在,搜尋呼叫程序的程序金鑰環。
搜尋呼叫程序的會話金鑰環。
一旦其中一個成功,所有待處理的錯誤都將被丟棄,並返回找到的金鑰。如果 CONFIG_KEYS_REQUEST_CACHE=y,則該金鑰被放置在每個任務的快取中,替換掉之前的金鑰。該快取會在退出時或在恢復使用者空間之前清除。
只有當所有這些都失敗時,整個操作才會以最高優先順序的錯誤失敗。請注意,有幾個錯誤可能來自 LSM。
錯誤優先順序為
EKEYREVOKED > EKEYEXPIRED > ENOKEY
EACCES/EPERM 僅在直接搜尋特定金鑰環且基礎金鑰環不授予搜尋許可權時返回。