UTMSWTPM.swift 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. //
  2. // Copyright © 2023 osy. All rights reserved.
  3. //
  4. // Licensed under the Apache License, Version 2.0 (the "License");
  5. // you may not use this file except in compliance with the License.
  6. // You may obtain a copy of the License at
  7. //
  8. // http://www.apache.org/licenses/LICENSE-2.0
  9. //
  10. // Unless required by applicable law or agreed to in writing, software
  11. // distributed under the License is distributed on an "AS IS" BASIS,
  12. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. // See the License for the specific language governing permissions and
  14. // limitations under the License.
  15. //
  16. import Foundation
  17. private typealias SwtpmMainFunction = @convention(c) (_ argc: Int32, _ argv: UnsafeMutablePointer<UnsafePointer<CChar>>, _ prgname: UnsafePointer<CChar>, _ iface: UnsafePointer<CChar>) -> Int32
  18. private let kMaxAttempts = 15
  19. private let kRetryDelay = 1*NSEC_PER_SEC
  20. class UTMSWTPM: UTMProcess {
  21. private var swtpmMain: SwtpmMainFunction!
  22. private var hasProcessExited: Bool = false
  23. private var lastErrorLine: String?
  24. var ctrlSocketUrl: URL?
  25. var dataUrl: URL?
  26. private override init(arguments: [String]) {
  27. super.init(arguments: arguments)
  28. entry = { process, argc, argv, envp in
  29. let _self = process as! UTMSWTPM
  30. return _self.swtpmMain(argc, argv, "swtpm", "socket")
  31. }
  32. standardError = Pipe()
  33. standardError!.fileHandleForReading.readabilityHandler = { [weak self] handle in
  34. let string = String(data: handle.availableData, encoding: .utf8) ?? ""
  35. logger.debug("\(string)")
  36. self?.lastErrorLine = string
  37. }
  38. standardOutput = Pipe()
  39. standardOutput!.fileHandleForReading.readabilityHandler = { handle in
  40. let string = String(data: handle.availableData, encoding: .utf8) ?? ""
  41. logger.debug("\(string)")
  42. }
  43. }
  44. convenience init() {
  45. self.init(arguments: [])
  46. }
  47. override func didLoadDylib(_ handle: UnsafeMutableRawPointer) -> Bool {
  48. let sym = dlsym(handle, "swtpm_main")
  49. swtpmMain = unsafeBitCast(sym, to: SwtpmMainFunction.self)
  50. return swtpmMain != nil
  51. }
  52. override func processHasExited(_ exitCode: Int, message: String?) {
  53. hasProcessExited = true
  54. if let message = message {
  55. logger.error("SWTPM exited: \(message)")
  56. }
  57. }
  58. func start() async throws {
  59. guard let ctrlSocketUrl = ctrlSocketUrl else {
  60. throw UTMSWTPMError.socketNotSpecified
  61. }
  62. guard let dataUrl = dataUrl else {
  63. throw UTMSWTPMError.dataNotSpecified
  64. }
  65. let fm = FileManager.default
  66. if !fm.fileExists(atPath: dataUrl.path) {
  67. fm.createFile(atPath: dataUrl.path, contents: nil)
  68. }
  69. let dataBookmark = try dataUrl.bookmarkData()
  70. let (success, _, _) = await accessData(withBookmark: dataBookmark, securityScoped: false)
  71. guard success else {
  72. throw UTMSWTPMError.cannotAccessTpmData
  73. }
  74. clearArgv()
  75. pushArgv("--ctrl")
  76. pushArgv("type=unixio,path=\(ctrlSocketUrl.lastPathComponent),terminate")
  77. pushArgv("--tpmstate")
  78. pushArgv("backend-uri=file://\(dataUrl.path)")
  79. pushArgv("--tpm2")
  80. hasProcessExited = false
  81. try? fm.removeItem(at: ctrlSocketUrl)
  82. try await start("swtpm.0")
  83. // monitor for socket to be created
  84. try await Task {
  85. let fm = FileManager.default
  86. for _ in 0...kMaxAttempts {
  87. if hasProcessExited {
  88. throw UTMSWTPMError.swtpmStartupFailed(lastErrorLine)
  89. }
  90. if fm.fileExists(atPath: ctrlSocketUrl.path) {
  91. return
  92. }
  93. try await Task.sleep(nanoseconds: kRetryDelay)
  94. }
  95. }.value
  96. }
  97. }
  98. enum UTMSWTPMError: Error {
  99. case socketNotSpecified
  100. case dataNotSpecified
  101. case cannotAccessTpmData
  102. case swtpmStartupFailed(String?)
  103. }
  104. extension UTMSWTPMError: LocalizedError {
  105. var errorDescription: String? {
  106. switch self {
  107. case .socketNotSpecified: return NSLocalizedString("Socket not specified.", comment: "UTMSWTPM")
  108. case .dataNotSpecified: return NSLocalizedString("Data not specified.", comment: "UTMSWTPM")
  109. case .cannotAccessTpmData: return NSLocalizedString("Cannot access TPM data.", comment: "UTMSWTPM")
  110. case .swtpmStartupFailed(let message): return String.localizedStringWithFormat(NSLocalizedString("SW TPM failed to start. %@", comment: "UTMSWTPM"), message ?? "")
  111. }
  112. }
  113. }