├── .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 |
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 |
768 |
769 |
770 |
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 |
--------------------------------------------------------------------------------