From 07ece653e936032e7a4206fb1c163e9ab9e1a78c Mon Sep 17 00:00:00 2001 From: Selim Mustafaev Date: Tue, 25 Apr 2023 01:45:30 +0300 Subject: [PATCH] Displaying Info.plist and entitlements --- reSign.xcodeproj/project.pbxproj | 24 ++++ reSign/AppPackage.swift | 7 +- reSign/ContentView.swift | 4 +- .../Extensions/URL+ExtendedAttributes.swift | 93 +++++++++++++++ reSign/Models/TreeItem.swift | 89 ++++++++++++++ reSign/Views/OutlineView.swift | 109 ++++++++++++++++++ reSign/Views/PlistView.swift | 23 ++++ 7 files changed, 344 insertions(+), 5 deletions(-) create mode 100644 reSign/Extensions/URL+ExtendedAttributes.swift create mode 100644 reSign/Models/TreeItem.swift create mode 100644 reSign/Views/OutlineView.swift create mode 100644 reSign/Views/PlistView.swift diff --git a/reSign.xcodeproj/project.pbxproj b/reSign.xcodeproj/project.pbxproj index 7ab7678..da6b554 100644 --- a/reSign.xcodeproj/project.pbxproj +++ b/reSign.xcodeproj/project.pbxproj @@ -15,6 +15,10 @@ 7A72231529DCABE400503F78 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A72231429DCABE400503F78 /* ContentView.swift */; }; 7A72231729DCABE500503F78 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7A72231629DCABE500503F78 /* Assets.xcassets */; }; 7A72231A29DCABE500503F78 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7A72231929DCABE500503F78 /* Preview Assets.xcassets */; }; + 7AF0C51829EDCF59008D4084 /* URL+ExtendedAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AF0C51729EDCF59008D4084 /* URL+ExtendedAttributes.swift */; }; + 7AF0C51A29EF43C2008D4084 /* OutlineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AF0C51929EF43C2008D4084 /* OutlineView.swift */; }; + 7AF0C51C29EF43CD008D4084 /* TreeItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AF0C51B29EF43CD008D4084 /* TreeItem.swift */; }; + 7AF0C53B29F72151008D4084 /* PlistView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AF0C53A29F72151008D4084 /* PlistView.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -28,6 +32,10 @@ 7A72231629DCABE500503F78 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 7A72231929DCABE500503F78 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 7A72231B29DCABE500503F78 /* reSign.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = reSign.entitlements; sourceTree = ""; }; + 7AF0C51729EDCF59008D4084 /* URL+ExtendedAttributes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URL+ExtendedAttributes.swift"; sourceTree = ""; }; + 7AF0C51929EF43C2008D4084 /* OutlineView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OutlineView.swift; sourceTree = ""; }; + 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 = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -44,6 +52,7 @@ 7A064BE129DE0FA900C5D978 /* Models */ = { isa = PBXGroup; children = ( + 7AF0C51B29EF43CD008D4084 /* TreeItem.swift */, 7A064BE329DE107000C5D978 /* Category.swift */, ); path = Models; @@ -52,7 +61,9 @@ 7A064BE529DE17A800C5D978 /* Views */ = { isa = PBXGroup; children = ( + 7AF0C51929EF43C2008D4084 /* OutlineView.swift */, 7A064BE829DE18C700C5D978 /* SignInfoView.swift */, + 7AF0C53A29F72151008D4084 /* PlistView.swift */, ); path = Views; sourceTree = ""; @@ -76,6 +87,7 @@ 7A72231129DCABE400503F78 /* reSign */ = { isa = PBXGroup; children = ( + 7AF0C51629EDCF3B008D4084 /* Extensions */, 7A064BE529DE17A800C5D978 /* Views */, 7A064BE129DE0FA900C5D978 /* Models */, 7A72231229DCABE400503F78 /* reSignApp.swift */, @@ -97,6 +109,14 @@ path = "Preview Content"; sourceTree = ""; }; + 7AF0C51629EDCF3B008D4084 /* Extensions */ = { + isa = PBXGroup; + children = ( + 7AF0C51729EDCF59008D4084 /* URL+ExtendedAttributes.swift */, + ); + path = Extensions; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -168,11 +188,15 @@ buildActionMask = 2147483647; files = ( 7A72231529DCABE400503F78 /* ContentView.swift in Sources */, + 7AF0C53B29F72151008D4084 /* PlistView.swift in Sources */, + 7AF0C51C29EF43CD008D4084 /* TreeItem.swift in Sources */, + 7AF0C51829EDCF59008D4084 /* URL+ExtendedAttributes.swift in Sources */, 7A064BEB29DF5BB800C5D978 /* AppPackage.swift in Sources */, 7A064BE929DE18C700C5D978 /* SignInfoView.swift in Sources */, 7A064BED29E2C91D00C5D978 /* Certificate.swift in Sources */, 7A72231329DCABE400503F78 /* reSignApp.swift in Sources */, 7A064BE429DE107000C5D978 /* Category.swift in Sources */, + 7AF0C51A29EF43C2008D4084 /* OutlineView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/reSign/AppPackage.swift b/reSign/AppPackage.swift index 56e7043..de0bafe 100644 --- a/reSign/AppPackage.swift +++ b/reSign/AppPackage.swift @@ -46,6 +46,8 @@ class AppPackage { var designatedRequirements: String? var implicitRequirements: String? var certificates: [Certificate]? + var infoPlist: [String: Any] = [:] + var entitlements: [String: Any] = [:] init(url: URL) { @@ -105,9 +107,8 @@ class AppPackage { designatedRequirements = try getRequirements(from: signingInfo, key: kSecCodeInfoDesignatedRequirement) implicitRequirements = try getRequirements(from: signingInfo, key: kSecCodeInfoImplicitDesignatedRequirement) certificates = try getCertificates(from: signingInfo) - - let infoPlist = signingInfo[kSecCodeInfoPList as String] - print(infoPlist) + infoPlist = signingInfo[kSecCodeInfoPList as String] as? [String: Any] ?? [:] + entitlements = signingInfo[kSecCodeInfoEntitlementsDict as String] as? [String: Any] ?? [:] } } diff --git a/reSign/ContentView.swift b/reSign/ContentView.swift index d762ec2..1af1aee 100644 --- a/reSign/ContentView.swift +++ b/reSign/ContentView.swift @@ -54,8 +54,8 @@ struct ContentView: View { } else { switch selection?.type { case .signInfo: SignInfoView(appPackage: appPackage!) - case .infoPlist: Text("Info.plist") - case .entitlements: Text("Entitlements") + case .infoPlist: PlistView(infoPlist: appPackage!.infoPlist) + case .entitlements: PlistView(infoPlist: appPackage!.entitlements) case .resign: Text("ReSign") default: EmptyView() } diff --git a/reSign/Extensions/URL+ExtendedAttributes.swift b/reSign/Extensions/URL+ExtendedAttributes.swift new file mode 100644 index 0000000..d7d2178 --- /dev/null +++ b/reSign/Extensions/URL+ExtendedAttributes.swift @@ -0,0 +1,93 @@ +// +// URL+ExtendedAttributes.swift +// reSign +// +// Created by Selim Mustafaev on 17.04.2023. +// + +import Foundation + +extension URL { + + /// Get extended attribute. + func extendedAttribute(forName name: String) throws -> Data? { + + let data = try self.withUnsafeFileSystemRepresentation { fileSystemPath -> Data? in + + // Determine attribute size: + let length = getxattr(fileSystemPath, name, nil, 0, 0, 0) + + guard length >= 0 else { + if length == ENOATTR { + return nil + } else { + throw URL.posixError(errno) + } + } + + // Create buffer with required size: + var data = Data(count: length) + + // Retrieve attribute: + let result = data.withUnsafeMutableBytes { [count = data.count] in + getxattr(fileSystemPath, name, $0.baseAddress, count, 0, 0) + } + guard result >= 0 else { throw URL.posixError(errno) } + return data + } + return data + } + + /// Set extended attribute. + func setExtendedAttribute(data: Data, forName name: String) throws { + + try self.withUnsafeFileSystemRepresentation { fileSystemPath in + let result = data.withUnsafeBytes { + setxattr(fileSystemPath, name, $0.baseAddress, data.count, 0, 0) + } + guard result >= 0 else { throw URL.posixError(errno) } + } + } + + /// Remove extended attribute. + func removeExtendedAttribute(forName name: String) throws { + + try self.withUnsafeFileSystemRepresentation { fileSystemPath in + let result = removexattr(fileSystemPath, name, 0) + guard result >= 0 else { throw URL.posixError(errno) } + } + } + + /// Get list of all extended attributes. + func listExtendedAttributes() throws -> [String] { + + let list = try self.withUnsafeFileSystemRepresentation { fileSystemPath -> [String] in + let length = listxattr(fileSystemPath, nil, 0, 0) + guard length >= 0 else { throw URL.posixError(errno) } + + // Create buffer with required size: + var namebuf = Array(repeating: 0, count: length) + + // Retrieve attribute list: + let result = listxattr(fileSystemPath, &namebuf, namebuf.count, 0) + guard result >= 0 else { throw URL.posixError(errno) } + + // Extract attribute names: + let list = namebuf.split(separator: 0).compactMap { + $0.withUnsafeBufferPointer { + $0.withMemoryRebound(to: UInt8.self) { + String(bytes: $0, encoding: .utf8) + } + } + } + return list + } + return list + } + + /// Helper function to create an NSError from a Unix errno. + private static func posixError(_ err: Int32) -> NSError { + return NSError(domain: NSPOSIXErrorDomain, code: Int(err), + userInfo: [NSLocalizedDescriptionKey: String(cString: strerror(err))]) + } +} diff --git a/reSign/Models/TreeItem.swift b/reSign/Models/TreeItem.swift new file mode 100644 index 0000000..9755582 --- /dev/null +++ b/reSign/Models/TreeItem.swift @@ -0,0 +1,89 @@ +// +// TreeItem.swift +// OutlineTest +// +// Created by Мустафаев Селим Мустафаевич on 11.04.2023. +// + +import Foundation + +enum DictValue: CustomStringConvertible { + + case string(value: String) + case number(value: Int) + case bool(value: Bool) + case array(value: [DictNode]) + case dictionary(value: [DictNode]) + case unknown + + var description: String { + switch self { + case .string(let value): return value + case .number(let value): return String(value) + case .bool(let value): return value ? "True" : "False" + case .array(let value): return "\(value.count) item(s)" + case .dictionary(let value): return "\(value.count) item(s)" + case .unknown: return "Unknown" + } + } + + var type: String { + switch self { + case .string: return "String" + case .number: return "Number" + case .bool: return "Boolean" + case .array: return "Array" + case .dictionary: return "Dictionary" + case .unknown: return "Unknown" + } + } + + var isReal: Bool { + switch self { + case .string, .number, .bool: return true + default: return false + } + } +} + +class DictNode: Identifiable { + + var name: String? + var value: DictValue? + var children: [DictNode] = [] + + init(name: String?, value: Any?) { + + self.name = name + + switch value { + case let stringVal as String: + self.value = .string(value: stringVal) + case let numVal as Int: + self.value = .number(value: numVal) + case let boolVal as Bool: + self.value = .bool(value: boolVal) + case let dict as [String: Any]: + self.children = dict.map { DictNode(name: $0.key, value: $0.value) } + self.value = .dictionary(value: self.children) + case let array as [Any]: + self.children = array.enumerated().map { (index, element) in + DictNode(name: "Item \(index)", value: element) + } + self.value = .array(value: self.children) + default: + self.value = .unknown + } + } + + init(dict: [String: Any]) { + + self.name = "Root" + self.children = dict.map { DictNode(name: $0.key, value: $0.value) } + } + + + var id: String { + name ?? "" + } +} diff --git a/reSign/Views/OutlineView.swift b/reSign/Views/OutlineView.swift new file mode 100644 index 0000000..cc9bd1e --- /dev/null +++ b/reSign/Views/OutlineView.swift @@ -0,0 +1,109 @@ +// +// OutlineView.swift +// OutlineTest +// +// Created by Мустафаев Селим Мустафаевич on 12.04.2023. +// + +import SwiftUI + +struct OutlineView: NSViewRepresentable { + + + let datasource: OutlineViewDataSource + + init(rootNode: DictNode) { + self.datasource = OutlineViewDataSource(data: rootNode) + } + + func makeNSView(context: Context) -> some NSView { + let view = NSOutlineView() + view.translatesAutoresizingMaskIntoConstraints = false + view.dataSource = datasource + view.delegate = datasource + view.usesAlternatingRowBackgroundColors = true + view.addTableColumn(createColumn(id: "Key", minWidth: 250)) + view.addTableColumn(createColumn(id: "Type", minWidth: 30)) + view.addTableColumn(createColumn(id: "Value", minWidth: 150)) + + let scroll = NSScrollView() + scroll.documentView = view + scroll.hasHorizontalScroller = false + scroll.hasVerticalScroller = true + + return scroll + } + + func updateNSView(_ nsView: NSViewType, context: Context) { + + } + + func createColumn(id: String, minWidth: CGFloat) -> NSTableColumn { + let column = NSTableColumn(identifier: NSUserInterfaceItemIdentifier(rawValue: id)) + column.title = id + column.minWidth = minWidth + return column + } +} + +@objc class OutlineViewDataSource: NSObject, NSOutlineViewDataSource, NSOutlineViewDelegate { + + let data: DictNode + + init(data: DictNode) { + self.data = data + } + + func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any { + guard let item = item as? DictNode else { + return data.children[index] + } + + return item.children[index] + } + + func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int { + guard let item = item as? DictNode else { + return data.children.count + } + + return item.children.count + } + + func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool { + guard let item = item as? DictNode else { + return false + } + + return !item.children.isEmpty + } + + func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? { + guard let item = item as? DictNode else { + return nil + } + + let value: String + switch tableColumn?.identifier.rawValue { + case "Key": value = item.name ?? "" + case "Type": value = item.value?.type ?? "" + case "Value": value = item.value?.description ?? "" + default: value = "" + } + + let shouldDeEmphasize = item.value?.isReal != true + && tableColumn?.identifier.rawValue == "Value" + + let text = NSTextField(labelWithString: value) + text.textColor = shouldDeEmphasize ? .secondaryLabelColor : .labelColor + let cell = NSTableCellView() + cell.addSubview(text) + text.drawsBackground = false + text.isBordered = false + text.translatesAutoresizingMaskIntoConstraints = false + cell.addConstraint(NSLayoutConstraint(item: text, attribute: .centerY, relatedBy: .equal, toItem: cell, attribute: .centerY, multiplier: 1, constant: 0)) + cell.addConstraint(NSLayoutConstraint(item: text, attribute: .left, relatedBy: .equal, toItem: cell, attribute: .left, multiplier: 1, constant: 13)) + cell.addConstraint(NSLayoutConstraint(item: text, attribute: .right, relatedBy: .equal, toItem: cell, attribute: .right, multiplier: 1, constant: -13)) + return cell + } +} diff --git a/reSign/Views/PlistView.swift b/reSign/Views/PlistView.swift new file mode 100644 index 0000000..ddb5ebc --- /dev/null +++ b/reSign/Views/PlistView.swift @@ -0,0 +1,23 @@ +// +// PlistView.swift +// reSign +// +// Created by Selim Mustafaev on 24.04.2023. +// + +import SwiftUI + +struct PlistView: View { + + var infoPlist: [String: Any] + + var body: some View { + OutlineView(rootNode: DictNode(dict: infoPlist)) + } +} + +struct InfoPlistView_Previews: PreviewProvider { + static var previews: some View { + PlistView(infoPlist: [:]) + } +}