Receiving signing identity
This commit is contained in:
parent
7f67752566
commit
7e500cb37d
@ -15,6 +15,8 @@
|
|||||||
7A72231529DCABE400503F78 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A72231429DCABE400503F78 /* ContentView.swift */; };
|
7A72231529DCABE400503F78 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A72231429DCABE400503F78 /* ContentView.swift */; };
|
||||||
7A72231729DCABE500503F78 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7A72231629DCABE500503F78 /* Assets.xcassets */; };
|
7A72231729DCABE500503F78 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7A72231629DCABE500503F78 /* Assets.xcassets */; };
|
||||||
7A72231A29DCABE500503F78 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7A72231929DCABE500503F78 /* Preview Assets.xcassets */; };
|
7A72231A29DCABE500503F78 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7A72231929DCABE500503F78 /* Preview Assets.xcassets */; };
|
||||||
|
7A9478012A0974BA00EC7329 /* ProcessTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A9478002A0974BA00EC7329 /* ProcessTask.swift */; };
|
||||||
|
7A9478032A0984D200EC7329 /* FormPickerItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A9478022A0984D200EC7329 /* FormPickerItem.swift */; };
|
||||||
7AF0C51829EDCF59008D4084 /* URL+ExtendedAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AF0C51729EDCF59008D4084 /* URL+ExtendedAttributes.swift */; };
|
7AF0C51829EDCF59008D4084 /* URL+ExtendedAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AF0C51729EDCF59008D4084 /* URL+ExtendedAttributes.swift */; };
|
||||||
7AF0C51A29EF43C2008D4084 /* OutlineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AF0C51929EF43C2008D4084 /* OutlineView.swift */; };
|
7AF0C51A29EF43C2008D4084 /* OutlineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AF0C51929EF43C2008D4084 /* OutlineView.swift */; };
|
||||||
7AF0C51C29EF43CD008D4084 /* TreeItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AF0C51B29EF43CD008D4084 /* TreeItem.swift */; };
|
7AF0C51C29EF43CD008D4084 /* TreeItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AF0C51B29EF43CD008D4084 /* TreeItem.swift */; };
|
||||||
@ -37,6 +39,8 @@
|
|||||||
7A72231629DCABE500503F78 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
7A72231629DCABE500503F78 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||||
7A72231929DCABE500503F78 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
|
7A72231929DCABE500503F78 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
|
||||||
7A72231B29DCABE500503F78 /* reSign.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = reSign.entitlements; sourceTree = "<group>"; };
|
7A72231B29DCABE500503F78 /* reSign.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = reSign.entitlements; sourceTree = "<group>"; };
|
||||||
|
7A9478002A0974BA00EC7329 /* ProcessTask.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProcessTask.swift; sourceTree = "<group>"; };
|
||||||
|
7A9478022A0984D200EC7329 /* FormPickerItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormPickerItem.swift; sourceTree = "<group>"; };
|
||||||
7AF0C51729EDCF59008D4084 /* URL+ExtendedAttributes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URL+ExtendedAttributes.swift"; sourceTree = "<group>"; };
|
7AF0C51729EDCF59008D4084 /* URL+ExtendedAttributes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URL+ExtendedAttributes.swift"; sourceTree = "<group>"; };
|
||||||
7AF0C51929EF43C2008D4084 /* OutlineView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OutlineView.swift; sourceTree = "<group>"; };
|
7AF0C51929EF43C2008D4084 /* OutlineView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OutlineView.swift; sourceTree = "<group>"; };
|
||||||
7AF0C51B29EF43CD008D4084 /* TreeItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TreeItem.swift; sourceTree = "<group>"; };
|
7AF0C51B29EF43CD008D4084 /* TreeItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TreeItem.swift; sourceTree = "<group>"; };
|
||||||
@ -75,6 +79,7 @@
|
|||||||
7AF0C51929EF43C2008D4084 /* OutlineView.swift */,
|
7AF0C51929EF43C2008D4084 /* OutlineView.swift */,
|
||||||
7A064BE829DE18C700C5D978 /* SignInfoView.swift */,
|
7A064BE829DE18C700C5D978 /* SignInfoView.swift */,
|
||||||
7AF0C53A29F72151008D4084 /* PlistView.swift */,
|
7AF0C53A29F72151008D4084 /* PlistView.swift */,
|
||||||
|
7A9478022A0984D200EC7329 /* FormPickerItem.swift */,
|
||||||
);
|
);
|
||||||
path = Views;
|
path = Views;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -107,6 +112,7 @@
|
|||||||
7A72231629DCABE500503F78 /* Assets.xcassets */,
|
7A72231629DCABE500503F78 /* Assets.xcassets */,
|
||||||
7A72231B29DCABE500503F78 /* reSign.entitlements */,
|
7A72231B29DCABE500503F78 /* reSign.entitlements */,
|
||||||
7A72231829DCABE500503F78 /* Preview Content */,
|
7A72231829DCABE500503F78 /* Preview Content */,
|
||||||
|
7A9478002A0974BA00EC7329 /* ProcessTask.swift */,
|
||||||
);
|
);
|
||||||
path = reSign;
|
path = reSign;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -226,11 +232,13 @@
|
|||||||
7A064BE929DE18C700C5D978 /* SignInfoView.swift in Sources */,
|
7A064BE929DE18C700C5D978 /* SignInfoView.swift in Sources */,
|
||||||
7AF0C54229FFBDB0008D4084 /* SignViewModel.swift in Sources */,
|
7AF0C54229FFBDB0008D4084 /* SignViewModel.swift in Sources */,
|
||||||
7AF0C54729FFCC66008D4084 /* Identity.swift in Sources */,
|
7AF0C54729FFCC66008D4084 /* Identity.swift in Sources */,
|
||||||
|
7A9478012A0974BA00EC7329 /* ProcessTask.swift in Sources */,
|
||||||
7A064BED29E2C91D00C5D978 /* Certificate.swift in Sources */,
|
7A064BED29E2C91D00C5D978 /* Certificate.swift in Sources */,
|
||||||
7A72231329DCABE400503F78 /* reSignApp.swift in Sources */,
|
7A72231329DCABE400503F78 /* reSignApp.swift in Sources */,
|
||||||
7AF0C53D29F9B7FE008D4084 /* Quarantine.swift in Sources */,
|
7AF0C53D29F9B7FE008D4084 /* Quarantine.swift in Sources */,
|
||||||
7AF0C54529FFCAD8008D4084 /* SecError.swift in Sources */,
|
7AF0C54529FFCAD8008D4084 /* SecError.swift in Sources */,
|
||||||
7AF0C53F29FFB765008D4084 /* SignView.swift in Sources */,
|
7AF0C53F29FFB765008D4084 /* SignView.swift in Sources */,
|
||||||
|
7A9478032A0984D200EC7329 /* FormPickerItem.swift in Sources */,
|
||||||
7A064BE429DE107000C5D978 /* Category.swift in Sources */,
|
7A064BE429DE107000C5D978 /* Category.swift in Sources */,
|
||||||
7AF0C51A29EF43C2008D4084 /* OutlineView.swift in Sources */,
|
7AF0C51A29EF43C2008D4084 /* OutlineView.swift in Sources */,
|
||||||
);
|
);
|
||||||
@ -371,6 +379,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/../Frameworks",
|
"@executable_path/../Frameworks",
|
||||||
);
|
);
|
||||||
|
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = pro.aliencat.reSign;
|
PRODUCT_BUNDLE_IDENTIFIER = pro.aliencat.reSign;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
@ -398,6 +407,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/../Frameworks",
|
"@executable_path/../Frameworks",
|
||||||
);
|
);
|
||||||
|
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = pro.aliencat.reSign;
|
PRODUCT_BUNDLE_IDENTIFIER = pro.aliencat.reSign;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
|||||||
193
reSign/ProcessTask.swift
Normal file
193
reSign/ProcessTask.swift
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
//
|
||||||
|
// Task.swift
|
||||||
|
// reSign
|
||||||
|
//
|
||||||
|
// Created by Selim Mustafaev on 08.05.2023.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
class ProcessTask {
|
||||||
|
|
||||||
|
enum TaskError: LocalizedError {
|
||||||
|
|
||||||
|
case unreadableOutput
|
||||||
|
|
||||||
|
var errorDescription: String? {
|
||||||
|
switch self {
|
||||||
|
case .unreadableOutput: return "Process output is not a string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static func run(url: URL, arguments: [String] = []) async throws -> String {
|
||||||
|
|
||||||
|
try await withCheckedThrowingContinuation { continuation in
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
launch(tool: url, arguments: arguments) { result, output in
|
||||||
|
switch result {
|
||||||
|
case .success(let code):
|
||||||
|
print("Process return code: \(code)")
|
||||||
|
if let str = String(data: output, encoding: .utf8) {
|
||||||
|
continuation.resume(returning: str)
|
||||||
|
} else {
|
||||||
|
continuation.resume(throwing: TaskError.unreadableOutput)
|
||||||
|
}
|
||||||
|
case .failure(let error):
|
||||||
|
continuation.resume(throwing: error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Runs the specified tool as a child process, supplying `stdin` and capturing `stdout`.
|
||||||
|
///
|
||||||
|
/// - important: Must be run on the main queue.
|
||||||
|
///
|
||||||
|
/// - Parameters:
|
||||||
|
/// - tool: The tool to run.
|
||||||
|
/// - arguments: The command-line arguments to pass to that tool; defaults to the empty array.
|
||||||
|
/// - input: Data to pass to the tool’s `stdin`; defaults to empty.
|
||||||
|
/// - completionHandler: Called on the main queue when the tool has terminated.
|
||||||
|
|
||||||
|
static func launch(tool: URL, arguments: [String] = [], input: Data = Data(), completionHandler: @escaping CompletionHandler) {
|
||||||
|
// This precondition is important; read the comment near the `run()` call to
|
||||||
|
// understand why.
|
||||||
|
dispatchPrecondition(condition: .onQueue(.main))
|
||||||
|
|
||||||
|
let group = DispatchGroup()
|
||||||
|
let inputPipe = Pipe()
|
||||||
|
let outputPipe = Pipe()
|
||||||
|
|
||||||
|
var errorQ: Error? = nil
|
||||||
|
var output = Data()
|
||||||
|
|
||||||
|
let proc = Process()
|
||||||
|
proc.executableURL = tool
|
||||||
|
proc.arguments = arguments
|
||||||
|
proc.standardInput = inputPipe
|
||||||
|
proc.standardOutput = outputPipe
|
||||||
|
group.enter()
|
||||||
|
proc.terminationHandler = { _ in
|
||||||
|
// This bounce to the main queue is important; read the comment near the
|
||||||
|
// `run()` call to understand why.
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
group.leave()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This runs the supplied block when all three events have completed (task
|
||||||
|
// termination and the end of both I/O channels).
|
||||||
|
//
|
||||||
|
// - important: If the process was never launched, requesting its
|
||||||
|
// termination status raises an Objective-C exception (ouch!). So, we only
|
||||||
|
// read `terminationStatus` if `errorQ` is `nil`.
|
||||||
|
|
||||||
|
group.notify(queue: .main) {
|
||||||
|
if let error = errorQ {
|
||||||
|
completionHandler(.failure(error), output)
|
||||||
|
} else {
|
||||||
|
completionHandler(.success(proc.terminationStatus), output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
func posixErr(_ error: Int32) -> Error { NSError(domain: NSPOSIXErrorDomain, code: Int(error), userInfo: nil) }
|
||||||
|
|
||||||
|
// If you write to a pipe whose remote end has closed, the OS raises a
|
||||||
|
// `SIGPIPE` signal whose default disposition is to terminate your
|
||||||
|
// process. Helpful! `F_SETNOSIGPIPE` disables that feature, causing
|
||||||
|
// the write to fail with `EPIPE` instead.
|
||||||
|
|
||||||
|
let fcntlResult = fcntl(inputPipe.fileHandleForWriting.fileDescriptor, F_SETNOSIGPIPE, 1)
|
||||||
|
guard fcntlResult >= 0 else { throw posixErr(errno) }
|
||||||
|
|
||||||
|
// Actually run the process.
|
||||||
|
|
||||||
|
try proc.run()
|
||||||
|
|
||||||
|
// At this point the termination handler could run and leave the group
|
||||||
|
// before we have a chance to enter the group for each of the I/O
|
||||||
|
// handlers. I avoid this problem by having the termination handler
|
||||||
|
// dispatch to the main thread. We are running on the main thread, so
|
||||||
|
// the termination handler can’t run until we return, at which point we
|
||||||
|
// have already entered the group for each of the I/O handlers.
|
||||||
|
//
|
||||||
|
// An alternative design would be to enter the group at the top of this
|
||||||
|
// block and then leave it in the error hander. I decided on this
|
||||||
|
// design because it has the added benefit of all my code running on the
|
||||||
|
// main queue and thus I can access shared mutable state, like `errorQ`,
|
||||||
|
// without worrying about thread safety.
|
||||||
|
|
||||||
|
// Enter the group and then set up a Dispatch I/O channel to write our
|
||||||
|
// data to the child’s `stdin`. When that’s done, record any error and
|
||||||
|
// leave the group.
|
||||||
|
//
|
||||||
|
// Note that we ignore the residual value passed to the
|
||||||
|
// `write(offset:data:queue:ioHandler:)` completion handler. Earlier
|
||||||
|
// versions of this code passed it along to our completion handler but
|
||||||
|
// the reality is that it’s not very useful. The pipe buffer is big
|
||||||
|
// enough that it usually soaks up all our data, so the residual is a
|
||||||
|
// very poor indication of how much data was actually read by the
|
||||||
|
// client.
|
||||||
|
|
||||||
|
group.enter()
|
||||||
|
let writeIO = DispatchIO(type: .stream, fileDescriptor: inputPipe.fileHandleForWriting.fileDescriptor, queue: .main) { _ in
|
||||||
|
// `FileHandle` will automatically close the underlying file
|
||||||
|
// descriptor when you release the last reference to it. By holidng
|
||||||
|
// on to `inputPipe` until here, we ensure that doesn’t happen. And
|
||||||
|
// as we have to hold a reference anyway, we might as well close it
|
||||||
|
// explicitly.
|
||||||
|
//
|
||||||
|
// We apply the same logic to `readIO` below.
|
||||||
|
try! inputPipe.fileHandleForWriting.close()
|
||||||
|
}
|
||||||
|
let inputDD = input.withUnsafeBytes { DispatchData(bytes: $0) }
|
||||||
|
writeIO.write(offset: 0, data: inputDD, queue: .main) { isDone, _, error in
|
||||||
|
if isDone || error != 0 {
|
||||||
|
writeIO.close()
|
||||||
|
if errorQ == nil && error != 0 { errorQ = posixErr(error) }
|
||||||
|
group.leave()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enter the group and then set up a Dispatch I/O channel to read data
|
||||||
|
// from the child’s `stdin`. When that’s done, record any error and
|
||||||
|
// leave the group.
|
||||||
|
|
||||||
|
group.enter()
|
||||||
|
let readIO = DispatchIO(type: .stream, fileDescriptor: outputPipe.fileHandleForReading.fileDescriptor, queue: .main) { _ in
|
||||||
|
try! outputPipe.fileHandleForReading.close()
|
||||||
|
}
|
||||||
|
readIO.read(offset: 0, length: .max, queue: .main) { isDone, chunkQ, error in
|
||||||
|
output.append(contentsOf: chunkQ ?? .empty)
|
||||||
|
if isDone || error != 0 {
|
||||||
|
readIO.close()
|
||||||
|
if errorQ == nil && error != 0 { errorQ = posixErr(error) }
|
||||||
|
group.leave()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// If either the `fcntl` or the `run()` call threw, we set the error
|
||||||
|
// and manually call the termination handler. Note that we’ve only
|
||||||
|
// entered the group once at this point, so the single leave done by the
|
||||||
|
// termination handler is enough to run the notify block and call the
|
||||||
|
// client’s completion handler.
|
||||||
|
errorQ = error
|
||||||
|
proc.terminationHandler!(proc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Called when the tool has terminated.
|
||||||
|
///
|
||||||
|
/// This must be run on the main queue.
|
||||||
|
///
|
||||||
|
/// - Parameters:
|
||||||
|
/// - result: Either the tool’s termination status or, if something went
|
||||||
|
/// wrong, an error indicating what that was.
|
||||||
|
/// - output: Data captured from the tool’s `stdout`.
|
||||||
|
|
||||||
|
typealias CompletionHandler = (_ result: Result<Int32, Error>, _ output: Data) -> Void
|
||||||
|
}
|
||||||
28
reSign/Views/FormPickerItem.swift
Normal file
28
reSign/Views/FormPickerItem.swift
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
//
|
||||||
|
// FormPickerItem.swift
|
||||||
|
// reSign
|
||||||
|
//
|
||||||
|
// Created by Selim Mustafaev on 08.05.2023.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct FormPickerItem: View {
|
||||||
|
|
||||||
|
let name: String
|
||||||
|
let options: [String]
|
||||||
|
@Binding var selectedItem: String
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
HStack {
|
||||||
|
Text(name)
|
||||||
|
Spacer()
|
||||||
|
Picker("", selection: $selectedItem) {
|
||||||
|
ForEach(options, id: \.self) { option in
|
||||||
|
Text(option)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.frame(minWidth: 100)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -14,7 +14,14 @@ struct SignView: View {
|
|||||||
@StateObject var viewModel = SignViewModel()
|
@StateObject var viewModel = SignViewModel()
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Text("Hello, World!")
|
Form {
|
||||||
|
Section {
|
||||||
|
FormPickerItem(name: "Signing Identity",
|
||||||
|
options: viewModel.signingIdentities,
|
||||||
|
selectedItem: $viewModel.selectedIdentity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.formStyle(.grouped)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -10,15 +10,22 @@ import SwiftUI
|
|||||||
class SignViewModel: ObservableObject {
|
class SignViewModel: ObservableObject {
|
||||||
|
|
||||||
@Published var signingIdentities: [String] = []
|
@Published var signingIdentities: [String] = []
|
||||||
|
@Published var selectedIdentity: String = ""
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
do {
|
updateSigningIdentities()
|
||||||
self.signingIdentities = try readSigningIdentities()
|
}
|
||||||
for identity in signingIdentities {
|
|
||||||
print(identity)
|
func updateSigningIdentities() {
|
||||||
|
Task { @MainActor in
|
||||||
|
do {
|
||||||
|
signingIdentities = try await readSigningIdentitiesFromCli()
|
||||||
|
selectedIdentity = signingIdentities.first ?? ""
|
||||||
|
} catch {
|
||||||
|
print("Error getting signing identity from cli (security command): ", error)
|
||||||
|
print("Trying read from keychain...")
|
||||||
|
signingIdentities = (try? readSigningIdentities()) ?? []
|
||||||
}
|
}
|
||||||
} catch {
|
|
||||||
print("SignViewModel init error: ", error.localizedDescription)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -37,4 +44,15 @@ class SignViewModel: ObservableObject {
|
|||||||
|
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func readSigningIdentitiesFromCli() async throws -> [String] {
|
||||||
|
let output = try await ProcessTask.run(url: URL(filePath: "/usr/bin/security"),
|
||||||
|
arguments: ["find-identity", "-v", "-p", "codesigning"])
|
||||||
|
|
||||||
|
return output.split(separator: "\n").map { line in
|
||||||
|
let parts = line.split(separator: "\"")
|
||||||
|
return parts.count >= 2 ? String(parts[1]) : nil
|
||||||
|
}
|
||||||
|
.compactMap { $0 }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user