I am writing a pice of software that needs to often run a command with root privileges.
Right now, I am doing this by asking the user for their password once, saving it and then providing that password to NSAppleScript
as an argument along with with administrator privileges
.
This obviously is really insecure for the user as someone could gain access to their password.
I've been searching for a better part of a week and cannot find the solution.
SMJobBless seems to allow you to install your application with a higher privilege.
I have followed app's example and I am getting an error from their SMJobBlessUtil script.
Here is the error:
SMJobBlessUtil.py: tool designated requirement (identifier "com.domain.AppName.SampleService" and anchor apple generic and certificate leaf[subject.CN] = "Mac Developer: firstName lastName (XXXXXXXXXX)" and certificate 1[field.1.2.840.113635.100.6.2.1] /* exists */) doesn't match entry in 'SMPrivilegedExecutables' (anchor apple generic and identifier "com.domain.AppName.SampleService" and (certificate leaf[field.1.2.840.113635.100.6.1.9] /* exists */ or certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.CN] = "Mac Developer: firstName lastName (XXXXXXXXXX)")
Obviously, something is wrong. Here are the respective plists
Services Info plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleIdentifier</key>
<string>com.domain.AppName.SampleService</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>SampleService</string>
<key>CFBundleVersion</key>
<string>6</string>
<key>SMAuthorizedClients</key>
<array>
<string>anchor apple generic and identifier "com.domain.AppName" and (certificate leaf[field.1.2.840.113635.100.6.1.9] /* exists */ or certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = xxxxxxxxxx)</string>
</array>
</dict>
</plist>
Apps Info plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleDisplayName</key>
<dict/>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleGetInfoString</key>
<dict/>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>Away</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0.99</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>9</string>
<key>LSApplicationCategoryType</key>
<string>public.app-category.utilities</string>
<key>LSMinimumSystemVersion</key>
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
<key>LSUIElement</key>
<true/>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2016 firstName lastName. All rights reserved.</string>
<key>NSMainStoryboardFile</key>
<string>Main</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
<key>SMPrivilegedExecutables</key>
<dict>
<key>com.domain.AppName.SampleService</key>
<string>anchor apple generic and identifier "com.domain.AppName.SampleService" and (certificate leaf[field.1.2.840.113635.100.6.1.9] /* exists */ or certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.CN] = "Mac Developer: firstName lastName (XXXXXXXXXX)"</string>
</dict>
</dict>
</plist>
I've looked at this stackoverflow post and many others like it. As I understand them , I have my plists setup correctly. What am I doing wrong?
The key part of this approach is described under the "PROPERTY LISTS" section under "How It Works" of the ReadMe.txt:
Since you are not signing the products (at least, not with the certificate described in your examples), this process will always fail.
If you are not in the Developer Program, you can create a self-signed certificate to sign with. However, this more or less defeats the purpose of the signing requirement. If you do not plan on enrolling in the Developer Program, you should be able to abbreviate the process as follows:
SMPrivilegedExecutables
to just match the helper's identifier:<string>identifier "com.domain.AppName.SampleService"</string>
SMAuthorizedClients
to just match the app's identifier:<string>identifier "com.domain.AppName"</string>
I can't say I recommend this of course; these signing requirements exist for good reason. It is at least better than the final alternative however, which would be using that NSAppleScript to give a helper executable a root setuid bit via
chmod
andchown
.Addendum to elaborate on some of the concepts in play here:
Running privileged code comes with a lot of potential security holes; safely authenticating the user is only the first step. Delegating all privileged operations to a separate process is another strong step, but the major issue that remains is how to ensure that your app – the one the user actually granted privileges for - is the only entity capable of utilizing the privileged access.
Apple's example demonstrates the use of code signing to solve this problem. Just in case you're not familiar: Code signing involves marking your final products cryptographically in such a way that OS X can verify your programs haven't been replaced with compromised versions. Those extra "certificate leaf" references in the original example's
SMAuthorizedClients
andSMPrivilegedExecutables
are specifically for this; they describe the the certificate that your app and helper must have been signed with in order to interact with one another.are documented to serve other purposes than validating code signing during interaction. As pointed out by this blog post and the corresponding CVE, client programmers are responsible for ensuring cryptographicly safe inter-process communication between helper tool and app, e.g. using the SecCode API.To help paint the picture a bit, here's a rough rundown of how this plays out:
com.domain.AppName.SampleService
.com.domain.AppName.SampleService
entry underSMPrivilegedExecutables
in your app's Info.plist; this describes the certificate that the helper's binary should be signed with. (If they do not match, then theoretically an attacker has replaced your helper tool with their own version in order to run it as root.) Note that the documentation specifically describes the keySMPrivilegedExecutables
to be only relevant for updating purposes:Getting back to your scenario, the way your products are currently working is by eliminating the signing steps. The only thing you have instructed launchd to check is whether your app's Info.plist lists its ID as "com.domain.AppName". Since there's nothing stopping an attacker from changing their Info.plist to say this as well, you're banking on the hope that they couldn't use your helper tool to do any harm once they have control of it.
Additional addendum outlining the alternatives:
As I alluded to previously, it is possible to sign your code using a certificate you created yourself in Keychain Access. However, since this certificate would not be registered with an authority (i.e. Apple), it wouldn't be any more difficult to spoof as
com.domain.AppName
would.Implement your own cryptographic solution as part of the communication between your app and helper. As an example, you could could generate a pair of keys during installation of the helper, store them via Keychain Services so that your programs have access to them, and verify them against one another when using the helper in the future.
Enroll in the Apple Developer program in order to sign your code as intended by Apple; this gives you the additional benefit of OS X not scaring away your users with the "unidentified developer" schtick.