瀏覽代碼

swtpm: add support for SWTPM

osy 2 年之前
父節點
當前提交
52506771a9

+ 1 - 0
Configuration/QEMUConstant.swift

@@ -389,6 +389,7 @@ enum QEMUPackageFileName: String {
     case images = "Images"
     case images = "Images"
     case debugLog = "debug.log"
     case debugLog = "debug.log"
     case efiVariables = "efi_vars.fd"
     case efiVariables = "efi_vars.fd"
+    case tpmData = "tpmdata"
 }
 }
 
 
 // MARK: Supported features
 // MARK: Supported features

+ 31 - 0
Configuration/UTMQemuConfiguration+Arguments.swift

@@ -56,6 +56,11 @@ import Virtualization // for getting network interfaces
         socketURL.appendingPathComponent(information.uuid.uuidString).appendingPathExtension("spice")
         socketURL.appendingPathComponent(information.uuid.uuidString).appendingPathExtension("spice")
     }
     }
     
     
+    /// Return the socket file for communicating with SWTPM
+    var swtpmSocketURL: URL {
+        socketURL.appendingPathComponent(information.uuid.uuidString).appendingPathExtension("swtpm")
+    }
+    
     /// Combined generated and user specified arguments.
     /// Combined generated and user specified arguments.
     @QEMUArgumentBuilder var allArguments: [QEMUArgument] {
     @QEMUArgumentBuilder var allArguments: [QEMUArgument] {
         generatedArguments
         generatedArguments
@@ -907,6 +912,32 @@ import Virtualization // for getting network interfaces
             f("-device")
             f("-device")
             f("virtio-balloon-pci")
             f("virtio-balloon-pci")
         }
         }
+        if qemu.hasTPMDevice {
+            tpmArguments
+        }
+    }
+    
+    @QEMUArgumentBuilder private var tpmArguments: [QEMUArgument] {
+        f("-chardev")
+        "socket"
+        "id=chrtpm0"
+        "path=\(swtpmSocketURL.lastPathComponent)"
+        f()
+        f("-tpmdev")
+        "emulator"
+        "id=tpm0"
+        "chardev=chrtpm0"
+        f()
+        f("-device")
+        if system.target.rawValue.hasPrefix("virt") {
+            "tpm-tis-device"
+        } else if system.architecture == .ppc64 {
+            "tpm-spapr"
+        } else {
+            "tpm-tis"
+        }
+        "tpmdev=tpm0"
+        f()
     }
     }
 }
 }
 
 

+ 29 - 20
Configuration/UTMQemuConfigurationQEMU.swift

@@ -24,6 +24,9 @@ struct UTMQemuConfigurationQEMU: Codable {
     /// EFI variables if EFI boot is enabled. This property is not saved to file.
     /// EFI variables if EFI boot is enabled. This property is not saved to file.
     var efiVarsURL: URL?
     var efiVarsURL: URL?
     
     
+    /// TPM data file if TPM is enabled. This property is not saved to file.
+    var tpmDataURL: URL?
+    
     /// If true, write standard output to debug.log in the VM bundle.
     /// If true, write standard output to debug.log in the VM bundle.
     var hasDebugLog: Bool = false
     var hasDebugLog: Bool = false
     
     
@@ -94,6 +97,7 @@ struct UTMQemuConfigurationQEMU: Codable {
         if let dataURL = decoder.userInfo[.dataURL] as? URL {
         if let dataURL = decoder.userInfo[.dataURL] as? URL {
             debugLogURL = dataURL.appendingPathComponent(QEMUPackageFileName.debugLog.rawValue)
             debugLogURL = dataURL.appendingPathComponent(QEMUPackageFileName.debugLog.rawValue)
             efiVarsURL = dataURL.appendingPathComponent(QEMUPackageFileName.efiVariables.rawValue)
             efiVarsURL = dataURL.appendingPathComponent(QEMUPackageFileName.efiVariables.rawValue)
+            tpmDataURL = dataURL.appendingPathComponent(QEMUPackageFileName.tpmData.rawValue)
         }
         }
     }
     }
     
     
