Fetching identities from keychain

This commit is contained in:
Selim Mustafaev 2023-05-01 22:50:36 +03:00
parent d093d9662e
commit 7f67752566
11 changed files with 244 additions and 81 deletions

View File

@ -20,6 +20,10 @@
7AF0C51C29EF43CD008D4084 /* TreeItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AF0C51B29EF43CD008D4084 /* TreeItem.swift */; }; 7AF0C51C29EF43CD008D4084 /* TreeItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AF0C51B29EF43CD008D4084 /* TreeItem.swift */; };
7AF0C53B29F72151008D4084 /* PlistView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AF0C53A29F72151008D4084 /* PlistView.swift */; }; 7AF0C53B29F72151008D4084 /* PlistView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AF0C53A29F72151008D4084 /* PlistView.swift */; };
7AF0C53D29F9B7FE008D4084 /* Quarantine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AF0C53C29F9B7FE008D4084 /* Quarantine.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 */ /* End PBXBuildFile section */
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
@ -38,6 +42,10 @@
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>"; };
7AF0C53A29F72151008D4084 /* PlistView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlistView.swift; sourceTree = "<group>"; }; 7AF0C53A29F72151008D4084 /* PlistView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlistView.swift; sourceTree = "<group>"; };
7AF0C53C29F9B7FE008D4084 /* Quarantine.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Quarantine.swift; sourceTree = "<group>"; }; 7AF0C53C29F9B7FE008D4084 /* Quarantine.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Quarantine.swift; sourceTree = "<group>"; };
7AF0C53E29FFB765008D4084 /* SignView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignView.swift; sourceTree = "<group>"; };
7AF0C54129FFBDB0008D4084 /* SignViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignViewModel.swift; sourceTree = "<group>"; };
7AF0C54429FFCAD8008D4084 /* SecError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecError.swift; sourceTree = "<group>"; };
7AF0C54629FFCC66008D4084 /* Identity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Identity.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */ /* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */ /* Begin PBXFrameworksBuildPhase section */
@ -63,6 +71,7 @@
7A064BE529DE17A800C5D978 /* Views */ = { 7A064BE529DE17A800C5D978 /* Views */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
7AF0C54029FFBD93008D4084 /* Sign */,
7AF0C51929EF43C2008D4084 /* OutlineView.swift */, 7AF0C51929EF43C2008D4084 /* OutlineView.swift */,
7A064BE829DE18C700C5D978 /* SignInfoView.swift */, 7A064BE829DE18C700C5D978 /* SignInfoView.swift */,
7AF0C53A29F72151008D4084 /* PlistView.swift */, 7AF0C53A29F72151008D4084 /* PlistView.swift */,
@ -89,14 +98,12 @@
7A72231129DCABE400503F78 /* reSign */ = { 7A72231129DCABE400503F78 /* reSign */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
7AF0C54329FFCABA008D4084 /* Security */,
7AF0C51629EDCF3B008D4084 /* Extensions */, 7AF0C51629EDCF3B008D4084 /* Extensions */,
7A064BE529DE17A800C5D978 /* Views */, 7A064BE529DE17A800C5D978 /* Views */,
7A064BE129DE0FA900C5D978 /* Models */, 7A064BE129DE0FA900C5D978 /* Models */,
7A72231229DCABE400503F78 /* reSignApp.swift */, 7A72231229DCABE400503F78 /* reSignApp.swift */,
7A72231429DCABE400503F78 /* ContentView.swift */, 7A72231429DCABE400503F78 /* ContentView.swift */,
7A064BEA29DF5BB800C5D978 /* AppPackage.swift */,
7A064BEC29E2C91D00C5D978 /* Certificate.swift */,
7AF0C53C29F9B7FE008D4084 /* Quarantine.swift */,
7A72231629DCABE500503F78 /* Assets.xcassets */, 7A72231629DCABE500503F78 /* Assets.xcassets */,
7A72231B29DCABE500503F78 /* reSign.entitlements */, 7A72231B29DCABE500503F78 /* reSign.entitlements */,
7A72231829DCABE500503F78 /* Preview Content */, 7A72231829DCABE500503F78 /* Preview Content */,
@ -120,6 +127,27 @@
path = Extensions; path = Extensions;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
7AF0C54029FFBD93008D4084 /* Sign */ = {
isa = PBXGroup;
children = (
7AF0C53E29FFB765008D4084 /* SignView.swift */,
7AF0C54129FFBDB0008D4084 /* SignViewModel.swift */,
);
path = Sign;
sourceTree = "<group>";
};
7AF0C54329FFCABA008D4084 /* Security */ = {
isa = PBXGroup;
children = (
7AF0C53C29F9B7FE008D4084 /* Quarantine.swift */,
7A064BEC29E2C91D00C5D978 /* Certificate.swift */,
7A064BEA29DF5BB800C5D978 /* AppPackage.swift */,
7AF0C54429FFCAD8008D4084 /* SecError.swift */,
7AF0C54629FFCC66008D4084 /* Identity.swift */,
);
path = Security;
sourceTree = "<group>";
};
/* End PBXGroup section */ /* End PBXGroup section */
/* Begin PBXNativeTarget section */ /* Begin PBXNativeTarget section */
@ -196,9 +224,13 @@
7AF0C51829EDCF59008D4084 /* URL+ExtendedAttributes.swift in Sources */, 7AF0C51829EDCF59008D4084 /* URL+ExtendedAttributes.swift in Sources */,
7A064BEB29DF5BB800C5D978 /* AppPackage.swift in Sources */, 7A064BEB29DF5BB800C5D978 /* AppPackage.swift in Sources */,
7A064BE929DE18C700C5D978 /* SignInfoView.swift in Sources */, 7A064BE929DE18C700C5D978 /* SignInfoView.swift in Sources */,
7AF0C54229FFBDB0008D4084 /* SignViewModel.swift in Sources */,
7AF0C54729FFCC66008D4084 /* Identity.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 */,
7AF0C53F29FFB765008D4084 /* SignView.swift in Sources */,
7A064BE429DE107000C5D978 /* Category.swift in Sources */, 7A064BE429DE107000C5D978 /* Category.swift in Sources */,
7AF0C51A29EF43C2008D4084 /* OutlineView.swift in Sources */, 7AF0C51A29EF43C2008D4084 /* OutlineView.swift in Sources */,
); );

