核心金鑰保留服務

此服務允許將加密金鑰、身份驗證令牌、跨域使用者對映等快取在核心中,以供檔案系統和其他核心服務使用。

允許使用金鑰環;金鑰環是一種特殊的金鑰,可以儲存指向其他金鑰的連結。程序各自具有三個標準金鑰環訂閱,核心服務可以搜尋這些訂閱以查詢相關的金鑰。

可以透過啟用以下選項配置金鑰服務:

“安全選項”/“啟用訪問金鑰保留支援”(CONFIG_KEYS)

本文件包含以下部分

金鑰概述

在此上下文中,金鑰表示加密資料單元、身份驗證令牌、金鑰環等。它們在核心中由 struct key 表示。

每個金鑰都有許多屬性

  • 一個序列號。

  • 一個型別。

  • 一個描述(用於在搜尋中匹配金鑰)。

  • 訪問控制資訊。

  • 一個過期時間。

  • 一個有效負載。

  • 狀態。

  • 每個金鑰都會被分配一個型別為 key_serial_t 的序列號,該序列號在該金鑰的生命週期內是唯一的。所有序列號都是正的非零 32 位整數。

    使用者空間程式可以使用金鑰的序列號來訪問它,但需要進行許可權檢查。

  • 每個金鑰都屬於定義的“型別”。在新增或使用該型別的金鑰之前,核心服務(如檔案系統)必須在核心中註冊型別。使用者空間程式不能直接定義新型別。

    金鑰型別在核心中由 struct key_type 表示。這定義了可以對該型別的金鑰執行的許多操作。

    如果從系統中刪除了某個型別,則該型別的所有金鑰都將失效。

  • 每個金鑰都有一個描述。這應該是一個可列印的字串。金鑰型別提供了一個操作,用於在金鑰的描述和條件字串之間執行匹配。

  • 每個金鑰都有一個所有者使用者 ID、一個組 ID 和一個許可權掩碼。這些用於控制程序可以從使用者空間對金鑰執行的操作,以及核心服務是否能夠找到該金鑰。

  • 每個金鑰都可以設定為在特定時間過期,由金鑰型別的例項化函式設定。金鑰也可以是不朽的。

  • 每個金鑰都可以有一個有效負載。這是代表實際“金鑰”的資料量。對於金鑰環,這是一個金鑰環連結到的金鑰列表;對於使用者定義的金鑰,它是一個任意的資料塊。

    不需要具有有效負載;實際上,有效負載可以只是儲存在 struct key 本身中的一個值。

    當例項化金鑰時,會使用資料塊呼叫金鑰型別的例項化函式,然後以某種方式建立金鑰的有效負載。

    同樣,當用戶空間想要讀取金鑰的內容時,如果允許,將呼叫另一個金鑰型別操作,以將金鑰附加的有效負載轉換回資料塊。

  • 每個金鑰都可以處於多個基本狀態之一

    • 未例項化。金鑰存在,但沒有附加任何資料。從使用者空間請求的金鑰將處於此狀態。

    • 已例項化。這是正常狀態。金鑰已完全形成,並已附加資料。

    • 否定。這是一個相對短暫的狀態。金鑰充當說明先前對使用者空間的呼叫失敗的註釋,並充當金鑰查詢的限制。可以將否定金鑰更新為正常狀態。

    • 已過期。金鑰可以設定生命週期。如果超過了其生命週期,它們將轉換為此狀態。可以將過期的金鑰更新回正常狀態。

    • 已撤銷。金鑰由使用者空間操作置於此狀態。無法找到或對其進行操作(除了取消連結之外)。

    • 已死亡。金鑰的型別已取消註冊,因此該金鑰現在無用。

最後三種狀態的金鑰需要進行垃圾回收。請參閱“垃圾回收”部分。

金鑰服務概述

除了金鑰之外,金鑰服務還提供許多功能

  • 金鑰服務定義了三種特殊金鑰型別

    (+)“金鑰環”

    金鑰環是包含其他金鑰列表的特殊金鑰。可以使用各種系統呼叫來修改金鑰環列表。建立金鑰環時,不應為其指定有效負載。

    (+)“使用者”

    此型別的金鑰具有描述和有效負載,它們是任意的資料塊。這些可以由使用者空間建立、更新和讀取,並且不打算供核心服務使用。

    (+)“登入”

    與“使用者”金鑰一樣,“登入”金鑰的有效負載也是任意的資料塊。它旨在儲存核心可以訪問但使用者空間程式無法訪問的機密。

    該描述可以是任意的,但必須以描述金鑰“子類”的非零長度字串作為字首。子類與描述的其餘部分用“:”分隔。可以從使用者空間建立和更新“登入”金鑰,但只能從核心空間讀取有效負載。

  • 每個程序都訂閱三個金鑰環:一個執行緒特定的金鑰環、一個程序特定的金鑰環和一個會話特定的金鑰環。

    當發生任何型別的 clone、fork、vfork 或 execve 時,執行緒特定的金鑰環將從子程序中丟棄。僅在需要時才建立新的金鑰環。

    除非提供了 CLONE_THREAD,否則在 clone、fork、vfork 時,程序特定的金鑰環將替換為子程序中的空金鑰環,在這種情況下,它將被共享。execve 也會丟棄程序的程序金鑰環並建立一個新的金鑰環。

    會話特定的金鑰環在 clone、fork、vfork 和 execve 中是持久的,即使後者執行 set-UID 或 set-GID 二進位制檔案也是如此。但是,程序可以使用 PR_JOIN_SESSION_KEYRING 將其當前會話金鑰環替換為新的金鑰環。允許請求匿名的新金鑰環,或嘗試建立或加入具有特定名稱的金鑰環。

    當執行緒的實際 UID 和 GID 發生更改時,執行緒金鑰環的所有權也會隨之更改。

  • 系統中存在的每個使用者 ID 都擁有兩個特殊的金鑰環:一個使用者特定的金鑰環和一個預設使用者會話金鑰環。預設會話金鑰環使用指向使用者特定金鑰環的連結進行初始化。

    當程序更改其真實 UID 時,如果它以前沒有會話金鑰,它將被訂閱到新 UID 的預設會話金鑰。

    如果程序嘗試訪問其會話金鑰但它沒有會話金鑰,它將被訂閱到其當前 UID 的預設金鑰。

  • 每個使用者都有兩個配額,用於跟蹤他們擁有的金鑰。一個限制金鑰和金鑰環的總數,另一個限制可以消耗的描述和有效負載空間的總量。

    使用者可以透過 procfs 檔案檢視有關此和其他統計資訊的資訊。root 使用者也可以透過 sysctl 檔案更改配額限制(請參閱“新的 procfs 檔案”部分)。

    程序特定和執行緒特定的金鑰環不計入使用者的配額。

    如果以某種方式修改金鑰或金鑰環的系統呼叫將使該使用者超出配額,則該操作將被拒絕並返回錯誤 EDQUOT。

  • 有一個系統呼叫介面,使用者空間程式可以透過該介面建立和操作金鑰和金鑰環。

  • 有一個核心介面,服務可以透過該介面註冊型別和搜尋金鑰。

  • 有一種方法可以使從核心完成的搜尋回撥到使用者空間,以請求無法在程序的金鑰環中找到的金鑰。

  • 可以透過可選的檔案系統檢視和操作金鑰資料庫。

金鑰訪問許可權

金鑰具有所有者使用者 ID、組訪問 ID 和許可權掩碼。該掩碼對於所有者、使用者、組和其他訪問許可權分別具有最多 8 位。僅定義了每組 8 位中的 6 位。授予的這些許可權是

  • 檢視

    這允許檢視金鑰或金鑰環的屬性,包括金鑰型別和描述。

  • 讀取

    這允許檢視金鑰的有效負載或金鑰環的連結金鑰列表。

  • 寫入

    這允許例項化或更新金鑰的有效負載,或者允許將連結新增到金鑰環或從金鑰環中刪除連結。

  • 搜尋

    這允許搜尋金鑰環並找到金鑰。搜尋只能遞迴到設定了搜尋許可權的巢狀金鑰環中。

  • 連結

    這允許將金鑰或金鑰環連結到。要建立從金鑰環到金鑰的連結,程序必須具有金鑰環的寫入許可權和金鑰的連結許可權。

  • 設定屬性

    這允許更改金鑰的 UID、GID 和許可權掩碼。

對於更改所有權、組 ID 或許可權掩碼,擁有金鑰的所有者身份或具有 sysadmin 功能就足夠了。

SELinux 支援