@@ -153,27 +157,32 @@ extension UTMQemuConfigurationQEMU {
 
 
 extension UTMQemuConfigurationQEMU {
 extension UTMQemuConfigurationQEMU {
     @MainActor mutating func saveData(to dataURL: URL, for system: UTMQemuConfigurationSystem) async throws -> [URL] {
     @MainActor mutating func saveData(to dataURL: URL, for system: UTMQemuConfigurationSystem) async throws -> [URL] {
-        guard hasUefiBoot else {
-            return []
-        }
-        let fileManager = FileManager.default
-        // save EFI variables
-        let resourceURL = Bundle.main.url(forResource: "qemu", withExtension: nil)!
-        let templateVarsURL: URL
-        if system.architecture == .arm || system.architecture == .aarch64 {
-            templateVarsURL = resourceURL.appendingPathComponent("edk2-arm-vars.fd")
-        } else if system.architecture == .i386 || system.architecture == .x86_64 {
-            templateVarsURL = resourceURL.appendingPathComponent("edk2-i386-vars.fd")
-        } else {
-            throw UTMQemuConfigurationError.uefiNotSupported
+        var existing: [URL] = []
+        if hasUefiBoot {
+            let fileManager = FileManager.default
+            // save EFI variables
+            let resourceURL = Bundle.main.url(forResource: "qemu", withExtension: nil)!
+            let templateVarsURL: URL
+            if system.architecture == .arm || system.architecture == .aarch64 {
+                templateVarsURL = resourceURL.appendingPathComponent("edk2-arm-vars.fd")
+            } else if system.architecture == .i386 || system.architecture == .x86_64 {
+                templateVarsURL = resourceURL.appendingPathComponent("edk2-i386-vars.fd")
+            } else {
+                throw UTMQemuConfigurationError.uefiNotSupported
+            }
+            let varsURL = dataURL.appendingPathComponent(QEMUPackageFileName.efiVariables.rawValue)
+            if !fileManager.fileExists(atPath: varsURL.path) {
+                try await Task.detached {
+                    try fileManager.copyItem(at: templateVarsURL, to: varsURL)
+                }.value
+            }
+            efiVarsURL = varsURL
+            existing.append(varsURL)
         }
         }
-        let varsURL = dataURL.appendingPathComponent(QEMUPackageFileName.efiVariables.rawValue)
-        if !fileManager.fileExists(atPath: varsURL.path) {
-            try await Task.detached {
-                try fileManager.copyItem(at: templateVarsURL, to: varsURL)
-            }.value
+        if hasTPMDevice {
+            tpmDataURL = dataURL.appendingPathComponent(QEMUPackageFileName.tpmData.rawValue)
+            existing.append(tpmDataURL!)
         }
         }
-        efiVarsURL = varsURL
-        return [varsURL]
+        return existing
     }
     }
 }
 }

+ 2 - 4
Platform/Shared/VMConfigQEMUView.swift

@@ -76,10 +76,8 @@ struct VMConfigQEMUView: View {
                         .help("Should be on always unless the guest cannot boot because of this.")
                         .help("Should be on always unless the guest cannot boot because of this.")
                     Toggle("Balloon Device", isOn: $config.hasBalloonDevice)
                     Toggle("Balloon Device", isOn: $config.hasBalloonDevice)
                         .help("Should be on always unless the guest cannot boot because of this.")
                         .help("Should be on always unless the guest cannot boot because of this.")
-                    #if false
-                    Toggle("TPM Device", isOn: $config.hasTPMDevice)
-                        .help("This is required to boot Windows 11.")
-                    #endif
+                    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.")
                     Toggle("Use Hypervisor", isOn: $config.hasHypervisor)
                     Toggle("Use Hypervisor", isOn: $config.hasHypervisor)
                         .help("Only available if host architecture matches the target. Otherwise, TCG emulation is used.")
                         .help("Only available if host architecture matches the target. Otherwise, TCG emulation is used.")
                         .disabled(!system.architecture.hasHypervisorSupport)
                         .disabled(!system.architecture.hasHypervisorSupport)

+ 5 - 1
QEMULauncher/Bootstrap.c

@@ -27,6 +27,7 @@ typedef struct {
     int (*qemu_init)(int, const char *[], const char *[]);
     int (*qemu_init)(int, const char *[], const char *[]);
     void (*qemu_main_loop)(void);
     void (*qemu_main_loop)(void);
     void (*qemu_cleanup)(void);
     void (*qemu_cleanup)(void);
+    int (*swtpm_main)(int argc, const char *argv[], const char *prgname, const char *iface);
 } qemu_main_t;
 } qemu_main_t;
 
 
 // http://mac-os-x.10953.n7.nabble.com/Ensure-NSTask-terminates-when-parent-application-does-td31477.html
 // http://mac-os-x.10953.n7.nabble.com/Ensure-NSTask-terminates-when-parent-application-does-td31477.html
@@ -61,7 +62,8 @@ static int loadQemu(const char *dylibPath, qemu_main_t *funcs) {
     funcs->qemu_init = dlsym(dlctx, "qemu_init");
     funcs->qemu_init = dlsym(dlctx, "qemu_init");
     funcs->qemu_main_loop = dlsym(dlctx, "qemu_main_loop");
     funcs->qemu_main_loop = dlsym(dlctx, "qemu_main_loop");
     funcs->qemu_cleanup = dlsym(dlctx, "qemu_cleanup");
     funcs->qemu_cleanup = dlsym(dlctx, "qemu_cleanup");
-    if (funcs->main == NULL && (funcs->qemu_init == NULL || funcs->qemu_main_loop == NULL || funcs->qemu_cleanup == NULL)) {
+    funcs->swtpm_main = dlsym(dlctx, "swtpm_main");
+    if (funcs->main == NULL && funcs->swtpm_main == NULL && (funcs->qemu_init == NULL || funcs->qemu_main_loop == NULL || funcs->qemu_cleanup == NULL)) {
         fprintf(stderr, "Error resolving %s: %s\n", dylibPath, dlerror());
         fprintf(stderr, "Error resolving %s: %s\n", dylibPath, dlerror());
         return -1;
         return -1;
     }
     }
@@ -77,6 +79,8 @@ static void __attribute__((noreturn)) runQemu(qemu_main_t *funcs, int argc, cons
     pthread_detach(thread);
     pthread_detach(thread);
     if (funcs->main) {
     if (funcs->main) {
         funcs->main(argc, argv);
         funcs->main(argc, argv);
+    } else if (funcs->swtpm_main) {
+        funcs->swtpm_main(argc, argv, "swtpm", "socket");
     } else {
     } else {
         funcs->qemu_main_loop();
         funcs->qemu_main_loop();
         funcs->qemu_cleanup();
         funcs->qemu_cleanup();

+ 1 - 0
Services/UTMProcess.h

@@ -45,6 +45,7 @@ typedef int (*UTMProcessThreadEntry)(UTMProcess *self, int argc, const char * _N
 - (void)accessDataWithBookmark:(NSData *)bookmark securityScoped:(BOOL)securityScoped completion:(void(^)(BOOL, NSData * _Nullable, NSString * _Nullable))completion;
 - (void)accessDataWithBookmark:(NSData *)bookmark securityScoped:(BOOL)securityScoped completion:(void(^)(BOOL, NSData * _Nullable, NSString * _Nullable))completion;
 - (void)stopAccessingPath:(nullable NSString *)path;
 - (void)stopAccessingPath:(nullable NSString *)path;
 - (void)processHasExited:(NSInteger)exitCode message:(nullable NSString *)message;
 - (void)processHasExited:(NSInteger)exitCode message:(nullable NSString *)message;
+- (BOOL)didLoadDylib:(void *)handle;
 
 
 @end
 @end
 
 

+ 14 - 0
Services/UTMQemuVirtualMachine.swift

@@ -138,6 +138,8 @@ final class UTMQemuVirtualMachine: UTMVirtualMachine {
     
     
     private var startTask: Task<Void, any Error>?
     private var startTask: Task<Void, any Error>?
     
     
+    private var swtpm: UTMSWTPM?
+    
     @MainActor required init(packageUrl: URL, configuration: UTMQemuConfiguration? = nil, isShortcut: Bool = false) throws {
     @MainActor required init(packageUrl: URL, configuration: UTMQemuConfiguration? = nil, isShortcut: Bool = false) throws {
         self.isScopedAccess = packageUrl.startAccessingSecurityScopedResource()
         self.isScopedAccess = packageUrl.startAccessingSecurityScopedResource()
         // load configuration
         // load configuration
@@ -241,6 +243,16 @@ extension UTMQemuVirtualMachine {
             config.qemu.isDisposable = isRunningAsDisposible
             config.qemu.isDisposable = isRunningAsDisposible
         }
         }
         
         
+        // start TPM
+        if await config.qemu.hasTPMDevice {
+            let swtpm = UTMSWTPM()
+            swtpm.ctrlSocketUrl = await config.swtpmSocketURL
+            swtpm.dataUrl = await config.qemu.tpmDataURL
+            swtpm.currentDirectoryUrl = await config.socketURL
+            try await swtpm.start()
+            self.swtpm = swtpm
+        }
+        
         let allArguments = await config.allArguments
         let allArguments = await config.allArguments
         let arguments = allArguments.map({ $0.string })
         let arguments = allArguments.map({ $0.string })
         let resources = allArguments.compactMap({ $0.fileUrls }).flatMap({ $0 })
         let resources = allArguments.compactMap({ $0.fileUrls }).flatMap({ $0 })
@@ -524,6 +536,8 @@ extension UTMQemuVirtualMachine: QEMUVirtualMachineDelegate {
     }
     }
     
     
     func qemuVMDidStop(_ qemuVM: QEMUVirtualMachine) {
     func qemuVMDidStop(_ qemuVM: QEMUVirtualMachine) {
+        swtpm?.stop()
+        swtpm = nil
         ioService = nil
         ioService = nil
         ioServiceDelegate = nil
         ioServiceDelegate = nil
         state = .stopped
         state = .stopped

+ 125 - 0
Services/UTMSWTPM.swift

@@ -0,0 +1,125 @@
+//
+// Copyright © 2023 osy. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+import Foundation
+
+private typealias SwtpmMainFunction = @convention(c) (_ argc: Int32, _ argv: UnsafeMutablePointer<UnsafePointer<CChar>>, _ prgname: UnsafePointer<CChar>, _ iface: UnsafePointer<CChar>) -> Int32
+
+private let kMaxAttempts = 15
+private let kRetryDelay = 1*NSEC_PER_SEC
+
+class UTMSWTPM: UTMProcess {
+    private var swtpmMain: SwtpmMainFunction!
+    private var hasProcessExited: Bool = false
+    private var lastErrorLine: String?
+    
+    var ctrlSocketUrl: URL?
+    var dataUrl: URL?
+    
+    private override init(arguments: [String]) {
+        super.init(arguments: arguments)
+        entry = { process, argc, argv, envp in
+            let _self = process as! UTMSWTPM
+            return _self.swtpmMain(argc, argv, "swtpm", "socket")
+        }
+        standardError = Pipe()
+        standardError!.fileHandleForReading.readabilityHandler = { [weak self] handle in
+            let string = String(data: handle.availableData, encoding: .utf8) ?? ""
+            logger.debug("\(string)")
+            self?.lastErrorLine = string
+        }
+        standardOutput = Pipe()
+        standardOutput!.fileHandleForReading.readabilityHandler = { handle in
+            let string = String(data: handle.availableData, encoding: .utf8) ?? ""
+            logger.debug("\(string)")
+        }
+    }
+    
+    convenience init() {
+        self.init(arguments: [])
+    }
+    
+    override func didLoadDylib(_ handle: UnsafeMutableRawPointer) -> Bool {
+        let sym = dlsym(handle, "swtpm_main")
+        swtpmMain = unsafeBitCast(sym, to: SwtpmMainFunction.self)
+        return swtpmMain != nil
+    }
+    
+    override func processHasExited(_ exitCode: Int, message: String?) {
+        hasProcessExited = true
+        if let message = message {
+            logger.error("SWTPM exited: \(message)")
+        }
+    }
+    
+    func start() async throws {
+        guard let ctrlSocketUrl = ctrlSocketUrl else {
+            throw UTMSWTPMError.socketNotSpecified
+        }
+        guard let dataUrl = dataUrl else {
+            throw UTMSWTPMError.dataNotSpecified
+        }
+        let fm = FileManager.default
+        if !fm.fileExists(atPath: dataUrl.path) {
+            fm.createFile(atPath: dataUrl.path, contents: nil)
+        }
+        let dataBookmark = try dataUrl.bookmarkData()
+        let (success, _, _) = await accessData(withBookmark: dataBookmark, securityScoped: false)
+        guard success else {
+            throw UTMSWTPMError.cannotAccessTpmData
+        }
+        clearArgv()
+        pushArgv("--ctrl")
+        pushArgv("type=unixio,path=\(ctrlSocketUrl.lastPathComponent),terminate")
+        pushArgv("--tpmstate")
+        pushArgv("backend-uri=file://\(dataUrl.path)")
+        pushArgv("--tpm2")
+        hasProcessExited = false
+        try? fm.removeItem(at: ctrlSocketUrl)
+        try await start("swtpm.0")
+        // monitor for socket to be created
+        try await Task {
+            let fm = FileManager.default
+            for _ in 0...kMaxAttempts {
+                if hasProcessExited {
+                    throw UTMSWTPMError.swtpmStartupFailed(lastErrorLine)
+                }
+                if fm.fileExists(atPath: ctrlSocketUrl.path) {
+                    return
+                }
+                try await Task.sleep(nanoseconds: kRetryDelay)
+            }
+        }.value
+    }
+}
+
+enum UTMSWTPMError: Error {
+    case socketNotSpecified
+    case dataNotSpecified
+    case cannotAccessTpmData
+    case swtpmStartupFailed(String?)
+}
+
+extension UTMSWTPMError: LocalizedError {
+    var errorDescription: String? {
+        switch self {
+        case .socketNotSpecified: return NSLocalizedString("Socket not specified.", comment: "UTMSWTPM")
+        case .dataNotSpecified: return NSLocalizedString("Data not specified.", comment: "UTMSWTPM")
+        case .cannotAccessTpmData: return NSLocalizedString("Cannot access TPM data.", comment: "UTMSWTPM")
+        case .swtpmStartupFailed(let message): return String.localizedStringWithFormat(NSLocalizedString("SW TPM failed to start. %@", comment: "UTMSWTPM"), message ?? "")
+        }
+    }
+}

+ 8 - 0
UTM.xcodeproj/project.pbxproj

@@ -141,6 +141,9 @@
 		845F1709289CA15C00944904 /* VMDisplayAppleTerminalWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845F1708289CA15C00944904 /* VMDisplayAppleTerminalWindowController.swift */; };
 		845F1709289CA15C00944904 /* VMDisplayAppleTerminalWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845F1708289CA15C00944904 /* VMDisplayAppleTerminalWindowController.swift */; };
 		845F170B289CB07200944904 /* VMDisplayAppleDisplayWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845F170A289CB07200944904 /* VMDisplayAppleDisplayWindowController.swift */; };
 		845F170B289CB07200944904 /* VMDisplayAppleDisplayWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845F170A289CB07200944904 /* VMDisplayAppleDisplayWindowController.swift */; };
 		845F170D289CB3DE00944904 /* VMDisplayTerminal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845F170C289CB3DE00944904 /* VMDisplayTerminal.swift */; };
 		845F170D289CB3DE00944904 /* VMDisplayTerminal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845F170C289CB3DE00944904 /* VMDisplayTerminal.swift */; };
+		845F95E32A57628400A016D7 /* UTMSWTPM.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845F95E22A57628400A016D7 /* UTMSWTPM.swift */; };
+		845F95E42A57628400A016D7 /* UTMSWTPM.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845F95E22A57628400A016D7 /* UTMSWTPM.swift */; };
+		845F95E52A57628400A016D7 /* UTMSWTPM.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845F95E22A57628400A016D7 /* UTMSWTPM.swift */; };
 		846D878629050B6B0095F10B /* InAppSettingsKit in Frameworks */ = {isa = PBXBuildFile; productRef = 846D878529050B6B0095F10B /* InAppSettingsKit */; };
 		846D878629050B6B0095F10B /* InAppSettingsKit in Frameworks */ = {isa = PBXBuildFile; productRef = 846D878529050B6B0095F10B /* InAppSettingsKit */; };
 		8471770627CC974F00D3A50B /* DefaultTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8471770527CC974F00D3A50B /* DefaultTextField.swift */; };
 		8471770627CC974F00D3A50B /* DefaultTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8471770527CC974F00D3A50B /* DefaultTextField.swift */; };
 		8471770727CC974F00D3A50B /* DefaultTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8471770527CC974F00D3A50B /* DefaultTextField.swift */; };
 		8471770727CC974F00D3A50B /* DefaultTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8471770527CC974F00D3A50B /* DefaultTextField.swift */; };
