Browse Source

config(qemu): preloading UEFI keys is optional

When resetting UEFI vars, add an option to preload Secure
Boot keys (previously this was always done). This allows for
the user to load their own UEFI keys.

Also update the preloaded UEFI keys to Microsoft 2023 keys.

Resolves #7005
Resolves #7257
osy 1 week ago
parent
commit
862c507a26

+ 12 - 2
Configuration/UTMQemuConfigurationQEMU.swift

@@ -76,6 +76,9 @@ struct UTMQemuConfigurationQEMU: Codable {
     /// Set to a password shared with the client. Not saved.
     var spiceServerPassword: String?
 
+    /// When resetting UEFI vars, also set up Secure Boot keys
+    var hasPreloadedSecureBootKeys: Bool = false
+
     enum CodingKeys: String, CodingKey {
         case hasDebugLog = "DebugLog"
         case hasUefiBoot = "UEFIBoot"
@@ -188,18 +191,25 @@ extension UTMQemuConfigurationQEMU {
             // save EFI variables
             let resourceURL = Bundle.main.url(forResource: "qemu", withExtension: nil)!
             let templateVarsURL: URL
+            let secure = hasPreloadedSecureBootKeys ? "-secure" : ""
             if let prefix = Self.uefiImagePrefix(forArchitecture: system.architecture, isVars: true) {
-                templateVarsURL = resourceURL.appendingPathComponent("\(prefix)-vars.fd")
+                templateVarsURL = resourceURL.appendingPathComponent("\(prefix)\(secure)-vars.fd")
             } else {
                 throw UTMQemuConfigurationError.uefiNotSupported
             }
             let varsURL = dataURL.appendingPathComponent(QEMUPackageFileName.efiVariables.rawValue)
-            if !fileManager.fileExists(atPath: varsURL.path) {
+            let isExisting = fileManager.fileExists(atPath: varsURL.path)
+            if !isExisting || isUefiVariableResetRequested {
                 try await Task.detached {
+                    if isExisting {
+                        try FileManager.default.removeItem(at: varsURL)
+                    }
                     try FileManager.default.copyItem(at: templateVarsURL, to: varsURL)
                     let permissions: FilePermissions = [.ownerReadWrite, .groupRead, .otherRead]
                     try FileManager.default.setAttributes([.posixPermissions: permissions.rawValue], ofItemAtPath: varsURL.path)
                 }.value
+                isUefiVariableResetRequested = false
+                hasPreloadedSecureBootKeys = false
             }
             efiVarsURL = varsURL
             existing.append(varsURL)

+ 16 - 0
Platform/Shared/VMConfigQEMUView.swift

@@ -70,6 +70,14 @@ struct VMConfigQEMUView: View {
                         .help("Should be on always unless the guest cannot boot because of this.")
                     Toggle("TPM 2.0 Device", isOn: $config.hasTPMDevice)
                         .help("TPM can be used to protect secrets in the guest operating system. Note that the host will always be able to read these secrets and therefore no expectation of physical security is provided.")
+                        .onChange(of: config.hasTPMDevice) { newValue in
+                            if newValue {
+                                config.isUefiVariableResetRequested = true
+                                config.hasPreloadedSecureBootKeys = true
+                            } else {
+                                config.hasPreloadedSecureBootKeys = false
+                            }
+                        }
                     Toggle("Use Hypervisor", isOn: $config.hasHypervisor)
                         .help("Only available if host architecture matches the target. Otherwise, TCG emulation is used.")
                         .disabled(!system.architecture.hasHypervisorSupport)
@@ -88,6 +96,14 @@ struct VMConfigQEMUView: View {
                     Toggle("Reset UEFI Variables", isOn: $config.isUefiVariableResetRequested)
                         .help("You can use this if your boot options are corrupted or if you wish to re-enroll in the default keys for secure boot.")
                         .disabled(!config.hasUefiBoot)
+                    Toggle("Preload Secure Boot Keys", isOn: $config.hasPreloadedSecureBootKeys)
+                        .help("Enable Secure Boot with Microsoft UEFI keys. This is required to Secure Boot Windows.")
+                        .disabled(!config.isUefiVariableResetRequested || !config.hasTPMDevice)
+                        .onChange(of: config.isUefiVariableResetRequested) { newValue in
+                            if !newValue {
+                                config.hasPreloadedSecureBootKeys = false
+                            }
+                        }
                 }
                 DetailedSection("QEMU Machine Properties", description: "This is appended to the -machine argument.") {
                     DefaultTextField("", text: $config.machinePropertyOverride.bound, prompt: "Default")

+ 1 - 0
Platform/Shared/VMWizardState.swift

@@ -456,6 +456,7 @@ struct AlertMessage: Identifiable {
             // only change UEFI settings for Windows or Other
             config.qemu.hasUefiBoot = systemBootUefi
             config.qemu.hasTPMDevice = operatingSystem == .Windows && systemBootTpm
+            config.qemu.hasPreloadedSecureBootKeys = config.qemu.hasTPMDevice
         } else if legacyHardware {
             config.qemu.hasUefiBoot = false
             config.qemu.hasTPMDevice = false

+ 3 - 9
Services/UTMQemuVirtualMachine.swift

@@ -238,15 +238,9 @@ extension UTMQemuVirtualMachine {
         guard let efiVarsURL = config.qemu.efiVarsURL else {
             return
         }
-        var doesVarsExist = FileManager.default.fileExists(atPath: efiVarsURL.path)
-        if config.qemu.isUefiVariableResetRequested {
-            if doesVarsExist {
-                try FileManager.default.removeItem(at: efiVarsURL)
-                doesVarsExist = false
-            }
-            config.qemu.isUefiVariableResetRequested = false
-        }
-        if !doesVarsExist {
+        if !FileManager.default.fileExists(atPath: efiVarsURL.path) {
+            config.qemu.isUefiVariableResetRequested = true
+            config.qemu.hasPreloadedSecureBootKeys = config.qemu.hasTPMDevice
             _ = try await config.qemu.saveData(to: efiVarsURL.deletingLastPathComponent(), for: config.system)
         }
     }

BIN
patches/data/qemu-10.0.2-utm/pc-bios/edk2-arm-secure-vars.fd.bz2


BIN
patches/data/qemu-10.0.2-utm/pc-bios/edk2-arm-vars.fd.bz2


BIN
patches/data/qemu-10.0.2-utm/pc-bios/edk2-i386-secure-vars.fd.bz2


BIN
patches/data/qemu-10.0.2-utm/pc-bios/edk2-i386-vars.fd.bz2


+ 37 - 0
patches/qemu-10.0.2-utm.patch

@@ -300,3 +300,40 @@ index 9fb9659c45..63e10cc6df 100644
  
  dtc = find_program('dtc', required: false)
 
+From a172998c2f8bcbd29afeb8cab9b97e43ef3a22b5 Mon Sep 17 00:00:00 2001
+From: osy <osy@turing.llc>
+Date: Sun, 10 Aug 2025 21:54:34 -0700
+Subject: [PATCH] pc-bios: use 2023 Microsoft UEFI certificates
+
+Restore non-secure vars variants as well.
+---
+ pc-bios/edk2-arm-secure-vars.fd.bz2  | Bin 0 -> 12654 bytes
+ pc-bios/edk2-arm-vars.fd.bz2         | Bin 6710 -> 595 bytes
+ pc-bios/edk2-i386-secure-vars.fd.bz2 | Bin 0 -> 12986 bytes
+ pc-bios/edk2-i386-vars.fd.bz2        | Bin 7727 -> 612 bytes
+ pc-bios/meson.build                  |   2 ++
+ 5 files changed, 2 insertions(+)
+ create mode 100644 pc-bios/edk2-arm-secure-vars.fd.bz2
+ create mode 100644 pc-bios/edk2-i386-secure-vars.fd.bz2
+
+diff --git a/pc-bios/meson.build b/pc-bios/meson.build
+index 63e10cc6df..1e1b553795 100644
+--- a/pc-bios/meson.build
++++ b/pc-bios/meson.build
+@@ -4,11 +4,13 @@ if unpack_edk2_blobs
+     'edk2-aarch64-code.fd',
+     'edk2-aarch64-secure-code.fd',
+     'edk2-arm-code.fd',
++    'edk2-arm-secure-vars.fd',
+     'edk2-arm-vars.fd',
+     'edk2-riscv-code.fd',
+     'edk2-riscv-vars.fd',
+     'edk2-i386-code.fd',
+     'edk2-i386-secure-code.fd',
++    'edk2-i386-secure-vars.fd',
+     'edk2-i386-vars.fd',
+     'edk2-x86_64-code.fd',
+     'edk2-x86_64-secure-code.fd',
+-- 
+2.41.0
+