安全類“key”已新增到 SELinux,以便可以將強制訪問控制應用於在各種上下文中建立的金鑰。此支援是初步的,並且很可能在不久的將來發生重大變化。當前,SELinux 中也提供了上面解釋的所有基本許可權;僅在執行了所有基本許可權檢查後才呼叫 SELinux。

檔案 /proc/self/attr/keycreate 的值會影響新建立金鑰的標記。如果該檔案的內容對應於 SELinux 安全上下文,則將為金鑰分配該上下文。否則,將為金鑰分配呼叫金鑰建立請求的任務的當前上下文。必須授予任務明確的許可權,才能使用金鑰安全類中的“建立”許可權,將特定的上下文分配給新建立的金鑰。

如果登入程式已進行檢測以在登入過程中正確初始化 keycreate,則與使用者關聯的預設金鑰環將被標記為使用者的預設上下文。否則,它們將被標記為登入程式本身的上下文。

但是請注意,與 root 使用者關聯的預設金鑰環被標記為預設核心上下文,因為它們是在啟動過程的早期建立的,此時 root 使用者尚未有機會登入。

與新執行緒關聯的金鑰環均標有其關聯執行緒的上下文,並且會話和程序金鑰環的處理方式也類似。

新的 ProcFS 檔案

管理員已將兩個檔案新增到 procfs 中,管理員可以透過這兩個檔案瞭解金鑰服務的狀態

  • /proc/keys

    這列出了當前可以被讀取檔案的任務檢視的金鑰,並提供了有關其型別、描述和許可權的資訊。無法透過這種方式檢視金鑰的有效負載,儘管可能會提供有關它的一些資訊。

    該列表中僅包含那些向讀取程序授予檢視許可權的金鑰,無論它是否擁有這些金鑰。請注意,LSM 安全檢查仍在執行,並且可能會進一步過濾掉當前程序未被授權檢視的金鑰。

    檔案的內容如下所示

    SERIAL   FLAGS  USAGE EXPY PERM     UID   GID   TYPE      DESCRIPTION: SUMMARY
    00000001 I-----    39 perm 1f3f0000     0     0 keyring   _uid_ses.0: 1/4
    00000002 I-----     2 perm 1f3f0000     0     0 keyring   _uid.0: empty
    00000007 I-----     1 perm 1f3f0000     0     0 keyring   _pid.1: empty
    0000018d I-----     1 perm 1f3f0000     0     0 keyring   _pid.412: empty
    000004d2 I--Q--     1 perm 1f3f0000    32    -1 keyring   _uid.32: 1/4
    000004d3 I--Q--     3 perm 1f3f0000    32    -1 keyring   _uid_ses.32: empty
    00000892 I--QU-     1 perm 1f000000     0     0 user      metal:copper: 0
    00000893 I--Q-N     1  35s 1f3f0000     0     0 user      metal:silver: 0
    00000894 I--Q--     1  10h 003f0000     0     0 user      metal:gold: 0
    

    標誌是

    I       Instantiated
    R       Revoked
    D       Dead
    Q       Contributes to user's quota
    U       Under construction by callback to userspace
    N       Negative key
    
  • /proc/key-users

    此檔案列出了系統上至少有一個金鑰的每個使用者的跟蹤資料。此類資料包括配額資訊和統計資訊

    [root@andromeda root]# cat /proc/key-users
    0:     46 45/45 1/100 13/10000
    29:     2 2/2 2/100 40/10000
    32:     2 2/2 2/100 40/10000
    38:     2 2/2 2/100 40/10000
    

    每行的格式為

    <UID>:                  User ID to which this applies
    <usage>                 Structure refcount
    <inst>/<keys>           Total number of keys and number instantiated
    <keys>/<max>            Key count quota
    <bytes>/<max>           Key size quota
    

還添加了四個新的 sysctl 檔案,用於控制金鑰的配額限制

  • /proc/sys/kernel/keys/root_maxkeys /proc/sys/kernel/keys/root_maxbytes

    這些檔案儲存了 root 使用者可以擁有的最大金鑰數以及 root 使用者可以在這些金鑰中儲存的最大資料位元組總數。

  • /proc/sys/kernel/keys/maxkeys /proc/sys/kernel/keys/maxbytes

    這些檔案儲存了每個非 root 使用者可以擁有的最大金鑰數以及每個使用者可以在其金鑰中儲存的最大資料位元組總數。

root 使用者可以透過將每個新的限制作為十進位制數字字串寫入相應的檔案來更改這些限制。

使用者空間系統呼叫介面

使用者空間可以透過三個新的 syscall 直接操作金鑰:add_key、request_key 和 keyctl。後者提供了許多用於操作金鑰的函式。

當直接引用金鑰時,使用者空間程式應使用金鑰的序列號(一個正的 32 位整數)。但是,有一些特殊值可用於引用與進行呼叫的程序相關的特殊金鑰和金鑰環

CONSTANT                        VALUE   KEY REFERENCED
==============================  ======  ===========================
KEY_SPEC_THREAD_KEYRING         -1      thread-specific keyring
KEY_SPEC_PROCESS_KEYRING        -2      process-specific keyring
KEY_SPEC_SESSION_KEYRING        -3      session-specific keyring
KEY_SPEC_USER_KEYRING           -4      UID-specific keyring
KEY_SPEC_USER_SESSION_KEYRING   -5      UID-session keyring
KEY_SPEC_GROUP_KEYRING          -6      GID-specific keyring
KEY_SPEC_REQKEY_AUTH_KEY        -7      assumed request_key()
                                          authorisation key

主要的 syscall 是

  • 建立一個給定型別、描述和有效負載的新金鑰,並將其新增到指定的金鑰環

    key_serial_t add_key(const char *type, const char *desc,
                         const void *payload, size_t plen,
                         key_serial_t keyring);
    

    如果金鑰環中已存在與建議的金鑰型別和描述相同的金鑰,這將嘗試使用給定的有效負載更新它,或者如果金鑰型別不支援該功能,則將返回錯誤 EEXIST。程序還必須具有寫入要更新的金鑰的許可權。新金鑰將授予所有使用者許可權,而不會授予任何組或第三方許可權。

    否則,這將嘗試建立指定型別和描述的新金鑰,並使用提供的有效負載例項化它,並將其附加到金鑰環。在這種情況下,如果該程序沒有寫入金鑰環的許可權,則會生成一個錯誤。

    如果金鑰型別支援,如果描述為 NULL 或空字串,則金鑰型別將嘗試從有效負載的內容生成描述。

    有效負載是可選的,如果型別不需要,則指標可以為 NULL。有效負載的大小為 plen,對於空有效負載,plen 可以為零。

    可以透過將型別設定為“keyring”、金鑰環名稱設定為描述(或 NULL)並將有效負載設定為 NULL 來生成新的金鑰環。

    可以透過指定型別“user”來建立使用者定義的金鑰。建議使用者定義的金鑰的描述以型別 ID 和冒號作為字首,例如 Kerberos 5 票證授予票證的“krb5tgt:”。

    任何其他型別必須已由核心服務(例如檔案系統)提前在核心中註冊。

    如果成功,將返回新的或更新的金鑰的 ID。

  • 在程序的金鑰環中搜索金鑰,可能會呼叫使用者空間來建立它

    key_serial_t request_key(const char *type, const char *description,
                             const char *callout_info,
                             key_serial_t dest_keyring);
    

    此函式按照執行緒、程序、會話的順序搜尋程序的所有金鑰環以查詢匹配的金鑰。這非常類似於 KEYCTL_SEARCH,包括將發現的金鑰可選地附加到金鑰環。

    如果找不到金鑰,並且 callout_info 不為 NULL,則將呼叫 /sbin/request-key 以嘗試獲取金鑰。callout_info 字串將作為引數傳遞給程式。

    要將金鑰連結到目標金鑰環,金鑰必須授予呼叫者連結金鑰的許可權,並且金鑰環必須授予寫入許可權。

    另請參閱 金鑰請求服務