@@ -1317,6 +1320,7 @@
 		845F1708289CA15C00944904 /* VMDisplayAppleTerminalWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VMDisplayAppleTerminalWindowController.swift; sourceTree = "<group>"; };
 		845F1708289CA15C00944904 /* VMDisplayAppleTerminalWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VMDisplayAppleTerminalWindowController.swift; sourceTree = "<group>"; };
 		845F170A289CB07200944904 /* VMDisplayAppleDisplayWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VMDisplayAppleDisplayWindowController.swift; sourceTree = "<group>"; };
 		845F170A289CB07200944904 /* VMDisplayAppleDisplayWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VMDisplayAppleDisplayWindowController.swift; sourceTree = "<group>"; };
 		845F170C289CB3DE00944904 /* VMDisplayTerminal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VMDisplayTerminal.swift; sourceTree = "<group>"; };
 		845F170C289CB3DE00944904 /* VMDisplayTerminal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VMDisplayTerminal.swift; sourceTree = "<group>"; };
+		845F95E22A57628400A016D7 /* UTMSWTPM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTMSWTPM.swift; sourceTree = "<group>"; };
 		8471770527CC974F00D3A50B /* DefaultTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultTextField.swift; sourceTree = "<group>"; };
 		8471770527CC974F00D3A50B /* DefaultTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultTextField.swift; sourceTree = "<group>"; };
 		8471772727CD3CAB00D3A50B /* DetailedSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailedSection.swift; sourceTree = "<group>"; };
 		8471772727CD3CAB00D3A50B /* DetailedSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailedSection.swift; sourceTree = "<group>"; };
 		847BF9A92A49C783000BD9AA /* VMData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VMData.swift; sourceTree = "<group>"; };
 		847BF9A92A49C783000BD9AA /* VMData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VMData.swift; sourceTree = "<group>"; };
@@ -2298,6 +2302,7 @@
 				E2D64BC7241DB24B0034E0C6 /* UTMSpiceIO.h */,
 				E2D64BC7241DB24B0034E0C6 /* UTMSpiceIO.h */,
 				E2D64BC8241DB24B0034E0C6 /* UTMSpiceIO.m */,
 				E2D64BC8241DB24B0034E0C6 /* UTMSpiceIO.m */,
 				E2D64BE0241EAEBE0034E0C6 /* UTMSpiceIODelegate.h */,
 				E2D64BE0241EAEBE0034E0C6 /* UTMSpiceIODelegate.h */,
