From 7f67752566bfdb462ff77e0a1b02874759e2754e Mon Sep 17 00:00:00 2001 From: Selim Mustafaev Date: Mon, 1 May 2023 22:50:36 +0300 Subject: [PATCH] Fetching identities from keychain --- reSign.xcodeproj/project.pbxproj | 38 +++++++++++++++-- reSign/ContentView.swift | 16 +++---- reSign/Quarantine.swift | 33 --------------- reSign/{ => Security}/AppPackage.swift | 42 +++---------------- reSign/{ => Security}/Certificate.swift | 0 reSign/Security/Identity.swift | 37 ++++++++++++++++ reSign/Security/Quarantine.swift | 56 +++++++++++++++++++++++++ reSign/Security/SecError.swift | 36 ++++++++++++++++ reSign/Views/Sign/SignView.swift | 25 +++++++++++ reSign/Views/Sign/SignViewModel.swift | 40 ++++++++++++++++++ reSign/Views/SignInfoView.swift | 2 +- 11 files changed, 244 insertions(+), 81 deletions(-) delete mode 100644 reSign/Quarantine.swift rename reSign/{ => Security}/AppPackage.swift (81%) rename reSign/{ => Security}/Certificate.swift (100%) create mode 100644 reSign/Security/Identity.swift create mode 100644 reSign/Security/Quarantine.swift create mode 100644 reSign/Security/SecError.swift create mode 100644 reSign/Views/Sign/SignView.swift create mode 100644 reSign/Views/Sign/SignViewModel.swift diff --git a/reSign.xcodeproj/project.pbxproj b/reSign.xcodeproj/project.pbxproj index b9a0ca4..96407a8 100644 --- a/reSign.xcodeproj/project.pbxproj +++ b/reSign.xcodeproj/project.pbxproj @@ -20,6 +20,10 @@ 7AF0C51C29EF43CD008D4084 /* TreeItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AF0C51B29EF43CD008D4084 /* TreeItem.swift */; }; 7AF0C53B29F72151008D4084 /* PlistView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AF0C53A29F72151008D4084 /* PlistView.swift */; }; 7AF0C53D29F9B7FE008D4084 /* Quarantine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AF0C53C29F9B7FE008D4084 /* Quarantine.swift */; }; + 7AF0C53F29FFB765008D4084 /* SignView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AF0C53E29FFB765008D4084 /* SignView.swift */; }; + 7AF0C54229FFBDB0008D4084 /* SignViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AF0C54129FFBDB0008D4084 /* SignViewModel.swift */; }; + 7AF0C54529FFCAD8008D4084 /* SecError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AF0C54429FFCAD8008D4084 /* SecError.swift */; }; + 7AF0C54729FFCC66008D4084 /* Identity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AF0C54629FFCC66008D4084 /* Identity.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -38,6 +42,10 @@ 7AF0C51B29EF43CD008D4084 /* TreeItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TreeItem.swift; sourceTree = ""; }; 7AF0C53A29F72151008D4084 /* PlistView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlistView.swift; sourceTree = ""; }; 7AF0C53C29F9B7FE008D4084 /* Quarantine.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Quarantine.swift; sourceTree = ""; }; + 7AF0C53E29FFB765008D4084 /* SignView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignView.swift; sourceTree = ""; }; + 7AF0C54129FFBDB0008D4084 /* SignViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignViewModel.swift; sourceTree = ""; }; + 7AF0C54429FFCAD8008D4084 /* SecError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecError.swift; sourceTree = ""; }; + 7AF0C54629FFCC66008D4084 /* Identity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Identity.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -63,6 +71,7 @@ 7A064BE529DE17A800C5D978 /* Views */ = { isa = PBXGroup; children = ( + 7AF0C54029FFBD93008D4084 /* Sign */, 7AF0C51929EF43C2008D4084 /* OutlineView.swift */, 7A064BE829DE18C700C5D978 /* SignInfoView.swift */, 7AF0C53A29F72151008D4084 /* PlistView.swift */, @@ -89,14 +98,12 @@ 7A72231129DCABE400503F78 /* reSign */ = { isa = PBXGroup; children = ( + 7AF0C54329FFCABA008D4084 /* Security */, 7AF0C51629EDCF3B008D4084 /* Extensions */, 7A064BE529DE17A800C5D978 /* Views */, 7A064BE129DE0FA900C5D978 /* Models */, 7A72231229DCABE400503F78 /* reSignApp.swift */, 7A72231429DCABE400503F78 /* ContentView.swift */, - 7A064BEA29DF5BB800C5D978 /* AppPackage.swift */, - 7A064BEC29E2C91D00C5D978 /* Certificate.swift */, - 7AF0C53C29F9B7FE008D4084 /* Quarantine.swift */, 7A72231629DCABE500503F78 /* Assets.xcassets */, 7A72231B29DCABE500503F78 /* reSign.entitlements */, 7A72231829DCABE500503F78 /* Preview Content */, @@ -120,6 +127,27 @@ path = Extensions; sourceTree = ""; }; + 7AF0C54029FFBD93008D4084 /* Sign */ = { + isa = PBXGroup; + children = ( + 7AF0C53E29FFB765008D4084 /* SignView.swift */, + 7AF0C54129FFBDB0008D4084 /* SignViewModel.swift */, + ); + path = Sign; + sourceTree = ""; + }; + 7AF0C54329FFCABA008D4084 /* Security */ = { + isa = PBXGroup; + children = ( + 7AF0C53C29F9B7FE008D4084 /* Quarantine.swift */, + 7A064BEC29E2C91D00C5D978 /* Certificate.swift */, + 7A064BEA29DF5BB800C5D978 /* AppPackage.swift */, + 7AF0C54429FFCAD8008D4084 /* SecError.swift */, + 7AF0C54629FFCC66008D4084 /* Identity.swift */, + ); + path = Security; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -196,9 +224,13 @@ 7AF0C51829EDCF59008D4084 /* URL+ExtendedAttributes.swift in Sources */, 7A064BEB29DF5BB800C5D978 /* AppPackage.swift in Sources */, 7A064BE929DE18C700C5D978 /* SignInfoView.swift in Sources */, + 7AF0C54229FFBDB0008D4084 /* SignViewModel.swift in Sources */, + 7AF0C54729FFCC66008D4084 /* Identity.swift in Sources */, 7A064BED29E2C91D00C5D978 /* Certificate.swift in Sources */, 7A72231329DCABE400503F78 /* reSignApp.swift in Sources */, 7AF0C53D29F9B7FE008D4084 /* Quarantine.swift in Sources */, + 7AF0C54529FFCAD8008D4084 /* SecError.swift in Sources */, + 7AF0C53F29FFB765008D4084 /* SignView.swift in Sources */, 7A064BE429DE107000C5D978 /* Category.swift in Sources */, 7AF0C51A29EF43C2008D4084 /* OutlineView.swift in Sources */, ); diff --git a/reSign/ContentView.swift b/reSign/ContentView.swift index 1af1aee..aebb1b1 100644 --- a/reSign/ContentView.swift +++ b/reSign/ContentView.swift @@ -43,22 +43,22 @@ struct ContentView: View { Button { openClicked() } label: { - Image(systemName: "doc") + Image(systemName: "plus") } } } } detail: { - if appPackage == nil { - EmptyView() - } else { + if let appPackage { switch selection?.type { - case .signInfo: SignInfoView(appPackage: appPackage!) - case .infoPlist: PlistView(infoPlist: appPackage!.infoPlist) - case .entitlements: PlistView(infoPlist: appPackage!.entitlements) - case .resign: Text("ReSign") + case .signInfo: SignInfoView(appPackage: appPackage) + case .infoPlist: PlistView(infoPlist: appPackage.infoPlist) + case .entitlements: PlistView(infoPlist: appPackage.entitlements) + case .resign: SignView(url: appPackage.url) default: EmptyView() } + } else { + EmptyView() } } .navigationTitle(appPackage?.name ?? "") diff --git a/reSign/Quarantine.swift b/reSign/Quarantine.swift deleted file mode 100644 index b2da72d..0000000 --- a/reSign/Quarantine.swift +++ /dev/null @@ -1,33 +0,0 @@ -// -// Quarantine.swift -// reSign -// -// Created by Мустафаев Селим Мустафаевич on 26.04.2023. -// - -import Foundation - -class Quarantine { - - var flags: Int - var date: Date - var agent: String - var uuid: String - - init?(url: URL) { - guard let data = try? url.extendedAttribute(forName: "com.apple.quarantine"), - let stringData = String(data: data, encoding: .utf8) - else { - return nil - } - - let parts = stringData.split(separator: ";") - - self.flags = Int(parts[0]) ?? 0 - self.date = Date(timeIntervalSince1970: TimeInterval(UInt32(parts[1], radix: 16) ?? 0)) - self.agent = String(parts[2]) - self.uuid = String(parts[3]) - - print(parts) - } -} diff --git a/reSign/AppPackage.swift b/reSign/Security/AppPackage.swift similarity index 81% rename from reSign/AppPackage.swift rename to reSign/Security/AppPackage.swift index c8cb24e..7953229 100644 --- a/reSign/AppPackage.swift +++ b/reSign/Security/AppPackage.swift @@ -8,24 +8,6 @@ import Foundation import Security -enum SecError: LocalizedError { - - case custom(message: String) - case staticCodeNil - case designatedRequirementsError - case certReadError - - var errorDescription: String? { - switch self { - case .custom(let message): return message - case .staticCodeNil: return "Failed to initialize SecStaticCode" - case .designatedRequirementsError: return "Failed to copy designated requirements" - case .certReadError: return "Error reading certificate" - } - } -} - - class AppPackage { public let url: URL @@ -62,22 +44,11 @@ class AppPackage { } } - // https://developer.apple.com/documentation/security/1394686-seccopyerrormessagestring - func checkResult(_ status: OSStatus) throws { - if status != 0 { - if let message = SecCopyErrorMessageString(status, nil) { - throw SecError.custom(message: message as String) - } else { - throw NSError(domain: NSOSStatusErrorDomain, code: Int(status)) - } - } - } - func getAppInfo() throws { var staticCode: SecStaticCode? = nil var status = SecStaticCodeCreateWithPath(url as CFURL, [], &staticCode) - try checkResult(status) + try SecError.check(status) guard let staticCode else { throw SecError.staticCodeNil @@ -90,7 +61,7 @@ class AppPackage { ] status = SecCodeCopySigningInformation(staticCode, signInfoFlags, &signingInfo) - try checkResult(status) + try SecError.check(status) if let signingInfo = signingInfo as? [String: Any] { @@ -137,12 +108,11 @@ class AppPackage { return nil } - let pReqStr: UnsafeMutablePointer = .allocate(capacity: 1) - defer { pReqStr.deallocate() } - let status = SecRequirementCopyString(reqAny as! SecRequirement, [], pReqStr) - try checkResult(status) + var reqStr: CFString? + let status = SecRequirementCopyString(reqAny as! SecRequirement, [], &reqStr) + try SecError.check(status) - if let reqStr = pReqStr.pointee as? String { + if let reqStr = reqStr as? String { return reqStr } diff --git a/reSign/Certificate.swift b/reSign/Security/Certificate.swift similarity index 100% rename from reSign/Certificate.swift rename to reSign/Security/Certificate.swift diff --git a/reSign/Security/Identity.swift b/reSign/Security/Identity.swift new file mode 100644 index 0000000..cac4f19 --- /dev/null +++ b/reSign/Security/Identity.swift @@ -0,0 +1,37 @@ +// +// Identity.swift +// reSign +// +// Created by Selim Mustafaev on 01.05.2023. +// + +import Foundation + +class Identity { + + let identity: SecIdentity + let certificate: Certificate + + var name: String { + certificate.commonName + } + + init(_ identity: SecIdentity) throws { + + self.identity = identity + self.certificate = try Identity.readCert(from: identity) + } + + static func readCert(from identity: SecIdentity) throws -> Certificate { + + var cert: SecCertificate? + let result = SecIdentityCopyCertificate(identity, &cert) + try SecError.check(result) + + if let cert { + return try Certificate(cert) + } else { + throw SecError.certReadError + } + } +} diff --git a/reSign/Security/Quarantine.swift b/reSign/Security/Quarantine.swift new file mode 100644 index 0000000..b1a63c4 --- /dev/null +++ b/reSign/Security/Quarantine.swift @@ -0,0 +1,56 @@ +// +// Quarantine.swift +// reSign +// +// Created by Мустафаев Селим Мустафаевич on 26.04.2023. +// + +import Foundation + +struct QuarantineFlags: OptionSet { + + let rawValue: UInt32 + + static let download = QuarantineFlags(rawValue: 1 << 0) + static let sandbox = QuarantineFlags(rawValue: 1 << 1) + static let hard = QuarantineFlags(rawValue: 1 << 2) + static let userApproved = QuarantineFlags(rawValue: 1 << 6) + + var description: String { + var options: [String] = [] + + if contains(.download) { options.append("download") } + if contains(.sandbox) { options.append("sandbox") } + if contains(.hard) { options.append("hard") } + if contains(.userApproved) { options.append("userApproved") } + + return options.joined(separator: " ") + } +} + +class Quarantine { + + var flagsStr: String + var flags: QuarantineFlags + var date: Date + var agent: String + var uuid: String + + init?(url: URL) { + guard let data = try? url.extendedAttribute(forName: "com.apple.quarantine"), + let stringData = String(data: data, encoding: .utf8) + else { + return nil + } + + let parts = stringData.split(separator: ";") + + self.flagsStr = String(parts[0]) + self.flags = QuarantineFlags(rawValue: UInt32(parts[0], radix: 16) ?? 0) + self.date = Date(timeIntervalSince1970: TimeInterval(UInt32(parts[1], radix: 16) ?? 0)) + self.agent = String(parts[2]) + self.uuid = String(parts[3]) + + print(parts) + } +} diff --git a/reSign/Security/SecError.swift b/reSign/Security/SecError.swift new file mode 100644 index 0000000..f38c4bb --- /dev/null +++ b/reSign/Security/SecError.swift @@ -0,0 +1,36 @@ +// +// SecError.swift +// reSign +// +// Created by Selim Mustafaev on 01.05.2023. +// + +import Foundation + +enum SecError: LocalizedError { + + case custom(message: String) + case staticCodeNil + case designatedRequirementsError + case certReadError + + var errorDescription: String? { + switch self { + case .custom(let message): return message + case .staticCodeNil: return "Failed to initialize SecStaticCode" + case .designatedRequirementsError: return "Failed to copy designated requirements" + case .certReadError: return "Error reading certificate" + } + } + + // https://developer.apple.com/documentation/security/1394686-seccopyerrormessagestring + static func check(_ status: OSStatus) throws { + if status != errSecSuccess { + if let message = SecCopyErrorMessageString(status, nil) { + throw SecError.custom(message: message as String) + } else { + throw NSError(domain: NSOSStatusErrorDomain, code: Int(status)) + } + } + } +} diff --git a/reSign/Views/Sign/SignView.swift b/reSign/Views/Sign/SignView.swift new file mode 100644 index 0000000..93a709d --- /dev/null +++ b/reSign/Views/Sign/SignView.swift @@ -0,0 +1,25 @@ +// +// SignView.swift +// reSign +// +// Created by Selim Mustafaev on 01.05.2023. +// + +import SwiftUI + +struct SignView: View { + + let url: URL + + @StateObject var viewModel = SignViewModel() + + var body: some View { + Text("Hello, World!") + } +} + +struct SignView_Previews: PreviewProvider { + static var previews: some View { + SignView(url: URL(filePath: "")) + } +} diff --git a/reSign/Views/Sign/SignViewModel.swift b/reSign/Views/Sign/SignViewModel.swift new file mode 100644 index 0000000..9702475 --- /dev/null +++ b/reSign/Views/Sign/SignViewModel.swift @@ -0,0 +1,40 @@ +// +// SignViewModel.swift +// reSign +// +// Created by Selim Mustafaev on 01.05.2023. +// + +import SwiftUI + +class SignViewModel: ObservableObject { + + @Published var signingIdentities: [String] = [] + + init() { + do { + self.signingIdentities = try readSigningIdentities() + for identity in signingIdentities { + print(identity) + } + } catch { + print("SignViewModel init error: ", error.localizedDescription) + } + } + + func readSigningIdentities() throws -> [String] { + var copyResult: CFTypeRef? = nil + let result = SecItemCopyMatching([kSecClass: kSecClassIdentity, + kSecAttrCanSign: true, + kSecAttrCanVerify: true, + kSecAttrCanEncrypt: true, + kSecMatchLimit: kSecMatchLimitAll, + kSecReturnRef: true] as NSDictionary, ©Result) + + if result == errSecSuccess, let secIdentities = copyResult as? [SecIdentity] { + return try secIdentities.map(Identity.init).map(\.name) + } + + return [] + } +} diff --git a/reSign/Views/SignInfoView.swift b/reSign/Views/SignInfoView.swift index e2f9ae9..353a9f4 100644 --- a/reSign/Views/SignInfoView.swift +++ b/reSign/Views/SignInfoView.swift @@ -91,7 +91,7 @@ struct SignInfoView: View { if let quarantine = appPackage.quarantine { Section("Quarantine") { - FormTextItem(name: "Flags", value: quarantine.flags) + FormTextItem(name: "Flags", value: "\(quarantine.flagsStr) (\(quarantine.flags.description))") FormTextItem(name: "Date", date: quarantine.date) FormTextItem(name: "Agent", value: quarantine.agent) FormTextItem(name: "UUID", value: quarantine.uuid)