keyctl syscall 函式是

  • 將特殊金鑰 ID 對映到此程序的真實金鑰 ID

    key_serial_t keyctl(KEYCTL_GET_KEYRING_ID, key_serial_t id,
                        int create);
    

    查詢由“id”指定的特殊金鑰(並在必要時建立金鑰),如果金鑰或金鑰環存在,則返回金鑰或金鑰環的 ID。

    如果金鑰尚不存在,如果“create”非零,則將建立金鑰;如果“create”為零,則將返回錯誤 ENOKEY。

  • 將此程序訂閱的會話金鑰環替換為新的金鑰環

    key_serial_t keyctl(KEYCTL_JOIN_SESSION_KEYRING, const char *name);
    

    如果 name 為 NULL,則建立一個匿名的金鑰環,並將其作為會話金鑰環附加到程序,從而替換舊的會話金鑰環。

    如果 name 不為 NULL,如果存在該名稱的金鑰環,則程序會嘗試將其作為會話金鑰環附加,如果該操作不被允許,則返回錯誤;否則,將建立一個該名稱的新金鑰環並將其作為會話金鑰環附加。

    要附加到命名的金鑰環,金鑰環必須具有該程序所有權的搜尋許可權。

    如果成功,將返回新會話金鑰環的 ID。

  • 更新指定的金鑰

    long keyctl(KEYCTL_UPDATE, key_serial_t key, const void *payload,
                size_t plen);
    

    這將嘗試使用給定的有效負載更新指定的金鑰,或者如果金鑰型別不支援該功能,則將返回錯誤 EOPNOTSUPP。程序還必須具有寫入金鑰的許可權才能更新它。

    有效負載的長度為 plen,並且可能像 add_key() 一樣不存在或為空。

  • 撤銷金鑰

    long keyctl(KEYCTL_REVOKE, key_serial_t key);
    

    這使金鑰無法用於進一步的操作。進一步使用金鑰的嘗試將遇到錯誤 EKEYREVOKED,並且將不再可以找到該金鑰。

  • 更改金鑰的所有權

    long keyctl(KEYCTL_CHOWN, key_serial_t key, uid_t uid, gid_t gid);
    

    此函式允許更改金鑰的所有者和組 ID。可以將 uid 或 gid 中的任何一個設定為 -1 以阻止該更改。

    只有超級使用者才能將金鑰的所有者更改為金鑰當前所有者之外的其他所有者。同樣,只有超級使用者才能將金鑰的組 ID 更改為呼叫程序的組 ID 或其組成員之外的其他組 ID。

  • 更改金鑰上的許可權掩碼

    long keyctl(KEYCTL_SETPERM, key_serial_t key, key_perm_t perm);
    

    此函式允許金鑰的所有者或超級使用者更改金鑰上的許可權掩碼。

    僅允許使用可用位;如果設定了任何其他位,則將返回錯誤 EINVAL。

  • 描述金鑰

    long keyctl(KEYCTL_DESCRIBE, key_serial_t key, char *buffer,
                size_t buflen);
    

    此函式以字串形式返回金鑰屬性(但不包括其有效負載資料)的摘要,放在提供的緩衝區中。

    除非發生錯誤,否則它始終返回它可以生成的資料量,即使該資料量對於緩衝區而言太大也是如此,但它不會將超過請求的資料複製到使用者空間。如果緩衝區指標為 NULL,則不會發生任何複製。

    程序必須具有金鑰的檢視許可權才能使此函式成功。

    如果成功,則將字串放置在緩衝區中,格式如下

    <type>;<uid>;<gid>;<perm>;<description>
    

    其中 type 和 description 是字串,uid 和 gid 是十進位制,perm 是十六進位制。如果緩衝區足夠大,則在字串末尾包含一個 NUL 字元。

    可以使用以下命令進行解析

    sscanf(buffer, "%[^;];%d;%d;%o;%s", type, &uid, &gid, &mode, desc);
    
  • 清除金鑰環

    long keyctl(KEYCTL_CLEAR, key_serial_t keyring);
    

    此函式清除附加到金鑰環的金鑰列表。呼叫程序必須對金鑰環具有寫入許可權,並且它必須是金鑰環(否則將導致錯誤 ENOTDIR)。

    如果使用者具有 CAP_SYS_ADMIN 功能,則此函式還可以用於清除特殊核心金鑰環(如果它們已正確標記)。DNS 解析器快取金鑰環就是此類金鑰環的一個示例。

  • 將金鑰連結到金鑰環中

    long keyctl(KEYCTL_LINK, key_serial_t keyring, key_serial_t key);
    

    此函式建立從金鑰環到金鑰的連結。該程序必須對金鑰環具有寫入許可權,並且必須對金鑰具有連結許可權。

    如果金鑰環不是金鑰環,則將導致錯誤 ENOTDIR;如果金鑰環已滿,則將導致錯誤 ENFILE。

    連結過程會檢查金鑰環的巢狀,如果它看起來太深,則返回 ELOOP,如果連結會引入迴圈,則返回 EDEADLK。

    金鑰環中型別和描述與新金鑰匹配的金鑰的任何連結都將從金鑰環中丟棄,因為添加了新金鑰。

  • 將金鑰從一個金鑰環移動到另一個金鑰環

    long keyctl(KEYCTL_MOVE,
                key_serial_t id,
                key_serial_t from_ring_id,
                key_serial_t to_ring_id,
                unsigned int flags);
    

    將由“id”指定的金鑰從由“from_ring_id”指定的金鑰環移動到由“to_ring_id”指定的金鑰環。如果兩個金鑰環相同,則不執行任何操作。

    “flags”可以在其中設定 KEYCTL_MOVE_EXCL,以使操作在目標金鑰環中存在匹配的金鑰時失敗,並顯示 EEXIST,否則此類金鑰將被替換。

    程序必須對該金鑰具有連結許可權才能使此函式成功,並且必須對兩個金鑰環都具有寫入許可權。KEYCTL_LINK 中可能發生的任何錯誤也適用於此處的目標金鑰環。

  • 取消金鑰或金鑰環與另一個金鑰環的連結

    long keyctl(KEYCTL_UNLINK, key_serial_t keyring, key_serial_t key);
    

    此函式在金鑰環中查詢指定金鑰的第一個連結,如果找到則將其刪除。忽略該金鑰的後續連結。程序必須對金鑰環具有寫入許可權。

    如果金鑰環不是金鑰環,則將導致錯誤 ENOTDIR;如果金鑰不存在,則將導致錯誤 ENOENT。

  • 在金鑰環樹中搜索金鑰

    key_serial_t keyctl(KEYCTL_SEARCH, key_serial_t keyring,
                        const char *type, const char *description,
                        key_serial_t dest_keyring);
    

    這會搜尋由指定金鑰環引導的金鑰環樹,直到找到與型別和描述條件匹配的金鑰。在遞迴到其子金鑰環之前,會檢查每個金鑰環的金鑰。

    程序必須對頂級金鑰環具有搜尋許可權,否則將導致錯誤 EACCES。僅會遞迴到程序具有搜尋許可權的金鑰環中,並且僅可以匹配程序具有搜尋許可權的金鑰和金鑰環。如果指定的金鑰環不是金鑰環,則將導致 ENOTDIR。

    如果搜尋成功,該函式將嘗試將找到的金鑰連結到目標金鑰環(如果提供了金鑰環(非零 ID))。所有適用於 KEYCTL_LINK 的約束也適用於這種情況。

    如果搜尋失敗,將返回錯誤 ENOKEY、EKEYREVOKED 或 EKEYEXPIRED。如果成功,將返回結果金鑰 ID。

  • 從金鑰中讀取有效負載資料

    long keyctl(KEYCTL_READ, key_serial_t keyring, char *buffer,
                size_t buflen);
    

    此函式嘗試將指定金鑰中的有效負載資料讀取到緩衝區中。程序必須具有金鑰的讀取許可權才能成功。

    將處理返回的資料以供金鑰型別呈現。例如,金鑰環將返回一個 key_serial_t 條目的陣列,表示它訂閱的所有金鑰的 ID。使用者定義的金鑰型別將按原樣返回其資料。如果金鑰型別未實現此函式,則將導致錯誤 EOPNOTSUPP。

    如果指定的緩衝區太小,則將返回所需緩衝區的大小。請注意,在這種情況下,緩衝區的內容可能已以某種未定義的方式被覆蓋。

    否則,如果成功,該函式將返回複製到緩衝區中的資料量。

  • 例項化部分構造的金鑰

    long keyctl(KEYCTL_INSTANTIATE, key_serial_t key,
                const void *payload, size_t plen,
                key_serial_t keyring);
    long keyctl(KEYCTL_INSTANTIATE_IOV, key_serial_t key,
                const struct iovec *payload_iov, unsigned ioc,
                key_serial_t keyring);
    

    如果核心回撥到使用者空間以完成金鑰的例項化,則使用者空間應使用此呼叫在呼叫的程序返回之前為金鑰提供資料,否則金鑰將自動標記為否定。

    程序必須具有金鑰的寫入許可權才能例項化它,並且金鑰必須是未例項化的。

    如果指定了金鑰環(非零),則該金鑰也將連結到該金鑰環中,但是所有適用於 KEYCTL_LINK 的約束也適用於這種情況。

    payload 和 plen 引數描述了有效負載資料,如 add_key() 所示。

    payload_iov 和 ioc 引數描述了 iovec 陣列中的有效負載資料,而不是單個緩衝區。

  • 以否定方式例項化部分構造的金鑰

    long keyctl(KEYCTL_NEGATE, key_serial_t key,
                unsigned timeout, key_serial_t keyring);
    long keyctl(KEYCTL_REJECT, key_serial_t key,
                unsigned timeout, unsigned error, key_serial_t keyring);
    

    如果核心回撥到使用者空間以完成金鑰的例項化,則使用者空間應在呼叫的程序返回之前使用此呼叫將金鑰標記為否定,如果它無法滿足請求。

    程序必須具有金鑰的寫入許可權才能例項化它,並且金鑰必須是未例項化的。

    如果指定了金鑰環(非零),則該金鑰也將連結到該金鑰環中,但是所有適用於 KEYCTL_LINK 的約束也適用於這種情況。

    如果金鑰被拒絕,則對它的未來搜尋將返回指定的錯誤程式碼,直到被拒絕的金鑰過期。否定金鑰與以 ENOKEY 作為錯誤程式碼拒絕金鑰相同。

  • 設定預設的請求金鑰目標金鑰環

    long keyctl(KEYCTL_SET_REQKEY_KEYRING, int reqkey_defl);
    

    這將設定此執行緒的隱式請求金鑰將附加到的預設金鑰環。reqkey_defl 應該是以下常量之一

    CONSTANT                                VALUE   NEW DEFAULT KEYRING
    ======================================  ======  =======================
    KEY_REQKEY_DEFL_NO_CHANGE               -1      No change
    KEY_REQKEY_DEFL_DEFAULT                 0       Default[1]
    KEY_REQKEY_DEFL_THREAD_KEYRING          1       Thread keyring
    KEY_REQKEY_DEFL_PROCESS_KEYRING         2       Process keyring
    KEY_REQKEY_DEFL_SESSION_KEYRING         3       Session keyring
    KEY_REQKEY_DEFL_USER_KEYRING            4       User keyring
    KEY_REQKEY_DEFL_USER_SESSION_KEYRING    5       User session keyring
    KEY_REQKEY_DEFL_GROUP_KEYRING           6       Group keyring
    

    如果成功,將返回舊的預設值,如果 reqkey_defl 不是上述值之一,則將返回錯誤 EINVAL。

    可以使用指示給 request_key() 系統呼叫的金鑰環來覆蓋預設金鑰環。

    請注意,此設定會在 fork/exec 中繼承。

    [1] 預設值為:如果存線上程金鑰環,則為執行緒金鑰環;否則,如果存在程序金鑰環,則為程序金鑰環;否則,如果存在會話金鑰環,則為會話金鑰環;否則,為使用者預設會話金鑰環。

  • 設定金鑰的超時

    long keyctl(KEYCTL_SET_TIMEOUT, key_serial_t key, unsigned timeout);
    

    這會設定或清除金鑰的超時。超時可以為 0 以清除超時,也可以是秒數以將過期時間設定為未來那麼遠。

    程序必須對金鑰具有屬性修改訪問許可權才能設定其超時。不能使用此函式在否定、撤銷或過期的金鑰上設定超時。

  • 假定授予例項化金鑰的許可權

    long keyctl(KEYCTL_ASSUME_AUTHORITY, key_serial_t key);
    

    這假定或剝奪了例項化指定金鑰所需的許可權。只有執行緒在其金鑰環中的某個位置具有與指定金鑰關聯的授權金鑰,才能假定授權。

    假定授權後,對金鑰的搜尋也將使用請求者的安全標籤、UID、GID 和組來搜尋請求者的金鑰環。

    如果請求的許可權不可用,則將返回錯誤 EPERM,同樣,如果因為目標金鑰已例項化而撤銷了許可權,也會返回錯誤 EPERM。

    如果指定的金鑰為 0,則將剝奪任何假定的許可權。

    假定的授權金鑰會在 fork 和 exec 中繼承。

  • 獲取附加到金鑰的 LSM 安全上下文

    long keyctl(KEYCTL_GET_SECURITY, key_serial_t key, char *buffer,
                size_t buflen)
    

    此函式返回一個字串,該字串表示附加到提供的緩衝區中的金鑰的 LSM 安全上下文。

    除非發生錯誤,否則它始終返回它可以生成的資料量,即使該資料量對於緩衝區而言太大也是如此,但它不會將超過請求的資料複製到使用者空間。如果緩衝區指標為 NULL,則不會發生任何複製。

    如果緩衝區足夠大,則字串末尾會包含一個 NUL 字元。這包含在返回的計數中。如果未強制執行 LSM,則將返回空字串。

    程序必須具有金鑰的檢視許可權才能使此函式成功。

  • 在其父程序上安裝呼叫程序的會話金鑰環

    long keyctl(KEYCTL_SESSION_TO_PARENT);
    

    此函式嘗試將呼叫程序的會話金鑰環安裝到其父程序上,替換父程序當前的會話金鑰環。

    呼叫程序必須與其父程序具有相同的屬主,金鑰環必須與呼叫程序具有相同的屬主,呼叫程序必須具有金鑰環的 LINK 許可權,並且活躍的 LSM 模組不能拒絕許可權,否則將返回錯誤 EPERM。

    如果記憶體不足以完成操作,將返回錯誤 ENOMEM,否則將返回 0 以表示成功。

    金鑰環將在父程序下次離開核心並恢復執行使用者空間時被替換。

  • 使金鑰失效

    long keyctl(KEYCTL_INVALIDATE, key_serial_t key);
    

    此函式將金鑰標記為無效,然後喚醒垃圾回收器。垃圾回收器會立即從所有金鑰環中移除無效金鑰,並在其引用計數達到零時刪除該金鑰。

    被標記為無效的金鑰會立即對正常的金鑰操作不可見,但它們在刪除之前仍然在 /proc/keys 中可見(它們被標記為“i”標誌)。

    程序必須具有金鑰的搜尋許可權,此函式才能成功。

  • 計算 Diffie-Hellman 共享金鑰或公鑰

    long keyctl(KEYCTL_DH_COMPUTE, struct keyctl_dh_params *params,
                char *buffer, size_t buflen, struct keyctl_kdf_params *kdf);
    

    params 結構包含三個金鑰的序列號

    - The prime, p, known to both parties
    - The local private key
    - The base integer, which is either a shared generator or the
      remote public key
    

    計算出的值為

    result = base ^ private (mod prime)
    

    如果 base 是共享生成器,則結果是本地公鑰。如果 base 是遠端公鑰,則結果是共享金鑰。

    如果引數 kdf 為 NULL,則適用以下內容

    • 緩衝區長度必須至少為素數的長度,或為零。

    • 如果緩衝區長度非零,則在成功計算並複製到緩衝區時,將返回結果的長度。當緩衝區長度為零時,將返回所需的最小緩衝區長度。

    kdf 引數允許呼叫者在 Diffie-Hellman 計算中應用金鑰派生函式 (KDF),其中僅將 KDF 的結果返回給呼叫者。KDF 的特徵由 struct keyctl_kdf_params 定義如下

    • char *hashname 指定 NUL 終止的字串,該字串標識核心加密 API 中使用的雜湊,並應用於 KDF 操作。KDF 實現符合 SP800-56A 以及 SP800-108(計數器 KDF)。

    • char *otherinfo 指定 SP800-56A 第 5.8.1.2 節中記錄的 OtherInfo 資料。緩衝區長度由 otherinfolen 給出。OtherInfo 的格式由呼叫者定義。如果不需要使用 OtherInfo,則 otherinfo 指標可以為 NULL。

    如果金鑰型別不受支援,此函式將返回錯誤 EOPNOTSUPP;如果找不到金鑰,則返回錯誤 ENOKEY;如果呼叫者不可讀取該金鑰,則返回錯誤 EACCES。此外,當引數 kdf 不為 NULL 且緩衝區長度或 OtherInfo 長度超過允許的長度時,該函式將返回 EMSGSIZE。

  • 限制金鑰環連結

    long keyctl(KEYCTL_RESTRICT_KEYRING, key_serial_t keyring,
                const char *type, const char *restriction);
    

    現有的金鑰環可以透過根據限制方案評估金鑰的內容來限制附加金鑰的連結。

    “keyring”是要應用限制的現有金鑰環的金鑰 ID。它可以為空,或者可能已經連結了金鑰。即使新的限制會拒絕它們,現有的連結金鑰仍將保留在金鑰環中。

    “type”是註冊的金鑰型別。

    “restriction”是一個字串,描述如何限制金鑰連結。格式因金鑰型別而異,該字串將傳遞給請求型別的 lookup_restriction() 函式。它可以指定限制的方法和相關資料,例如簽名驗證或對金鑰負載的約束。如果以後取消註冊請求的金鑰型別,則移除金鑰型別後,不能將任何金鑰新增到金鑰環。

    要應用金鑰環限制,程序必須具有設定屬性許可權,並且金鑰環以前不得受到限制。

    受限金鑰環的一個應用是使用非對稱金鑰型別驗證 X.509 證書鏈或單個證書籤名。有關適用於非對稱金鑰型別的特定限制,請參閱 非對稱/公鑰加密金鑰型別

  • 查詢非對稱金鑰

    long keyctl(KEYCTL_PKEY_QUERY,
                key_serial_t key_id, unsigned long reserved,
                const char *params,
                struct keyctl_pkey_query *info);
    

    獲取有關非對稱金鑰的資訊。可以使用 params 引數查詢特定的演算法和編碼。這是一個字串,包含以空格或製表符分隔的鍵值對字串。當前支援的鍵包括 enchash。資訊在 keyctl_pkey_query 結構中返回

    __u32   supported_ops;
    __u32   key_size;
    __u16   max_data_size;
    __u16   max_sig_size;
    __u16   max_enc_size;
    __u16   max_dec_size;
    __u32   __spare[10];
    

    supported_ops 包含一個標誌位掩碼,指示支援哪些操作。它由按位或構成

    KEYCTL_SUPPORTS_{ENCRYPT,DECRYPT,SIGN,VERIFY}
    

    key_size 指示金鑰的大小(以位為單位)。

    max_*_size 指示要簽名的資料 blob、簽名 blob、要加密的 blob 和要解密的 blob 的最大大小(以位元組為單位)。

    __spare[] 必須設定為 0。這用於將來傳遞解鎖金鑰所需的一個或多個密碼短語。

    如果成功,則返回 0。如果金鑰不是非對稱金鑰,則返回 EOPNOTSUPP。

  • 使用非對稱金鑰加密、解密、簽名或驗證 blob

    long keyctl(KEYCTL_PKEY_ENCRYPT,
                const struct keyctl_pkey_params *params,
                const char *info,
                const void *in,
                void *out);
    
    long keyctl(KEYCTL_PKEY_DECRYPT,
                const struct keyctl_pkey_params *params,
                const char *info,
                const void *in,
                void *out);
    
    long keyctl(KEYCTL_PKEY_SIGN,
                const struct keyctl_pkey_params *params,
                const char *info,
                const void *in,
                void *out);
    
    long keyctl(KEYCTL_PKEY_VERIFY,
                const struct keyctl_pkey_params *params,
                const char *info,
                const void *in,
                const void *in2);
    

    使用非對稱金鑰對資料 blob 執行公鑰加密操作。對於加密和驗證,非對稱金鑰可能只需要公有部分可用,但對於解密和簽名,還需要私有部分。

    params 指向的引數塊包含多個整數值

    __s32           key_id;
    __u32           in_len;
    __u32           out_len;
    __u32           in2_len;
    

    key_id 是要使用的非對稱金鑰的 ID。in_lenin2_len 指示 in 和 in2 緩衝區中的資料量,而 out_len 指示 out 緩衝區的大小,如上述操作適用。

    對於給定的操作,in 和 out 緩衝區的使用方式如下

    Operation ID            in,in_len       out,out_len     in2,in2_len
    ======================= =============== =============== ===============
    KEYCTL_PKEY_ENCRYPT     Raw data        Encrypted data  -
    KEYCTL_PKEY_DECRYPT     Encrypted data  Raw data        -
    KEYCTL_PKEY_SIGN        Raw data        Signature       -
    KEYCTL_PKEY_VERIFY      Raw data        -               Signature
    

    info 是鍵=值對的字串,用於提供補充資訊。其中包括

    enc=<encoding> 加密/簽名 blob 的編碼。此

    可以是 RSASSA-PKCS1-v1.5 或 RSAES-PKCS1-v1.5 的“pkcs1”;“RSASSA-PSS”的“pss”;“RSAES-OAEP”的“oaep”。如果省略或為“raw”,則指定加密函式的原始輸出。

    hash=<algo> 如果資料緩衝區包含雜湊的輸出

    函式,並且編碼包括一些指示使用了哪個雜湊函式的指示,則可以使用此方法指定雜湊函式,例如“hash=sha256”。

    引數塊中的 __spare[] 空間必須設定為 0。這旨在允許傳遞解鎖金鑰所需的密碼短語等。

    如果成功,encrypt、decrypt 和 sign 都將返回寫入輸出緩衝區的資料量。驗證成功時返回 0。

  • 監視金鑰或金鑰環的更改

    long keyctl(KEYCTL_WATCH_KEY, key_serial_t key, int queue_fd,
                const struct watch_notification_filter *filter);
    

    這將設定或刪除對指定金鑰或金鑰環更改的監視。

    “key”是要監視的金鑰的 ID。

    “queue_fd”是指向開放管道的檔案描述符,該管道管理將傳遞通知的緩衝區。

    “filter”可以是 NULL 以刪除監視,也可以是篩選器規範以指示來自金鑰的所需事件。

    有關更多資訊,請參閱 通用通知機制

    請注意,對於任何特定的 { key, queue_fd } 組合,只能放置一個監視。

    通知記錄如下所示

    struct key_notification {
            struct watch_notification watch;
            __u32   key_id;
            __u32   aux;
    };
    

    其中,watch::type 將為“WATCH_TYPE_KEY_NOTIFY”,子型別將為以下之一

    NOTIFY_KEY_INSTANTIATED
    NOTIFY_KEY_UPDATED
    NOTIFY_KEY_LINKED
    NOTIFY_KEY_UNLINKED
    NOTIFY_KEY_CLEARED
    NOTIFY_KEY_REVOKED
    NOTIFY_KEY_INVALIDATED
    NOTIFY_KEY_SETATTR
    

    這些指示金鑰正在被例項化/拒絕、更新、在金鑰環中建立連結、從金鑰環中刪除連結、金鑰環正在被清除、金鑰正在被撤銷、金鑰正在失效或金鑰的屬性之一正在更改(使用者、組、許可權、超時、限制)。

    如果監視的金鑰被刪除,則將發出一個基本的 watch_notification,其中“type”設定為 WATCH_TYPE_META,而“subtype”設定為 watch_meta_removal_notification。監視點 ID 將設定在“info”欄位中。

    這需要透過啟用以下選項進行配置

    “提供金鑰/金鑰環更改通知”(KEY_NOTIFICATIONS)