+				845F95E22A57628400A016D7 /* UTMSWTPM.swift */,
 				CE020BB524B14F8400B44AB6 /* UTMVirtualMachine.swift */,
 				CE020BB524B14F8400B44AB6 /* UTMVirtualMachine.swift */,
 				CE928C2926ABE6690099F293 /* UTMAppleVirtualMachine.swift */,
 				CE928C2926ABE6690099F293 /* UTMAppleVirtualMachine.swift */,
 				841E999728AC817D003C6CB6 /* UTMQemuVirtualMachine.swift */,
 				841E999728AC817D003C6CB6 /* UTMQemuVirtualMachine.swift */,
@@ -2948,6 +2953,7 @@
 				CE2D956924AD4F990059923A /* VMPlaceholderView.swift in Sources */,
 				CE2D956924AD4F990059923A /* VMPlaceholderView.swift in Sources */,
 				CE2D931524AD46670059923A /* VMDisplayMetalViewController+Pointer.m in Sources */,
 				CE2D931524AD46670059923A /* VMDisplayMetalViewController+Pointer.m in Sources */,
 				84018692288A73310050AC51 /* VMDisplayViewController.m in Sources */,
 				84018692288A73310050AC51 /* VMDisplayViewController.m in Sources */,
+				845F95E32A57628400A016D7 /* UTMSWTPM.swift in Sources */,
 				84CE3DB12904C7A100FF068B /* UTMSettingsView.swift in Sources */,
 				84CE3DB12904C7A100FF068B /* UTMSettingsView.swift in Sources */,
 				843BF83428450C0B0029D60D /* UTMQemuConfigurationSound.swift in Sources */,
 				843BF83428450C0B0029D60D /* UTMQemuConfigurationSound.swift in Sources */,
 				CE2D932224AD46670059923A /* VMScroll.m in Sources */,
 				CE2D932224AD46670059923A /* VMScroll.m in Sources */,
