In this example a simple UI application (Client
target) launches a Helper Tool (root process, see com.smjobblesssample.installer
target) in order to perform installation of some other application. Installation usually requires root privileges, so we take this use case as an example.
Also this sample shows how to establish an XPC connection between UI application and the Helper Tool.
In order to see the code in this repository working: Replace "Mac Developer: mail@example.com (ABCDEFGHIJ)" with the certificate, that you use to sign targets.
Client
's Info.plistcom.smjobblesssample.installer
's Info.plist- XPCServer.swift,
entitlements
constant.
The first Info.plist file is located in Client
target, the second — in com.smjobblesssample.installer
target.
The correct name of the certificate you use can be found in Keychain
. Double-click the certificate in the list and copy it's Common name
.
Term | Definition |
---|---|
Client | UI application, that requires some installation services |
Server | Helper tool (provides some installation services) |
launchd |
A system daemon, that manages loading all other processes |
launchd job label |
A unique string, that describes the service, that is provided by the Helper Tool. In order to start the Helper tool, we need to register its label with launchd . A convention is to use reverse DNS notation, like com.myApp.myService or com.myCompany.myApp.myService . For example: com.superDruperReader.installation . |
- Create а Client target; should be a bundle.
Cocoa Application
target type works great for this purpose.- Client's Signing & Capabilities -> remove App Sandbox.
- Create а Server target (
Command-Line Tool
). The product name for this target should be the same as the launchd job label.
-
Client's Build Phases → Target Dependencies → add Server target to dependencies list
-
Client's Build Phases → Add
Copy Files
phase. Destination: select 'Wrapper' Subpath: pasteContents/Library/LaunchServices
Add the Server application to this Copy Files phase.
- Check that you sign both applications.
- To client's
Info.plist
addSMPrivilegedExecutables
key with typeDictionary
Add there a key-value pair:
Key: launchd job label
Value: signing requirements
Example:
Key | Value |
---|---|
com.smjobblesssample.installer | identifier "com.smjobblesssample.installer" and anchor apple generic and certificate leaf[subject.CN] = "Mac Developer: mail@example.com (ABCDEFGHIJ)" |
- Create an
Info.plist
andlaunchd.plist
files (you can name them whatever) for the Server.
- Add
Label
key with launchd job label value. - Add
MachServices
key withDictionary
type. Add there a key-value pair with launchd job label as key andYES
Boolean as a value. - Now we need to embed this file into the Helper binary file. Go to
Build Settings
and findOther Linker Flags
(OTHER_LDFLAGS
). Add 4 rows in given order:
-sectcreate
__TEXT
__launchd_plist
$(SRCROOT)/${TARGET_NAME}/launchd.plist
-
Add
CFBundleIdentifier
and paste there its bundle identifier. -
Add
CFBundleInfoDictionaryVersion
with string value6.0
. -
Add
SMAuthorizedClients
key with Array of Strings type to Info.plist Every entry in this array is a description for signing requirements for each client.
Example:
identifier "com.smjobblesssample.uiapplication" and anchor apple generic and certificate leaf[subject.CN] = "Mac Developer: mail@example.com (ABCDEFGHIJ)"
-
Open Server's
Build Settings
and find a setting calledInfo.plist File
(INFOPLIST_FILE
). Set the path to the Server's Info.plist. For example:$(SRCROOT)/$(TARGET_NAME)/Info.plist
-
Now when the Server target knows where it's Info.plist is located, let's build it into the resulting binary file. Find another Build Setting:
Create Info.plist Section in Binary
(CREATE_INFOPLIST_SECTION_IN_BINARY
) and set it toYES
.
The SMJobBless() is designed in a way, that it is easy to make mistakes, that lead to serious security flaws. For details see SecuringXPCConnection.md
During debug you will probably need to uninstall the Privileged Helper several times. To make it more convenient I have added a bash script, that one can execute in Terminal every time they need to uninstall the Helper. It is located in smjobbless/Handy Scripts/uninstall_privileged_helper.sh
.
If SMJobBless() fails, look for error code explanation in SMErrors.h
header of the ServiceManagement framework. At the time this instruction is created it contains such errors:
* @const kSMErrorInternalFailure
* An internal failure has occurred.
*
* @const kSMErrorInvalidSignature
* The Application's code signature does not meet the requirements to perform
* the operation.
*
* @const kSMErrorAuthorizationFailure
* The request required authorization (i.e. adding a job to the
* {@link kSMDomainSystemLaunchd} domain) but the AuthorizationRef did not
* contain the required right.
*
* @const kSMErrorToolNotValid
* The specified path does not exist or the tool at the specified path is not
* valid.
*
* @const kSMErrorJobNotFound
* A job with the given label could not be found.
*
* @const kSMErrorServiceUnavailable
* The service required to perform this operation is unavailable or is no longer
* accepting requests.
*/
enum {
kSMErrorInternalFailure = 2,
kSMErrorInvalidSignature,
kSMErrorAuthorizationFailure,
kSMErrorToolNotValid,
kSMErrorJobNotFound,
kSMErrorServiceUnavailable,
kSMErrorJobPlistNotFound,
kSMErrorJobMustBeEnabled,
kSMErrorInvalidPlist,
};
As you can see, there are lots of nuances to remember. In order to help the developers Apple provides a python script SMJobBlessUtil.py
, which is located in the 'Handy Scripts' directory in the root of this sample project. It offers 2 functions:
check
setreq
check
allows to find mistakes in setup. Just run path/to/SMJobBlessUtil.py check path/to/built/application
.
Example:
/Users/aronskaya/Projects/SMJobBlessTest/SMJobBlessUtil.py check /Users/aronskaya/Library/Developer/Xcode/DerivedData/SMJobBlessSample-fhbrrsjbucwivtanoulbnntvssky/Build/Products/Debug/Client.app
setreq
allows to update info.plist files in order to fulfill requirements. Run it like that: setreq /path/to/app /path/to/app/Info.plist /path/to/tool/Info.plist
Example:
/Users/aronskaya/Projects/SMJobBlessTest/SMJobBlessUtil.py setreq /Users/aronskaya/Library/Developer/Xcode/DerivedData/SMJobBlessSample-fhbrrsjbucwivtanoulbnntvssky/Build/Products/Debug/Client.app /Users/aronskaya/Projects/SMJobBlessTest/Client/Info.plist /Users/aronskaya/Projects/SMJobBlessTest/com.smjobblesssample.installer/Info.plist
It is especially useful if you encounter difficulties with signing requirements. The tool will print the exact signing requrement, that you should put into the Info.plist.
When your Helper app launches, it prints its NSLog statements into Console.app (it is not attached to Xcode's debugger). Look for the logs there.
In this instruction you could see some 'magic' steps. If you are interested in details, why it works the way it does, please, refer to some documentation:
- How to describe code signing requirements: Apple's Code Signing Requirements Language
- Documentation on making
.plist
s for registration of the Server with launchd: seeman launchd.plist
in Terminal.