核心服務

金鑰管理的核心服務相對簡單易用。它們可以分為兩個領域:金鑰和金鑰型別。

處理金鑰非常簡單。首先,核心服務註冊其型別,然後搜尋該型別的金鑰。它應保留金鑰,只要它需要它,然後它應釋放它。對於檔案系統或裝置檔案,可能會在開啟呼叫期間執行搜尋,並在關閉時釋放金鑰。如何處理由於兩個不同的使用者開啟同一個檔案而導致的衝突金鑰留給檔案系統作者解決。

要訪問金鑰管理器,必須 #include 以下標頭檔案

<linux/key.h>

特定的金鑰型別應該在 include/keys/ 下有一個頭檔案,該標頭檔案應該用於訪問該型別。例如,對於型別為“user”的金鑰,這將是

<keys/user-type.h>

請注意,可能遇到兩種不同型別的金鑰指標

  • struct key *

    這只是指向金鑰結構本身。金鑰結構將至少是四位元組對齊的。

  • key_ref_t

    這等同於 struct key *,但如果呼叫者“擁有”金鑰,則設定最低有效位。“擁有”是指呼叫程序從其金鑰環之一到金鑰具有可搜尋的連結。有三個函式用於處理這些

    key_ref_t make_key_ref(const struct key *key, bool possession);
    
    struct key *key_ref_to_ptr(const key_ref_t key_ref);
    
    bool is_key_possessed(const key_ref_t key_ref);
    

    第一個函式從金鑰指標和所有權資訊(必須為真或假)構造金鑰引用。

    第二個函式從引用中檢索金鑰指標,第三個函式檢索所有權標誌。