@@ -3031,6 +3037,7 @@
 				CE2D957624AD4F990059923A /* VMConfigInputView.swift in Sources */,
 				CE2D957624AD4F990059923A /* VMConfigInputView.swift in Sources */,
 				84F746B9276FF40900A20C87 /* VMDisplayAppleWindowController.swift in Sources */,
 				84F746B9276FF40900A20C87 /* VMDisplayAppleWindowController.swift in Sources */,
 				CE2D958E24AD4F990059923A /* UTMApp.swift in Sources */,
 				CE2D958E24AD4F990059923A /* UTMApp.swift in Sources */,
+				845F95E52A57628400A016D7 /* UTMSWTPM.swift in Sources */,
 				844EC0FB2773EE49003C104A /* UTMDownloadIPSWTask.swift in Sources */,
 				844EC0FB2773EE49003C104A /* UTMDownloadIPSWTask.swift in Sources */,
 				8432329228C2CDAD00CFBC97 /* VMNavigationListView.swift in Sources */,
 				8432329228C2CDAD00CFBC97 /* VMNavigationListView.swift in Sources */,
 				CE0B6CF324AD568400FE012D /* UTMLegacyQemuConfiguration.m in Sources */,
 				CE0B6CF324AD568400FE012D /* UTMLegacyQemuConfiguration.m in Sources */,
