概述
顧名思義,在開啟該功能之后,內(nèi)核在加載內(nèi)核模塊時(shí),會(huì)對(duì)內(nèi)核模塊的簽名進(jìn)行檢查。
如果內(nèi)核模塊本身沒有經(jīng)過簽名,或者簽名值與預(yù)期值不符,這兩種情況都會(huì)被認(rèn)為是簽名認(rèn)證失敗。根據(jù)策略的不同,簽名認(rèn)證失敗可能會(huì)導(dǎo)致模塊被拒絕加載,也可能是繼續(xù)正常加載但內(nèi)核會(huì)顯示一條警告信息。
內(nèi)核模塊簽名功能的本質(zhì)是限制root用戶載入惡意的內(nèi)核模塊。當(dāng)root用戶加載一個(gè)內(nèi)核模塊時(shí),內(nèi)核在分辨是系統(tǒng)管理員還是攻擊者的時(shí)候,依靠的就是能夠進(jìn)行身份認(rèn)證的可信的X.509證書和與之對(duì)應(yīng)的私鑰。只要私鑰存儲(chǔ)妥當(dāng)不發(fā)生泄露,攻擊者就無法偽造X.509證書,因此也就不可能提供含有正確簽名的內(nèi)核模塊;而系統(tǒng)管理員是唯一合法的X.509證書的使用者,是可以用合法的證書對(duì)應(yīng)的私鑰對(duì)內(nèi)核模塊進(jìn)行簽名的。
最佳實(shí)踐
- 用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的內(nèi)容如下:
[ 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
- 配置內(nèi)核
CONFIG_MODULE_SIG=y
CONFIG_MODULE_SIG_FORCE=y
CONFIG_MODULE_SIG_KEY=<前一個(gè)步驟生成的system_key.pem文件的路徑>
CONFIG_MODULE_SIG_HASH="sha256"
CONFIG_MODULE_SIG_SHA256=y
CONFIG_CRYPTO_SHA256=y
CONFIG_MODULE_SIG_ALL=y - 編譯內(nèi)核和模塊
make bzImage modules
- 安裝內(nèi)核和內(nèi)核模塊
sudo make modules_install install
- 對(duì)內(nèi)核模塊進(jìn)行簽名
在指定了CONFIG_MODULE_SIG_ALL=y的情況下,kbuild系統(tǒng)可以自動(dòng)對(duì)模塊進(jìn)行簽名,而且該步驟通常無需手動(dòng)運(yùn)行,會(huì)在module_install時(shí)自動(dòng)執(zhí)行簽名。
make modules_sign
- 用openssl全手動(dòng)簽名和驗(yàn)簽
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
內(nèi)核配置選項(xiàng)
CONFIG_MODULE_SIG:Module signature verification
如果開啟了該選項(xiàng),在內(nèi)核在加載內(nèi)核模塊時(shí),會(huì)對(duì)內(nèi)核模塊的簽名進(jìn)行檢查。
默認(rèn)情況下,在加載沒有簽名或者是簽名不正確的內(nèi)核模塊時(shí),內(nèi)核僅僅是打印一條提示信息,比如:
k_netlink: module verification failed: signature and/or required key missing - tainting kernel
同時(shí)將內(nèi)核標(biāo)記為tainted,然后繼續(xù)正常加載簽名有問題的模塊。
CONFIG_MODULE_SIG_FORCE: Require modules to be validly signed
如果開啟了該選項(xiàng),在加載沒有簽名或者是簽名不正確的內(nèi)核模塊時(shí),內(nèi)核會(huì)直接拒絕加載簽名有問題的內(nèi)核模塊。
CONFIG_MODULE_SIG_HASH: Which hash algorithm should modules be signed with?
該選項(xiàng)用于決定簽名時(shí)使用的摘要算法。每一種算法都有對(duì)應(yīng)的內(nèi)核選項(xiàng):
- 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
注意:內(nèi)核會(huì)使用crypto子系統(tǒng)中實(shí)現(xiàn)的摘要算法內(nèi)核模塊來計(jì)算摘要值,這就意味著這些實(shí)現(xiàn)摘要算法的模塊必須已經(jīng)事先編譯為內(nèi)置,否則就會(huì)發(fā)生“為了計(jì)算簽名摘要值而要加載對(duì)應(yīng)的模塊但該模塊的簽名因缺少摘要算法模塊而無法計(jì)算“的窘?jīng)r。
CONFIG_MODULE_SIG_KEY: File name or PKCS#11 URI of module signing key
該選項(xiàng)指定了用于對(duì)內(nèi)核模塊簽名的PEM格式的簽名key文件的路徑,或者是一個(gè)PKCS#11 URI(在實(shí)際進(jìn)行簽名前,簽名工具會(huì)先把這個(gè)URI指定的key下載下來)。該選項(xiàng)的默認(rèn)值是"certs/signing_key.pem"。如果沒有修改這個(gè)默認(rèn)值,內(nèi)核會(huì)自動(dòng)創(chuàng)建"certs/signing_key.pem"文件用于內(nèi)核模塊簽名。
kbuild會(huì)將這個(gè)X.509證書文件轉(zhuǎn)換為system certificate list并編譯進(jìn)內(nèi)核中,然后在system trusted keyring初始化階段將這個(gè)list中的每一個(gè)X.509證書都添加到builtin trusted keyring中。此時(shí),每一個(gè)X.509證書就是一把system trusted key。因此,該選項(xiàng)指定的key文件中的所有X.509證書還可以當(dāng)成system trusted key]來用。key文件至少包含一個(gè)PEM格式的私鑰和與之關(guān)聯(lián)的、PEM格式的X.509證書。可以通過級(jí)聯(lián)的方式將更多的X.509證書添加到該key文件中。
但是反過來卻是不成立的:并不是所有的system trusted key都可以用來驗(yàn)證內(nèi)核模塊的簽名,比如secondary trusted keyring中的key就不能用來驗(yàn)證內(nèi)核模塊的簽名;只有builtin trusted keyring中的key才可以用來驗(yàn)證內(nèi)核模塊的簽名。
最后要提醒的是,用于模塊簽名的簽名key可以是自簽名的,也可以不是。如果不是自簽名的,只要確保簽名key和父key能被導(dǎo)入到builtin trusted keyring即可。不要求父key的父key也必須在builtin trusted keyring中。
CONFIG_MODULE_SIG_ALL: Automatically sign all modules
選中該選項(xiàng)后,kbuild會(huì)在執(zhí)行make modules_install的時(shí)候?qū)λ械膬?nèi)核模塊進(jìn)行簽名。如果沒有選中該選項(xiàng),則需要使用者自己手動(dòng)調(diào)用scripts/sign-file簽名工具對(duì)內(nèi)核模塊進(jìn)行簽名。
內(nèi)核啟動(dòng)參數(shù)
module.sig_enforce
如果將該參數(shù)傳給內(nèi)核,表示強(qiáng)制驗(yàn)證內(nèi)核模塊簽名,效果上等價(jià)于CONFIG_MODULE_SIG_FORCE=y。如果內(nèi)核在編譯時(shí)已經(jīng)將CONFIG_MODULE_SIG_FORCE設(shè)為了y,那這里的內(nèi)核選項(xiàng)是不會(huì)起任何作用的。
該選項(xiàng)為內(nèi)核強(qiáng)制驗(yàn)證功能在策略上提供了一定的靈活性,比如運(yùn)行系統(tǒng)需要DKMS或者SystemTap支持的話,如果沒有實(shí)現(xiàn)配套的PKI簽名服務(wù)機(jī)制,最好將CONFIG_MODULE_SIG_FORCE設(shè)為n,同時(shí)為了保證安全在內(nèi)核命令行參數(shù)中指定module.sig_enforce。在必要時(shí),可以臨時(shí)去掉module.sig_enforce,以便系統(tǒng)維護(hù)或調(diào)試。
實(shí)現(xiàn)細(xì)節(jié)
內(nèi)核編譯階段
如果CONFIG_MODULE_SIG_KEY參數(shù)為默認(rèn)值,自動(dòng)生成內(nèi)核模塊簽名key文件certs/signing_key.pem
如果CONFIG_MODULE_SIG_KEY的值為默認(rèn)值certs/signing_key.pem,表示用戶希望使用由kbuild自動(dòng)生成的key文件對(duì)內(nèi)核模塊進(jìn)行簽名。
kbuild會(huì)自動(dòng)執(zhí)行以下命令生成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
該命令的含義是:根據(jù)X509證書請(qǐng)求配置模板文件的內(nèi)容,生成一個(gè)自簽名的X509證書。生成的key文件保存為certs/signing_key.pem, 同時(shí)將自簽名的X509證書附在key內(nèi)容的后面。
其中生成X509證書請(qǐng)求時(shí)使用的配置模板文件的內(nèi)容為:
[ 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
將內(nèi)核模塊簽名key文件build到system certificate list中
kbuild會(huì)調(diào)用scripts/extract-cert程序?qū)ONFIG_MODULE_SIG_KEY指定的PEM格式的X.509證書文件轉(zhuǎn)換為DER格式的certs/signing_key.x509文件:
scripts/extract-cert $(CONFIG_MODULE_SIG_KEY) signing_key.x509
如果CONFIG_MODULE_SIG_KEY指定的一個(gè)PKCS#11 URI,scripts/extract-cert程序也會(huì)在簽名之前先下載好再使用。
接下來,上一個(gè)步驟生成的certs/signing_key.x509文件會(huì)連同system_certificates.S一起被編譯為certs/system_certificates.o。該object文件的內(nèi)容會(huì)被包含到內(nèi)核image的.init.rodata節(jié)中。
__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證書最終都會(huì)被加載到builtin trusted keyring中,并作為system trusted key來使用。
順便一提,由內(nèi)核選項(xiàng)CONFIG_SYSTEM_TRUSTED_KEYS指定的system trusted key文件(即certs/x509_certificate_list)也會(huì)成為system certificate list的一部分。準(zhǔn)確來說,它會(huì)在內(nèi)核初始化system trusted klseyring時(shí)加入到builtin trusted keyring中。
Kernel啟動(dòng)階段
初始化system trusted keyrings
由內(nèi)核選項(xiàng)CONFIG_MODULE_SIG_KEY指定的對(duì)內(nèi)核模塊進(jìn)行簽名的key文件會(huì)在這個(gè)階段被加載到builtin trusted keyring中。
用戶態(tài)運(yùn)行時(shí)階段
加載內(nèi)核模塊
- 當(dāng)加載內(nèi)核模塊時(shí),會(huì)對(duì)內(nèi)核模塊的簽名進(jìn)行認(rèn)證。最終認(rèn)證的結(jié)果會(huì)保存在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;
...
- 如果認(rèn)證失敗,info->sig_ok返回0,因此內(nèi)核會(huì)打印一條信息:: module verification failed: signature and/or required key missing - tainting kernel,然后會(huì)將kernel狀態(tài)標(biāo)記為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
- 對(duì)內(nèi)核模塊的簽名認(rèn)證的過程如下:
- 首先檢查flags是否為0。當(dāng)執(zhí)行modprobe -f時(shí),會(huì)要求內(nèi)核忽略所有的版本檢查。這會(huì)導(dǎo)致作為內(nèi)核模塊的數(shù)據(jù)的一部分的版本信息將被完全忽略。因此如果一個(gè)曾經(jīng)簽過名的模塊有安全漏洞,那么攻擊者可以在新內(nèi)核上強(qiáng)行加載這個(gè)有漏洞的模塊以便攻擊新內(nèi)核。因此,內(nèi)核認(rèn)為flags為非0值的時(shí)候,是可能存在上述跨內(nèi)核版本攻擊的可能性的,因此執(zhí)行modprobe -f一定會(huì)導(dǎo)致簽名認(rèn)證失敗。
- 檢查內(nèi)核模塊尾部的mark字段是否為“
MODULE_SIG_STRINGn”。 - 如果mark字段存在,進(jìn)行簽名檢查。
- 如果mark不存在,根據(jù)當(dāng)前的模塊簽名檢查策略,決定返回值;如果使用了強(qiáng)制簽名檢查策略(CONFIG_MODULE_SIG_FORCE=y或指定了module.sig_enforce),則返回-ENOKEY;如果沒有使用強(qiáng)制簽名檢查策略,返回0。
- 檢查flags是否為0
結(jié)論:執(zhí)行modprobe -f一定會(huì)導(dǎo)致簽名認(rèn)證失敗, 這一點(diǎn)所有的責(zé)任人都要注意。原因是:作為內(nèi)核模塊的數(shù)據(jù)的一部分的版本信息將被modprobe -f完全忽略,如果一個(gè)曾經(jīng)簽過名的模塊有安全漏洞,那么攻擊者可以在新內(nèi)核上強(qiáng)行加載這個(gè)有漏洞的模塊以便攻擊新內(nèi)核。
#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簽名格式中的各個(gè)字段,然后調(diào)用verify_pkcs7_signature()驗(yàn)證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包括模塊內(nèi)容+PKCS#7消息
sig_len = be32_to_cpu(ms.sig_len); // PKCS#7消息的長度
if (sig_len >= modlen)
return -EBADMSG;
modlen -= sig_len; // 僅包含模塊內(nèi)容的長度
*_modlen = modlen; // 返回模塊內(nèi)容的長度
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);
}
- 對(duì)內(nèi)核模塊中的PKCS#7格式的消息進(jìn)行驗(yàn)證。注意其中的trusted_keys參數(shù)為NULL,因此能夠用于驗(yàn)證內(nèi)核模塊簽名的,僅限builtin trusted keyring中的key;而導(dǎo)入到secondary trusted keyring中的key是不能用于內(nèi)核模塊簽名的。
#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;
// 解析內(nèi)核模塊簽名中的PKCS#7消息(DER格式),并將相關(guān)字段
// 填充到pkcs7數(shù)據(jù)結(jié)構(gòu)中。
// struct pkcs7_message {
// PKCS#7中的signer證書列表(可以為空)
// struct x509_certificate *certs;
// 證書撤銷列表CRL(可以為空)
// struct x509_certificate *crl;
// Signer信息列表
// struct pkcs7_signed_info *signed_infos;
// PKCS#7消息格式版本號(hào)。1: PKCS#7或CMS; 3:CMS
// u8 version;
// 表示PKCS#7消息中是否包含authenticatedAttributes字段
// bool have_authattrs;
// 內(nèi)容類型
// enum OID data_type;
// 被簽名的內(nèi)容的長度
// size_t data_len;
// 被簽名的內(nèi)容的ASN.1 header的長度
// size_t data_hdrlen;
// 被簽名的內(nèi)容(如果為NULL表示是detached content)
// const void *data;
// };
//
// Signer信息
// struct pkcs7_signed_info {
// 指向下一個(gè)signer信息
// struct pkcs7_signed_info *next;
// 指向位于pkcs7_message.certs的signer證書(如果有的話)
// struct x509_certificate *signer;
// 當(dāng)前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. */
// 對(duì)于內(nèi)核模塊來說,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;
}
// 驗(yàn)證PKCS#7簽名。過程如下:
// 1. 計(jì)算內(nèi)核模塊主體內(nèi)容的摘要值
// 2. 遍歷每一個(gè)signer信息,如果PKCS#7中包含了signer的X.509證書的話,使用其
// 證書中的公鑰驗(yàn)證signer的簽名值
// 3. 如果signer信息中不包含signer的X.509證書的話,這里同樣也會(huì)返回0,目的是
// 為了讓接下來的system trusted key來驗(yàn)證signer中的的簽名值
ret = pkcs7_verify(pkcs7, usage);
if (ret < 0)
goto error;
// 確定驗(yàn)證簽名的、且可信的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證書來驗(yàn)證
// PKCS#7中包含的每一個(gè)signer的簽名值。這里的邏輯很簡單,既然PKCS#7中可能
// 沒有包含signer的證書,那么如果能在system trusted keyring中找到signer的證書
// 來驗(yàn)證每一個(gè)signer的簽名值也是可以的。
// 這里只考慮最簡單的情況:signer信息中不包含X.509證書的情況。
// 首先用auth_ids[0]、即通過signer.IssuerAndSerialNumbe字段
// 計(jì)算出的key id在system trusted keyring中搜索與之匹配的system trusted key。
// 如果沒有找到,返回-ENOKEY,同時(shí)打印錯(cuò)誤;如果找到匹配的system trusted key,
// 則使用該key驗(yàn)證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都可以用來對(duì)內(nèi)核模塊進(jìn)行簽名。
FAQ
如何判斷一個(gè)內(nèi)核模塊是否簽過名?
有一種不太準(zhǔn)確的方法是直接檢查內(nèi)核模塊的二進(jìn)制內(nèi)容:
tail < module.ko >
如果有出現(xiàn)“ Module signature appended ”字樣,大多數(shù)情況下可以認(rèn)為該內(nèi)核模塊是有簽名的。之所以這里的措辭很謹(jǐn)慎,原因是這里沒有用科學(xué)的方法去解析該模塊的簽名格式是否正確,也沒有去驗(yàn)證里面的key是否真的有效。
如果添加額外的內(nèi)核模塊簽名key?
- 在重編內(nèi)核的前提下,向CONFIG_MODULE_SIG_KEY指定的key文件級(jí)聯(lián)更多的key即可。
- 在不重編內(nèi)核的前提下,借用CONFIG_SYSTEM_EXTRA_CERTIFICATE可以添加額外的內(nèi)核模塊簽名key。這種方法要求內(nèi)核在build時(shí)要事先保留足夠大的空間。如果該空間的總?cè)萘勘旧砭秃苄。弥荒芫喴獙?dǎo)入的X.509證書的內(nèi)容;如果是因?yàn)橐呀?jīng)添加過別的額外的system trusted key或內(nèi)核模塊簽名key導(dǎo)致空間不足的話,可以考慮從內(nèi)核image中刪除其他不再使用的key。
如何刪除內(nèi)核模塊的簽名?
strip --keep-file-symbols < module_name >
Troubleshooting
加載內(nèi)核模塊時(shí)出現(xiàn)錯(cuò)誤“module verification failed: signature and/or required key missing - tainting kernel”
該錯(cuò)誤信息表示該內(nèi)核模塊沒有經(jīng)過簽名,但是由于內(nèi)核沒有開啟強(qiáng)制驗(yàn)證,因此該內(nèi)核模塊還是被加載了。
加載內(nèi)核模塊時(shí)出現(xiàn)錯(cuò)誤“ERROR: could not insert module : Required key not available”(errno為-ENOKEY)
如果伴隨著這個(gè)錯(cuò)誤的同時(shí),內(nèi)核還報(bào)“PKCS#7 signature not signed with a trusted key”這個(gè)錯(cuò)誤的話,說明被加載的模塊雖然經(jīng)過了簽名,但簽名key并不存在于builtin trusted keyring中,因此內(nèi)核無法驗(yàn)證簽名的有效性。
如果內(nèi)核沒有報(bào)“PKCS#7 signature not signed with a trusted key”這個(gè)錯(cuò)誤,說明被加載的模塊根本沒有經(jīng)過簽名。
加載內(nèi)核模塊時(shí)出現(xiàn)錯(cuò)誤“ERROR: could not insert module : Key was rejected by service”(errno為-EKEYREJECTED)
說明被加載的模塊雖然經(jīng)過了簽名,但簽名key并不存在于builtin system trusted keyring中,因此內(nèi)核拒絕加載該內(nèi)核模塊。
注意這個(gè)錯(cuò)誤與錯(cuò)誤“Required key not available”+“PKCS#7 signature not signed with a trusted key”的區(qū)別:后者表示簽名key與位于builtin system trusted keyring中的key是有關(guān)聯(lián)的,比如builtin system trusted keyring中只包含了父key,而簽名key是由父key簽發(fā)的;或者是builtin system trusted keyring中只包含了由父key簽發(fā)的簽名key,而簽名key是父key。前者則表示簽名key與位于builtin system trusted keyring中的key(如果有key的話)是沒有任何關(guān)聯(lián)性的。
另外之前碰到過一個(gè)case也會(huì)返回-EKEYREJECTED:trusted keyring里帶了2個(gè)不同的證書,但是卻具有相同的issuer和serial number。問題現(xiàn)象是:用這2個(gè)證書對(duì)應(yīng)的私鑰來簽名,總有一個(gè)會(huì)失敗。根因是認(rèn)證PKCS#7 signer簽名的時(shí)候,是用生成簽名的證書key id(issuer+證書序列號(hào))作為KEY來search trusted keyring的。因此,如果用第一個(gè)被search到的證書key id對(duì)應(yīng)的證書不是生成簽名的證書,就會(huì)導(dǎo)致-EKEYREJECTED錯(cuò)誤。解決方法很簡單:用不同的serial number來生成同一個(gè)CA生成的子證書。
對(duì)內(nèi)核模塊簽名后又strip導(dǎo)致內(nèi)核模塊簽名驗(yàn)證失敗
模塊一旦被簽名好,再次對(duì)其內(nèi)容的修改會(huì)導(dǎo)致簽名驗(yàn)證失敗。
這種修改可能有多種來源: - rpmbuild可能會(huì)考慮到減小initramfs的尺寸進(jìn)而在打包時(shí)strip掉內(nèi)核模塊的debuginfo。
RHEL 3.10的內(nèi)核模塊簽名格式
RHEL 3.10的簽名格式是redhat自己弄的,和standard linux kernel的格式不兼容。
連modinfo這個(gè)開源工具,對(duì)模塊簽名格式的支持的code,也是redhat寫的。最新的modinfo對(duì)5.x的模塊簽名都不支持顯示,但能正確顯示3.10的redhat自己實(shí)現(xiàn)和port的模塊簽名的格式。
TODO
使用PKCS#11 URI進(jìn)行內(nèi)核模塊簽名
PKCS#11由RFC7512定義。它主要是定義了HSM的使用接口。雖然使用HSM不太方便,但是安全性還是有一定保障的,而且如果現(xiàn)場出了嚴(yán)重,必須親自上陣的話,用HSM可能反而是最穩(wěn)妥的辦法。
BUG
- 如果內(nèi)核不支持內(nèi)核模塊簽名,但插入的內(nèi)核模塊是經(jīng)過簽名的,則插入失敗并返回-ENOPKG。
- 在沒有啟用內(nèi)核簽名強(qiáng)制驗(yàn)證的情況下,如果模塊的簽名key不在builtin trusted keyring中,會(huì)被拒絕加載。
- 在啟用內(nèi)核簽名強(qiáng)制驗(yàn)證的情況下,如果模塊沒有簽名的話,會(huì)報(bào)“PKCS#7 signature not signed with a trusted key”這個(gè)有誤導(dǎo)性的錯(cuò)誤信息。
附錄
簽名工具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格式;是與私鑰文件關(guān)聯(lián)的X.509證書文件(PEM或DER格式都可以);是被簽名的內(nèi)核模塊的名字;是簽名后的內(nèi)核模塊文件。
參數(shù)
-k
沒有指定-k參數(shù)的話,OpenSSL使用issuer name和issuer序列號(hào)來標(biāo)識(shí)簽名證書;如果指定了-k參數(shù)的話,則使用subject key identifier(SKID)來標(biāo)識(shí)簽名證書。(注釋:證書的AKID表示父CA證書的SKID;如果一個(gè)證書的AKID等于SKID,表示該證書是自簽名的;因?yàn)閕ssuer name和issuer都存在重名的情況,因此x509 v3增加了SKID和AKID擴(kuò)展)。
默認(rèn)情況下,kbuild沒有設(shè)置該參數(shù)。
具體來說,在認(rèn)證PKCS#7 signer簽名的時(shí)候,是用生成簽名的證書key id(issuer+證書序列號(hào),由于沒有指定-k參數(shù))作為KEY來search trusted keyring的。
-p
將PKCS#7消息單獨(dú)存為以.p7s為后綴的文件。可以用openssl pkcs7 -text -print -inform der -in .p7s來查看PKCS#7消息的詳細(xì)內(nèi)容。
默認(rèn)情況下,kbuild沒有設(shè)置該參數(shù)。
-s
指定由-p參數(shù)生成的.p7s文件作為內(nèi)核模塊簽名中的部分。
默認(rèn)情況下,kbuild沒有設(shè)置該參數(shù)。
-d
只是走一下簽名的流程,并不真的對(duì)模塊進(jìn)行簽名;同時(shí)兼具-p的功能。
默認(rèn)情況下,kbuild沒有設(shè)置該參數(shù)。
環(huán)境變量
KBUILD_SIGN_PIN:
該環(huán)境變量有兩個(gè)作用: - 在不使用PKCS#11訪問簽名用私鑰的情況下,用來指定私鑰文件的解密口令。 - 在使用PKCS#11訪問簽名用私鑰的情況下,用來指定PKCS#11的PIN碼。
源碼分析
sign-file會(huì)使用X.509證書中的issuer和serial來設(shè)置PKCS#7 signer info中的issuer和serial中的字段。因此,如果內(nèi)核模塊是由子key簽的,但放到system trusted keyring中只有父key的話,內(nèi)核在驗(yàn)證簽名時(shí)會(huì)使用issuer為父key+序號(hào)為子key證書的key id組合到system trusted keyring中查找匹配的key,結(jié)果肯定是不匹配。
模塊簽名的格式
< 內(nèi)核模塊的內(nèi)容 >
< PKCS#7消息 >
< 模塊簽名 >
< Mark字符串"~Module signature appended~n"(共28字節(jié),不包括結(jié)尾的NULL字符) >
PKCS#7消息的格式
PKCS#7是一種加密消息的語法標(biāo)準(zhǔn),由RSA安全體系在公鑰加密系統(tǒng)中交換數(shù)字證書產(chǎn)生的一種加密標(biāo)準(zhǔn)。有關(guān)其詳細(xì)格式,請(qǐng)參考RFC 2315 - PKCS #7: Cryptographic Message Syntax Version 1.5。
PKCS#7消息的格式的主體是content,共支持6種content類型:
- data
- signedData
- envelopedData
- signedAndEnvelopedData
- digestedData
- encryptedData
content的具體格式由content類型的定義來決定。目前內(nèi)核中的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將被簽名的數(shù)據(jù)以及authenticated attribute(如果有的話)作為輸入,計(jì)算出摘要值。 注意:authenticated attribute中真正參與摘要計(jì)算的只有DER格式的Attributes的值:
authenticatedAttributes
[0] IMPLICIT Attributes OPTIONAL,
Attributes值的tag是SET OF,不包括前面的IMPLICIT [0] tag。
- Signer將計(jì)算出來的摘要值和使用的摘要算法組織成下面的BER格式:
DigestInfo ::= SEQUENCE {
digestAlgorithm DigestAlgorithmIdentifier, // 摘要算法OID
digest Digest // 摘要值, 類型為OCTET STRING
}
- Signer用自己的私鑰對(duì)DigestInfo進(jìn)行加密。
- 上述操作通常無需signer自己手動(dòng)完成,因?yàn)镻KCS#1 v1.5在底層會(huì)自動(dòng)完成上述操作。
- 將Signer的證書以及關(guān)聯(lián)的issuer證書以及其他相關(guān)信息封裝成PKCS#7消息格式。
PKCS#7允許多個(gè)signer對(duì)相同的輸入內(nèi)容進(jìn)行簽名,每個(gè)簽名以及signer信息都會(huì)封裝到PKCS#7消息中。因此一個(gè)PKCS#7消息可以同時(shí)對(duì)多個(gè)signer進(jìn)行認(rèn)證。
signedData類型的PKCS#7消息格式詳解
signedData類型的PKCS#7消息由一組signer信息以及一組signer/issuer的證書組成。signer信息包括特定signer提供的簽名值,signer證書包括signer的實(shí)體信息以及公鑰。issuer證書用于對(duì)signer進(jìn)行身份認(rèn)證。
// PKCS#7類型為signedData的格式定義
SignedData ::= SEQUENCE {
// 表示PKCS#7消息格式語法的版本號(hào)
// 該字段的類型為:Version ::= INTEGER
version Version,
// 表示所有signer使用的摘要算法的集合。
// 該字段的類型為:
// DigestAlgorithmIdentifiers ::= SET OF DigestAlgorithmIdentifier
// DigestAlgorithmIdentifier ::= AlgorithmIdentifier
digestAlgorithms DigestAlgorithmIdentifiers,
// 包含被簽名的內(nèi)容。
// contentType表示內(nèi)容的類型;content表示實(shí)際被簽名的內(nèi)容。
// 注意:真正被簽名的只有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格式的擴(kuò)展證書或X.509證書。理論上來說,signer的
// 證書,以及所有與signer相關(guān)聯(lián)的全部issuer證書甚至連根證書都可以放
// 在該字段中,從而構(gòu)成一個(gè)證書層級(jí)結(jié)構(gòu);但實(shí)際上該字段也是可以為空的。
// 該字段的類型為:
// ExtendedCertificatesAndCertificates ::=
// SET OF ExtendedCertificateOrCertificate
// 可以是PKCS#6格式的擴(kuò)展證書或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的相關(guān)信息
signerInfos SignerInfos
}
// 包含一組signer的相關(guān)信息
SignerInfos ::= SET OF SignerInfo
// 包含每個(gè)signer的相關(guān)信息:signer使用的簽名證書,摘要算法,
// 簽名算法,簽名以及屬性字段等。
SignerInfo ::= SEQUENCE {
// 表示PKCS#7消息格式語法的版本號(hào)
// 本字段的類型為: Version ::= INTEGER
version Version,
// 每個(gè)證書issuer都會(huì)為其所頒發(fā)的證書分配一個(gè)證書序列號(hào),且該
// 序列號(hào)在其所頒發(fā)的所有證書中是唯一的。因此在給定證書issuer的
// 專有名稱以及其頒發(fā)的證書序列號(hào)的共同作用下,本字段可以為用
// 于唯一標(biāo)識(shí)指定證書issuer頒發(fā)的某個(gè)特定的證書。
// 本字段的類型為:
// IssuerAndSerialNumber ::= SEQUENCE {
// issuer Name, 證書issuer的專有名稱
// serialNumber CertificateSerialNumber
// }
issuerAndSerialNumber IssuerAndSerialNumber,
// Signer使用的摘要算法
// 本字段的類型為: DigestAlgorithmIdentifier ::= AlgorithmIdentifier
digestAlgorithm DigestAlgorithmIdentifier,
// 一組經(jīng)過簽名或認(rèn)證的屬性信息
authenticatedAttributes
[0] IMPLICIT Attributes OPTIONAL,
// Signer使用的簽名算法,比如PKCS#1 RSA。
// 本字段的類型為:
// DigestEncryptionAlgorithmIdentifier ::= AlgorithmIdentifier
digestEncryptionAlgorithm
DigestEncryptionAlgorithmIdentifier,
// 用signer的私鑰加密后的結(jié)果,即所謂的簽名值
encryptedDigest EncryptedDigest,
// 一組未經(jīng)簽名或認(rèn)證的屬性信息
unauthenticatedAttributes
[1] IMPLICIT Attributes OPTIONAL }
內(nèi)核模塊簽名中的PKCS#7消息
內(nèi)核對(duì)內(nèi)核模塊簽名中的PKCS#7消息格式有如下硬性要求:
- PKCS#7消息的content類型必須是signedData。
- contentInfo包含的data的content類型必須是data。
- 版本號(hào)必須為1(表示PKCS#7 v1, 見RFC2315 9.1節(jié);或CMS v1,見RFC5652 5.1節(jié))或3( CMS v 3 RFC2315 5.1節(jié))。
- 必須包含至少一個(gè)signer的信息。
- 所有的signer信息都不能包含authenticatedAttributes字段。
- 忽略所有的unauthenticatedAttributes字段。
- 簽名算法必須是PKCS#1 RSA加密算法。
module_signature的格式
該簽名格式包含了模塊簽名的各種元數(shù)據(jù),并采用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的簽名格式,當(dāng)前僅支持PKEY_ID_PKCS7(2)。
- signer_len 未使用,必須為0。
- key_id_len 未使用,必須為0。
- __pad[3] 填充字段,不使用,必須為0.
- sig_len 表示PKCS#7消息字段的字節(jié)長度。
-
LINUX內(nèi)核
+關(guān)注
關(guān)注
1文章
316瀏覽量
21619
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論