内核模块签名工具¶
概述¶
内核模块签名工具可以在安装期间对模块进行加密签名,然后在加载模块时检查签名。 这可以通过禁止加载未签名的模块或使用无效密钥签名的模块来提高内核安全性。 模块签名通过使其更难以将恶意模块加载到内核中来提高安全性。 模块签名检查由内核完成,因此无需具有受信任的用户空间位。
该工具使用 X.509 ITU-T 标准证书来编码所涉及的公钥。 签名本身未以任何工业标准类型编码。 内置工具目前仅支持 RSA & NIST P-384 ECDSA 公钥签名标准(但它是可插拔的并允许使用其他标准)。 可以使用的哈希算法有 SHA-2 和 SHA-3,大小为 256、384 和 512(该算法由签名中的数据选择)。
配置模块签名¶
可以通过转到内核配置的
部分并启用CONFIG_MODULE_SIG "Module signature verification"
这有许多可用的选项
(
CONFIG_MODULE_SIG_FORCE
)这指定内核应如何处理具有未知密钥的签名或未签名模块的模块。
如果此选项关闭(即“允许”),则允许使用密钥不可用的模块和未签名的模块,但内核将被标记为已污染,并且相关的模块将被标记为已污染,并带有字符“E”。
如果此选项打开(即“限制性”),则仅加载具有有效签名且可以通过内核拥有的公钥验证的模块。 所有其他模块将生成错误。
无论此处的设置如何,如果模块具有无法解析的签名块,它将被立即拒绝。
(
CONFIG_MODULE_SIG_ALL
)如果此选项打开,则将在构建的 modules_install 阶段自动签名模块。 如果此选项关闭,则必须使用以下命令手动签名模块
scripts/sign-file
这提供了一个选择,安装阶段将使用哪种哈希算法对模块进行签名
CONFIG_MODULE_SIG_SHA256
CONFIG_MODULE_SIG_SHA384
CONFIG_MODULE_SIG_SHA512
CONFIG_MODULE_SIG_SHA3_256
CONFIG_MODULE_SIG_SHA3_384
CONFIG_MODULE_SIG_SHA3_512
此处选择的算法也将内置到内核中(而不是作为模块),以便可以使用该算法签名的模块检查其签名,而不会导致依赖关系循环。
(
CONFIG_MODULE_SIG_KEY
)将此选项设置为除其默认值
certs/signing_key.pem
之外的值将禁用签名密钥的自动生成,并允许使用您选择的密钥对内核模块进行签名。 提供的字符串应标识一个文件,该文件以 PEM 形式包含私钥及其对应的 X.509 证书,或者在 OpenSSL ENGINE_pkcs11 功能正常的系统上,标识一个由 RFC7512 定义的 PKCS#11 URI。 在后一种情况下,PKCS#11 URI 应同时引用证书和私钥。如果包含私钥的 PEM 文件已加密,或者如果 PKCS#11 令牌需要 PIN,则可以在构建时通过
KBUILD_SIGN_PIN
变量提供。(
CONFIG_SYSTEM_TRUSTED_KEYS
)可以将此选项设置为 PEM 编码文件的文件名,该文件包含将默认包含在系统密钥环中的其他证书。
请注意,启用模块签名会将 OpenSSL devel 包的依赖项添加到内核构建过程中,用于执行签名的工具。
生成签名密钥¶
需要加密密钥对来生成和检查签名。 私钥用于生成签名,相应的公钥用于检查签名。 仅在构建期间需要私钥,之后可以删除或安全存储。 公钥内置到内核中,以便可以在加载模块时使用它来检查签名。
在正常情况下,当 CONFIG_MODULE_SIG_KEY
与其默认值保持不变时,如果文件中不存在新的密钥对,内核构建将自动使用 openssl 生成一个新的密钥对
certs/signing_key.pem
在构建 vmlinux 期间(密钥的公共部分需要构建到 vmlinux 中),使用以下参数:
certs/x509.genkey
文件中的参数(如果该文件尚不存在,也会生成该文件)。
可以选择 RSA (MODULE_SIG_KEY_TYPE_RSA
) 和 ECDSA (MODULE_SIG_KEY_TYPE_ECDSA
) 来生成 RSA 4k 或 NIST P-384 密钥对。
强烈建议您提供自己的 x509.genkey 文件。
最值得注意的是,在 x509.genkey 文件中,req_distinguished_name 部分应从默认值更改
[ req_distinguished_name ]
#O = Unspecified company
CN = Build time autogenerated kernel key
#emailAddress = unspecified.user@unspecified.company
生成的 RSA 密钥大小也可以使用以下命令设置
[ req ]
default_bits = 4096
也可以使用 Linux 内核源代码树的根节点中的 x509.genkey 密钥生成配置文件和 openssl 命令手动生成密钥私有/公共文件。 以下是生成公共/私有密钥文件的示例
openssl req -new -nodes -utf8 -sha256 -days 36500 -batch -x509 \
-config x509.genkey -outform PEM -out kernel_key.pem \
-keyout kernel_key.pem
然后可以在 CONFIG_MODULE_SIG_KEY
选项中指定生成的 kernel_key.pem 文件的完整路径名,并且将使用其中的证书和密钥,而不是自动生成的密钥对。
内核中的公钥¶
内核包含一个公钥环,root 用户可以查看它。 它们位于一个名为“.builtin_trusted_keys”的密钥环中,可以通过以下方式查看
[root@deneb ~]# cat /proc/keys
...
223c7853 I------ 1 perm 1f030000 0 0 keyring .builtin_trusted_keys: 1
302d2d52 I------ 1 perm 1f010000 0 0 asymmetri Fedora kernel signing key: d69a84e6bce3d216b979e9505b3e3ef9a7118079: X509.RSA a7118079 []
...
除了专门为模块签名生成的公钥之外,还可以在 CONFIG_SYSTEM_TRUSTED_KEYS
配置选项引用的 PEM 编码文件中提供其他受信任的证书。
此外,架构代码可以从硬件存储中获取公钥并将它们也添加到其中(例如,来自 UEFI 密钥数据库)。
最后,可以通过执行以下操作来添加其他公钥
keyctl padd asymmetric "" [.builtin_trusted_keys-ID] <[key-file]
例如
keyctl padd asymmetric "" 0x223c7853 <my_public_key.x509
但是请注意,内核仅在新密钥的 X.509 包装器已由密钥有效签名的情况下,才允许将密钥添加到 .builtin_trusted_keys
,该密钥在添加密钥时已驻留在 .builtin_trusted_keys
中。
手动签名模块¶
要手动签名模块,请使用 Linux 内核源代码树中提供的 scripts/sign-file 工具。 该脚本需要 4 个参数
哈希算法(例如,sha256)
私钥文件名或 PKCS#11 URI
公钥文件名
要签名的内核模块
以下是签名内核模块的示例
scripts/sign-file sha512 kernel-signkey.priv \
kernel-signkey.x509 module.ko
使用的哈希算法不必与配置的算法匹配,但如果不匹配,您应该确保该哈希算法已内置到内核中,或者可以在不需要自身的情况下加载。
如果私钥需要密码或 PIN,则可以在 $KBUILD_SIGN_PIN 环境变量中提供它。
已签名模块和剥离¶
已签名模块只是在末尾附加了一个数字签名。 模块文件末尾的字符串 ~Module signature appended~.
确认存在签名,但不确认签名是否有效!
已签名模块是脆弱的,因为签名位于定义的 ELF 容器之外。 因此,一旦计算并附加签名,就可能无法剥离它们。 请注意,整个模块都是已签名的有效负载,包括签名时存在的任何和所有调试信息。
加载已签名模块¶
使用 insmod、modprobe、init_module()
或 finit_module()
加载模块,与未签名模块完全相同,因为用户空间中没有进行任何处理。 签名检查全部在内核中完成。
无效签名和未签名模块¶
如果启用了 CONFIG_MODULE_SIG_FORCE
或在内核命令行上提供了 module.sig_enforce=1,则内核只会加载它具有公钥的有效签名模块。 否则,它也会加载未签名的模块。 对于内核拥有密钥但证明签名不匹配的任何模块,将不允许加载。
将拒绝任何具有无法解析的签名的模块。
管理/保护私钥¶
由于私钥用于签名模块,因此病毒和恶意软件可以使用私钥签名模块并危及操作系统。 私钥必须被销毁或移动到安全位置,并且不能保留在内核源代码树的根节点中。
如果您使用相同的私钥对多个内核配置的模块进行签名,则必须确保模块版本信息足以防止将模块加载到不同的内核中。 可以设置 CONFIG_MODVERSIONS=y
或通过更改 EXTRAVERSION
或 CONFIG_LOCALVERSION
来确保每个配置都具有不同的内核发行字符串。