@@ -3195,6 +3202,7 @@
 				84B36D2A27B790BE00C22685 /* DestructiveButton.swift in Sources */,
 				84B36D2A27B790BE00C22685 /* DestructiveButton.swift in Sources */,
 				842B9F8E28CC58B700031EE7 /* UTMPatches.swift in Sources */,
 				842B9F8E28CC58B700031EE7 /* UTMPatches.swift in Sources */,
 				CEE7E937287CFDB100282049 /* UTMLegacyQemuConfiguration+Constants.m in Sources */,
 				CEE7E937287CFDB100282049 /* UTMLegacyQemuConfiguration+Constants.m in Sources */,
+				845F95E42A57628400A016D7 /* UTMSWTPM.swift in Sources */,
 				841619AB284315F9000034B2 /* UTMConfigurationInfo.swift in Sources */,
 				841619AB284315F9000034B2 /* UTMConfigurationInfo.swift in Sources */,
 				CEA45E6F263519B5002FA97D /* VMConfigInfoView.swift in Sources */,
 				CEA45E6F263519B5002FA97D /* VMConfigInfoView.swift in Sources */,
 				CEA45E72263519B5002FA97D /* VMKeyboardView.m in Sources */,
 				CEA45E72263519B5002FA97D /* VMKeyboardView.m in Sources */,