1. 深入理解Linux内核模块签名与Secure Boot的协同机制

当你在Ubuntu服务器上尝试加载一个第三方硬件驱动时,突然看到"Key was rejected by service"的错误提示,这实际上是Secure Boot安全机制在发挥作用。Secure Boot是现代计算机固件中的一项关键安全功能,它通过验证所有启动组件的数字签名来防止恶意软件在启动过程中加载。但这项安全特性也给系统管理员带来了新的挑战——如何在不降低系统安全性的前提下加载必要的未签名内核模块。

1.1 Secure Boot的工作原理与内核模块验证

Secure Boot是UEFI固件规范的一部分,它建立了一个从硬件到操作系统的信任链。整个过程可以分为几个关键阶段:

  1. 固件验证 :UEFI固件验证引导加载程序(如shim)的数字签名
  2. 引导加载程序验证 :shim验证GRUB引导加载程序的签名
  3. 内核验证 :GRUB验证Linux内核的签名
  4. 模块验证 :加载内核模块时验证其签名

这种层层验证的机制确保了只有经过授权的代码才能在系统启动过程中执行。当Secure Boot启用时,Linux内核会强制执行模块签名验证,这就是为什么未签名的模块会被拒绝加载。

内核模块签名验证流程 :

1. 内核检查模块的签名是否存在
2. 如果存在签名,验证签名使用的密钥是否在系统信任的密钥数据库中
3. 如果验证通过,允许加载模块;否则拒绝并记录错误

1.2 为什么不应该简单地禁用Secure Boot

许多初级管理员遇到模块加载问题时,第一反应是进入BIOS禁用Secure Boot。这种方法虽然简单,但会带来严重的安全隐患:

  • 削弱系统防护 :使系统易受bootkit等固件级恶意软件攻击
  • 合规性问题 :许多行业安全标准(如PCI DSS)要求启用Secure Boot
  • 潜在的功能限制 :某些云计算平台要求实例必须启用Secure Boot

更专业的解决方案是建立一个合法的模块签名流程,这样既能保持系统的安全性,又能加载必要的驱动程序。这种方案的核心是使用机器所有者密钥(MOK, Machine Owner Key)机制。

1.3 MOK机制详解

MOK是UEFI规范中设计的一个灵活方案,它允许机器所有者在不影响平台默认安全策略的情况下,添加自己信任的签名密钥。与平台密钥(PK)和密钥交换密钥(KEK)不同,MOK专门用于以下场景:

  • 签名第三方内核模块
  • 签名自定义编译的内核
  • 在保持Secure Boot启用的同时扩展信任链

MOK的工作流程具有以下特点:

  1. 用户控制 :只有物理接触机器的用户才能添加MOK
  2. 交互式验证 :添加新密钥需要重启并在MOK管理界面确认
  3. 密码保护 :密钥管理操作需要预先设置的密码

这种设计在安全性和灵活性之间取得了良好的平衡,特别适合企业环境中需要加载专有驱动的情况。

2. 构建完整的模块签名环境

2.1 准备工作与依赖安装

在开始签名内核模块前,需要确保系统具备必要的工具链。对于基于Debian的系统(如Ubuntu),需要安装以下软件包:

sudo apt update
sudo apt install -y mokutil openssl shim-signed linux-headers-$(uname -r)

这些软件包各自的作用:

  • mokutil :管理MOK密钥的命令行工具
  • openssl :生成RSA密钥对
  • shim-signed :包含Microsoft签名的shim引导加载程序
  • linux-headers :提供模块签名所需的脚本和工具

注意:确保系统已启用UEFI模式启动。可以通过以下命令检查:

[ -d /sys/firmware/efi ] && echo "UEFI" || echo "BIOS"

2.2 创建安全的签名密钥对

密钥对是模块签名的基础,其安全性直接关系到整个系统的可信度。以下是创建密钥的最佳实践:

  1. 创建一个专用目录存储密钥材料,限制访问权限:
sudo mkdir -p /etc/secureboot/keys
sudo chmod 700 /etc/secureboot/keys
cd /etc/secureboot/keys
  1. 使用OpenSSL生成RSA 2048位密钥对(生产环境建议使用4096位):
