├── .gitignore ├── LICENSE ├── README.md └── SwiftPrivilegedHelperApplication ├── Extensions └── ExtensionsNSTextView.swift ├── Scripts └── CodeSignUpdate.sh ├── SwiftPrivilegedHelper ├── Helper-Info.plist ├── Helper-Launchd.plist ├── Helper.swift ├── HelperAuthorization.swift ├── HelperAuthorizationRight.swift ├── HelperConstants.swift ├── HelperProtocol.swift └── main.swift ├── SwiftPrivilegedHelperApplication.xcodeproj ├── project.pbxproj └── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── SwiftPrivilegedHelperApplication ├── AppDelegate.swift ├── AppProtocol.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json ├── Base.lproj │ └── MainMenu.xib └── Info.plist └── Utility └── CodesignCheck.swift /.gitignore: -------------------------------------------------------------------------------- 1 | # macOS General 2 | .DS_Store 3 | .AppleDouble 4 | .LSOverride 5 | 6 | # Icon must end with two \r 7 | Icon 8 | 9 | # Thumbnails 10 | ._* 11 | 12 | # Files that might appear in the root of a volume 13 | .DocumentRevisions-V100 14 | .fseventsd 15 | .Spotlight-V100 16 | .TemporaryItems 17 | .Trashes 18 | .VolumeIcon.icns 19 | .com.apple.timemachine.donotpresent 20 | 21 | # Directories potentially created on remote AFP share 22 | .AppleDB 23 | .AppleDesktop 24 | Network Trash Folder 25 | Temporary Items 26 | .apdisk 27 | 28 | # Xcode 29 | # 30 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 31 | 32 | ## Build generated 33 | build/ 34 | DerivedData/ 35 | 36 | ## Various settings 37 | *.pbxuser 38 | !default.pbxuser 39 | *.mode1v3 40 | !default.mode1v3 41 | *.mode2v3 42 | !default.mode2v3 43 | *.perspectivev3 44 | !default.perspectivev3 45 | xcuserdata/ 46 | 47 | ## Other 48 | *.moved-aside 49 | *.xccheckout 50 | *.xcscmblueprint 51 | 52 | ## Obj-C/Swift specific 53 | *.hmap 54 | *.ipa 55 | *.dSYM.zip 56 | *.dSYM 57 | 58 | ## Playgrounds 59 | timeline.xctimeline 60 | playground.xcworkspace 61 | 62 | # Swift Package Manager 63 | # 64 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 65 | # Packages/ 66 | # Package.pins 67 | .build/ 68 | 69 | # CocoaPods 70 | # 71 | # We recommend against adding the Pods directory to your .gitignore. However 72 | # you should judge for yourself, the pros and cons are mentioned at: 73 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 74 | # 75 | # Pods/ 76 | 77 | # Carthage 78 | # 79 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 80 | # Carthage/Checkouts 81 | 82 | Carthage/Build 83 | 84 | # fastlane 85 | # 86 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 87 | # screenshots whenever they are needed. 88 | # For more information about the recommended setup visit: 89 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 90 | 91 | fastlane/report.xml 92 | fastlane/Preview.html 93 | fastlane/screenshots 94 | fastlane/test_output 95 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Erik Berglund 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SwiftPrivilegedHelper 2 | 3 | This is an example application to demonstrate how to use a privileged helper tool with authentication in Swift 4.2. 4 | 5 | **NOTE: Privilege escalation is not supported in sandboxed applications.** 6 | 7 | Please undestand the code and improve and customize it to suit your needs and your application. 8 | 9 | ## Updated for Swift 4.2 10 | 11 | I have rewritten the whole project for Swift 4.2 and improved the example in many places: 12 | 13 | * **Automatic Code Signing** 14 | Now the project uses automatic code signing, you no longer have to manually edit the code signing information in the app or helper plists. 15 | 16 | * **Connection Validation** 17 | Now the helper validates that the calling application is signed using the same signing certificate as the calling application to avoid a simple attack vector for helper tools. 18 | 19 | # Index 20 | 21 | * [Requirements](https://github.com/erikberglund/SwiftPrivilegedHelper#requirements) 22 | * [Setup](https://github.com/erikberglund/SwiftPrivilegedHelper#setup) 23 | * [Application](https://github.com/erikberglund/SwiftPrivilegedHelper#application) 24 | * [References](https://github.com/erikberglund/SwiftPrivilegedHelper#references) 25 | 26 | # Requirements 27 | 28 | * **Tool and language versions** 29 | This project was created and only tested using Xcode Version 10.0 (10A255) and Swift 4.2. 30 | 31 | * **Developer Certificate** 32 | To use a privileged helper tool the application and helper has to be signed by a valid deverloper certificate. 33 | 34 | * **SMJobBlessUtil** 35 | The python tool for verifying signing of applications using SMJobBless included in the [SMJobBless](https://developer.apple.com/library/content/samplecode/SMJobBless/Introduction/Intro.html#//apple_ref/doc/uid/DTS40010071-Intro-DontLinkElementID_2) example project is extremely useful for troubleshooting signing issues. 36 | 37 | Dowload it here: [SMJobBlessUtil.py](https://developer.apple.com/library/content/samplecode/SMJobBless/Listings/SMJobBlessUtil_py.html) 38 | 39 | Use it like this: `./SMJobBlessUtil.py check /path/to/MyApplication.app` 40 | 41 | # Setup 42 | 43 | To test the project, you need to update it to use your own signing certificate. 44 | 45 | ### Select signing team 46 | 1. Select the project in the navigator. 47 | 2. For **both** the application and helper targets: 48 | 3. Change the signing Team to your Team. 49 | 50 | ### Signing Troubleshooting 51 | 52 | Use [SMJobBlessUtil.py](https://developer.apple.com/library/content/samplecode/SMJobBless/Listings/SMJobBlessUtil_py.html) and correct all issues reported until it doesn't show any output. 53 | 54 | ### Note: Changing BundleIdentifier 55 | 56 | The project uses a [bash script](https://github.com/erikberglund/SwiftPrivilegedHelper/blob/master/SwiftPrivilegedHelperApplication/Scripts/CodeSignUpdate.sh) to automatically update the needed code signing requirements for your current certificate so that they will be correct both during normal builds and archiving. 57 | 58 | This script has hardcoded bundle identifiers for the app and the helper, so if you change the bundle identifiers for any of these you have to update the script accordingly. 59 | 60 | # Application 61 | 62 | The helper is installed by using [SMJobBless](https://developer.apple.com/reference/servicemanagement/1431078-smjobbless?language=swift). 63 | 64 | When installed, you can enter a directory path in the text field at the top and select to run the `/bin/ls` command (with the entered path as argument) using the helper tool with or without requiring authorization. 65 | 66 | The application caches the authorization reference which means that you only have to authorize that action once until you press the "Destroy Cached Authorization" or restart the application. 67 | 68 | This behaviour can easily be changed to either require authrization every time, after x seconds or never. 69 | 70 | # Adding or removing code in the helper 71 | 72 | If you modify or add/remove code that's used in the helper tool you **MUST** update the helper bundle version so when run the next time the application will recognize the helper has to be updated. Or else your new code will never be run. 73 | 74 | # References 75 | 76 | Links to documentation on the authorization system on macOS. 77 | 78 | * [Authorization Services Programming Guide](https://developer.apple.com/library/content/documentation/Security/Conceptual/authorization_concepts/01introduction/introduction.html#//apple_ref/doc/uid/TP30000995-CH204-TP1) 79 | * [Technical Note TN2095 - Authorization for Everyone](https://developer.apple.com/library/content/technotes/tn2095/_index.html) 80 | -------------------------------------------------------------------------------- /SwiftPrivilegedHelperApplication/Extensions/ExtensionsNSTextView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExtensionsNSTextView.swift 3 | // SwiftPrivilegedHelperApplication 4 | // 5 | // Created by Erik Berglund on 2018-10-01. 6 | // Copyright © 2018 Erik Berglund. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | extension NSTextView { 12 | func appendText(_ text: String) { 13 | DispatchQueue.main.async { 14 | let attrDict = [NSAttributedString.Key.font: NSFont.systemFont(ofSize: NSFont.smallSystemFontSize)] 15 | let astring = NSAttributedString(string: "\(text)\n", attributes: attrDict) 16 | self.textStorage?.append(astring) 17 | let loc = self.string.lengthOfBytes(using: String.Encoding.utf8) 18 | let range = NSRange(location: loc, length: 0) 19 | self.scrollRangeToVisible(range) 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /SwiftPrivilegedHelperApplication/Scripts/CodeSignUpdate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # CodeSignUpdate.sh 4 | # SwiftPrivilegedHelperApplication 5 | # 6 | # Created by Erik Berglund. 7 | # Copyright © 2018 Erik Berglund. All rights reserved. 8 | 9 | set -e 10 | 11 | ### 12 | ### CUSTOM VARIABLES 13 | ### 14 | 15 | bundleIdentifierApplication="com.github.erikberglund.SwiftPrivilegedHelperApplication" 16 | bundleIdentifierHelper="com.github.erikberglund.SwiftPrivilegedHelper" 17 | 18 | ### 19 | ### STATIC VARIABLES 20 | ### 21 | 22 | infoPlist="${INFOPLIST_FILE}" 23 | 24 | if [[ $( /usr/libexec/PlistBuddy -c "Print NSPrincipalClass" "${infoPlist}" 2>/dev/null ) == "NSApplication" ]]; then 25 | target="application" 26 | else 27 | target="helper" 28 | fi 29 | 30 | oidAppleDeveloperIDCA="1.2.840.113635.100.6.2.6" 31 | oidAppleDeveloperIDApplication="1.2.840.113635.100.6.1.13" 32 | oidAppleMacAppStoreApplication="1.2.840.113635.100.6.1.9" 33 | oidAppleWWDRIntermediate="1.2.840.113635.100.6.2.1" 34 | 35 | ### 36 | ### FUNCTIONS 37 | ### 38 | 39 | function appleGeneric { 40 | printf "%s" "anchor apple generic" 41 | } 42 | 43 | function appleDeveloperID { 44 | printf "%s" "certificate leaf[field.${oidAppleMacAppStoreApplication}] /* exists */ or certificate 1[field.${oidAppleDeveloperIDCA}] /* exists */ and certificate leaf[field.${oidAppleDeveloperIDApplication}] /* exists */" 45 | } 46 | 47 | function appleMacDeveloper { 48 | printf "%s" "certificate 1[field.${oidAppleWWDRIntermediate}]" 49 | } 50 | 51 | function identifierApplication { 52 | printf "%s" "identifier \"${bundleIdentifierApplication}\"" 53 | } 54 | 55 | function identifierHelper { 56 | printf "%s" "identifier \"${bundleIdentifierHelper}\"" 57 | } 58 | 59 | 60 | function developerID { 61 | developmentTeamIdentifier="${DEVELOPMENT_TEAM}" 62 | if ! [[ ${developmentTeamIdentifier} =~ ^[A-Z0-9]{10}$ ]]; then 63 | printf "%s\n" "Invalid Development Team Identifier: ${developmentTeamIdentifier}" 64 | exit 1 65 | fi 66 | 67 | printf "%s" "certificate leaf[subject.OU] = ${developmentTeamIdentifier}" 68 | } 69 | 70 | function macDeveloper { 71 | macDeveloperCN="${EXPANDED_CODE_SIGN_IDENTITY_NAME}" 72 | if ! [[ ${macDeveloperCN} =~ ^Mac\ Developer:\ .*\ \([A-Z0-9]{10}\)$ ]]; then 73 | printf "%s\n" "Invalid Mac Developer CN: ${macDeveloperCN}" 74 | exit 1 75 | fi 76 | 77 | printf "%s" "certificate leaf[subject.CN] = \"${macDeveloperCN}\"" 78 | } 79 | 80 | function updateSMPrivilegedExecutables { 81 | /usr/libexec/PlistBuddy -c 'Delete SMPrivilegedExecutables' "${infoPlist}" 82 | /usr/libexec/PlistBuddy -c 'Add SMPrivilegedExecutables dict' "${infoPlist}" 83 | /usr/libexec/PlistBuddy -c 'Add SMPrivilegedExecutables:'"${bundleIdentifierHelper}"' string '"$( sed -E 's/\"/\\\"/g' <<< ${1})"'' "${infoPlist}" 84 | } 85 | 86 | function updateSMAuthorizedClients { 87 | /usr/libexec/PlistBuddy -c 'Delete SMAuthorizedClients' "${infoPlist}" 88 | /usr/libexec/PlistBuddy -c 'Add SMAuthorizedClients array' "${infoPlist}" 89 | /usr/libexec/PlistBuddy -c 'Add SMAuthorizedClients: string '"$( sed -E 's/\"/\\\"/g' <<< ${1})"'' "${infoPlist}" 90 | } 91 | 92 | ### 93 | ### MAIN SCRIPT 94 | ### 95 | 96 | case "${ACTION}" in 97 | "build") 98 | appString=$( identifierApplication ) 99 | appString="${appString} and $( appleGeneric )" 100 | appString="${appString} and $( macDeveloper )" 101 | appString="${appString} and $( appleMacDeveloper )" 102 | appString="${appString} /* exists */" 103 | 104 | helperString=$( identifierHelper ) 105 | helperString="${helperString} and $( appleGeneric )" 106 | helperString="${helperString} and $( macDeveloper )" 107 | helperString="${helperString} and $( appleMacDeveloper )" 108 | helperString="${helperString} /* exists */" 109 | ;; 110 | "install") 111 | appString=$( appleGeneric ) 112 | appString="${appString} and $( identifierApplication )" 113 | appString="${appString} and ($( appleDeveloperID )" 114 | appString="${appString} and $( developerID ))" 115 | 116 | helperString=$( appleGeneric ) 117 | helperString="${helperString} and $( identifierHelper )" 118 | helperString="${helperString} and ($( appleDeveloperID )" 119 | helperString="${helperString} and $( developerID ))" 120 | ;; 121 | *) 122 | printf "%s\n" "Unknown Xcode Action: ${ACTION}" 123 | exit 1 124 | ;; 125 | esac 126 | 127 | case "${target}" in 128 | "helper") 129 | updateSMAuthorizedClients "${appString}" 130 | ;; 131 | "application") 132 | updateSMPrivilegedExecutables "${helperString}" 133 | ;; 134 | *) 135 | printf "%s\n" "Unknown Target: ${target}" 136 | exit 1 137 | ;; 138 | esac 139 | -------------------------------------------------------------------------------- /SwiftPrivilegedHelperApplication/SwiftPrivilegedHelper/Helper-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleIdentifier 6 | com.github.erikberglund.SwiftPrivilegedHelper 7 | CFBundleInfoDictionaryVersion 8 | 6.0 9 | CFBundleName 10 | com.github.erikberglund.SwiftPrivilegedHelper 11 | CFBundleShortVersionString 12 | 1.0 13 | CFBundleVersion 14 | 1 15 | SMAuthorizedClients 16 | 17 | identifier "com.github.erikberglund.SwiftPrivilegedHelperApplication" and anchor apple generic and certificate leaf[subject.CN] = "Mac Developer: Erik Berglund (BXUF2UUW7E)" and certificate 1[field.1.2.840.113635.100.6.2.1] /* exists */ 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /SwiftPrivilegedHelperApplication/SwiftPrivilegedHelper/Helper-Launchd.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Label 6 | com.github.erikberglund.SwiftPrivilegedHelper 7 | MachServices 8 | 9 | com.github.erikberglund.SwiftPrivilegedHelper 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /SwiftPrivilegedHelperApplication/SwiftPrivilegedHelper/Helper.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Helper.swift 3 | // SwiftPrivilegedHelper 4 | // 5 | // Created by Erik Berglund on 2018-10-01. 6 | // Copyright © 2018 Erik Berglund. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class Helper: NSObject, NSXPCListenerDelegate, HelperProtocol { 12 | 13 | // MARK: - 14 | // MARK: Private Constant Variables 15 | 16 | private let listener: NSXPCListener 17 | 18 | // MARK: - 19 | // MARK: Private Variables 20 | 21 | private var connections = [NSXPCConnection]() 22 | private var shouldQuit = false 23 | private var shouldQuitCheckInterval = 1.0 24 | 25 | // MARK: - 26 | // MARK: Initialization 27 | 28 | override init() { 29 | self.listener = NSXPCListener(machServiceName: HelperConstants.machServiceName) 30 | super.init() 31 | self.listener.delegate = self 32 | } 33 | 34 | public func run() { 35 | self.listener.resume() 36 | 37 | // Keep the helper tool running until the variable shouldQuit is set to true. 38 | // The variable should be changed in the "listener(_ listener:shoudlAcceptNewConnection:)" function. 39 | 40 | while !self.shouldQuit { 41 | RunLoop.current.run(until: Date(timeIntervalSinceNow: self.shouldQuitCheckInterval)) 42 | } 43 | } 44 | 45 | // MARK: - 46 | // MARK: NSXPCListenerDelegate Methods 47 | 48 | func listener(_ listener: NSXPCListener, shouldAcceptNewConnection connection: NSXPCConnection) -> Bool { 49 | 50 | // Verify that the calling application is signed using the same code signing certificate as the helper 51 | guard self.isValid(connection: connection) else { 52 | return false 53 | } 54 | 55 | // Set the protocol that the calling application conforms to. 56 | connection.remoteObjectInterface = NSXPCInterface(with: AppProtocol.self) 57 | 58 | // Set the protocol that the helper conforms to. 59 | connection.exportedInterface = NSXPCInterface(with: HelperProtocol.self) 60 | connection.exportedObject = self 61 | 62 | // Set the invalidation handler to remove this connection when it's work is completed. 63 | connection.invalidationHandler = { 64 | if let connectionIndex = self.connections.firstIndex(of: connection) { 65 | self.connections.remove(at: connectionIndex) 66 | } 67 | 68 | if self.connections.isEmpty { 69 | self.shouldQuit = true 70 | } 71 | } 72 | 73 | self.connections.append(connection) 74 | connection.resume() 75 | 76 | return true 77 | } 78 | 79 | // MARK: - 80 | // MARK: HelperProtocol Methods 81 | 82 | func getVersion(completion: (String) -> Void) { 83 | completion(Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? "0") 84 | } 85 | 86 | func runCommandLs(withPath path: String, completion: @escaping (NSNumber) -> Void) { 87 | 88 | // For security reasons, all commands should be hardcoded in the helper 89 | let command = "/bin/ls" 90 | let arguments = [path] 91 | 92 | // Run the task 93 | self.runTask(command: command, arguments: arguments, completion: completion) 94 | } 95 | 96 | func runCommandLs(withPath path: String, authData: NSData?, completion: @escaping (NSNumber) -> Void) { 97 | 98 | // Check the passed authorization, if the user need to authenticate to use this command the user might be prompted depending on the settings and/or cached authentication. 99 | 100 | guard self.verifyAuthorization(authData, forCommand: #selector(HelperProtocol.runCommandLs(withPath:authData:completion:))) else { 101 | completion(kAuthorizationFailedExitCode) 102 | return 103 | } 104 | 105 | self.runCommandLs(withPath: path, completion: completion) 106 | } 107 | 108 | // MARK: - 109 | // MARK: Private Helper Methods 110 | 111 | private func isValid(connection: NSXPCConnection) -> Bool { 112 | do { 113 | return try CodesignCheck.codeSigningMatches(pid: connection.processIdentifier) 114 | } catch { 115 | NSLog("Code signing check failed with error: \(error)") 116 | return false 117 | } 118 | } 119 | 120 | private func verifyAuthorization(_ authData: NSData?, forCommand command: Selector) -> Bool { 121 | do { 122 | try HelperAuthorization.verifyAuthorization(authData, forCommand: command) 123 | } catch { 124 | if let remoteObject = self.connection()?.remoteObjectProxy as? AppProtocol { 125 | remoteObject.log(stdErr: "Authentication Error: \(error)") 126 | } 127 | return false 128 | } 129 | return true 130 | } 131 | 132 | private func connection() -> NSXPCConnection? { 133 | return self.connections.last 134 | } 135 | 136 | private func runTask(command: String, arguments: Array, completion:@escaping ((NSNumber) -> Void)) -> Void { 137 | let task = Process() 138 | let stdOut = Pipe() 139 | 140 | let stdOutHandler = { (file: FileHandle!) -> Void in 141 | let data = file.availableData 142 | guard let output = NSString(data: data, encoding: String.Encoding.utf8.rawValue) else { return } 143 | if let remoteObject = self.connection()?.remoteObjectProxy as? AppProtocol { 144 | remoteObject.log(stdOut: output as String) 145 | } 146 | } 147 | stdOut.fileHandleForReading.readabilityHandler = stdOutHandler 148 | 149 | let stdErr:Pipe = Pipe() 150 | let stdErrHandler = { (file: FileHandle!) -> Void in 151 | let data = file.availableData 152 | guard let output = NSString(data: data, encoding: String.Encoding.utf8.rawValue) else { return } 153 | if let remoteObject = self.connection()?.remoteObjectProxy as? AppProtocol { 154 | remoteObject.log(stdErr: output as String) 155 | } 156 | } 157 | stdErr.fileHandleForReading.readabilityHandler = stdErrHandler 158 | 159 | task.launchPath = command 160 | task.arguments = arguments 161 | task.standardOutput = stdOut 162 | task.standardError = stdErr 163 | 164 | task.terminationHandler = { task in 165 | completion(NSNumber(value: task.terminationStatus)) 166 | } 167 | 168 | task.launch() 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /SwiftPrivilegedHelperApplication/SwiftPrivilegedHelper/HelperAuthorization.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HelperAuthorization.swift 3 | // SwiftPrivilegedHelper 4 | // 5 | // Created by Erik Berglund on 2018-10-01. 6 | // Copyright © 2018 Erik Berglund. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum HelperAuthorizationError: Error { 12 | case message(String) 13 | } 14 | 15 | class HelperAuthorization { 16 | 17 | // MARK: - 18 | // MARK: Variables 19 | 20 | // FIXME: Add all functions that require authentication here. 21 | 22 | static let authorizationRights = [ 23 | HelperAuthorizationRight(command: #selector(HelperProtocol.runCommandLs(withPath:authData:completion:)), 24 | description: "SwiftPrivilegedHelper wants to run the command /bin/ls", 25 | ruleCustom: [kAuthorizationRightKeyClass: "user", kAuthorizationRightKeyGroup: "admin", kAuthorizationRightKeyVersion: 1]) 26 | ] 27 | 28 | // MARK: - 29 | // MARK: AuthorizationRights 30 | 31 | static func authorizationRight(forCommand command: Selector) -> HelperAuthorizationRight? { 32 | return self.authorizationRights.first { $0.command == command } 33 | } 34 | 35 | static func authorizationRightsUpdateDatabase() throws { 36 | guard let authRef = try self.emptyAuthorizationRef() else { 37 | throw HelperAuthorizationError.message("Failed to get empty authorization ref") 38 | } 39 | 40 | for authorizationRight in self.authorizationRights { 41 | 42 | var osStatus = errAuthorizationSuccess 43 | var currentRule: CFDictionary? 44 | 45 | osStatus = AuthorizationRightGet(authorizationRight.name, ¤tRule) 46 | if osStatus == errAuthorizationDenied || self.authorizationRuleUpdateRequired(currentRule, authorizationRight: authorizationRight) { 47 | osStatus = AuthorizationRightSet(authRef, 48 | authorizationRight.name, 49 | authorizationRight.rule(), 50 | authorizationRight.description as CFString, 51 | nil, 52 | nil) 53 | } 54 | 55 | guard osStatus == errAuthorizationSuccess else { 56 | NSLog("AuthorizationRightSet or Get failed with error: \(String(describing: SecCopyErrorMessageString(osStatus, nil)))") 57 | continue 58 | } 59 | } 60 | } 61 | 62 | static func authorizationRuleUpdateRequired(_ currentRuleCFDict: CFDictionary?, authorizationRight: HelperAuthorizationRight) -> Bool { 63 | guard let currentRuleDict = currentRuleCFDict as? [String: Any] else { 64 | return true 65 | } 66 | let newRule = authorizationRight.rule() 67 | if CFGetTypeID(newRule) == CFStringGetTypeID() { 68 | if 69 | let currentRule = currentRuleDict[kAuthorizationRightKeyRule] as? [String], 70 | let newRule = authorizationRight.ruleConstant { 71 | return currentRule != [newRule] 72 | 73 | } 74 | } else if CFGetTypeID(newRule) == CFDictionaryGetTypeID() { 75 | if let currentVersion = currentRuleDict[kAuthorizationRightKeyVersion] as? Int, 76 | let newVersion = authorizationRight.ruleCustom?[kAuthorizationRightKeyVersion] as? Int { 77 | return currentVersion != newVersion 78 | } 79 | } 80 | return true 81 | } 82 | 83 | // MARK: - 84 | // MARK: Authorization Wrapper 85 | 86 | private static func executeAuthorizationFunction(_ authorizationFunction: () -> (OSStatus) ) throws { 87 | let osStatus = authorizationFunction() 88 | guard osStatus == errAuthorizationSuccess else { 89 | throw HelperAuthorizationError.message(String(describing: SecCopyErrorMessageString(osStatus, nil))) 90 | } 91 | } 92 | 93 | // MARK: - 94 | // MARK: AuthorizationRef 95 | 96 | static func authorizationRef(_ rights: UnsafePointer?, 97 | _ environment: UnsafePointer?, 98 | _ flags: AuthorizationFlags) throws -> AuthorizationRef? { 99 | var authRef: AuthorizationRef? 100 | try executeAuthorizationFunction { AuthorizationCreate(rights, environment, flags, &authRef) } 101 | return authRef 102 | } 103 | 104 | static func authorizationRef(fromExternalForm data: NSData) throws -> AuthorizationRef? { 105 | 106 | // Create an AuthorizationExternalForm from it's data representation 107 | var authRef: AuthorizationRef? 108 | let authRefExtForm: UnsafeMutablePointer = UnsafeMutablePointer.allocate(capacity: kAuthorizationExternalFormLength * MemoryLayout.size) 109 | memcpy(authRefExtForm, data.bytes, data.length) 110 | 111 | // Extract the AuthorizationRef from it's external form 112 | try executeAuthorizationFunction { AuthorizationCreateFromExternalForm(authRefExtForm, &authRef) } 113 | return authRef 114 | } 115 | 116 | 117 | // MARK: - 118 | // MARK: Empty Authorization Refs 119 | 120 | static func emptyAuthorizationRef() throws -> AuthorizationRef? { 121 | var authRef: AuthorizationRef? 122 | 123 | // Create an empty AuthorizationRef 124 | try executeAuthorizationFunction { AuthorizationCreate(nil, nil, [], &authRef) } 125 | return authRef 126 | } 127 | 128 | static func emptyAuthorizationExternalForm() throws -> AuthorizationExternalForm? { 129 | 130 | // Create an empty AuthorizationRef 131 | guard let authorizationRef = try self.emptyAuthorizationRef() else { return nil } 132 | 133 | // Make an external form of the AuthorizationRef 134 | var authRefExtForm = AuthorizationExternalForm() 135 | try executeAuthorizationFunction { AuthorizationMakeExternalForm(authorizationRef, &authRefExtForm) } 136 | return authRefExtForm 137 | } 138 | 139 | static func emptyAuthorizationExternalFormData() throws -> NSData? { 140 | guard var authRefExtForm = try self.emptyAuthorizationExternalForm() else { return nil } 141 | 142 | // Encapsulate the external form AuthorizationRef in an NSData object 143 | return NSData(bytes: &authRefExtForm, length: kAuthorizationExternalFormLength) 144 | } 145 | 146 | // MARK: - 147 | // MARK: Verification 148 | 149 | static func verifyAuthorization(_ authExtData: NSData?, forCommand command: Selector) throws { 150 | 151 | // Verity that the passed authExtData looks reasonable 152 | guard let authorizationExtData = authExtData, authorizationExtData.length == kAuthorizationExternalFormLength else { 153 | throw HelperAuthorizationError.message("Invalid Authorization External Form Data") 154 | } 155 | 156 | // Convert the external form to an AuthorizationRef 157 | guard let authorizationRef = try self.authorizationRef(fromExternalForm: authorizationExtData) else { 158 | throw HelperAuthorizationError.message("Failed to convert the Authorization External Form to an Authorization Reference") 159 | } 160 | 161 | // Get the authorization right struct for the passed command 162 | guard let authorizationRight = self.authorizationRight(forCommand: command) else { 163 | throw HelperAuthorizationError.message("Failed to get the correct authorization right for command: \(command)") 164 | } 165 | 166 | // Verity the user has the right to run the passed command 167 | try self.verifyAuthorization(authorizationRef, forAuthenticationRight: authorizationRight) 168 | } 169 | 170 | static func verifyAuthorization(_ authRef: AuthorizationRef, forAuthenticationRight authRight: HelperAuthorizationRight) throws { 171 | 172 | // Get the authorization name in the correct format 173 | guard let authRightName = (authRight.name as NSString).utf8String else { 174 | throw HelperAuthorizationError.message("Failed to convert authorization name to cString") 175 | } 176 | 177 | // Create an AuthorizationItem using the authorization right name 178 | var authItem = AuthorizationItem(name: authRightName, valueLength: 0, value: UnsafeMutableRawPointer(bitPattern: 0), flags: 0) 179 | 180 | // Create the AuthorizationRights for using the AuthorizationItem 181 | var authRights = AuthorizationRights(count: 1, items: &authItem) 182 | 183 | // Check if the user is authorized for the AuthorizationRights. If not the user might be asked for an admin credential. 184 | try executeAuthorizationFunction { AuthorizationCopyRights(authRef, &authRights, nil, [.extendRights, .interactionAllowed], nil) } 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /SwiftPrivilegedHelperApplication/SwiftPrivilegedHelper/HelperAuthorizationRight.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HelperAuthorizationRight.swift 3 | // SwiftPrivilegedHelper 4 | // 5 | // Created by Erik Berglund on 2018-10-01. 6 | // Copyright © 2018 Erik Berglund. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct HelperAuthorizationRight { 12 | 13 | let command: Selector 14 | let name: String 15 | let description: String 16 | let ruleCustom: [String: Any]? 17 | let ruleConstant: String? 18 | 19 | init(command: Selector, name: String? = nil, description: String, ruleCustom: [String: Any]? = nil, ruleConstant: String? = nil) { 20 | self.command = command 21 | self.name = name ?? HelperConstants.machServiceName + "." + command.description 22 | self.description = description 23 | self.ruleCustom = ruleCustom 24 | self.ruleConstant = ruleConstant 25 | } 26 | 27 | func rule() -> CFTypeRef { 28 | let rule: CFTypeRef 29 | if let ruleCustom = self.ruleCustom as CFDictionary? { 30 | rule = ruleCustom 31 | } else if let ruleConstant = self.ruleConstant as CFString? { 32 | rule = ruleConstant 33 | } else { 34 | rule = kAuthorizationRuleAuthenticateAsAdmin as CFString 35 | } 36 | 37 | return rule 38 | } 39 | } 40 | 41 | -------------------------------------------------------------------------------- /SwiftPrivilegedHelperApplication/SwiftPrivilegedHelper/HelperConstants.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HelperConstants.swift 3 | // SwiftPrivilegedHelper 4 | // 5 | // Created by Erik Berglund on 2018-10-01. 6 | // Copyright © 2018 Erik Berglund. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | let kAuthorizationRightKeyClass = "class" 12 | let kAuthorizationRightKeyGroup = "group" 13 | let kAuthorizationRightKeyRule = "rule" 14 | let kAuthorizationRightKeyTimeout = "timeout" 15 | let kAuthorizationRightKeyVersion = "version" 16 | 17 | let kAuthorizationFailedExitCode = NSNumber(value: 503340) 18 | 19 | struct HelperConstants { 20 | static let machServiceName = "com.github.erikberglund.SwiftPrivilegedHelper" 21 | } 22 | -------------------------------------------------------------------------------- /SwiftPrivilegedHelperApplication/SwiftPrivilegedHelper/HelperProtocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HelperProtocol.swift 3 | // SwiftPrivilegedHelper 4 | // 5 | // Created by Erik Berglund on 2018-10-01. 6 | // Copyright © 2018 Erik Berglund. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | @objc(HelperProtocol) 12 | protocol HelperProtocol { 13 | func getVersion(completion: @escaping (String) -> Void) 14 | func runCommandLs(withPath: String, completion: @escaping (NSNumber) -> Void) 15 | func runCommandLs(withPath: String, authData: NSData?, completion: @escaping (NSNumber) -> Void) 16 | } 17 | -------------------------------------------------------------------------------- /SwiftPrivilegedHelperApplication/SwiftPrivilegedHelper/main.swift: -------------------------------------------------------------------------------- 1 | // 2 | // main.swift 3 | // SwiftPrivilegedHelper 4 | // 5 | // Created by Erik Berglund on 2018-10-01. 6 | // Copyright © 2018 Erik Berglund. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | let helper = Helper() 12 | helper.run() 13 | -------------------------------------------------------------------------------- /SwiftPrivilegedHelperApplication/SwiftPrivilegedHelperApplication.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 532308FA2163A87600A456CF /* ExtensionsNSTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 532308F92163A87600A456CF /* ExtensionsNSTextView.swift */; }; 11 | 5332883321660B2400028C27 /* HelperAuthorization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53EE3960215E3068007310F5 /* HelperAuthorization.swift */; }; 12 | 5332883421660D7A00028C27 /* HelperAuthorizationRight.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53EE3964215E3A85007310F5 /* HelperAuthorizationRight.swift */; }; 13 | 535731AF215E1E5A009FDE00 /* HelperConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53604C21215E13E200071149 /* HelperConstants.swift */; }; 14 | 53604C07215E12F300071149 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53604C06215E12F300071149 /* AppDelegate.swift */; }; 15 | 53604C09215E12F400071149 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 53604C08215E12F400071149 /* Assets.xcassets */; }; 16 | 53604C0C215E12F400071149 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 53604C0A215E12F400071149 /* MainMenu.xib */; }; 17 | 53604C1B215E131E00071149 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53604C1A215E131E00071149 /* main.swift */; }; 18 | 53604C20215E134500071149 /* Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53604C1F215E134500071149 /* Helper.swift */; }; 19 | 53604C22215E13E200071149 /* HelperConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53604C21215E13E200071149 /* HelperConstants.swift */; }; 20 | 53604C26215E185D00071149 /* HelperProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53604C25215E185D00071149 /* HelperProtocol.swift */; }; 21 | 53604C2A215E1B0100071149 /* AppProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53604C29215E1B0100071149 /* AppProtocol.swift */; }; 22 | 53604C2C215E1C2700071149 /* AppProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53604C29215E1B0100071149 /* AppProtocol.swift */; }; 23 | 53604C2E215E1CFB00071149 /* com.github.erikberglund.SwiftPrivilegedHelper in Copy Helper */ = {isa = PBXBuildFile; fileRef = 53604C18215E131D00071149 /* com.github.erikberglund.SwiftPrivilegedHelper */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; 24 | 53C6B95E216767F0001A80FD /* CodesignCheck.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53C6B95921676219001A80FD /* CodesignCheck.swift */; }; 25 | 53EE395F215E2631007310F5 /* HelperProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53604C25215E185D00071149 /* HelperProtocol.swift */; }; 26 | 53EE3961215E3068007310F5 /* HelperAuthorization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53EE3960215E3068007310F5 /* HelperAuthorization.swift */; }; 27 | 53EE3965215E3A85007310F5 /* HelperAuthorizationRight.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53EE3964215E3A85007310F5 /* HelperAuthorizationRight.swift */; }; 28 | /* End PBXBuildFile section */ 29 | 30 | /* Begin PBXContainerItemProxy section */ 31 | 53EE395B215E22CE007310F5 /* PBXContainerItemProxy */ = { 32 | isa = PBXContainerItemProxy; 33 | containerPortal = 53604BFB215E12F300071149 /* Project object */; 34 | proxyType = 1; 35 | remoteGlobalIDString = 53604C17215E131D00071149; 36 | remoteInfo = com.github.erikberglund.SwiftPrivilegedHelper; 37 | }; 38 | /* End PBXContainerItemProxy section */ 39 | 40 | /* Begin PBXCopyFilesBuildPhase section */ 41 | 53604C16215E131D00071149 /* CopyFiles */ = { 42 | isa = PBXCopyFilesBuildPhase; 43 | buildActionMask = 2147483647; 44 | dstPath = /usr/share/man/man1/; 45 | dstSubfolderSpec = 0; 46 | files = ( 47 | ); 48 | runOnlyForDeploymentPostprocessing = 1; 49 | }; 50 | 53604C2D215E1CE600071149 /* Copy Helper */ = { 51 | isa = PBXCopyFilesBuildPhase; 52 | buildActionMask = 2147483647; 53 | dstPath = Contents/Library/LaunchServices; 54 | dstSubfolderSpec = 1; 55 | files = ( 56 | 53604C2E215E1CFB00071149 /* com.github.erikberglund.SwiftPrivilegedHelper in Copy Helper */, 57 | ); 58 | name = "Copy Helper"; 59 | runOnlyForDeploymentPostprocessing = 0; 60 | }; 61 | /* End PBXCopyFilesBuildPhase section */ 62 | 63 | /* Begin PBXFileReference section */ 64 | 532308F42163910700A456CF /* CodeSignUpdate.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = CodeSignUpdate.sh; sourceTree = ""; }; 65 | 532308F92163A87600A456CF /* ExtensionsNSTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionsNSTextView.swift; sourceTree = ""; }; 66 | 53604C03215E12F300071149 /* SwiftPrivilegedHelperApplication.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SwiftPrivilegedHelperApplication.app; sourceTree = BUILT_PRODUCTS_DIR; }; 67 | 53604C06215E12F300071149 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 68 | 53604C08215E12F400071149 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 69 | 53604C0B215E12F400071149 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; 70 | 53604C0D215E12F400071149 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 71 | 53604C18215E131D00071149 /* com.github.erikberglund.SwiftPrivilegedHelper */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = com.github.erikberglund.SwiftPrivilegedHelper; sourceTree = BUILT_PRODUCTS_DIR; }; 72 | 53604C1A215E131E00071149 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; 73 | 53604C1F215E134500071149 /* Helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Helper.swift; sourceTree = ""; }; 74 | 53604C21215E13E200071149 /* HelperConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HelperConstants.swift; sourceTree = ""; }; 75 | 53604C23215E14C900071149 /* Helper-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Helper-Info.plist"; sourceTree = ""; }; 76 | 53604C24215E150C00071149 /* Helper-Launchd.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Helper-Launchd.plist"; sourceTree = ""; }; 77 | 53604C25215E185D00071149 /* HelperProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HelperProtocol.swift; sourceTree = ""; }; 78 | 53604C29215E1B0100071149 /* AppProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppProtocol.swift; sourceTree = ""; }; 79 | 53C6B95921676219001A80FD /* CodesignCheck.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CodesignCheck.swift; sourceTree = ""; }; 80 | 53EE3960215E3068007310F5 /* HelperAuthorization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HelperAuthorization.swift; sourceTree = ""; }; 81 | 53EE3964215E3A85007310F5 /* HelperAuthorizationRight.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HelperAuthorizationRight.swift; sourceTree = ""; }; 82 | /* End PBXFileReference section */ 83 | 84 | /* Begin PBXFrameworksBuildPhase section */ 85 | 53604C00215E12F300071149 /* Frameworks */ = { 86 | isa = PBXFrameworksBuildPhase; 87 | buildActionMask = 2147483647; 88 | files = ( 89 | ); 90 | runOnlyForDeploymentPostprocessing = 0; 91 | }; 92 | 53604C15215E131D00071149 /* Frameworks */ = { 93 | isa = PBXFrameworksBuildPhase; 94 | buildActionMask = 2147483647; 95 | files = ( 96 | ); 97 | runOnlyForDeploymentPostprocessing = 0; 98 | }; 99 | /* End PBXFrameworksBuildPhase section */ 100 | 101 | /* Begin PBXGroup section */ 102 | 532308F12163908D00A456CF /* Scripts */ = { 103 | isa = PBXGroup; 104 | children = ( 105 | 532308F42163910700A456CF /* CodeSignUpdate.sh */, 106 | ); 107 | path = Scripts; 108 | sourceTree = ""; 109 | }; 110 | 53604BFA215E12F300071149 = { 111 | isa = PBXGroup; 112 | children = ( 113 | 53604C05215E12F300071149 /* SwiftPrivilegedHelperApplication */, 114 | 53604C19215E131E00071149 /* SwiftPrivilegedHelper */, 115 | 53C6B95B2167621D001A80FD /* Extensions */, 116 | 53C6B95C21676229001A80FD /* Utility */, 117 | 532308F12163908D00A456CF /* Scripts */, 118 | 53604C04215E12F300071149 /* Products */, 119 | ); 120 | sourceTree = ""; 121 | }; 122 | 53604C04215E12F300071149 /* Products */ = { 123 | isa = PBXGroup; 124 | children = ( 125 | 53604C03215E12F300071149 /* SwiftPrivilegedHelperApplication.app */, 126 | 53604C18215E131D00071149 /* com.github.erikberglund.SwiftPrivilegedHelper */, 127 | ); 128 | name = Products; 129 | sourceTree = ""; 130 | }; 131 | 53604C05215E12F300071149 /* SwiftPrivilegedHelperApplication */ = { 132 | isa = PBXGroup; 133 | children = ( 134 | 53604C06215E12F300071149 /* AppDelegate.swift */, 135 | 53604C29215E1B0100071149 /* AppProtocol.swift */, 136 | 53604C08215E12F400071149 /* Assets.xcassets */, 137 | 53604C0A215E12F400071149 /* MainMenu.xib */, 138 | 53604C0D215E12F400071149 /* Info.plist */, 139 | ); 140 | path = SwiftPrivilegedHelperApplication; 141 | sourceTree = ""; 142 | }; 143 | 53604C19215E131E00071149 /* SwiftPrivilegedHelper */ = { 144 | isa = PBXGroup; 145 | children = ( 146 | 53604C1A215E131E00071149 /* main.swift */, 147 | 53604C1F215E134500071149 /* Helper.swift */, 148 | 53EE3964215E3A85007310F5 /* HelperAuthorizationRight.swift */, 149 | 53EE3960215E3068007310F5 /* HelperAuthorization.swift */, 150 | 53604C21215E13E200071149 /* HelperConstants.swift */, 151 | 53604C25215E185D00071149 /* HelperProtocol.swift */, 152 | 53604C23215E14C900071149 /* Helper-Info.plist */, 153 | 53604C24215E150C00071149 /* Helper-Launchd.plist */, 154 | ); 155 | path = SwiftPrivilegedHelper; 156 | sourceTree = ""; 157 | }; 158 | 53C6B95B2167621D001A80FD /* Extensions */ = { 159 | isa = PBXGroup; 160 | children = ( 161 | 532308F92163A87600A456CF /* ExtensionsNSTextView.swift */, 162 | ); 163 | path = Extensions; 164 | sourceTree = ""; 165 | }; 166 | 53C6B95C21676229001A80FD /* Utility */ = { 167 | isa = PBXGroup; 168 | children = ( 169 | 53C6B95921676219001A80FD /* CodesignCheck.swift */, 170 | ); 171 | path = Utility; 172 | sourceTree = ""; 173 | }; 174 | /* End PBXGroup section */ 175 | 176 | /* Begin PBXNativeTarget section */ 177 | 53604C02215E12F300071149 /* SwiftPrivilegedHelperApplication */ = { 178 | isa = PBXNativeTarget; 179 | buildConfigurationList = 53604C11215E12F400071149 /* Build configuration list for PBXNativeTarget "SwiftPrivilegedHelperApplication" */; 180 | buildPhases = ( 181 | 5361D2122162410E0036E296 /* ShellScript */, 182 | 53604BFF215E12F300071149 /* Sources */, 183 | 53604C00215E12F300071149 /* Frameworks */, 184 | 53604C01215E12F300071149 /* Resources */, 185 | 53604C2D215E1CE600071149 /* Copy Helper */, 186 | ); 187 | buildRules = ( 188 | ); 189 | dependencies = ( 190 | 53EE395C215E22CE007310F5 /* PBXTargetDependency */, 191 | ); 192 | name = SwiftPrivilegedHelperApplication; 193 | productName = SwiftPrivilegedHelperApplication; 194 | productReference = 53604C03215E12F300071149 /* SwiftPrivilegedHelperApplication.app */; 195 | productType = "com.apple.product-type.application"; 196 | }; 197 | 53604C17215E131D00071149 /* com.github.erikberglund.SwiftPrivilegedHelper */ = { 198 | isa = PBXNativeTarget; 199 | buildConfigurationList = 53604C1C215E131E00071149 /* Build configuration list for PBXNativeTarget "com.github.erikberglund.SwiftPrivilegedHelper" */; 200 | buildPhases = ( 201 | 5361D213216254BC0036E296 /* ShellScript */, 202 | 53604C14215E131D00071149 /* Sources */, 203 | 53604C15215E131D00071149 /* Frameworks */, 204 | 53604C16215E131D00071149 /* CopyFiles */, 205 | ); 206 | buildRules = ( 207 | ); 208 | dependencies = ( 209 | ); 210 | name = com.github.erikberglund.SwiftPrivilegedHelper; 211 | productName = SwiftPrivilegedHelper; 212 | productReference = 53604C18215E131D00071149 /* com.github.erikberglund.SwiftPrivilegedHelper */; 213 | productType = "com.apple.product-type.tool"; 214 | }; 215 | /* End PBXNativeTarget section */ 216 | 217 | /* Begin PBXProject section */ 218 | 53604BFB215E12F300071149 /* Project object */ = { 219 | isa = PBXProject; 220 | attributes = { 221 | LastSwiftUpdateCheck = 1000; 222 | LastUpgradeCheck = 1000; 223 | ORGANIZATIONNAME = "Erik Berglund"; 224 | TargetAttributes = { 225 | 53604C02215E12F300071149 = { 226 | CreatedOnToolsVersion = 10.0; 227 | SystemCapabilities = { 228 | com.apple.Sandbox = { 229 | enabled = 0; 230 | }; 231 | }; 232 | }; 233 | 53604C17215E131D00071149 = { 234 | CreatedOnToolsVersion = 10.0; 235 | }; 236 | }; 237 | }; 238 | buildConfigurationList = 53604BFE215E12F300071149 /* Build configuration list for PBXProject "SwiftPrivilegedHelperApplication" */; 239 | compatibilityVersion = "Xcode 9.3"; 240 | developmentRegion = en; 241 | hasScannedForEncodings = 0; 242 | knownRegions = ( 243 | en, 244 | Base, 245 | ); 246 | mainGroup = 53604BFA215E12F300071149; 247 | productRefGroup = 53604C04215E12F300071149 /* Products */; 248 | projectDirPath = ""; 249 | projectRoot = ""; 250 | targets = ( 251 | 53604C02215E12F300071149 /* SwiftPrivilegedHelperApplication */, 252 | 53604C17215E131D00071149 /* com.github.erikberglund.SwiftPrivilegedHelper */, 253 | ); 254 | }; 255 | /* End PBXProject section */ 256 | 257 | /* Begin PBXResourcesBuildPhase section */ 258 | 53604C01215E12F300071149 /* Resources */ = { 259 | isa = PBXResourcesBuildPhase; 260 | buildActionMask = 2147483647; 261 | files = ( 262 | 53604C09215E12F400071149 /* Assets.xcassets in Resources */, 263 | 53604C0C215E12F400071149 /* MainMenu.xib in Resources */, 264 | ); 265 | runOnlyForDeploymentPostprocessing = 0; 266 | }; 267 | /* End PBXResourcesBuildPhase section */ 268 | 269 | /* Begin PBXShellScriptBuildPhase section */ 270 | 5361D2122162410E0036E296 /* ShellScript */ = { 271 | isa = PBXShellScriptBuildPhase; 272 | buildActionMask = 2147483647; 273 | files = ( 274 | ); 275 | inputFileListPaths = ( 276 | ); 277 | inputPaths = ( 278 | ); 279 | outputFileListPaths = ( 280 | ); 281 | outputPaths = ( 282 | ); 283 | runOnlyForDeploymentPostprocessing = 0; 284 | shellPath = /bin/sh; 285 | shellScript = "\"${SRCROOT}\"/Scripts/CodeSignUpdate.sh\n"; 286 | }; 287 | 5361D213216254BC0036E296 /* ShellScript */ = { 288 | isa = PBXShellScriptBuildPhase; 289 | buildActionMask = 2147483647; 290 | files = ( 291 | ); 292 | inputFileListPaths = ( 293 | ); 294 | inputPaths = ( 295 | ); 296 | outputFileListPaths = ( 297 | ); 298 | outputPaths = ( 299 | ); 300 | runOnlyForDeploymentPostprocessing = 0; 301 | shellPath = /bin/sh; 302 | shellScript = "\"${SRCROOT}\"/Scripts/CodeSignUpdate.sh\n"; 303 | }; 304 | /* End PBXShellScriptBuildPhase section */ 305 | 306 | /* Begin PBXSourcesBuildPhase section */ 307 | 53604BFF215E12F300071149 /* Sources */ = { 308 | isa = PBXSourcesBuildPhase; 309 | buildActionMask = 2147483647; 310 | files = ( 311 | 53604C07215E12F300071149 /* AppDelegate.swift in Sources */, 312 | 535731AF215E1E5A009FDE00 /* HelperConstants.swift in Sources */, 313 | 53604C2A215E1B0100071149 /* AppProtocol.swift in Sources */, 314 | 53EE395F215E2631007310F5 /* HelperProtocol.swift in Sources */, 315 | 53EE3965215E3A85007310F5 /* HelperAuthorizationRight.swift in Sources */, 316 | 532308FA2163A87600A456CF /* ExtensionsNSTextView.swift in Sources */, 317 | 53EE3961215E3068007310F5 /* HelperAuthorization.swift in Sources */, 318 | ); 319 | runOnlyForDeploymentPostprocessing = 0; 320 | }; 321 | 53604C14215E131D00071149 /* Sources */ = { 322 | isa = PBXSourcesBuildPhase; 323 | buildActionMask = 2147483647; 324 | files = ( 325 | 53C6B95E216767F0001A80FD /* CodesignCheck.swift in Sources */, 326 | 53604C2C215E1C2700071149 /* AppProtocol.swift in Sources */, 327 | 53604C1B215E131E00071149 /* main.swift in Sources */, 328 | 53604C26215E185D00071149 /* HelperProtocol.swift in Sources */, 329 | 53604C22215E13E200071149 /* HelperConstants.swift in Sources */, 330 | 53604C20215E134500071149 /* Helper.swift in Sources */, 331 | 5332883421660D7A00028C27 /* HelperAuthorizationRight.swift in Sources */, 332 | 5332883321660B2400028C27 /* HelperAuthorization.swift in Sources */, 333 | ); 334 | runOnlyForDeploymentPostprocessing = 0; 335 | }; 336 | /* End PBXSourcesBuildPhase section */ 337 | 338 | /* Begin PBXTargetDependency section */ 339 | 53EE395C215E22CE007310F5 /* PBXTargetDependency */ = { 340 | isa = PBXTargetDependency; 341 | target = 53604C17215E131D00071149 /* com.github.erikberglund.SwiftPrivilegedHelper */; 342 | targetProxy = 53EE395B215E22CE007310F5 /* PBXContainerItemProxy */; 343 | }; 344 | /* End PBXTargetDependency section */ 345 | 346 | /* Begin PBXVariantGroup section */ 347 | 53604C0A215E12F400071149 /* MainMenu.xib */ = { 348 | isa = PBXVariantGroup; 349 | children = ( 350 | 53604C0B215E12F400071149 /* Base */, 351 | ); 352 | name = MainMenu.xib; 353 | sourceTree = ""; 354 | }; 355 | /* End PBXVariantGroup section */ 356 | 357 | /* Begin XCBuildConfiguration section */ 358 | 53604C0F215E12F400071149 /* Debug */ = { 359 | isa = XCBuildConfiguration; 360 | buildSettings = { 361 | ALWAYS_SEARCH_USER_PATHS = NO; 362 | CLANG_ANALYZER_NONNULL = YES; 363 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 364 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 365 | CLANG_CXX_LIBRARY = "libc++"; 366 | CLANG_ENABLE_MODULES = YES; 367 | CLANG_ENABLE_OBJC_ARC = YES; 368 | CLANG_ENABLE_OBJC_WEAK = YES; 369 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 370 | CLANG_WARN_BOOL_CONVERSION = YES; 371 | CLANG_WARN_COMMA = YES; 372 | CLANG_WARN_CONSTANT_CONVERSION = YES; 373 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 374 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 375 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 376 | CLANG_WARN_EMPTY_BODY = YES; 377 | CLANG_WARN_ENUM_CONVERSION = YES; 378 | CLANG_WARN_INFINITE_RECURSION = YES; 379 | CLANG_WARN_INT_CONVERSION = YES; 380 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 381 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 382 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 383 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 384 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 385 | CLANG_WARN_STRICT_PROTOTYPES = YES; 386 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 387 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 388 | CLANG_WARN_UNREACHABLE_CODE = YES; 389 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 390 | CODE_SIGN_IDENTITY = "Mac Developer"; 391 | COPY_PHASE_STRIP = NO; 392 | DEBUG_INFORMATION_FORMAT = dwarf; 393 | ENABLE_STRICT_OBJC_MSGSEND = YES; 394 | ENABLE_TESTABILITY = YES; 395 | GCC_C_LANGUAGE_STANDARD = gnu11; 396 | GCC_DYNAMIC_NO_PIC = NO; 397 | GCC_NO_COMMON_BLOCKS = YES; 398 | GCC_OPTIMIZATION_LEVEL = 0; 399 | GCC_PREPROCESSOR_DEFINITIONS = ( 400 | "DEBUG=1", 401 | "$(inherited)", 402 | ); 403 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 404 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 405 | GCC_WARN_UNDECLARED_SELECTOR = YES; 406 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 407 | GCC_WARN_UNUSED_FUNCTION = YES; 408 | GCC_WARN_UNUSED_VARIABLE = YES; 409 | MACOSX_DEPLOYMENT_TARGET = 10.13; 410 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 411 | MTL_FAST_MATH = YES; 412 | ONLY_ACTIVE_ARCH = YES; 413 | SDKROOT = macosx; 414 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 415 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 416 | }; 417 | name = Debug; 418 | }; 419 | 53604C10215E12F400071149 /* Release */ = { 420 | isa = XCBuildConfiguration; 421 | buildSettings = { 422 | ALWAYS_SEARCH_USER_PATHS = NO; 423 | CLANG_ANALYZER_NONNULL = YES; 424 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 425 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 426 | CLANG_CXX_LIBRARY = "libc++"; 427 | CLANG_ENABLE_MODULES = YES; 428 | CLANG_ENABLE_OBJC_ARC = YES; 429 | CLANG_ENABLE_OBJC_WEAK = YES; 430 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 431 | CLANG_WARN_BOOL_CONVERSION = YES; 432 | CLANG_WARN_COMMA = YES; 433 | CLANG_WARN_CONSTANT_CONVERSION = YES; 434 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 435 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 436 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 437 | CLANG_WARN_EMPTY_BODY = YES; 438 | CLANG_WARN_ENUM_CONVERSION = YES; 439 | CLANG_WARN_INFINITE_RECURSION = YES; 440 | CLANG_WARN_INT_CONVERSION = YES; 441 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 442 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 443 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 444 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 445 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 446 | CLANG_WARN_STRICT_PROTOTYPES = YES; 447 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 448 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 449 | CLANG_WARN_UNREACHABLE_CODE = YES; 450 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 451 | CODE_SIGN_IDENTITY = "Mac Developer"; 452 | COPY_PHASE_STRIP = NO; 453 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 454 | ENABLE_NS_ASSERTIONS = NO; 455 | ENABLE_STRICT_OBJC_MSGSEND = YES; 456 | GCC_C_LANGUAGE_STANDARD = gnu11; 457 | GCC_NO_COMMON_BLOCKS = YES; 458 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 459 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 460 | GCC_WARN_UNDECLARED_SELECTOR = YES; 461 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 462 | GCC_WARN_UNUSED_FUNCTION = YES; 463 | GCC_WARN_UNUSED_VARIABLE = YES; 464 | MACOSX_DEPLOYMENT_TARGET = 10.13; 465 | MTL_ENABLE_DEBUG_INFO = NO; 466 | MTL_FAST_MATH = YES; 467 | SDKROOT = macosx; 468 | SWIFT_COMPILATION_MODE = wholemodule; 469 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 470 | }; 471 | name = Release; 472 | }; 473 | 53604C12215E12F400071149 /* Debug */ = { 474 | isa = XCBuildConfiguration; 475 | buildSettings = { 476 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 477 | CODE_SIGN_STYLE = Automatic; 478 | COMBINE_HIDPI_IMAGES = YES; 479 | DEVELOPMENT_TEAM = Y7QFC8672N; 480 | INFOPLIST_FILE = SwiftPrivilegedHelperApplication/Info.plist; 481 | LD_RUNPATH_SEARCH_PATHS = ( 482 | "$(inherited)", 483 | "@executable_path/../Frameworks", 484 | ); 485 | MACOSX_DEPLOYMENT_TARGET = 10.11; 486 | PRODUCT_BUNDLE_IDENTIFIER = com.github.erikberglund.SwiftPrivilegedHelperApplication; 487 | PRODUCT_NAME = "$(TARGET_NAME)"; 488 | SWIFT_VERSION = 4.2; 489 | }; 490 | name = Debug; 491 | }; 492 | 53604C13215E12F400071149 /* Release */ = { 493 | isa = XCBuildConfiguration; 494 | buildSettings = { 495 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 496 | CODE_SIGN_STYLE = Automatic; 497 | COMBINE_HIDPI_IMAGES = YES; 498 | DEVELOPMENT_TEAM = Y7QFC8672N; 499 | INFOPLIST_FILE = SwiftPrivilegedHelperApplication/Info.plist; 500 | LD_RUNPATH_SEARCH_PATHS = ( 501 | "$(inherited)", 502 | "@executable_path/../Frameworks", 503 | ); 504 | MACOSX_DEPLOYMENT_TARGET = 10.11; 505 | PRODUCT_BUNDLE_IDENTIFIER = com.github.erikberglund.SwiftPrivilegedHelperApplication; 506 | PRODUCT_NAME = "$(TARGET_NAME)"; 507 | SWIFT_VERSION = 4.2; 508 | }; 509 | name = Release; 510 | }; 511 | 53604C1D215E131E00071149 /* Debug */ = { 512 | isa = XCBuildConfiguration; 513 | buildSettings = { 514 | CODE_SIGN_STYLE = Automatic; 515 | DEVELOPMENT_TEAM = Y7QFC8672N; 516 | INFOPLIST_FILE = "$(SRCROOT)/SwiftPrivilegedHelper/Helper-Info.plist"; 517 | MACOSX_DEPLOYMENT_TARGET = 10.11; 518 | OTHER_LDFLAGS = ( 519 | "-sectcreate", 520 | __TEXT, 521 | __info_plist, 522 | "\"$(SRCROOT)/SwiftPrivilegedHelper/Helper-Info.plist\"", 523 | "-sectcreate", 524 | __TEXT, 525 | __launchd_plist, 526 | "\"$(SRCROOT)/SwiftPrivilegedHelper/Helper-Launchd.plist\"", 527 | ); 528 | PRODUCT_BUNDLE_IDENTIFIER = com.github.erikberglund.SwiftPrivilegedHelper; 529 | PRODUCT_NAME = "$(TARGET_NAME)"; 530 | SKIP_INSTALL = YES; 531 | SWIFT_VERSION = 4.2; 532 | }; 533 | name = Debug; 534 | }; 535 | 53604C1E215E131E00071149 /* Release */ = { 536 | isa = XCBuildConfiguration; 537 | buildSettings = { 538 | CODE_SIGN_STYLE = Automatic; 539 | DEVELOPMENT_TEAM = Y7QFC8672N; 540 | INFOPLIST_FILE = "$(SRCROOT)/SwiftPrivilegedHelper/Helper-Info.plist"; 541 | MACOSX_DEPLOYMENT_TARGET = 10.11; 542 | OTHER_LDFLAGS = ( 543 | "-sectcreate", 544 | __TEXT, 545 | __info_plist, 546 | "\"$(SRCROOT)/SwiftPrivilegedHelper/Helper-Info.plist\"", 547 | "-sectcreate", 548 | __TEXT, 549 | __launchd_plist, 550 | "\"$(SRCROOT)/SwiftPrivilegedHelper/Helper-Launchd.plist\"", 551 | ); 552 | PRODUCT_BUNDLE_IDENTIFIER = com.github.erikberglund.SwiftPrivilegedHelper; 553 | PRODUCT_NAME = "$(TARGET_NAME)"; 554 | SKIP_INSTALL = YES; 555 | SWIFT_VERSION = 4.2; 556 | }; 557 | name = Release; 558 | }; 559 | /* End XCBuildConfiguration section */ 560 | 561 | /* Begin XCConfigurationList section */ 562 | 53604BFE215E12F300071149 /* Build configuration list for PBXProject "SwiftPrivilegedHelperApplication" */ = { 563 | isa = XCConfigurationList; 564 | buildConfigurations = ( 565 | 53604C0F215E12F400071149 /* Debug */, 566 | 53604C10215E12F400071149 /* Release */, 567 | ); 568 | defaultConfigurationIsVisible = 0; 569 | defaultConfigurationName = Release; 570 | }; 571 | 53604C11215E12F400071149 /* Build configuration list for PBXNativeTarget "SwiftPrivilegedHelperApplication" */ = { 572 | isa = XCConfigurationList; 573 | buildConfigurations = ( 574 | 53604C12215E12F400071149 /* Debug */, 575 | 53604C13215E12F400071149 /* Release */, 576 | ); 577 | defaultConfigurationIsVisible = 0; 578 | defaultConfigurationName = Release; 579 | }; 580 | 53604C1C215E131E00071149 /* Build configuration list for PBXNativeTarget "com.github.erikberglund.SwiftPrivilegedHelper" */ = { 581 | isa = XCConfigurationList; 582 | buildConfigurations = ( 583 | 53604C1D215E131E00071149 /* Debug */, 584 | 53604C1E215E131E00071149 /* Release */, 585 | ); 586 | defaultConfigurationIsVisible = 0; 587 | defaultConfigurationName = Release; 588 | }; 589 | /* End XCConfigurationList section */ 590 | }; 591 | rootObject = 53604BFB215E12F300071149 /* Project object */; 592 | } 593 | -------------------------------------------------------------------------------- /SwiftPrivilegedHelperApplication/SwiftPrivilegedHelperApplication.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SwiftPrivilegedHelperApplication/SwiftPrivilegedHelperApplication.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /SwiftPrivilegedHelperApplication/SwiftPrivilegedHelperApplication/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // SwiftPrivilegedHelperApplication 4 | // 5 | // Created by Erik Berglund on 2018-10-01. 6 | // Copyright © 2018 Erik Berglund. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | import ServiceManagement 11 | 12 | @NSApplicationMain 13 | class AppDelegate: NSObject, NSApplicationDelegate, AppProtocol { 14 | 15 | // MARK: - 16 | // MARK: IBOutlets 17 | 18 | @IBOutlet weak var window: NSWindow! 19 | 20 | @IBOutlet weak var buttonInstallHelper: NSButton! 21 | @IBOutlet weak var buttonDestroyCachedAuthorization: NSButton! 22 | @IBOutlet weak var buttonRunCommand: NSButton! 23 | 24 | @IBOutlet weak var textFieldHelperInstalled: NSTextField! 25 | @IBOutlet weak var textFieldAuthorizationCached: NSTextField! 26 | @IBOutlet weak var textFieldInput: NSTextField! 27 | 28 | @IBOutlet var textViewOutput: NSTextView! 29 | 30 | @IBOutlet weak var checkboxRequireAuthentication: NSButton! 31 | @IBOutlet weak var checkboxCacheAuthentication: NSButton! 32 | 33 | // MARK: - 34 | // MARK: Variables 35 | 36 | private var currentHelperConnection: NSXPCConnection? 37 | 38 | @objc dynamic private var currentHelperAuthData: NSData? 39 | private let currentHelperAuthDataKeyPath: String 40 | 41 | @objc dynamic private var helperIsInstalled = false 42 | private let helperIsInstalledKeyPath: String 43 | 44 | // MARK: - 45 | // MARK: Computed Variables 46 | 47 | var inputPath: String? { 48 | if self.textFieldInput.stringValue.isEmpty { 49 | self.textViewOutput.appendText("You need to enter a path to a directory!") 50 | return nil 51 | } 52 | 53 | let inputURL = URL(fileURLWithPath: self.textFieldInput.stringValue) 54 | do { 55 | guard try inputURL.checkResourceIsReachable() else { return nil } 56 | } catch { 57 | self.textViewOutput.appendText("\(self.textFieldInput.stringValue) is not a valid path!") 58 | return nil 59 | } 60 | return inputURL.path 61 | } 62 | 63 | // MARK: - 64 | // MARK: NSApplicationDelegate Methods 65 | 66 | override init() { 67 | self.currentHelperAuthDataKeyPath = NSStringFromSelector(#selector(getter: self.currentHelperAuthData)) 68 | self.helperIsInstalledKeyPath = NSStringFromSelector(#selector(getter: self.helperIsInstalled)) 69 | super.init() 70 | } 71 | 72 | override func awakeFromNib() { 73 | self.configureBindings() 74 | } 75 | 76 | func applicationDidFinishLaunching(_ aNotification: Notification) { 77 | 78 | // Update the current authorization database right 79 | // This will prmpt the user for authentication if something needs updating. 80 | 81 | do { 82 | try HelperAuthorization.authorizationRightsUpdateDatabase() 83 | } catch { 84 | self.textViewOutput.appendText("Failed to update the authorization database rights with error: \(error)") 85 | } 86 | 87 | // Check if the current embedded helper tool is installed on the machine. 88 | 89 | self.helperStatus { installed in 90 | OperationQueue.main.addOperation { 91 | self.textFieldHelperInstalled.stringValue = (installed) ? "Yes" : "No" 92 | self.setValue(installed, forKey: self.helperIsInstalledKeyPath) 93 | } 94 | } 95 | } 96 | 97 | // MARK: - 98 | // MARK: Initialization 99 | 100 | func configureBindings() { 101 | 102 | // Button: Install Helper 103 | self.buttonInstallHelper.bind(.enabled, 104 | to: self, 105 | withKeyPath: self.helperIsInstalledKeyPath, 106 | options: [.continuouslyUpdatesValue: true, 107 | .valueTransformerName: NSValueTransformerName.negateBooleanTransformerName]) 108 | 109 | // Button: Run Command 110 | self.buttonRunCommand.bind(.enabled, 111 | to: self, 112 | withKeyPath: self.helperIsInstalledKeyPath, 113 | options: [.continuouslyUpdatesValue: true]) 114 | 115 | } 116 | 117 | // MARK: - 118 | // MARK: IBActions 119 | 120 | @IBAction func buttonInstallHelper(_ sender: Any) { 121 | do { 122 | if try self.helperInstall() { 123 | OperationQueue.main.addOperation { 124 | self.textViewOutput.appendText("Helper installed successfully.") 125 | self.textFieldHelperInstalled.stringValue = "Yes" 126 | self.setValue(true, forKey: self.helperIsInstalledKeyPath) 127 | } 128 | return 129 | } else { 130 | OperationQueue.main.addOperation { 131 | self.textFieldHelperInstalled.stringValue = "No" 132 | self.textViewOutput.appendText("Failed install helper with unknown error.") 133 | } 134 | } 135 | } catch { 136 | OperationQueue.main.addOperation { 137 | self.textViewOutput.appendText("Failed to install helper with error: \(error)") 138 | } 139 | } 140 | OperationQueue.main.addOperation { 141 | self.textFieldHelperInstalled.stringValue = "No" 142 | self.setValue(false, forKey: self.helperIsInstalledKeyPath) 143 | } 144 | } 145 | 146 | @IBAction func buttonDestroyCachedAuthorization(_ sender: Any) { 147 | self.currentHelperAuthData = nil 148 | self.textFieldAuthorizationCached.stringValue = "No" 149 | self.buttonDestroyCachedAuthorization.isEnabled = false 150 | } 151 | 152 | @IBAction func buttonRunCommand(_ sender: Any) { 153 | guard 154 | let inputPath = self.inputPath, 155 | let helper = self.helper(nil) else { return } 156 | 157 | if self.checkboxRequireAuthentication.state == .on { 158 | do { 159 | guard let authData = try self.currentHelperAuthData ?? HelperAuthorization.emptyAuthorizationExternalFormData() else { 160 | self.textViewOutput.appendText("Failed to get the empty authorization external form") 161 | return 162 | } 163 | 164 | helper.runCommandLs(withPath: inputPath, authData: authData) { (exitCode) in 165 | OperationQueue.main.addOperation { 166 | 167 | // Verify that authentication was successful 168 | 169 | guard exitCode != kAuthorizationFailedExitCode else { 170 | self.textViewOutput.appendText("Authentication Failed") 171 | return 172 | } 173 | 174 | self.textViewOutput.appendText("Command exit code: \(exitCode)") 175 | if self.checkboxCacheAuthentication.state == .on, self.currentHelperAuthData == nil { 176 | self.currentHelperAuthData = authData 177 | self.textFieldAuthorizationCached.stringValue = "Yes" 178 | self.buttonDestroyCachedAuthorization.isEnabled = true 179 | } 180 | 181 | } 182 | } 183 | } catch { 184 | self.textViewOutput.appendText("Command failed with error: \(error)") 185 | } 186 | } else { 187 | helper.runCommandLs(withPath: inputPath) { (exitCode) in 188 | self.textViewOutput.appendText("Command exit code: \(exitCode)") 189 | } 190 | } 191 | } 192 | 193 | // MARK: - 194 | // MARK: AppProtocol Methods 195 | 196 | func log(stdOut: String) { 197 | guard !stdOut.isEmpty else { return } 198 | OperationQueue.main.addOperation { 199 | self.textViewOutput.appendText(stdOut) 200 | } 201 | } 202 | 203 | func log(stdErr: String) { 204 | guard !stdErr.isEmpty else { return } 205 | OperationQueue.main.addOperation { 206 | self.textViewOutput.appendText(stdErr) 207 | } 208 | } 209 | 210 | // MARK: - 211 | // MARK: Helper Connection Methods 212 | 213 | func helperConnection() -> NSXPCConnection? { 214 | guard self.currentHelperConnection == nil else { 215 | return self.currentHelperConnection 216 | } 217 | 218 | let connection = NSXPCConnection(machServiceName: HelperConstants.machServiceName, options: .privileged) 219 | connection.exportedInterface = NSXPCInterface(with: AppProtocol.self) 220 | connection.exportedObject = self 221 | connection.remoteObjectInterface = NSXPCInterface(with: HelperProtocol.self) 222 | connection.invalidationHandler = { 223 | self.currentHelperConnection?.invalidationHandler = nil 224 | OperationQueue.main.addOperation { 225 | self.currentHelperConnection = nil 226 | } 227 | } 228 | 229 | self.currentHelperConnection = connection 230 | self.currentHelperConnection?.resume() 231 | 232 | return self.currentHelperConnection 233 | } 234 | 235 | func helper(_ completion: ((Bool) -> Void)?) -> HelperProtocol? { 236 | 237 | // Get the current helper connection and return the remote object (Helper.swift) as a proxy object to call functions on. 238 | 239 | guard let helper = self.helperConnection()?.remoteObjectProxyWithErrorHandler({ error in 240 | self.textViewOutput.appendText("Helper connection was closed with error: \(error)") 241 | if let onCompletion = completion { onCompletion(false) } 242 | }) as? HelperProtocol else { return nil } 243 | return helper 244 | } 245 | 246 | func helperStatus(completion: @escaping (_ installed: Bool) -> Void) { 247 | 248 | // Comppare the CFBundleShortVersionString from the Info.plist in the helper inside our application bundle with the one on disk. 249 | 250 | let helperURL = Bundle.main.bundleURL.appendingPathComponent("Contents/Library/LaunchServices/" + HelperConstants.machServiceName) 251 | guard 252 | let helperBundleInfo = CFBundleCopyInfoDictionaryForURL(helperURL as CFURL) as? [String: Any], 253 | let helperVersion = helperBundleInfo["CFBundleShortVersionString"] as? String, 254 | let helper = self.helper(completion) else { 255 | completion(false) 256 | return 257 | } 258 | 259 | helper.getVersion { installedHelperVersion in 260 | completion(installedHelperVersion == helperVersion) 261 | } 262 | } 263 | 264 | func helperInstall() throws -> Bool { 265 | 266 | // Install and activate the helper inside our application bundle to disk. 267 | 268 | var cfError: Unmanaged? 269 | var authItem = AuthorizationItem(name: kSMRightBlessPrivilegedHelper, valueLength: 0, value:UnsafeMutableRawPointer(bitPattern: 0), flags: 0) 270 | var authRights = AuthorizationRights(count: 1, items: &authItem) 271 | 272 | guard 273 | let authRef = try HelperAuthorization.authorizationRef(&authRights, nil, [.interactionAllowed, .extendRights, .preAuthorize]), 274 | SMJobBless(kSMDomainSystemLaunchd, HelperConstants.machServiceName as CFString, authRef, &cfError) else { 275 | if let error = cfError?.takeRetainedValue() { throw error } 276 | return false 277 | } 278 | 279 | self.currentHelperConnection?.invalidate() 280 | self.currentHelperConnection = nil 281 | 282 | return true 283 | } 284 | } 285 | 286 | -------------------------------------------------------------------------------- /SwiftPrivilegedHelperApplication/SwiftPrivilegedHelperApplication/AppProtocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppProtocol.swift 3 | // SwiftPrivilegedHelperApplication 4 | // 5 | // Created by Erik Berglund on 2018-10-01. 6 | // Copyright © 2018 Erik Berglund. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | @objc(AppProtocol) 12 | protocol AppProtocol { 13 | func log(stdOut: String) -> Void 14 | func log(stdErr: String) -> Void 15 | } 16 | -------------------------------------------------------------------------------- /SwiftPrivilegedHelperApplication/SwiftPrivilegedHelperApplication/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "size" : "16x16", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "size" : "16x16", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "size" : "32x32", 16 | "scale" : "1x" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "size" : "32x32", 21 | "scale" : "2x" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "size" : "128x128", 26 | "scale" : "1x" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "size" : "128x128", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "size" : "256x256", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "size" : "256x256", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "size" : "512x512", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "size" : "512x512", 51 | "scale" : "2x" 52 | } 53 | ], 54 | "info" : { 55 | "version" : 1, 56 | "author" : "xcode" 57 | } 58 | } -------------------------------------------------------------------------------- /SwiftPrivilegedHelperApplication/SwiftPrivilegedHelperApplication/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /SwiftPrivilegedHelperApplication/SwiftPrivilegedHelperApplication/Base.lproj/MainMenu.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | 536 | 537 | 538 | 539 | 540 | 541 | 542 | 543 | 544 | 545 | 546 | 547 | 548 | Default 549 | 550 | 551 | 552 | 553 | 554 | 555 | Left to Right 556 | 557 | 558 | 559 | 560 | 561 | 562 | Right to Left 563 | 564 | 565 | 566 | 567 | 568 | 569 | 570 | 571 | 572 | 573 | Default 574 | 575 | 576 | 577 | 578 | 579 | 580 | Left to Right 581 | 582 | 583 | 584 | 585 | 586 | 587 | Right to Left 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 | 612 | 613 | 614 | 615 | 616 | 617 | 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | 631 | 632 | 633 | 634 | 635 | 636 | 637 | 638 | 639 | 640 | 641 | 642 | 643 | 644 | 645 | 646 | 647 | 648 | 649 | 650 | 651 | 652 | 653 | 654 | 655 | 656 | 657 | 658 | 659 | 660 | 661 | 662 | 663 | 664 | 665 | 666 | 667 | 668 | 669 | 670 | 671 | 672 | 673 | 674 | 675 | 676 | 677 | 678 | 679 | 680 | 681 | 682 | 683 | 684 | 685 | 686 | 687 | 688 | 689 | 690 | 691 | 692 | 693 | 694 | 695 | 696 | 697 | 698 | 699 | 700 | 701 | 702 | 703 | 704 | 705 | 706 | 707 | 708 | 709 | 710 | 711 | 712 | 713 | 714 | 715 | 716 | 717 | 718 | 719 | 720 | 730 | 731 | 732 | 733 | 734 | 735 | 736 | 737 | 738 | 739 | 740 | 741 | 751 | 752 | 753 | 754 | 755 | 756 | 757 | 758 | 759 | 760 | 761 | 762 | 763 | 764 | 765 | 766 | 767 | 771 | 772 | 773 | 774 | 775 | 776 | 786 | 787 | 788 | 789 | 790 | 791 | 792 | 793 | 794 | 795 | 796 | 797 | 798 | 799 | 800 | 801 | 802 | 803 | 804 | 805 | 812 | 813 | 814 | 815 | 816 | 817 | 818 | 819 | 820 | 827 | 828 | 829 | 830 | 831 | 832 | 833 | 834 | 835 | 836 | 837 | 838 | 839 | 840 | 841 | 842 | 843 | 844 | 845 | 846 | 847 | 848 | 849 | 850 | 851 | 852 | 853 | 854 | 855 | 856 | 857 | 858 | 859 | 860 | 861 | 862 | 863 | 864 | 865 | 866 | 867 | 868 | 869 | 870 | 871 | 872 | 873 | -------------------------------------------------------------------------------- /SwiftPrivilegedHelperApplication/SwiftPrivilegedHelperApplication/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSHumanReadableCopyright 26 | Copyright © 2018 Erik Berglund. All rights reserved. 27 | NSMainNibFile 28 | MainMenu 29 | NSPrincipalClass 30 | NSApplication 31 | SMPrivilegedExecutables 32 | 33 | com.github.erikberglund.SwiftPrivilegedHelper 34 | identifier "com.github.erikberglund.SwiftPrivilegedHelper" and anchor apple generic and certificate leaf[subject.CN] = "Mac Developer: Erik Berglund (BXUF2UUW7E)" and certificate 1[field.1.2.840.113635.100.6.2.1] /* exists */ 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /SwiftPrivilegedHelperApplication/Utility/CodesignCheck.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CodesignCheck.swift 3 | // SwiftPrivilegedHelperApplication 4 | // 5 | // Created by Erik Berglund on 2018-10-01. 6 | // Copyright © 2018 Erik Berglund. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Security 11 | 12 | let kSecCSDefaultFlags = 0 13 | 14 | enum CodesignCheckError: Error { 15 | case message(String) 16 | } 17 | 18 | struct CodesignCheck { 19 | 20 | // MARK: - 21 | // MARK: Compare Functions 22 | 23 | public static func codeSigningMatches(pid: pid_t) throws -> Bool { 24 | return try self.codeSigningCertificatesForSelf() == self.codeSigningCertificates(forPID: pid) 25 | } 26 | 27 | // MARK: - 28 | // MARK: Public Functions 29 | 30 | public static func codeSigningCertificatesForSelf() throws -> [SecCertificate] { 31 | guard let secStaticCode = try secStaticCodeSelf() else { return [] } 32 | return try codeSigningCertificates(forStaticCode: secStaticCode) 33 | } 34 | 35 | public static func codeSigningCertificates(forPID pid: pid_t) throws -> [SecCertificate] { 36 | guard let secStaticCode = try secStaticCode(forPID: pid) else { return [] } 37 | return try codeSigningCertificates(forStaticCode: secStaticCode) 38 | } 39 | 40 | public static func codeSigningCertificates(forURL url: URL) throws -> [SecCertificate] { 41 | guard let secStaticCode = try secStaticCode(forURL: url) else { return [] } 42 | return try codeSigningCertificates(forStaticCode: secStaticCode) 43 | } 44 | 45 | // MARK: - 46 | // MARK: Private Functions 47 | 48 | private static func executeSecFunction(_ secFunction: () -> (OSStatus) ) throws { 49 | let osStatus = secFunction() 50 | guard osStatus == errSecSuccess else { 51 | throw CodesignCheckError.message(String(describing: SecCopyErrorMessageString(osStatus, nil))) 52 | } 53 | } 54 | 55 | private static func secStaticCodeSelf() throws -> SecStaticCode? { 56 | var secCodeSelf: SecCode? 57 | try executeSecFunction { SecCodeCopySelf(SecCSFlags(rawValue: 0), &secCodeSelf) } 58 | guard let secCode = secCodeSelf else { 59 | throw CodesignCheckError.message("SecCode returned empty from SecCodeCopySelf") 60 | } 61 | return try secStaticCode(forSecCode: secCode) 62 | } 63 | 64 | private static func secStaticCode(forPID pid: pid_t) throws -> SecStaticCode? { 65 | var secCodePID: SecCode? 66 | try executeSecFunction { SecCodeCopyGuestWithAttributes(nil, [kSecGuestAttributePid: pid] as CFDictionary, [], &secCodePID) } 67 | guard let secCode = secCodePID else { 68 | throw CodesignCheckError.message("SecCode returned empty from SecCodeCopyGuestWithAttributes") 69 | } 70 | return try secStaticCode(forSecCode: secCode) 71 | } 72 | 73 | private static func secStaticCode(forURL url: URL) throws -> SecStaticCode? { 74 | var secStaticCodePath: SecStaticCode? 75 | try executeSecFunction { SecStaticCodeCreateWithPath(url as CFURL, [], &secStaticCodePath) } 76 | guard let secStaticCode = secStaticCodePath else { 77 | throw CodesignCheckError.message("SecStaticCode returned empty from SecStaticCodeCreateWithPath") 78 | } 79 | return secStaticCode 80 | } 81 | 82 | private static func secStaticCode(forSecCode secCode: SecCode) throws -> SecStaticCode? { 83 | var secStaticCodeCopy: SecStaticCode? 84 | try executeSecFunction { SecCodeCopyStaticCode(secCode, [], &secStaticCodeCopy) } 85 | guard let secStaticCode = secStaticCodeCopy else { 86 | throw CodesignCheckError.message("SecStaticCode returned empty from SecCodeCopyStaticCode") 87 | } 88 | return secStaticCode 89 | } 90 | 91 | private static func isValid(secStaticCode: SecStaticCode) throws { 92 | try executeSecFunction { SecStaticCodeCheckValidity(secStaticCode, SecCSFlags(rawValue: kSecCSDoNotValidateResources | kSecCSCheckNestedCode), nil) } 93 | } 94 | 95 | private static func secCodeInfo(forStaticCode secStaticCode: SecStaticCode) throws -> [String: Any]? { 96 | try isValid(secStaticCode: secStaticCode) 97 | var secCodeInfoCFDict: CFDictionary? 98 | try executeSecFunction { SecCodeCopySigningInformation(secStaticCode, SecCSFlags(rawValue: kSecCSSigningInformation), &secCodeInfoCFDict) } 99 | guard let secCodeInfo = secCodeInfoCFDict as? [String: Any] else { 100 | throw CodesignCheckError.message("CFDictionary returned empty from SecCodeCopySigningInformation") 101 | } 102 | return secCodeInfo 103 | } 104 | 105 | private static func codeSigningCertificates(forStaticCode secStaticCode: SecStaticCode) throws -> [SecCertificate] { 106 | guard 107 | let secCodeInfo = try secCodeInfo(forStaticCode: secStaticCode), 108 | let secCertificates = secCodeInfo[kSecCodeInfoCertificates as String] as? [SecCertificate] else { return [] } 109 | return secCertificates 110 | } 111 | } 112 | --------------------------------------------------------------------------------