當訪問金鑰的負載內容時,必須採取某些預防措施以防止訪問與修改競爭。有關更多資訊,請參閱“有關訪問負載內容的注意事項”部分。

  • 要搜尋金鑰,請呼叫

    struct key *request_key(const struct key_type *type,
                            const char *description,
                            const char *callout_info);
    

    這用於請求具有與根據金鑰型別的 match_preparse() 方法指定的描述匹配的描述的金鑰或金鑰環。這允許發生近似匹配。如果 callout_string 不為 NULL,則將呼叫 /sbin/request-key 以嘗試從使用者空間獲取金鑰。在這種情況下,callout_string 將作為引數傳遞給程式。

    如果函式失敗,將返回錯誤 ENOKEY、EKEYEXPIRED 或 EKEYREVOKED。

    如果成功,金鑰將被附加到隱式獲取的請求金鑰的預設金鑰環,如 KEYCTL_SET_REQKEY_KEYRING 所設定。

    另請參閱 金鑰請求服務

  • 要在特定域中搜索金鑰,請呼叫

    struct key *request_key_tag(const struct key_type *type,
                                const char *description,
                                struct key_tag *domain_tag,
                                const char *callout_info);
    

    這與 request_key() 相同,只是可以指定一個域標記,該標記導致搜尋演算法僅匹配與該標記匹配的金鑰。domain_tag 可以為 NULL,指定一個與任何指定的域分離的全域性域。

  • 要搜尋金鑰,將輔助資料傳遞給 upcaller,請呼叫

    struct key *request_key_with_auxdata(const struct key_type *type,
                                         const char *description,
                                         struct key_tag *domain_tag,
                                         const void *callout_info,
                                         size_t callout_len,
                                         void *aux);
    

    這與 request_key_tag() 相同,只是如果 key_type->request_key() op 存在,則輔助資料將傳遞給它,並且 callout_info 是長度為 callout_len 的 blob(如果給定)(長度可以為 0)。

  • 要在 RCU 條件下搜尋金鑰,請呼叫

    struct key *request_key_rcu(const struct key_type *type,
                                const char *description,
                                struct key_tag *domain_tag);
    

    它類似於 request_key_tag(),只是它不檢查正在構建的金鑰,並且如果找不到匹配項,則不會呼叫使用者空間來構建金鑰。

  • 當不再需要時,應使用以下命令釋放金鑰

    void key_put(struct key *key);
    

    void key_ref_put(key_ref_t key_ref);
    

    這些可以從中斷上下文中呼叫。如果未設定 CONFIG_KEYS,則不會解析該引數。

  • 可以透過呼叫以下函式之一來對金鑰進行額外的引用

    struct key *__key_get(struct key *key);
    struct key *key_get(struct key *key);
    

    因此,需要在使用完金鑰後透過呼叫 key_put() 來處理這些引用。傳入的金鑰指標將被返回。

    在 key_get() 的情況下,如果指標為 NULL 或未設定 CONFIG_KEYS,則金鑰將不會被取消引用,也不會發生遞增。

  • 可以透過呼叫以下命令獲取金鑰的序列號

    key_serial_t key_serial(struct key *key);
    

    如果 key 為 NULL 或未設定 CONFIG_KEYS,則將返回 0(在後一種情況下,不解析引數)。

  • 如果在搜尋中找到金鑰環,則可以透過以下方式進一步搜尋

    key_ref_t keyring_search(key_ref_t keyring_ref,
                             const struct key_type *type,
                             const char *description,
                             bool recurse)
    

    這僅搜尋指定的金鑰環(recurse == false)或指定的金鑰環樹(recurse == true)以查詢匹配的金鑰。失敗時返回錯誤 ENOKEY(使用 IS_ERR/PTR_ERR 確定)。如果成功,則需要釋放返回的金鑰。

    金鑰環引用中的所有權屬性用於透過許可權掩碼控制訪問,如果成功,則傳播到返回的金鑰引用指標。

  • 可以透過以下方式建立金鑰環

    struct key *keyring_alloc(const char *description, uid_t uid, gid_t gid,
                              const struct cred *cred,
                              key_perm_t perm,
                              struct key_restriction *restrict_link,
                              unsigned long flags,
                              struct key *dest);
    

    這將使用給定的屬性建立一個金鑰環並返回它。如果 dest 不為 NULL,則新的金鑰環將連結到它指向的金鑰環中。不對目標金鑰環進行任何許可權檢查。

    如果金鑰環將使配額過載,則可以返回錯誤 EDQUOT(如果在金鑰環不應計入使用者的配額時,在標誌中傳遞 KEY_ALLOC_NOT_IN_QUOTA)。還可以返回錯誤 ENOMEM。

    如果 restrict_link 不為 NULL,則它應指向一個結構,該結構包含每次嘗試將金鑰連結到新金鑰環時將呼叫的函式。該結構還可以包含金鑰指標和關聯的金鑰型別。呼叫該函式是為了檢查是否可以將金鑰新增到金鑰環中。垃圾回收器使用金鑰型別來清理此結構中的函式或資料指標(如果給定的金鑰型別已取消註冊)。核心中 key_create_or_update() 的呼叫者可以傳遞 KEY_ALLOC_BYPASS_RESTRICTION 以禁止檢查。使用此方法的一個示例是管理核心啟動時設定的加密金鑰環,其中也允許使用者空間新增金鑰 - 前提是它們可以透過核心已經擁有的金鑰進行驗證。

    呼叫時,限制函式將被傳遞要新增到的金鑰環、金鑰型別、要新增的金鑰的負載以及用於限制檢查的資料。請注意,當建立新金鑰時,這將在負載預解析和實際金鑰建立之間呼叫。該函式應返回 0 以允許連結,或返回錯誤以拒絕它。

    一個方便的函式 restrict_link_reject 存在,在這種情況下總是返回 -EPERM。

  • 要檢查金鑰的有效性,可以呼叫此函式

    int validate_key(struct key *key);
    

    這將檢查所討論的金鑰是否已過期或已被撤銷。如果金鑰無效,將返回錯誤 EKEYEXPIRED 或 EKEYREVOKED。如果金鑰為 NULL 或未設定 CONFIG_KEYS,則將返回 0(在後一種情況下,不解析引數)。

  • 要註冊金鑰型別,應呼叫以下函式

    int register_key_type(struct key_type *type);
    

    如果已存在具有相同名稱的型別,這將返回錯誤 EEXIST。

  • 要取消註冊金鑰型別,請呼叫

    void unregister_key_type(struct key_type *type);
    