View File

@ -43,22 +43,22 @@ struct ContentView: View {
Button { Button {
openClicked() openClicked()
} label: { } label: {
Image(systemName: "doc") Image(systemName: "plus")
} }
} }
} }
} detail: { } detail: {
if appPackage == nil { if let appPackage {
EmptyView()
} else {
switch selection?.type { switch selection?.type {
case .signInfo: SignInfoView(appPackage: appPackage!) case .signInfo: SignInfoView(appPackage: appPackage)
case .infoPlist: PlistView(infoPlist: appPackage!.infoPlist) case .infoPlist: PlistView(infoPlist: appPackage.infoPlist)
case .entitlements: PlistView(infoPlist: appPackage!.entitlements) case .entitlements: PlistView(infoPlist: appPackage.entitlements)
case .resign: Text("ReSign") case .resign: SignView(url: appPackage.url)
default: EmptyView() default: EmptyView()
} }
} else {
EmptyView()
} }
} }
.navigationTitle(appPackage?.name ?? "") .navigationTitle(appPackage?.name ?? "")

View File

@ -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)
}
}

View File

@ -8,24 +8,6 @@
import Foundation import Foundation
import Security 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 { class AppPackage {
public let url: URL 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 { func getAppInfo() throws {
var staticCode: SecStaticCode? = nil var staticCode: SecStaticCode? = nil
var status = SecStaticCodeCreateWithPath(url as CFURL, [], &staticCode) var status = SecStaticCodeCreateWithPath(url as CFURL, [], &staticCode)
try checkResult(status) try SecError.check(status)
guard let staticCode else { guard let staticCode else {
throw SecError.staticCodeNil throw SecError.staticCodeNil
@ -90,7 +61,7 @@ class AppPackage {
] ]
status = SecCodeCopySigningInformation(staticCode, signInfoFlags, &signingInfo) status = SecCodeCopySigningInformation(staticCode, signInfoFlags, &signingInfo)
try checkResult(status) try SecError.check(status)
if let signingInfo = signingInfo as? [String: Any] { if let signingInfo = signingInfo as? [String: Any] {
@ -137,12 +108,11 @@ class AppPackage {
return nil return nil
} }
let pReqStr: UnsafeMutablePointer<CFString?> = .allocate(capacity: 1) var reqStr: CFString?
defer { pReqStr.deallocate() } let status = SecRequirementCopyString(reqAny as! SecRequirement, [], &reqStr)
let status = SecRequirementCopyString(reqAny as! SecRequirement, [], pReqStr) try SecError.check(status)
try checkResult(status)
if let reqStr = pReqStr.pointee as? String { if let reqStr = reqStr as? String {
return reqStr return reqStr
} }

View File

@ -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
}
}
}

View File

@ -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)
}
}

View File

@ -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))
}
}
}
}

View File

@ -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: ""))
}
}

View File

@ -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, &copyResult)
if result == errSecSuccess, let secIdentities = copyResult as? [SecIdentity] {
return try secIdentities.map(Identity.init).map(\.name)
}
return []
}
}

View File

@ -91,7 +91,7 @@ struct SignInfoView: View {
if let quarantine = appPackage.quarantine { if let quarantine = appPackage.quarantine {
Section("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: "Date", date: quarantine.date)
FormTextItem(name: "Agent", value: quarantine.agent) FormTextItem(name: "Agent", value: quarantine.agent)
FormTextItem(name: "UUID", value: quarantine.uuid) FormTextItem(name: "UUID", value: quarantine.uuid)