sudo openssl req -new -x509 -newkey rsa:2048 \
    -keyout MOK.priv -outform DER -out MOK.der \
    -nodes -days 3650 -subj "/CN=$(hostname)-$(date +%Y%m%d)/"

命令参数说明:

参数 说明
-newkey rsa:2048 生成2048位RSA密钥
-keyout MOK.priv 输出私钥文件
-outform DER 公钥输出为DER格式
-nodes 不加密私钥(便于自动化)
-days 3650 密钥有效期(10年)
-subj 密钥主题,包含主机名和日期
  1. 设置严格的文件权限:
sudo chmod 600 MOK.priv
sudo chmod 644 MOK.der

重要安全提示:私钥(MOK.priv)是签名能力的核心,必须妥善保管。在生产环境中,建议:

  • 将私钥存储在加密的存储介质上
  • 设置严格的访问控制
  • 考虑使用HSM(硬件安全模块)保护私钥

2.3 将公钥导入系统信任链

生成密钥对后,需要将公钥添加到系统的信任数据库中:

sudo mokutil --import /etc/secureboot/keys/MOK.der

系统会提示输入并确认一个密码,这个密码将在后续的MOK注册过程中使用。选择密码时应注意:

  • 不要使用与系统账户相同的密码
  • 长度至少12个字符
  • 包含大小写字母、数字和特殊字符
  • 在企业环境中,应使用密码管理器生成并存储

成功导入后,可以验证密钥是否在待处理列表中:

sudo mokutil --list-new

3. 完成MOK注册流程

3.1 系统重启与交互式注册

MOK密钥的注册是一个需要物理确认的过程,这是安全设计的重要部分:

  1. 重启系统:
sudo reboot
  1. 在系统启动过程中,当出现MOK管理界面时(通常在GRUB之后),选择"Enroll MOK"选项。

  2. 按照屏幕提示:

    • 选择"Continue"
    • 选择"Yes"确认注册密钥
    • 输入之前通过mokutil设置的密码
    • 选择"OK"完成并继续启动

常见问题:如果未看到MOK界面,可能原因有:

  • 系统未以UEFI模式启动
  • shim引导加载程序未正确安装
  • 虚拟机可能需要额外配置EFI支持

3.2 验证密钥注册结果

系统重新启动后,可以通过以下方式验证密钥是否成功注册:

  1. 检查当前加载的MOK密钥:
sudo mokutil --list-enrolled
  1. 验证密钥是否在系统信任的密钥数据库中:
sudo cat /proc/keys | grep -i mok
  1. 检查内核是否启用了模块签名验证:
cat /proc/sys/kernel/modules_restrict

值为1表示强制模块签名验证已启用。

4. 签名内核模块的完整流程

4.1 手动签名单个模块

有了注册的MOK密钥后,就可以对内核模块进行签名了。以常见的第三方驱动模块为例:

  1. 定位需要签名的模块文件(通常以.ko结尾):
sudo find /lib/modules/$(uname -r) -name "*.ko" | grep your_module
  1. 使用内核源码树中的sign-file脚本进行签名:
sudo /usr/src/linux-headers-$(uname -r)/scripts/sign-file \
    sha256 \
    /etc/secureboot/keys/MOK.priv \
    /etc/secureboot/keys/MOK.der \
    /path/to/module.ko

签名过程实际上执行了以下操作:

  1. 计算模块的SHA256哈希值

  2. 使用私钥对哈希值进行加密(签名)

  3. 将签名和公钥信息附加到模块文件中

  4. 验证签名是否成功:

modinfo /path/to/module.ko

输出中应包含"signature"字段,类似于:

signature: SHA256:3F:4A:...:A2:1C
sig_key: SHA256:5B:CE:...:34:AA

4.2 自动化签名脚本

对于需要频繁签名多个模块或内核升级后的场景,可以创建自动化脚本:

#!/bin/bash
# /usr/local/bin/sign-modules

KEY_DIR="/etc/secureboot/keys"
MODULES=(
    "/lib/modules/$(uname -r)/kernel/drivers/misc/module1.ko"
    "/lib/modules/$(uname -r)/kernel/drivers/misc/module2.ko"
)