在某些情況下,可能需要處理金鑰包。該工具提供了對金鑰環型別的訪問,用於管理此類包

struct key_type key_type_keyring;

這可以與諸如 request_key() 之類的函式一起使用,以查詢程序金鑰環中的特定金鑰環。然後可以使用 keyring_search() 搜尋如此找到的金鑰環。請注意,無法使用 request_key() 搜尋特定的金鑰環,因此以這種方式使用金鑰環的實用性有限。

有關訪問負載內容的注意事項

最簡單的負載只是直接儲存在 key->payload 中的資料。在這種情況下,在訪問負載時無需進行 RCU 或鎖定。

更復雜的負載內容必須被分配,並且指向它們的指標必須設定在 key->payload.data[] 陣列中。必須選擇以下方法之一來訪問資料

  1. 不可修改的金鑰型別。

    如果金鑰型別沒有修改方法,則可以在沒有任何形式的鎖定的情況下訪問金鑰的負載,前提是已知該金鑰已被例項化(未例項化的金鑰無法“找到”)。

  2. 金鑰的訊號量。

    訊號量可用於控制對負載的訪問和負載指標。它必須被寫鎖定以進行修改,並且必須被讀鎖定以進行一般訪問。這樣做的缺點是訪問者可能需要休眠。

  3. RCU。

    當尚未持有訊號量時,必須使用 RCU;如果持有訊號量,則內容無法在您不知情的情況下意外更改,因為訊號量仍必須用於序列化對金鑰的修改。金鑰管理程式碼會為金鑰型別處理此問題。

    但是,這意味著使用

    rcu_read_lock() ... rcu_dereference() ... rcu_read_unlock()
    

    讀取指標,以及

    rcu_dereference() ... rcu_assign_pointer() ... call_rcu()
    

    設定指標並在寬限期後處理舊內容。請注意,只有金鑰型別應修改金鑰的負載。

    此外,RCU 控制的負載必須儲存一個 struct rcu_head 以供 call_rcu() 使用,並且如果負載的大小可變,則儲存負載的長度。如果未持有金鑰的訊號量,則無法依賴 key->datalen 與剛取消引用的負載保持一致。

    請注意,key->payload.data[0] 有一個陰影,標記為 __rcu 用途。這稱為 key->payload.rcu_data0。以下訪問器包裝對該元素的 RCU 呼叫

    1. 設定或更改第一個負載指標

      rcu_assign_keypointer(struct key *key, void *data);
      
    2. 在持有金鑰訊號量的情況下讀取第一個負載指標

             [const] void *dereference_key_locked([const] struct key *key);
      
      Note that the return value will inherit its constness from the key
      parameter.  Static analysis will give an error if it things the lock
      isn't held.
      
    3. 在持有 RCU 讀取鎖的情況下讀取第一個負載指標

      const void *dereference_key_rcu(const struct key *key);
      

