【译】如何为 Secure Boot 签名
本文最早于 2017 年 8 月 1 日发表于 Matt's blog: How to sign things for Secure Boot,译于 2022 年 9 月 30 日,到今天为止内容可能已有更新,仅作参考。
Secure Boot 签名
Secure Boot(安全启动)的概念要求从最早的硬件加载的固件代码,一直到作为内核的一部分加载的内核模块之间存在一个信任链。换句话说,不仅是固件和 bootloader(引导加载程序)需要签名,内核和内核模块同样需要签名。人们通常不会更改固件或 bootloader,但是很可能会重新构建内核或添加硬件制造商提供的额外模块。
在 Ubuntu 中可能会发生这样的事情:你可能想构建自己的内核(但我们希望你可以只使用我们在源中提供的通用内核),并且你可能要安装自己的内核模块。这意味着要对 UEFI 二进制文件和内核模块进行签名,这可以使用自己的一套工具来完成。
但是首先,要了解用于 Secure Boot 的信任链的更多信息。
shim 中的证书
想要为 UEFI Secure Boot 签名,你需要创建一个可以在固件中导入的 X509 证书,随后可以直接通过制造商固件导入,也可以很容易地通过 shim 导入。
创建用于 UEFI Secure Boot 的证书相对简单。openssl 可以通过运行几个 SSL 命令来完成。现在,我们需要为模块签名创建一个 SSL 证书。
首先创建一些配置,让 openssl 知道我们想要创建什么(命名为 openssl.cnf
):
1 | # This definition stops the following lines choking if HOME isn't |
可以选择修改 [ req_distinguished_name ]
下的值,或者完全删除该部分与 distinguished_name
和 prompt
字段,此时 openssl 会要求你输入值来为证书设置标识。
标识本身并不重要,但后面的一些值很重要:例如,请确保 extendedKeyUsage
字段中有 1.3.6.1.4.1.2312.16.1.2
,这是一个 OID,可以告诉 shim 生成的是一个模块签名证书。
然后,我们可以开始有趣的部分:创建私钥和公钥。
1 | openssl req -config ./openssl.cnf \ |
此命令将创建证书的私钥和公钥来进行签名。你需要这两个文件来签名,并且在 shim 中只需要导入公钥(MOK.der
)。
译者注: 1. 若生成密钥时出现类似
Can't load /home/username/.rnd into RNG
的错误,可能是因为没有生成.rnd
随机数文件,可以在 home 目录使用openssl rand -writerand .rnd
生成,或修改配置文件中RANDFILE
字段的值。 2.extendedKeyUsage
字段限定了该证书的用途。 1.1.3.6.1.4.1.2312.16.1.2
是一个只能用于模块签名的 OID,若将生成的密钥用于签名内核是无效的,即使签名了也会无法通过 shim 的启动验证。Linux 内核相关的常用 OID 号: -1.3.6.1.4.1.2312.16
用于内核的 OID -1.3.6.1.4.1.2312.16.1
- X.509 extendedKeyUsage 限制集 -1.3.6.1.4.1.2312.16.1.1
- 仅可用于固件签名 -1.3.6.1.4.1.2312.16.1.2
- 仅可用于模块签名 -1.3.6.1.4.1.2312.16.1.3
- 仅可用于 Kexecable image(内核)签名 2. 更多 OID 选项可以参考 Global OID reference database
注册密钥
现在,让我们在 shim 中注册我们刚刚创建的密钥。这使得它可以被接受为内核想要加载的任何模块的有效签名密钥,或者你想要构建自己的 bootloader 或内核的有效密钥(前提是在 openssl.cnf
中没有设置前面讨论的 1.3.6.1.4.1.2312.16.1.2
OID)。
要注册密钥,请使用 mokutil
命令:
1 | sudo mokutil --import MOK.der |
按照提示输入密码。
完成此操作后,重新启动系统。就在加载 GRUB 之前,shim 将显示蓝屏(实际上是 shim 项目的另一部分,称为 MokManager)。在该界面选择“注册 MOK”,然后跟随菜单完成注册过程。你还可以使用“View key”查看要添加的密钥的某些属性,以确保添加正确。MokManager 会询问你在早些时候运行 mokutil
时输入的密码,并保存密钥,再次重新启动。
译者注: 1. MokManager 启动(蓝屏出现)后需要在短时间内按下任意键进入,否则会跳过导入向导直接进入系统,此时需要重新运行
mokutil
命令导入。 2. 导入完成后可以在系统中使用mokutil --list-enrolled
查看到已导入的证书。 3. 重复导入已导入的证书时也会提示SKIP: MOK.der is already enrolled
。
对模块签名
在签名之前,可以查看 /proc/keys
以确保我们添加的密钥已经在内核中可见。
1 | $ sudo cat /proc/keys |
确保里面存在一个带有之前输入的属性(如 commonName
等)的密钥。
要对内核模块进行签名,可以使用 kmodsign
命令:
1 | kmodsign sha512 MOK.priv MOK.der module.ko |
其中 module.ko
是要签名的内核模块文件的文件名。kmodsign
会将签名附加到文件中。如果你希望保持签名独立并手动将其对应到模块,请参见 kmosign –help
。
你可以通过检查模块是否包含字符串 ~Module signature appended~
来验证模块是否已签名成功:
1 | $ hexdump -Cv module.ko | tail -n 5 |
你也可以用这种方式(使用 hexdump
)来检查签名的密钥是否是你创建的密钥。
对内核与 bootloader 签名
要对希望用 shim 加载的自定义内核或其他 EFI 二进制文件进行签名,需要使用不同的命令:sbsign
。此时我们需要不同格式的证书。
将之前创建的证书转换为 PEM:
1 | openssl x509 -in MOK.der -inform DER -outform PEM -out MOK.pem |
用 PEM 文件签署 EFI 二进制文件:
1 | sbsign --key MOK.priv --cert MOK.pem my_binary.efi --output my_binary.efi.signed |
只要签名密钥注册在 shim 中,并且不包含前面的 OID(因为这限制了密钥只能用于内核模块签名,译者注:指不包含 1.3.6.1.4.1.2312.16.1.2
),就可以用 shim 加载二进制文件。
译者注:可以使用
sbverify
命令检查文件是否签名成功,具体用法请参考sbverify -help
。
不使用 shim 处理签名
如果你不想使用 shim 来处理(译者注:导入)密钥(但我建议你使用它),你需要创建不同的证书:一个是系统的 PK (Platform Key),你可以直接通过 KeyTool 或系统提供的固件工具注册密钥到固件中。本文不会详细说明在固件中注册密钥的步骤,因为它往往因系统而异,但主要思想是让系统进入 Secure Boot 的 “Setup Mode”;运行 KeyTool,并注册密钥——首先安装 KEK 和 DB 密钥,然后是 PK。这些文件需要被放在某些 FAT 分区。
我有个脚本来生成正确的证书和文件,在这里分享(它们本身是从某个地方复制过来的,记不清了):
1 | !/bin/bash |
与以前相同的逻辑相同:根据需要使用 sbsign
或 kmodsign
签名(sbsign
使用 .crt
文件,kmodsign
使用 .cer
文件);只要密钥正确地注册到固件或 shim 中,它们就能成功加载。
Ubuntu 的 Secure Boot 有何进展
签名是一件复杂的事情 —— 你需要创建 SSL 证书,将它们注册到固件或 shim 中……你需要对 Secure Boot 的工作原理以及要使用的命令有相当的了解。很明显,这并不是每个人都能做到的,而且一开始就有点糟糕。出于这个原因,我们正在努力使安装 DKMS 模块时的密钥创建、注册和签名更加容易。
update-secureboot-policy
应该很快就能让你生成并注册一个密钥,并且 DKMS 将能够使用该密钥自行签名。