问题
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?
回答1:
The key part of this approach is described under the "PROPERTY LISTS" section under "How It Works" of the ReadMe.txt:
[…] when you sign the helper tool with a Developer ID, Xcode automatically sets the helper tool's designated requirement like this, and that's what you should use for SMPrivilegedExecutables. Moreover, this is what the "setreq" command shown above does: extracts the designated requirement from the built tool and put it into the app's Info.plist source code.
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:
In your app's Info.plist, abbreviate the requirement under
SMPrivilegedExecutables
to just match the helper's identifier:<string>identifier "com.domain.AppName.SampleService"</string>
- In your helper's Info.plist, abbreviate the requirement under
SMAuthorizedClients
to just match the app's identifier:
<string>identifier "com.domain.AppName"</string>
- Ignore the "Building and Running the Sample" instructions of the ReadMe.txt, and intead simply build and run the project as normal.
- In your helper's Info.plist, abbreviate the requirement under
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
and chown
.
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 are in the original example's SMAuthorizedClients
and SMPrivilegedExecutables
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.
To help paint the picture a bit, here's a rough rundown of how this plays out:
- Your user grants authorization to launchd to install the helper daemon labeled
com.domain.AppName.SampleService
. - launchd locates the
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.) - With the valid helper tool installed, your app makes a request to launchd to spawn the helper under your control. At this point, launchd consults the
SMAuthorizedClients
section of your helper tool's Info.plist to ensure the app actually does have the right to run the tool. And, of course, it verifies your app's signature to ensure it hasn't been tampered with.
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.
回答2:
You are moving in right direction. Currently privileged helper tool is the best practice for executing task in privileged mode. For doing that you can use Swift too but just replace C version of functions call with Swift. (Apple has introduced alternatives in 10.11 SDK) For example instead of
Boolean SMJobBless( CFStringRef domain, CFStringRef executableLabel, AuthorizationRef auth, CFErrorRef *outError);
you can use:
SMJobBless(_: CFString!, _: CFString, _: AuthorizationRef, _: UnsafeMutablePointer<Unmanaged<CFError>?>) -> UInt8
But I have never seen examples of privileged helper tool in Internet... So you need see into Objective C code. Fortunately Obj C code not much.
来源:https://stackoverflow.com/questions/34939243/gain-administration-privileges-with-swift-for-a-mac-application