定義金鑰型別

核心服務可能想要定義自己的金鑰型別。例如,AFS 檔案系統可能想要定義 Kerberos 5 票證金鑰型別。為此,作者填寫一個 key_type 結構並將其註冊到系統中。

實現金鑰型別的原始檔應包含以下標頭檔案

<linux/key-type.h>

該結構有許多欄位,其中一些是強制性的

  • const char *name

    金鑰型別的名稱。這用於將使用者空間提供的金鑰型別名稱轉換為指向結構的指標。

  • size_t def_datalen

    這是可選的 - 它提供預設的負載資料長度,該長度作為配額貢獻。如果金鑰型別的負載始終或幾乎始終具有相同的大小,那麼這是一種更有效的方法。

    透過呼叫以下命令,始終可以在例項化或更新期間更改特定金鑰上的資料長度(和配額)

    int key_payload_reserve(struct key *key, size_t datalen);
    

    使用修改後的資料長度。如果不可行,將返回錯誤 EDQUOT。

  • int (*vet_description)(const char *description);

    此可選方法用於審查金鑰描述。如果金鑰型別不批准金鑰描述,則可以返回一個錯誤,否則應返回 0。

  • int (*preparse)(struct key_preparsed_payload *prep);

    此可選方法允許金鑰型別嘗試在建立金鑰(新增金鑰)或獲取金鑰訊號量(更新或例項化金鑰)之前解析負載。prep 指向的結構如下所示

    struct key_preparsed_payload {
            char            *description;
            union key_payload payload;
            const void      *data;
            size_t          datalen;
            size_t          quotalen;
            time_t          expiry;
    };
    

    在呼叫該方法之前,呼叫者將使用負載 blob 引數填寫 data 和 datalen;將使用金鑰型別的預設配額大小填寫 quotalen;expiry 將設定為 TIME_T_MAX,其餘將被清除。

    如果可以從負載內容中提出描述,則應將其作為字串附加到 description 欄位。如果 add_key() 的呼叫者傳遞 NULL 或 "",這將用於金鑰描述。

    該方法可以將它喜歡的任何東西附加到負載。這僅傳遞給 instantiate() 或 update() 操作。如果設定了 expiry,則如果從該資料例項化金鑰,則該時間將應用於金鑰。

    如果成功,該方法應返回 0,否則應返回一個負錯誤程式碼。

  • void (*free_preparse)(struct key_preparsed_payload *prep);

    只有在提供了 preparse() 方法時才需要此方法,否則不使用它。它清除 key_preparsed_payload 結構的 description 和 payload 欄位中附加的任何內容,如 preparse() 方法所填充。它總是在 preparse() 成功返回後呼叫,即使 instantiate() 或 update() 成功。

  • int (*instantiate)(struct key *key, struct key_preparsed_payload *prep);

    在構造期間,呼叫此方法將負載附加到金鑰。附加的負載不必與傳遞給此函式的資料有任何關係。

    prep->data 和 prep->datalen 欄位將定義原始負載 blob。如果提供了 preparse(),則也可以填寫其他欄位。

    如果附加到金鑰的資料量與 keytype->def_datalen 中的大小不同,則應呼叫 key_payload_reserve()。

    此方法不必鎖定金鑰即可附加負載。key->flags 中未設定 KEY_FLAG_INSTANTIATED 這一事實會阻止其他任何東西訪問金鑰。

    在此方法中休眠是安全的。

    提供了 generic_key_instantiate() 來簡單地將資料從 prep->payload.data[] 複製到 key->payload.data[],並在第一個元素上進行 RCU 安全分配。然後,它將清除 prep->payload.data[],以便 free_preparse 方法不釋放資料。

  • int (*update)(struct key *key, const void *data, size_t datalen);

    如果可以更新此型別的金鑰,則應提供此方法。呼叫此方法是為了從提供的資料 blob 更新金鑰的負載。

    prep->data 和 prep->datalen 欄位將定義原始負載 blob。如果提供了 preparse(),則也可以填寫其他欄位。

    應在進行任何實際更改之前呼叫 key_payload_reserve()(如果資料長度可能會更改)。請注意,如果這成功了,則該型別已提交更改金鑰,因為它已被更改,因此必須首先完成所有記憶體分配。

    在呼叫此方法之前,金鑰將具有其訊號量寫鎖定,但這隻會阻止其他寫入者;對金鑰負載的任何更改都必須在 RCU 條件下進行,並且必須使用 call_rcu() 來處理舊負載。

    應在進行更改之前呼叫 key_payload_reserve(),但在完成所有分配和其他可能失敗的函式呼叫之後。

    在此方法中休眠是安全的。

  • int (*match_preparse)(struct key_match_data *match_data);

    此方法是可選的。當要執行金鑰搜尋時,將呼叫此方法。它被賦予以下結構

    struct key_match_data {
            bool (*cmp)(const struct key *key,
                        const struct key_match_data *match_data);
            const void      *raw_data;
            void            *preparsed;
            unsigned        lookup_type;
    };
    

    在條目中,raw_data 將指向要用於呼叫者匹配金鑰的標準,並且不應修改。 (*cmp)() 將指向預設匹配器函式(它對 raw_data 執行精確的描述匹配),lookup_type 將設定為指示直接查詢。

    以下 lookup_type 值可用

    • KEYRING_SEARCH_LOOKUP_DIRECT - 直接查詢會雜湊型別和描述,以將搜尋範圍縮小到少量金鑰。

    • KEYRING_SEARCH_LOOKUP_ITERATE - 迭代查詢會遍歷金鑰環中的所有金鑰,直到找到匹配的金鑰。這必須用於任何不在金鑰描述上進行簡單直接匹配的搜尋。

    該方法可以將 cmp 設定為指向其選擇的函式,該函式執行其他形式的匹配,可以將 lookup_type 設定為 KEYRING_SEARCH_LOOKUP_ITERATE,並且可以將某些東西附加到預解析的指標以供 (*cmp)() 使用。如果金鑰匹配,(*cmp)() 應返回 true,否則返回 false。

    如果設定了預解析,則可能需要使用 match_free() 方法來清理它。

    如果成功,該方法應返回 0,否則應返回一個負錯誤程式碼。

    允許在此方法中休眠,但 (*cmp)() 可能不會休眠,因為將在其上持有鎖。

    如果未提供 match_preparse(),則此型別的金鑰將透過其描述進行精確匹配。

  • void (*match_free)(struct key_match_data *match_data);

    此方法是可選的。如果給定,則呼叫它以在成功呼叫 match_preparse() 後清理 match_data->preparsed。

  • void (*revoke)(struct key *key);

    此方法是可選的。呼叫它以在金鑰被撤銷時丟棄部分負載資料。呼叫者將具有金鑰訊號量寫鎖定。

    在此方法中休眠是安全的,但應注意避免與金鑰訊號量發生死鎖。

  • void (*destroy)(struct key *key);

    此方法是可選的。當金鑰被銷燬時,呼叫它以丟棄金鑰上的負載資料。

    此方法不需要鎖定金鑰即可訪問負載;它可以認為此時金鑰不可訪問。請注意,金鑰的型別可能在此函式被呼叫之前已更改。

    在此方法中休眠是不安全的;呼叫者可能持有自旋鎖。

  • void (*describe)(const struct key *key, struct seq_file *p);

    此方法是可選的。在 /proc/keys 讀取期間,呼叫它以文字形式總結金鑰的描述和負載。

    將在持有 RCU 讀取鎖的情況下呼叫此方法。如果要訪問負載,則應使用 rcu_dereference() 讀取負載指標。如果未持有金鑰的訊號量,則無法信任 key->datalen 與負載的內容保持一致。

    描述不會更改,但金鑰的狀態可能會更改。

    在此方法中休眠是不安全的;RCU 讀取鎖由呼叫者持有。

  • long (*read)(const struct key *key, char __user *buffer, size_t buflen);

    此方法是可選的。它由 KEYCTL_READ 呼叫,以將金鑰的負載轉換為使用者空間要處理的資料 blob。理想情況下,blob 應與傳遞給例項化和更新方法的格式相同。

    如果成功,應返回可以生成的 blob 大小,而不是複製的大小。

    將在金鑰的訊號量讀鎖定的情況下呼叫此方法。這將防止金鑰的負載更改。在訪問金鑰的負載時,沒有必要使用 RCU 鎖定。在此方法中休眠是安全的,例如在訪問使用者空間緩衝區時可能會發生這種情況。

  • int (*request_key)(struct key_construction *cons, const char *op, void *aux);

    此方法是可選的。如果提供了此方法,request_key() 及其相關函式將呼叫此函式,而不是向上呼叫 /sbin/request-key 來操作此型別的金鑰。

    aux 引數與傳遞給 request_key_async_with_auxdata() 和類似函式的方式相同,否則為 NULL。傳遞的還有要操作的金鑰的構造記錄以及操作型別(目前只有 “create”)。

    此方法允許在 upcall 完成之前返回,但在任何情況下都必須呼叫以下函式來完成例項化過程,無論它是否成功,是否有錯誤。

    void complete_request_key(struct key_construction *cons, int error);
    

    error 引數在成功時應為 0,在出錯時應為 -ve。構造記錄會被此操作銷燬,並且授權金鑰將被撤銷。如果指示錯誤,則正在構造中的金鑰如果尚未例項化,將被否定例項化。

    如果此方法返回錯誤,則該錯誤將返回給 request_key*() 的呼叫者。必須在返回之前呼叫 complete_request_key()。

    正在構造中的金鑰和授權金鑰可以在 cons 指向的 key_construction 結構中找到。

    • struct key *key;

      正在構造中的金鑰。

    • struct key *authkey;

      授權金鑰。

  • struct key_restriction *(*lookup_restriction)(const char *params);

    此可選方法用於啟用金鑰環限制的使用者空間配置。將傳入限制引數字串(不包括金鑰型別名稱),此方法返回一個指向 key_restriction 結構的指標,該結構包含評估每次嘗試的金鑰連結操作的相關函式和資料。如果沒有匹配項,則返回 -EINVAL。

  • asym_eds_opasym_verify_signature

    int (*asym_eds_op)(struct kernel_pkey_params *params,
                       const void *in, void *out);
    int (*asym_verify_signature)(struct kernel_pkey_params *params,
                                 const void *in, const void *in2);
    

    這些方法是可選的。如果提供了第一個方法,則允許使用金鑰來加密、解密或簽名資料塊,第二個方法允許金鑰驗證簽名。

    在所有情況下,params 塊中都提供了以下資訊

    struct kernel_pkey_params {
            struct key      *key;
            const char      *encoding;
            const char      *hash_algo;
            char            *info;
            __u32           in_len;
            union {
                    __u32   out_len;
                    __u32   in2_len;
            };
            enum kernel_pkey_operation op : 8;
    };
    

    這包括要使用的金鑰;一個指示要使用的編碼的字串(例如,“pkcs1” 可以與 RSA 金鑰一起使用,以指示 RSASSA-PKCS1-v1.5 或 RSAES-PKCS1-v1.5 編碼,或者 “raw” 表示沒有編碼);用於生成簽名資料的雜湊演算法的名稱(如果適用);輸入和輸出(或第二個輸入)緩衝區的大小;以及要執行的操作的 ID。

    對於給定的操作 ID,輸入和輸出緩衝區的使用方式如下

    Operation ID            in,in_len       out,out_len     in2,in2_len
    ======================= =============== =============== ===============
    kernel_pkey_encrypt     Raw data        Encrypted data  -
    kernel_pkey_decrypt     Encrypted data  Raw data        -
    kernel_pkey_sign        Raw data        Signature       -
    kernel_pkey_verify      Raw data        -               Signature
    

    asym_eds_op() 處理 params->op 指定的加密、解密和簽名建立。請注意,params->op 也為 asym_verify_signature() 設定。

    加密和簽名建立都在輸入緩衝區中獲取原始資料,並在輸出緩衝區中返回加密結果。如果設定了編碼,則可能已新增填充。對於簽名建立,根據編碼,建立的填充可能需要指示摘要演算法 - 其名稱應在 hash_algo 中提供。

    解密在輸入緩衝區中獲取加密資料,並在輸出緩衝區中返回原始資料。如果設定了編碼,填充將被檢查和刪除。

    驗證在輸入緩衝區中獲取原始資料,並在第二個輸入緩衝區中獲取簽名,並檢查兩者是否匹配。將驗證填充。根據編碼,用於生成原始資料的摘要演算法可能需要在 hash_algo 中指示。

    如果成功,asym_eds_op() 應該返回寫入輸出緩衝區的位元組數。 asym_verify_signature() 應該返回 0。

    可能會返回各種錯誤,包括如果不支援該操作,則返回 EOPNOTSUPP;如果驗證失敗,則返回 EKEYREJECTED;如果所需的加密不可用,則返回 ENOPKG。

  • asym_query:

    int (*asym_query)(const struct kernel_pkey_params *params,
                      struct kernel_pkey_query *info);
    

    此方法是可選的。如果提供了此方法,則允許確定金鑰中儲存的公共或非對稱金鑰的資訊。

    引數塊與 asym_eds_op() 和 co. 相同,但 in_len 和 out_len 未使用。編碼和 hash_algo 欄位應用於適當減少返回的緩衝區/資料大小。

    如果成功,將填寫以下資訊

    struct kernel_pkey_query {
            __u32           supported_ops;
            __u32           key_size;
            __u16           max_data_size;
            __u16           max_sig_size;
            __u16           max_enc_size;
            __u16           max_dec_size;
    };
    

    supported_ops 欄位將包含一個位掩碼,指示金鑰支援哪些操作,包括加密資料塊、解密資料塊、簽名資料塊和驗證資料塊上的簽名。為此定義了以下常量

    KEYCTL_SUPPORTS_{ENCRYPT,DECRYPT,SIGN,VERIFY}
    

    key_size 欄位是以位為單位的金鑰大小。 max_data_size 和 max_sig_size 是用於建立和驗證簽名的最大原始資料和簽名大小; max_enc_size 和 max_dec_size 是用於加密和解密的最大原始資料和簽名大小。 max_*_size 欄位以位元組為單位測量。

    如果成功,將返回 0。如果金鑰不支援此功能,將返回 EOPNOTSUPP。