for module in "${MODULES[@]}"; do
    if [ -f "$module" ]; then
        echo "Signing $module..."
        /usr/src/linux-headers-$(uname -r)/scripts/sign-file \
            sha256 \
            "$KEY_DIR/MOK.priv" \
            "$KEY_DIR/MOK.der" \
            "$module"
    else
        echo "Warning: $module not found"
    fi
done

echo "Updating initramfs..."
sudo update-initramfs -u -k all

使脚本可执行并测试:

sudo chmod +x /usr/local/bin/sign-modules
sudo /usr/local/bin/sign-modules

4.3 处理内核升级后的签名

当系统通过apt升级内核时,新安装的内核模块不会被自动签名。可以通过以下方法确保无缝升级:

  1. 创建APT钩子脚本自动签名新内核的模块:
sudo mkdir -p /etc/kernel/postinst.d
sudo tee /etc/kernel/postinst.d/sign-modules <<'EOF'
#!/bin/bash
KERNEL_VERSION="$1"
/usr/local/bin/sign-modules
EOF
sudo chmod +x /etc/kernel/postinst.d/sign-modules
  1. 更新所有现有内核的模块签名:
for kernel in /lib/modules/*; do
    sudo /usr/local/bin/sign-modules
done

5. 高级管理与故障排除

5.1 密钥轮换与撤销

出于安全考虑,应定期轮换签名密钥。以下是安全更换密钥的步骤:

  1. 生成新密钥对:
sudo openssl req -new -x509 -newkey rsa:2048 \
    -keyout /etc/secureboot/keys/MOK_new.priv \
    -outform DER -out /etc/secureboot/keys/MOK_new.der \
    -nodes -days 365 -subj "/CN=$(hostname)-$(date +%Y%m%d)/"
  1. 导入并注册新密钥:
sudo mokutil --import /etc/secureboot/keys/MOK_new.der
sudo reboot
  1. 使用新密钥重新签名所有模块
  2. 确认所有模块正常工作后,撤销旧密钥:
sudo mokutil --revoke /etc/secureboot/keys/MOK.der
sudo reboot

5.2 常见问题解决

问题1 :模块加载仍然失败,显示"Required key not available"

解决方案 :

  1. 确认密钥已正确注册:
sudo mokutil --list-enrolled | grep -i "your key"
  1. 检查模块签名使用的密钥指纹是否匹配:
modinfo /path/to/module.ko | grep sig_key
sudo openssl x509 -inform DER -in /etc/secureboot/keys/MOK.der -noout -fingerprint

问题2 :虚拟机中无法访问MOK管理界面

解决方案 :

  1. 确保虚拟机配置了EFI启动:
# 对于libvirt/KVM
<domain>
  ...
  <os>
    <loader readonly='yes' type='pflash'>/usr/share/OVMF/OVMF_CODE.fd</loader>
    <nvram>/var/lib/libvirt/qemu/nvram/guest_VARS.fd</nvram>
  </os>
  ...
</domain>
  1. 增加显示延迟以便有时间按键:
# 在/etc/default/grub中设置
GRUB_TIMEOUT=10
sudo update-grub

问题3 :忘记MOK管理密码

解决方案 :

  1. 清除所有MOK密钥(需要物理访问):
sudo mokutil --reset
sudo reboot
  1. 在MOK界面选择"Reset MOK list"
  2. 重新开始注册流程

5.3 安全最佳实践

  1. 密钥管理 :

    • 定期轮换签名密钥(建议每年一次)
    • 将私钥存储在加密的专用设备上
    • 限制对私钥的访问权限
  2. 审计与监控 :

    • 记录所有模块签名操作:
    sudo auditctl -w /usr/src/linux-headers-*/scripts/sign-file -p x -k module_signing
    
    • 监控内核日志中的模块加载事件:
    sudo journalctl -k --grep="loading out-of-tree module"
    
  3. 系统加固 :

    • 启用内核模块黑名单:
    echo "install blacklisted_module /bin/false" | sudo tee /etc/modprobe.d/blacklist.conf
    
    • 限制模块自动加载:
    echo "kernel.modules_disabled=1" | sudo tee /etc/sysctl.d/99-disable-modules.conf
    sudo sysctl -p
    

在企业环境中,可以考虑部署中央化的模块签名服务,通过自动化流水线对所有需要部署的内核模块进行统一签名和验证,确保整个基础设施的一致性和安全性。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