概述
顧名思義,在開啟該功能之后,內核在加載內核模塊時,會對內核模塊的簽名進行檢查。
如果內核模塊本身沒有經過簽名,或者簽名值與預期值不符,這兩種情況都會被認為是簽名認證失敗。根據策略的不同,簽名認證失敗可能會導致模塊被拒絕加載,也可能是繼續正常加載但內核會顯示一條警告信息。
內核模塊簽名功能的本質是限制root用戶載入惡意的內核模塊。當root用戶加載一個內核模塊時,內核在分辨是系統管理員還是攻擊者的時候,依靠的就是能夠進行身份認證的可信的X.509證書和與之對應的私鑰。只要私鑰存儲妥當不發生泄露,攻擊者就無法偽造X.509證書,因此也就不可能提供含有正確簽名的內核模塊;而系統管理員是唯一合法的X.509證書的使用者,是可以用合法的證書對應的私鑰對內核模塊進行簽名的。
最佳實踐
- 用openssl命令生成PEM格式的簽名key文件。
openssl req -new -nodes -utf8 -sha256 -days 36500
-batch -x509 -config x509.genkey
-outform PEM -out system_key.pem
-keyout system_key.pem
其中x509.genkey的內容如下:
[ req ]
default_bits = 2048
distinguished_name = req_distinguished_name
prompt = no
string_mask = utf8only
x509_extensions = v3_req
[ req_distinguished_name ]
O = Alibaba Group
OU = < your_organization >
CN = Test modsign key
emailAddress = < your_user_name >@alibaba-inc.com
[ v3_req ]
basicConstraints=critical,CA:FALSE
keyUsage=digitalSignature
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid:always
- 配置內核
CONFIG_MODULE_SIG=y
CONFIG_MODULE_SIG_FORCE=y
CONFIG_MODULE_SIG_KEY=<前一個步驟生成的system_key.pem文件的路徑>
CONFIG_MODULE_SIG_HASH="sha256"
CONFIG_MODULE_SIG_SHA256=y
CONFIG_CRYPTO_SHA256=y
CONFIG_MODULE_SIG_ALL=y - 編譯內核和模塊
make bzImage modules
- 安裝內核和內核模塊
sudo make modules_install install
- 對內核模塊進行簽名
在指定了CONFIG_MODULE_SIG_ALL=y的情況下,kbuild系統可以自動對模塊進行簽名,而且該步驟通常無需手動運行,會在module_install時自動執行簽名。
make modules_sign
- 用openssl全手動簽名和驗簽
openssl smime -sign -nocerts -noattr -binary -in < module > -inkey
< key > -signer < x509 > -outform der -out < raw sig >
openssl smime -verify -in < raw sig > -inform der -content < module >
-certfile < x509 > -noverify -out /dev/null
內核配置選項
CONFIG_MODULE_SIG:Module signature verification
如果開啟了該選項,在內核在加載內核模塊時,會對內核模塊的簽名進行檢查。
默認情況下,在加載沒有簽名或者是簽名不正確的內核模塊時,內核僅僅是打印一條提示信息,比如:
k_netlink: module verification failed: signature and/or required key missing - tainting kernel
同時將內核標記為tainted,然后繼續正常加載簽名有問題的模塊。
CONFIG_MODULE_SIG_FORCE: Require modules to be validly signed
如果開啟了該選項,在加載沒有簽名或者是簽名不正確的內核模塊時,內核會直接拒絕加載簽名有問題的內核模塊。
CONFIG_MODULE_SIG_HASH: Which hash algorithm should modules be signed with?
該選項用于決定簽名時使用的摘要算法。每一種算法都有對應的內核選項:
- CONFIG_MODULE_SIG_SHA1:sha1
- CONFIG_MODULE_SIG_SHA224:sha224
- CONFIG_MODULE_SIG_SHA256:sha256
- CONFIG_MODULE_SIG_SHA384:sha384
- CONFIG_MODULE_SIG_SHA512:sha512
注意:內核會使用crypto子系統中實現的摘要算法內核模塊來計算摘要值,這就意味著這些實現摘要算法的模塊必須已經事先編譯為內置,否則就會發生“為了計算簽名摘要值而要加載對應的模塊但該模塊的簽名因缺少摘要算法模塊而無法計算“的窘況。
CONFIG_MODULE_SIG_KEY: File name or PKCS#11 URI of module signing key
該選項指定了用于對內核模塊簽名的PEM格式的簽名key文件的路徑,或者是一個PKCS#11 URI(在實際進行簽名前,簽名工具會先把這個URI指定的key下載下來)。該選項的默認值是"certs/signing_key.pem"。如果沒有修改這個默認值,內核會自動創建"certs/signing_key.pem"文件用于內核模塊簽名。
kbuild會將這個X.509證書文件轉換為system certificate list并編譯進內核中,然后在system trusted keyring初始化階段將這個list中的每一個X.509證書都添加到builtin trusted keyring中。此時,每一個X.509證書就是一把system trusted key。因此,該選項指定的key文件中的所有X.509證書還可以當成system trusted key]來用。key文件至少包含一個PEM格式的私鑰和與之關聯的、PEM格式的X.509證書。可以通過級聯的方式將更多的X.509證書添加到該key文件中。
但是反過來卻是不成立的:并不是所有的system trusted key都可以用來驗證內核模塊的簽名,比如secondary trusted keyring中的key就不能用來驗證內核模塊的簽名;只有builtin trusted keyring中的key才可以用來驗證內核模塊的簽名。
最后要提醒的是,用于模塊簽名的簽名key可以是自簽名的,也可以不是。如果不是自簽名的,只要確保簽名key和父key能被導入到builtin trusted keyring即可。不要求父key的父key也必須在builtin trusted keyring中。
CONFIG_MODULE_SIG_ALL: Automatically sign all modules
選中該選項后,kbuild會在執行make modules_install的時候對所有的內核模塊進行簽名。如果沒有選中該選項,則需要使用者自己手動調用scripts/sign-file簽名工具對內核模塊進行簽名。
內核啟動參數
module.sig_enforce
如果將該參數傳給內核,表示強制驗證內核模塊簽名,效果上等價于CONFIG_MODULE_SIG_FORCE=y。如果內核在編譯時已經將CONFIG_MODULE_SIG_FORCE設為了y,那這里的內核選項是不會起任何作用的。
該選項為內核強制驗證功能在策略上提供了一定的靈活性,比如運行系統需要DKMS或者SystemTap支持的話,如果沒有實現配套的PKI簽名服務機制,最好將CONFIG_MODULE_SIG_FORCE設為n,同時為了保證安全在內核命令行參數中指定module.sig_enforce。在必要時,可以臨時去掉module.sig_enforce,以便系統維護或調試。
實現細節
內核編譯階段
如果CONFIG_MODULE_SIG_KEY參數為默認值,自動生成內核模塊簽名key文件certs/signing_key.pem
如果CONFIG_MODULE_SIG_KEY的值為默認值certs/signing_key.pem,表示用戶希望使用由kbuild自動生成的key文件對內核模塊進行簽名。
kbuild會自動執行以下命令生成key文件:
openssl req -new -nodes -utf8 -$(CONFIG_MODULE_SIG_HASH) -days 36500
-batch -x509 -config certs/x509.genkey
-outform PEM -out $(obj)/signing_key.pem
-keyout certs/signing_key.pem
該命令的含義是:根據X509證書請求配置模板文件的內容,生成一個自簽名的X509證書。生成的key文件保存為certs/signing_key.pem, 同時將自簽名的X509證書附在key內容的后面。
其中生成X509證書請求時使用的配置模板文件的內容為:
[ req ]
default_bits = 4096
distinguished_name = req_distinguished_name
prompt = no
string_mask = utf8only
x509_extensions = myexts
[ req_distinguished_name ]
#O = Unspecified company
CN = Build time autogenerated kernel key
#emailAddress = unspecified.user@unspecified.company
[ myexts ]
basicConstraints=critical,CA:FALSE
keyUsage=digitalSignature
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid
將內核模塊簽名key文件build到system certificate list中
kbuild會調用scripts/extract-cert程序將CONFIG_MODULE_SIG_KEY指定的PEM格式的X.509證書文件轉換為DER格式的certs/signing_key.x509文件:
scripts/extract-cert $(CONFIG_MODULE_SIG_KEY) signing_key.x509
如果CONFIG_MODULE_SIG_KEY指定的一個PKCS#11 URI,scripts/extract-cert程序也會在簽名之前先下載好再使用。
接下來,上一個步驟生成的certs/signing_key.x509文件會連同system_certificates.S一起被編譯為certs/system_certificates.o。該object文件的內容會被包含到內核image的.init.rodata節中。
__INITRODATA
.align 8
.globl VMLINUX_SYMBOL(system_certificate_list)
VMLINUX_SYMBOL(system_certificate_list):
__cert_list_start:
#ifdef CONFIG_MODULE_SIG
.incbin "certs/signing_key.x509"
#endif
.incbin "certs/x509_certificate_list"
__cert_list_end:
該system certificate list中的所有X.509證書最終都會被加載到builtin trusted keyring中,并作為system trusted key來使用。
順便一提,由內核選項CONFIG_SYSTEM_TRUSTED_KEYS指定的system trusted key文件(即certs/x509_certificate_list)也會成為system certificate list的一部分。準確來說,它會在內核初始化system trusted klseyring時加入到builtin trusted keyring中。
Kernel啟動階段
初始化system trusted keyrings
由內核選項CONFIG_MODULE_SIG_KEY指定的對內核模塊進行簽名的key文件會在這個階段被加載到builtin trusted keyring中。
用戶態運行時階段
加載內核模塊
- 當加載內核模塊時,會對內核模塊的簽名進行認證。最終認證的結果會保存在info->sig_ok中。
static int load_module(struct load_info *info, const char __user *uargs,
int flags)
...
err = module_sig_check(info, flags);
if (err)
goto free_copy;
...
- 如果認證失敗,info->sig_ok返回0,因此內核會打印一條信息:: module verification failed: signature and/or required key missing - tainting kernel,然后會將kernel狀態標記為TAINT_UNSIGNED_MODULE。
...
#ifdef CONFIG_MODULE_SIG
mod- >sig_ok = info- >sig_ok;
if (!mod- >sig_ok) {
pr_notice_once("%s: module verification failed: signature "
"and/or required key missing - tainting "
"kerneln", mod- >name);
add_taint_module(mod, TAINT_UNSIGNED_MODULE, LOCKDEP_STILL_OK);
}
#endif
- 對內核模塊的簽名認證的過程如下:
- 首先檢查flags是否為0。當執行modprobe -f時,會要求內核忽略所有的版本檢查。這會導致作為內核模塊的數據的一部分的版本信息將被完全忽略。因此如果一個曾經簽過名的模塊有安全漏洞,那么攻擊者可以在新內核上強行加載這個有漏洞的模塊以便攻擊新內核。因此,內核認為flags為非0值的時候,是可能存在上述跨內核版本攻擊的可能性的,因此執行modprobe -f一定會導致簽名認證失敗。
- 檢查內核模塊尾部的mark字段是否為“
MODULE_SIG_STRINGn”。 - 如果mark字段存在,進行簽名檢查。
- 如果mark不存在,根據當前的模塊簽名檢查策略,決定返回值;如果使用了強制簽名檢查策略(CONFIG_MODULE_SIG_FORCE=y或指定了module.sig_enforce),則返回-ENOKEY;如果沒有使用強制簽名檢查策略,返回0。
- 檢查flags是否為0
結論:執行modprobe -f一定會導致簽名認證失敗, 這一點所有的責任人都要注意。原因是:作為內核模塊的數據的一部分的版本信息將被modprobe -f完全忽略,如果一個曾經簽過名的模塊有安全漏洞,那么攻擊者可以在新內核上強行加載這個有漏洞的模塊以便攻擊新內核。
#ifdef CONFIG_MODULE_SIG
static int module_sig_check(struct load_info *info, int flags)
{
int err = -ENOKEY;
const unsigned long markerlen = sizeof(MODULE_SIG_STRING) - 1;
const void *mod = info- >hdr;
/*
* Require flags == 0, as a module with version information
* removed is no longer the module that was signed
*/
if (flags == 0 &&
info- >len > markerlen &&
memcmp(mod + info- >len - markerlen, MODULE_SIG_STRING, markerlen) == 0) {
/* We truncate the module to discard the signature */
info- >len -= markerlen;
err = mod_verify_sig(mod, &info- >len);
}
if (!err) {
info- >sig_ok = true;
return 0;
}
/* Not having a signature is only an error if we're strict. */
if (err == -ENOKEY && !sig_enforce)
err = 0;
return err;
}
#endif
- 檢查module簽名格式中的各個字段,然后調用verify_pkcs7_signature()驗證PKCS#7消息。
int mod_verify_sig(const void *mod, unsigned long *_modlen)
{
struct module_signature ms;
size_t modlen = *_modlen, sig_len;
pr_devel("== >%s(,%zu)n", __func__, modlen);
if (modlen <= sizeof(ms))
return -EBADMSG;
memcpy(&ms, mod + (modlen - sizeof(ms)), sizeof(ms));
modlen -= sizeof(ms); // modlen包括模塊內容+PKCS#7消息
sig_len = be32_to_cpu(ms.sig_len); // PKCS#7消息的長度
if (sig_len >= modlen)
return -EBADMSG;
modlen -= sig_len; // 僅包含模塊內容的長度
*_modlen = modlen; // 返回模塊內容的長度
if (ms.id_type != PKEY_ID_PKCS7) {
pr_err("Module is not signed with expected PKCS#7 messagen");
return -ENOPKG;
}
if (ms.algo != 0 ||
ms.hash != 0 ||
ms.signer_len != 0 ||
ms.key_id_len != 0 ||
ms.__pad[0] != 0 ||
ms.__pad[1] != 0 ||
ms.__pad[2] != 0) {
pr_err("PKCS#7 signature info has unexpected non-zero paramsn");
return -EBADMSG;
}
return verify_pkcs7_signature(mod, modlen, mod + modlen, sig_len,
NULL, VERIFYING_MODULE_SIGNATURE,
NULL, NULL);
}
- 對內核模塊中的PKCS#7格式的消息進行驗證。注意其中的trusted_keys參數為NULL,因此能夠用于驗證內核模塊簽名的,僅限builtin trusted keyring中的key;而導入到secondary trusted keyring中的key是不能用于內核模塊簽名的。
#ifdef CONFIG_SYSTEM_DATA_VERIFICATION
/**
* verify_pkcs7_signature - Verify a PKCS#7-based signature on system data.
* @data: The data to be verified (NULL if expecting internal data).
* @len: Size of @data.
* @raw_pkcs7: The PKCS#7 message that is the signature.
* @pkcs7_len: The size of @raw_pkcs7.
* @trusted_keys: Trusted keys to use (NULL for builtin trusted keys only,
* (void *)1UL for all trusted keys).
* @usage: The use to which the key is being put.
* @view_content: Callback to gain access to content.
* @ctx: Context for callback.
*/
int verify_pkcs7_signature(const void *data, size_t len,
const void *raw_pkcs7, size_t pkcs7_len,
struct key *trusted_keys,
enum key_being_used_for usage,
int (*view_content)(void *ctx,
const void *data, size_t len,
size_t asn1hdrlen),
void *ctx)
{
struct pkcs7_message *pkcs7;
int ret;
// 解析內核模塊簽名中的PKCS#7消息(DER格式),并將相關字段
// 填充到pkcs7數據結構中。
// struct pkcs7_message {
// PKCS#7中的signer證書列表(可以為空)
// struct x509_certificate *certs;
// 證書撤銷列表CRL(可以為空)
// struct x509_certificate *crl;
// Signer信息列表
// struct pkcs7_signed_info *signed_infos;
// PKCS#7消息格式版本號。1: PKCS#7或CMS; 3:CMS
// u8 version;
// 表示PKCS#7消息中是否包含authenticatedAttributes字段
// bool have_authattrs;
// 內容類型
// enum OID data_type;
// 被簽名的內容的長度
// size_t data_len;
// 被簽名的內容的ASN.1 header的長度
// size_t data_hdrlen;
// 被簽名的內容(如果為NULL表示是detached content)
// const void *data;
// };
//
// Signer信息
// struct pkcs7_signed_info {
// 指向下一個signer信息
// struct pkcs7_signed_info *next;
// 指向位于pkcs7_message.certs的signer證書(如果有的話)
// struct x509_certificate *signer;
// 當前signer信息在signer信息列表中的位置
// unsigned index;
// True if not usable due to missing crypto
// bool unsupported_crypto;
//
// Auth屬性值: Message digest - the digest of the Content Data (or NULL)
// const void *msgdigest;
// unsigned msgdigest_len;
//
// Authenticated Attribute data (or NULL) */
// unsigned authattrs_len;
// const void *authattrs;
//
// Auth屬性值
// unsigned long aa_set;
// Auth屬性值
// time64_t signing_time;
//
// 指向signer生成的簽名信息
// struct public_key_signature *sig;
// };
//
// struct public_key_signature {
// [0]包含的是由signer的issuerAndSerialNumber字段生成的key id
// [1]包含的則是skid(可以在X.509證書中的X509v3 Subject Key Identifier字段找到該值)
// struct asymmetric_key_id *auth_ids[2];
// 具體的簽名值
// u8 *s;
// 簽名值的長度
// u32 s_size;
// 摘要值
// u8 *digest;
// 摘要值的長度
// u8 digest_size;
// 簽名算法,目前僅支持"rsa"
// const char *pkey_algo;
// 摘要算法,比如"sha256"等
// const char *hash_algo;
// };
pkcs7 = pkcs7_parse_message(raw_pkcs7, pkcs7_len);
if (IS_ERR(pkcs7))
return PTR_ERR(pkcs7);
/* The data should be detached - so we need to supply it. */
// 對于內核模塊來說,content總是detached的。
if (data && pkcs7_supply_detached_data(pkcs7, data, len) < 0) {
pr_err("PKCS#7 signature with non-detached datan");
ret = -EBADMSG;
goto error;
}
// 驗證PKCS#7簽名。過程如下:
// 1. 計算內核模塊主體內容的摘要值
// 2. 遍歷每一個signer信息,如果PKCS#7中包含了signer的X.509證書的話,使用其
// 證書中的公鑰驗證signer的簽名值
// 3. 如果signer信息中不包含signer的X.509證書的話,這里同樣也會返回0,目的是
// 為了讓接下來的system trusted key來驗證signer中的的簽名值
ret = pkcs7_verify(pkcs7, usage);
if (ret < 0)
goto error;
// 確定驗證簽名的、且可信的issuer證書的來源
if (!trusted_keys) {
trusted_keys = builtin_trusted_keys;
} else if (trusted_keys == (void *)1UL) {
#ifdef CONFIG_SECONDARY_TRUSTED_KEYRING
trusted_keys = secondary_trusted_keys;
#else
trusted_keys = builtin_trusted_keys;
#endif
}
// 用system trusted keyring中的system trusted key作為signer的X.509證書來驗證
// PKCS#7中包含的每一個signer的簽名值。這里的邏輯很簡單,既然PKCS#7中可能
// 沒有包含signer的證書,那么如果能在system trusted keyring中找到signer的證書
// 來驗證每一個signer的簽名值也是可以的。
// 這里只考慮最簡單的情況:signer信息中不包含X.509證書的情況。
// 首先用auth_ids[0]、即通過signer.IssuerAndSerialNumbe字段
// 計算出的key id在system trusted keyring中搜索與之匹配的system trusted key。
// 如果沒有找到,返回-ENOKEY,同時打印錯誤;如果找到匹配的system trusted key,
// 則使用該key驗證PKCS#7中的簽名。
ret = pkcs7_validate_trust(pkcs7, trusted_keys);
if (ret < 0) {
if (ret == -ENOKEY)
pr_err("PKCS#7 signature not signed with a trusted keyn");
goto error;
}
if (view_content) {
size_t asn1hdrlen;
ret = pkcs7_get_content_data(pkcs7, &data, &len, &asn1hdrlen);
if (ret < 0) {
if (ret == -ENODATA)
pr_devel("PKCS#7 message does not contain datan");
goto error;
}
ret = view_content(ctx, data, len, asn1hdrlen);
}
error:
pkcs7_free_message(pkcs7);
pr_devel("<==%s() = %dn", __func__, ret);
return ret;
}
EXPORT_SYMBOL_GPL(verify_pkcs7_signature);
#endif /* CONFIG_SYSTEM_DATA_VERIFICATION */
查看builtin trusted keyring
$ sudo keyctl list %:.builtin_trusted_keys
6 keys in keyring:
...
所有builtin trusted keyring中的key都可以用來對內核模塊進行簽名。
FAQ
如何判斷一個內核模塊是否簽過名?
有一種不太準確的方法是直接檢查內核模塊的二進制內容:
tail < module.ko >
如果有出現“ Module signature appended ”字樣,大多數情況下可以認為該內核模塊是有簽名的。之所以這里的措辭很謹慎,原因是這里沒有用科學的方法去解析該模塊的簽名格式是否正確,也沒有去驗證里面的key是否真的有效。
如果添加額外的內核模塊簽名key?
- 在重編內核的前提下,向CONFIG_MODULE_SIG_KEY指定的key文件級聯更多的key即可。
- 在不重編內核的前提下,借用CONFIG_SYSTEM_EXTRA_CERTIFICATE可以添加額外的內核模塊簽名key。這種方法要求內核在build時要事先保留足夠大的空間。如果該空間的總容量本身就很小,拿只能精簡要導入的X.509證書的內容;如果是因為已經添加過別的額外的system trusted key或內核模塊簽名key導致空間不足的話,可以考慮從內核image中刪除其他不再使用的key。
如何刪除內核模塊的簽名?
strip --keep-file-symbols < module_name >
Troubleshooting
加載內核模塊時出現錯誤“module verification failed: signature and/or required key missing - tainting kernel”
該錯誤信息表示該內核模塊沒有經過簽名,但是由于內核沒有開啟強制驗證,因此該內核模塊還是被加載了。
加載內核模塊時出現錯誤“ERROR: could not insert module : Required key not available”(errno為-ENOKEY)
如果伴隨著這個錯誤的同時,內核還報“PKCS#7 signature not signed with a trusted key”這個錯誤的話,說明被加載的模塊雖然經過了簽名,但簽名key并不存在于builtin trusted keyring中,因此內核無法驗證簽名的有效性。
如果內核沒有報“PKCS#7 signature not signed with a trusted key”這個錯誤,說明被加載的模塊根本沒有經過簽名。
加載內核模塊時出現錯誤“ERROR: could not insert module : Key was rejected by service”(errno為-EKEYREJECTED)
說明被加載的模塊雖然經過了簽名,但簽名key并不存在于builtin system trusted keyring中,因此內核拒絕加載該內核模塊。
注意這個錯誤與錯誤“Required key not available”+“PKCS#7 signature not signed with a trusted key”的區別:后者表示簽名key與位于builtin system trusted keyring中的key是有關聯的,比如builtin system trusted keyring中只包含了父key,而簽名key是由父key簽發的;或者是builtin system trusted keyring中只包含了由父key簽發的簽名key,而簽名key是父key。前者則表示簽名key與位于builtin system trusted keyring中的key(如果有key的話)是沒有任何關聯性的。
另外之前碰到過一個case也會返回-EKEYREJECTED:trusted keyring里帶了2個不同的證書,但是卻具有相同的issuer和serial number。問題現象是:用這2個證書對應的私鑰來簽名,總有一個會失敗。根因是認證PKCS#7 signer簽名的時候,是用生成簽名的證書key id(issuer+證書序列號)作為KEY來search trusted keyring的。因此,如果用第一個被search到的證書key id對應的證書不是生成簽名的證書,就會導致-EKEYREJECTED錯誤。解決方法很簡單:用不同的serial number來生成同一個CA生成的子證書。
對內核模塊簽名后又strip導致內核模塊簽名驗證失敗
模塊一旦被簽名好,再次對其內容的修改會導致簽名驗證失敗。
這種修改可能有多種來源: - rpmbuild可能會考慮到減小initramfs的尺寸進而在打包時strip掉內核模塊的debuginfo。
RHEL 3.10的內核模塊簽名格式
RHEL 3.10的簽名格式是redhat自己弄的,和standard linux kernel的格式不兼容。
連modinfo這個開源工具,對模塊簽名格式的支持的code,也是redhat寫的。最新的modinfo對5.x的模塊簽名都不支持顯示,但能正確顯示3.10的redhat自己實現和port的模塊簽名的格式。
TODO
使用PKCS#11 URI進行內核模塊簽名
PKCS#11由RFC7512定義。它主要是定義了HSM的使用接口。雖然使用HSM不太方便,但是安全性還是有一定保障的,而且如果現場出了嚴重,必須親自上陣的話,用HSM可能反而是最穩妥的辦法。
BUG
- 如果內核不支持內核模塊簽名,但插入的內核模塊是經過簽名的,則插入失敗并返回-ENOPKG。
- 在沒有啟用內核簽名強制驗證的情況下,如果模塊的簽名key不在builtin trusted keyring中,會被拒絕加載。
- 在啟用內核簽名強制驗證的情況下,如果模塊沒有簽名的話,會報“PKCS#7 signature not signed with a trusted key”這個有誤導性的錯誤信息。
附錄
簽名工具sign-file
sign-file工具的源碼位于kernel源碼目錄中的script目錄下。
用法
scripts/sign-file [-dp] < hash_algo > < private_key_name >
< x509_name > < module_name > [< dest_name >]
scripts/sign-file -s < raw_sig_name > < hash_algo > < x509_name > < module_name > [< dest >]
是簽名使用的摘要算法;是簽名的私鑰文件,支持pkcs#11或PEM格式;是與私鑰文件關聯的X.509證書文件(PEM或DER格式都可以);是被簽名的內核模塊的名字;是簽名后的內核模塊文件。
參數
-k
沒有指定-k參數的話,OpenSSL使用issuer name和issuer序列號來標識簽名證書;如果指定了-k參數的話,則使用subject key identifier(SKID)來標識簽名證書。(注釋:證書的AKID表示父CA證書的SKID;如果一個證書的AKID等于SKID,表示該證書是自簽名的;因為issuer name和issuer都存在重名的情況,因此x509 v3增加了SKID和AKID擴展)。
默認情況下,kbuild沒有設置該參數。
具體來說,在認證PKCS#7 signer簽名的時候,是用生成簽名的證書key id(issuer+證書序列號,由于沒有指定-k參數)作為KEY來search trusted keyring的。
-p
將PKCS#7消息單獨存為以.p7s為后綴的文件。可以用openssl pkcs7 -text -print -inform der -in .p7s來查看PKCS#7消息的詳細內容。
默認情況下,kbuild沒有設置該參數。
-s
指定由-p參數生成的.p7s文件作為內核模塊簽名中的部分。
默認情況下,kbuild沒有設置該參數。
-d
只是走一下簽名的流程,并不真的對模塊進行簽名;同時兼具-p的功能。
默認情況下,kbuild沒有設置該參數。
環境變量
KBUILD_SIGN_PIN:
該環境變量有兩個作用: - 在不使用PKCS#11訪問簽名用私鑰的情況下,用來指定私鑰文件的解密口令。 - 在使用PKCS#11訪問簽名用私鑰的情況下,用來指定PKCS#11的PIN碼。
源碼分析
sign-file會使用X.509證書中的issuer和serial來設置PKCS#7 signer info中的issuer和serial中的字段。因此,如果內核模塊是由子key簽的,但放到system trusted keyring中只有父key的話,內核在驗證簽名時會使用issuer為父key+序號為子key證書的key id組合到system trusted keyring中查找匹配的key,結果肯定是不匹配。
模塊簽名的格式
< 內核模塊的內容 >
< PKCS#7消息 >
< 模塊簽名 >
< Mark字符串"~Module signature appended~n"(共28字節,不包括結尾的NULL字符) >
PKCS#7消息的格式
PKCS#7是一種加密消息的語法標準,由RSA安全體系在公鑰加密系統中交換數字證書產生的一種加密標準。有關其詳細格式,請參考RFC 2315 - PKCS #7: Cryptographic Message Syntax Version 1.5。
PKCS#7消息的格式的主體是content,共支持6種content類型:
- data
- signedData
- envelopedData
- signedAndEnvelopedData
- digestedData
- encryptedData
content的具體格式由content類型的定義來決定。目前內核中的PKCS#7 parser僅支持content類型為signedData的PKCS#7消息。
下面是PKCS#7的格式定義:
ContentInfo ::= SEQUENCE {
// 定義了content的類型。
// 該字段的類型為:ContentType ::= OBJECT IDENTIFIER
contentType ContentType,
// content的具體格式由contentType字段的定義來決定。
content
[0] EXPLICIT ANY DEFINED BY contentType OPTIONAL }
生成signedData類型的PKCS#7消息
生成signedData類型的PKCS#7消息的過程如下:
- Signer選定digest-encryption算法。
- Signer將被簽名的數據以及authenticated attribute(如果有的話)作為輸入,計算出摘要值。 注意:authenticated attribute中真正參與摘要計算的只有DER格式的Attributes的值:
authenticatedAttributes
[0] IMPLICIT Attributes OPTIONAL,
Attributes值的tag是SET OF,不包括前面的IMPLICIT [0] tag。
- Signer將計算出來的摘要值和使用的摘要算法組織成下面的BER格式:
DigestInfo ::= SEQUENCE {
digestAlgorithm DigestAlgorithmIdentifier, // 摘要算法OID
digest Digest // 摘要值, 類型為OCTET STRING
}
- Signer用自己的私鑰對DigestInfo進行加密。
- 上述操作通常無需signer自己手動完成,因為PKCS#1 v1.5在底層會自動完成上述操作。
- 將Signer的證書以及關聯的issuer證書以及其他相關信息封裝成PKCS#7消息格式。
PKCS#7允許多個signer對相同的輸入內容進行簽名,每個簽名以及signer信息都會封裝到PKCS#7消息中。因此一個PKCS#7消息可以同時對多個signer進行認證。
signedData類型的PKCS#7消息格式詳解
signedData類型的PKCS#7消息由一組signer信息以及一組signer/issuer的證書組成。signer信息包括特定signer提供的簽名值,signer證書包括signer的實體信息以及公鑰。issuer證書用于對signer進行身份認證。
// PKCS#7類型為signedData的格式定義
SignedData ::= SEQUENCE {
// 表示PKCS#7消息格式語法的版本號
// 該字段的類型為:Version ::= INTEGER
version Version,
// 表示所有signer使用的摘要算法的集合。
// 該字段的類型為:
// DigestAlgorithmIdentifiers ::= SET OF DigestAlgorithmIdentifier
// DigestAlgorithmIdentifier ::= AlgorithmIdentifier
digestAlgorithms DigestAlgorithmIdentifiers,
// 包含被簽名的內容。
// contentType表示內容的類型;content表示實際被簽名的內容。
// 注意:真正被簽名的只有content自身,不包括DER編碼中的id和長度字段。
// 如果content置空,表示真正被簽名的content不在PKCS#7中,這種情況被稱
// 為detached content。
// 該字段的類型為:
// ContentInfo ::= SEQUENCE {
// contentType ContentType,
// content
// [0] EXPLICIT ANY DEFINED BY contentType OPTIONAL }
contentInfo ContentInfo,
// 一組證書PKCS#6格式的擴展證書或X.509證書。理論上來說,signer的
// 證書,以及所有與signer相關聯的全部issuer證書甚至連根證書都可以放
// 在該字段中,從而構成一個證書層級結構;但實際上該字段也是可以為空的。
// 該字段的類型為:
// ExtendedCertificatesAndCertificates ::=
// SET OF ExtendedCertificateOrCertificate
// 可以是PKCS#6格式的擴展證書或X.509證書。
// ExtendedCertificateOrCertificate ::= CHOICE {
// certificate Certificate, -- X.509
// extendedCertificate [0] IMPLICIT ExtendedCertificate }
certificates
[0] IMPLICIT ExtendedCertificatesAndCertificates
OPTIONAL,
// 包含一組撤銷證書。
// 該字段的類型為:
// CertificateRevocationLists ::= SET OF CertificateRevocationList
crls
[1] IMPLICIT CertificateRevocationLists OPTIONAL,
// 包含一組signer的相關信息
signerInfos SignerInfos
}
// 包含一組signer的相關信息
SignerInfos ::= SET OF SignerInfo
// 包含每個signer的相關信息:signer使用的簽名證書,摘要算法,
// 簽名算法,簽名以及屬性字段等。
SignerInfo ::= SEQUENCE {
// 表示PKCS#7消息格式語法的版本號
// 本字段的類型為: Version ::= INTEGER
version Version,
// 每個證書issuer都會為其所頒發的證書分配一個證書序列號,且該
// 序列號在其所頒發的所有證書中是唯一的。因此在給定證書issuer的
// 專有名稱以及其頒發的證書序列號的共同作用下,本字段可以為用
// 于唯一標識指定證書issuer頒發的某個特定的證書。
// 本字段的類型為:
// IssuerAndSerialNumber ::= SEQUENCE {
// issuer Name, 證書issuer的專有名稱
// serialNumber CertificateSerialNumber
// }
issuerAndSerialNumber IssuerAndSerialNumber,
// Signer使用的摘要算法
// 本字段的類型為: DigestAlgorithmIdentifier ::= AlgorithmIdentifier
digestAlgorithm DigestAlgorithmIdentifier,
// 一組經過簽名或認證的屬性信息
authenticatedAttributes
[0] IMPLICIT Attributes OPTIONAL,
// Signer使用的簽名算法,比如PKCS#1 RSA。
// 本字段的類型為:
// DigestEncryptionAlgorithmIdentifier ::= AlgorithmIdentifier
digestEncryptionAlgorithm
DigestEncryptionAlgorithmIdentifier,
// 用signer的私鑰加密后的結果,即所謂的簽名值
encryptedDigest EncryptedDigest,
// 一組未經簽名或認證的屬性信息
unauthenticatedAttributes
[1] IMPLICIT Attributes OPTIONAL }
內核模塊簽名中的PKCS#7消息
內核對內核模塊簽名中的PKCS#7消息格式有如下硬性要求:
- PKCS#7消息的content類型必須是signedData。
- contentInfo包含的data的content類型必須是data。
- 版本號必須為1(表示PKCS#7 v1, 見RFC2315 9.1節;或CMS v1,見RFC5652 5.1節)或3( CMS v 3 RFC2315 5.1節)。
- 必須包含至少一個signer的信息。
- 所有的signer信息都不能包含authenticatedAttributes字段。
- 忽略所有的unauthenticatedAttributes字段。
- 簽名算法必須是PKCS#1 RSA加密算法。
module_signature的格式
該簽名格式包含了模塊簽名的各種元數據,并采用big endian編碼。
struct module_signature {
uint8_t algo; /* Public-key crypto algorithm [0] */
uint8_t hash; /* Digest algorithm [0] */
uint8_t id_type; /* Key identifier type [PKEY_ID_PKCS7] */
uint8_t signer_len; /* Length of signer's name [0] */
uint8_t key_id_len; /* Length of key identifier [0] */
uint8_t __pad[3];
uint32_t sig_len; /* Length of signature data */ htonl(size of .p7s)
};
- algo 表示簽名算法ID,必須為0。
- hash 表示摘要算法ID,必須為0。
- id_type module的簽名格式,當前僅支持PKEY_ID_PKCS7(2)。
- signer_len 未使用,必須為0。
- key_id_len 未使用,必須為0。
- __pad[3] 填充字段,不使用,必須為0.
- sig_len 表示PKCS#7消息字段的字節長度。
-
LINUX內核
+關注
關注
1文章
316瀏覽量
21703
發布評論請先 登錄
相關推薦
評論