Request-Key 回撥服務

要建立新金鑰,核心將嘗試執行以下命令列

/sbin/request-key create <key> <uid> <gid> \
        <threadring> <processring> <sessionring> <callout_info>

<key> 是正在構造的金鑰,三個金鑰環是導致發出搜尋的程序的程序金鑰環。包含這些有兩個原因

1 其中一個金鑰環中可能有一個身份驗證令牌,該令牌是

獲取金鑰所需的,例如:Kerberos Ticket-Granting Ticket。

2 新金鑰可能應該快取在這些環中的一箇中。

此程式應將其 UID 和 GID 設定為指定的那些,然後再嘗試訪問更多金鑰。然後,它可能會尋找一個使用者特定的程序來將請求傳遞給它(可能是 KDE 桌面管理器放置在另一個金鑰中的路徑)。

該程式(或它呼叫的任何程式)應透過呼叫 KEYCTL_INSTANTIATE 或 KEYCTL_INSTANTIATE_IOV 來完成金鑰的構造,這也允許它在返回之前將金鑰快取在其中一個金鑰環中(可能是會話環)。或者,可以使用 KEYCTL_NEGATE 或 KEYCTL_REJECT 將金鑰標記為否定;這也允許將金鑰快取在其中一個金鑰環中。

如果它返回時金鑰仍處於未構造狀態,則該金鑰將被標記為否定,它將被新增到會話金鑰環,並且將向金鑰請求者返回錯誤。

可以從呼叫此服務的任何人或任何事物提供補充資訊。這將作為 <callout_info> 引數傳遞。如果沒有此類資訊可用,則將改為傳遞“-”。

類似地,核心可能會嘗試透過執行來更新過期的或即將過期的金鑰

/sbin/request-key update <key> <uid> <gid> \
        <threadring> <processring> <sessionring>

在這種情況下,不需要程式實際將金鑰附加到環上;提供這些環僅供參考。

垃圾回收

死金鑰(已刪除其型別的金鑰)將自動從指向它們的那些金鑰環中取消連結,並由後臺垃圾回收器儘快刪除。

類似地,已撤銷和過期的金鑰將被垃圾回收,但僅在經過一定時間後。此時間設定為以下位置的秒數

/proc/sys/kernel/keys/gc_delay