1#!/usr/bin/env python3 2# Copyright 2022 The ChromiumOS Authors 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5 6"""Tests for sign_uefi.py. 7 8This is run as part of `make runtests`, or `make runtestscripts` if you 9want something a little faster. 10""" 11 12from pathlib import Path 13import tempfile 14import unittest 15from unittest import mock 16 17import sign_uefi 18 19 20class TestSign(unittest.TestCase): 21 """Test signing functions in sign_uefi.py.""" 22 23 def setUp(self): 24 # pylint: disable=consider-using-with 25 self.tempdir = tempfile.TemporaryDirectory() 26 tempdir = Path(self.tempdir.name) 27 28 # Get key paths. 29 self.keys = sign_uefi.Keys( 30 private_key=tempdir / "private_key.rsa", 31 sign_cert=tempdir / "sign_cert.pem", 32 verify_cert=tempdir / "verify_cert.pem", 33 kernel_subkey_vbpubk=tempdir / "kernel_subkey.vbpubk", 34 crdyshim_private_key=tempdir / "crdyshim.priv.pem", 35 ) 36 37 # Get target paths. 38 self.target_dir = tempdir / "boot" 39 self.syslinux_dir = self.target_dir / "syslinux" 40 self.efi_boot_dir = self.target_dir / "efi/boot" 41 42 # Make test dirs. 43 self.syslinux_dir.mkdir(parents=True) 44 self.efi_boot_dir.mkdir(parents=True) 45 46 # Make key files. 47 (self.keys.private_key).touch() 48 (self.keys.sign_cert).touch() 49 (self.keys.verify_cert).touch() 50 (self.keys.kernel_subkey_vbpubk).touch() 51 (self.keys.crdyshim_private_key).touch() 52 53 # Make EFI files. 54 (self.efi_boot_dir / "bootia32.efi").touch() 55 (self.efi_boot_dir / "bootx64.efi").touch() 56 (self.efi_boot_dir / "testia32.efi").touch() 57 (self.efi_boot_dir / "testx64.efi").touch() 58 (self.efi_boot_dir / "crdybootia32.efi").touch() 59 (self.efi_boot_dir / "crdybootx64.efi").touch() 60 (self.syslinux_dir / "vmlinuz.A").touch() 61 (self.syslinux_dir / "vmlinuz.B").touch() 62 (self.target_dir / "vmlinuz-5.10.156").touch() 63 (self.target_dir / "vmlinuz").symlink_to( 64 self.target_dir / "vmlinuz-5.10.156" 65 ) 66 67 def tearDown(self): 68 self.tempdir.cleanup() 69 70 @mock.patch("sign_uefi.inject_vbpubk") 71 @mock.patch.object(sign_uefi.Signer, "create_detached_signature") 72 @mock.patch.object(sign_uefi.Signer, "sign_efi_file") 73 def test_sign_target_dir( 74 self, mock_sign, mock_detached_sig, mock_inject_vbpubk 75 ): 76 # Set an EFI glob that matches only some of the EFI files. 77 efi_glob = "test*.efi" 78 79 # Sign, but with the actual signing mocked out. 80 sign_uefi.sign_target_dir(self.target_dir, self.keys, efi_glob) 81 82 # Check that the correct list of files got signed. 83 self.assertEqual( 84 mock_sign.call_args_list, 85 [ 86 # The test*.efi files match the glob, 87 # the boot*.efi files don't. 88 mock.call(self.efi_boot_dir / "testia32.efi"), 89 mock.call(self.efi_boot_dir / "testx64.efi"), 90 # Two syslinux kernels. 91 mock.call(self.syslinux_dir / "vmlinuz.A"), 92 mock.call(self.syslinux_dir / "vmlinuz.B"), 93 # One kernel in the target dir. 94 mock.call(self.target_dir / "vmlinuz-5.10.156"), 95 ], 96 ) 97 98 # Check that `inject_vbpubk` was called on both the crdyboot 99 # executables. 100 self.assertEqual( 101 mock_inject_vbpubk.call_args_list, 102 [ 103 mock.call(self.efi_boot_dir / "crdybootia32.efi", self.keys), 104 mock.call(self.efi_boot_dir / "crdybootx64.efi", self.keys), 105 ], 106 ) 107 108 # Check that `create_detached_signature` was called on both 109 # the crdyboot executables. 110 self.assertEqual( 111 mock_detached_sig.call_args_list, 112 [ 113 mock.call(self.efi_boot_dir / "crdybootia32.efi"), 114 mock.call(self.efi_boot_dir / "crdybootx64.efi"), 115 ], 116 ) 117 118 @mock.patch("sign_uefi.inject_vbpubk") 119 @mock.patch.object(sign_uefi.Signer, "create_detached_signature") 120 @mock.patch.object(sign_uefi.Signer, "sign_efi_file") 121 def test_no_crdyshim_key( 122 self, _mock_sign, _mock_detached_sig, _mock_inject_vbpubk 123 ): 124 """Test for older keysets that don't have the crdyshim key.""" 125 self.keys.crdyshim_private_key.unlink() 126 127 # Error: crdyboot files are supposed to be signed, but the 128 # crdyshim key isn't present. 129 with self.assertRaises(SystemExit): 130 sign_uefi.sign_target_dir( 131 self.target_dir, self.keys, "crdyboot*.efi" 132 ) 133 134 # Success: the crdyboot files aren't present, so the crdyshim 135 # key is not required. 136 (self.efi_boot_dir / "crdybootia32.efi").unlink() 137 (self.efi_boot_dir / "crdybootx64.efi").unlink() 138 sign_uefi.sign_target_dir(self.target_dir, self.keys, "crdyboot*.efi") 139 140 @mock.patch.object(sign_uefi.Signer, "sign_efi_file") 141 def test_sign_target_file(self, mock_sign): 142 # Test signing a specific file. 143 sign_uefi.sign_target_file( 144 self.efi_boot_dir / "bootia32.efi", self.keys 145 ) 146 147 # Check that we made the expected signer call. 148 self.assertIn( 149 [ 150 mock.call(self.efi_boot_dir / "bootia32.efi"), 151 ], 152 mock_sign.call_args_list, 153 ) 154 155 @mock.patch("sign_uefi.subprocess.run") 156 def test_inject_vbpubk(self, mock_run): 157 efi_file = self.efi_boot_dir / "crdybootx64.efi" 158 sign_uefi.inject_vbpubk(efi_file, self.keys) 159 160 # Check that the expected command runs. 161 self.assertEqual( 162 mock_run.call_args_list, 163 [ 164 mock.call( 165 [ 166 "sudo", 167 "objcopy", 168 "--update-section", 169 f".vbpubk={self.keys.kernel_subkey_vbpubk}", 170 efi_file, 171 ], 172 check=True, 173 ) 174 ], 175 ) 176 177 @mock.patch("sign_uefi.subprocess.run") 178 def test_create_detached_signature(self, mock_run): 179 with tempfile.TemporaryDirectory() as tempdir: 180 tempdir = Path(tempdir) 181 signer = sign_uefi.Signer(tempdir, self.keys) 182 183 efi_file = self.efi_boot_dir / "crdybootx64.efi" 184 signer.create_detached_signature(efi_file) 185 186 # Check that the expected commands run. 187 self.assertEqual( 188 mock_run.call_args_list, 189 [ 190 mock.call( 191 [ 192 "openssl", 193 "pkeyutl", 194 "-sign", 195 "-rawin", 196 "-in", 197 efi_file, 198 "-inkey", 199 self.keys.crdyshim_private_key, 200 "-out", 201 tempdir / "crdybootx64.sig", 202 ], 203 check=True, 204 ), 205 mock.call( 206 [ 207 "sudo", 208 "cp", 209 tempdir / "crdybootx64.sig", 210 self.efi_boot_dir / "crdybootx64.sig", 211 ], 212 check=True, 213 ), 214 ], 215 ) 216 217 218class TestUtils(unittest.TestCase): 219 """Test utility functions in sign_uefi.py.""" 220 221 def test_is_pkcs11_key_path(self): 222 self.assertFalse(sign_uefi.is_pkcs11_key_path(Path("private_key.rsa"))) 223 224 self.assertTrue( 225 sign_uefi.is_pkcs11_key_path("pkcs11:object=private_key") 226 ) 227 228 229if __name__ == "__main__": 230 unittest.main() 231