How to extract the signer’s certificate and verify the signature of a Linux kernel image
It’s been quite a while since the introduction of UEFI and Secure Boot, which ensures that only code bearing a signature by a trusted party will get executed by the firmware. After a brief period of uncertainty this is now well supported by default in many Linux distributions. The Zero trust security model gaining popularity lately also stipulates – “never trust, always verify”, and do it at every step, always. For this to be effective it has to start at the moment you power on your computer.
The usual boot process for a PC running Linux nowadays is that the UEFI loads a small piece of software (called shim), which in turn loads the bootloader (usually GRUB), and at each step the previous one makes sure the next is digitally signed by a trusted party. To extend this “chain of trust” one step further the Linux kernel has gained support for digitally signing kernel images and modules too. So now the bootloader (or even the UEFI firmware directly) can verify the signature on the kernel image it’s loading. With the correct config options enabled (CONFIG_MODULE_SIG_FORCE and CONFIG_KEXEC_SIG_FORCE) the kernel will itself check the signatures when loading a module or performing a kexec .
Unfortunately this extra security comes at the price of additional complexity. When loading a module or doing a kexec fails because of a problem with signature verification it may not be trivial to find out exactly why. The gory details of how all this intricate machinery works are way outside the scope of this write up, but one possible reason for the failure is that the module or kernel image either doesn’t contain a valid signature at all or it contains one, but the signer is not in the kernel’s list of trusted parties.
With the above in mind when debugging such an issue a logical first step is to check if the module or kernel contains a valid signature and if yes by whom.
Some documentation exists about how to do this for modules, and there are even some helper scripts in the kernel sources, but I didn’t find anything about kernel images.
First, I assume you have a bzImage file on your hands. To figure out if it contains a signature and by whom we need to understand a bit more about it’s file format. And you may be surprised to find that it is actually a Portable Executable file. Yes, your Linux kernel image is in a Microsoft Windows executable file format! Or, more precisely, it pretends to be one just closely enough, so that if you have the CONFIG_EFI_STUB option enabled UEFI can load the Linux kernel directly and verify it’s signature.
Since the PE file format signature was already there the Linux kernel developers decided to reuse it for kexec too. More details about the signatures in a PE executable can be found in this (Microsoft Word!) document.
There are libraries to work with PE files for various languages but I’ve found it simpler to use the standalone osslsigncode utility. If you are using openSUSE Tumbleweed it is just a ‘zypper in osslsigncode’ command away and if not check your favorite distribution’s package manager or compile it from sources. Note that you should be using a fairly recent version of osslsigncode (>=2.6), as I’ve encountered problems with both signature extraction and verification using previous versions.
Once you have that in place, let’s see if the kernel image we are dealing with has a valid signature and if yes, extract it to a file. I’ll use the kernel that came with the distribution for the examples below and run this command:
osslsigncode extract-signature -in /boot/vmlinuz-6.5.2-1-default -out kernel.sig
Succeeded
If the image is signed we get a “Succeeded” message and the signature stored in the kernel.sig file. And if not, you will see a “Unable to extract existing signature, Failed” message.
Now we can use openssl to see a human readable version of the certificates in the pkcs7 signature:
openssl pkcs7 -in kernel.sig -inform der -print_certs -noout -text
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
fa:be:d8:bf:40:9a:5e:65
Signature Algorithm: sha256WithRSAEncryption
Issuer: CN=openSUSE Secure Boot CA, C=DE, L=Nuremberg, O=openSUSE Project/emailAddress=build@opensuse.org
Validity
Not Before: Jun 13 13:22:16 2022 GMT
Not After : Apr 21 13:22:16 2032 GMT
Subject: CN=openSUSE Secure Boot Signkey, C=DE, L=Nuremberg, O=openSUSE Project/emailAddress=build@opensuse.org
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (2048 bit)
Modulus:
00:b2:29:40:7e:ad:9f:73:5e:44:6a:e4:56:0a:f3:
6e:7a:05:49:68:3f:b2:4f:13:41:82:3d:dd:80:c4:
f1:cb:11:06:48:fd:a5:90:19:0e:91:7d:da:6e:98:
ee:97:e1:43:90:bc:78:e5:30:c0:19:91:c6:3f:dc:
29:e0:af:a3:d8:41:84:dd:fd:90:19:cf:d1:4d:1f:
1f:97:84:e5:64:81:93:6f:87:d8:34:f9:4d:e1:8a:
87:7e:69:c6:a3:d4:5c:6c:b3:e7:01:6d:21:d5:46:
94:37:92:3b:e5:ef:15:bf:36:49:ed:8c:48:98:67:
04:ed:00:1c:c3:f4:8d:da:a6:f0:ce:95:3b:03:95:
79:86:fd:f5:84:c9:70:24:69:82:59:8c:86:59:2a:
ca:d0:a0:86:60:0b:5d:d9:fc:01:c2:39:73:0b:88:
9e:47:83:1b:29:ec:8d:82:66:81:a0:0e:5a:1e:95:
97:60:f5:1e:f0:b3:27:66:d5:13:0f:31:df:8e:9b:
b7:40:0a:cd:2f:22:31:8a:49:e5:30:cb:59:f5:79:
eb:92:fa:2d:35:6a:9e:2d:48:3c:67:e9:a4:3b:4e:
77:d2:fb:a1:cf:ff:4a:e7:c6:31:6a:69:61:3f:05:
04:38:c3:aa:e3:52:9d:78:7c:d1:01:3e:bd:61:d5:
17:e7
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Basic Constraints: critical
CA:FALSE
X509v3 Subject Key Identifier:
FD:9F:2C:12:E5:99:D6:7C:C7:F9:06:75:41:AD:F4:26:B7:12:46:9E
X509v3 Authority Key Identifier:
keyid:68:42:60:0D:E2:2C:4C:47:7E:95:BE:23:DF:EA:95:13:E5:97:17:62
DirName:/CN=openSUSE Secure Boot CA/C=DE/L=Nuremberg/O=openSUSE Project/emailAddress=build@opensuse.org
serial:01
X509v3 Key Usage: critical
Digital Signature
X509v3 Extended Key Usage:
Code Signing
Signature Algorithm: sha256WithRSAEncryption
Signature Value:
5b:93:17:61:67:e9:2b:4a:f2:54:30:5d:5c:63:cb:5a:93:91:
80:a8:7a:25:3e:27:4b:bb:d4:e4:15:b9:7d:7d:43:21:cd:1f:
84:4f:6a:3e:6c:70:31:7e:f8:3a:35:d9:df:9d:b4:35:f5:75:
8e:b0:20:fd:d9:b2:cd:41:ae:e2:9c:af:99:37:d1:6f:05:f0:
78:39:c6:d6:dd:f3:6f:43:d4:d6:7b:5f:cd:18:6d:c2:77:d0:
1a:6d:74:78:80:99:34:a4:f0:c6:9f:43:f5:c6:ba:8c:83:f4:
a5:02:57:8a:54:52:05:2a:99:a7:0d:29:34:13:de:5f:91:41:
f3:b0:c1:26:70:e4:a6:cc:55:ec:5a:f3:47:e5:e3:21:9c:05:
7c:11:8d:79:cc:90:74:20:62:09:7b:46:51:4e:de:0d:32:aa:
b4:84:cb:d1:6c:f0:27:5f:21:78:52:ac:97:0b:5c:a0:44:a2:
eb:14:92:8e:c5:43:b2:4d:20:c8:bc:5b:1a:50:09:cb:45:5e:
11:bd:58:86:52:55:6e:f6:62:09:c1:18:ab:95:be:53:e4:b7:
d7:cd:3b:53:eb:33:d6:70:7e:cc:0c:9c:ec:91:11:26:04:87:
c0:f2:b4:d9:5c:0d:95:8f:bd:e7:9f:15:f7:92:e4:a7:e1:99:
45:23:49:10
From the above output we can see that the certificate used to sign our kernel image bears the “openSUSE Secure Boot Signkey” common name, and that it was issued by the “openSUSE Secure Boot CA” certificate authority. In order to verify the signature we’re going to also need the latter to complete the ‘chain of trust’.
If you have your own PKI you will use own certificates, but in this case the openSUSE CA certificate can be easily obtained by installing the shim package (zypper in shim) and found under /usr/share/efi/x86_64/shim-opensuse.der. We need to convert it from DER to PEM format first, as it’s the one expected by osslsigncode:
openssl x509 -in /usr/share/efi/x86_64/shim-opensuse.der -inform DER -out opensuse-ca.pem -outform PEM
Finally we can use osslsigncode to verify the signature on our kernel image:
osslsigncode verify -in /boot/vmlinuz-6.5.2-1-default -CAfile opensuse-ca.pem
Current PE checksum : 00000000
Calculated PE checksum: 00DC76A1
Warning: invalid PE checksum
Message digest algorithm : SHA256
Current message digest : 9564082AFF7452005D600F5D7C9884CD2F2D7C63544BEBAEC4C9DA1FF7A7EB21
Calculated message digest : 9564082AFF7452005D600F5D7C9884CD2F2D7C63544BEBAEC4C9DA1FF7A7EB21
Signature Index: 0 (Primary Signature)
Signer's certificate:
Signer #0:
Subject: /CN=openSUSE Secure Boot Signkey/C=DE/L=Nuremberg/O=openSUSE Project/emailAddress=build@opensuse.org
Issuer : /CN=openSUSE Secure Boot CA/C=DE/L=Nuremberg/O=openSUSE Project/emailAddress=build@opensuse.org
Serial : FABED8BF409A5E65
Certificate expiration date:
notBefore : Jun 13 13:22:16 2022 GMT
notAfter : Apr 21 13:22:16 2032 GMT
Number of certificates: 1
Signer #0:
Subject: /CN=openSUSE Secure Boot Signkey/C=DE/L=Nuremberg/O=openSUSE Project/emailAddress=build@opensuse.org
Issuer : /CN=openSUSE Secure Boot CA/C=DE/L=Nuremberg/O=openSUSE Project/emailAddress=build@opensuse.org
Serial : FABED8BF409A5E65
Certificate expiration date:
notBefore : Jun 13 13:22:16 2022 GMT
notAfter : Apr 21 13:22:16 2032 GMT
Message digest algorithm: SHA256
Authenticated attributes:
Signing time: Sep 11 20:04:11 2023 GMT
Message digest: C852448C1DBAC25B0F18D4F9711DA864F0E75A1A8FDD207315353042B84AC576
CAfile: osslsigncode/osslsigncode-2.7/build/opensuse-ca.pem
Timestamp is not available
Signature verification: ok
Number of verified signatures: 1
Succeeded
If all of the above looks good (don’t be alarmed by the invalid ‘PE checksum’ warning) and you still can’t kexec the image, then the problem must be a missing certificate in the ‘trusted signers’ list of the running kernel. Certificates in this list can be added either at kernel compile time, or during runtime using the ‘keyctl’ utility, but that may be a topic for another article …
Happy debugging!
Related Articles
Jun 14th, 2024
No comments yet