Fetching identities from keychain
This commit is contained in:
parent
d093d9662e
commit
7f67752566
@ -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 = "<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>"; };
|
||||
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 */
|
||||
|
||||
/* 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 = "<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 */
|
||||
|
||||
/* 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 */,
|
||||
);
|
||||
|
||||
@ -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 ?? "")
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
@ -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<CFString?> = .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
|
||||
}
|
||||
|
||||
37
reSign/Security/Identity.swift
Normal file
37
reSign/Security/Identity.swift
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
56
reSign/Security/Quarantine.swift
Normal file
56
reSign/Security/Quarantine.swift
Normal 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)
|
||||
}
|
||||
}
|
||||
36
reSign/Security/SecError.swift
Normal file
36
reSign/Security/SecError.swift
Normal 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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
25
reSign/Views/Sign/SignView.swift
Normal file
25
reSign/Views/Sign/SignView.swift
Normal 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: ""))
|
||||
}
|
||||
}
|
||||
40
reSign/Views/Sign/SignViewModel.swift
Normal file
40
reSign/Views/Sign/SignViewModel.swift
Normal 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, ©Result)
|
||||
|
||||
if result == errSecSuccess, let secIdentities = copyResult as? [SecIdentity] {
|
||||
return try secIdentities.map(Identity.init).map(\.name)
|
||||
}
|
||||
|
||||
return []
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user