erikberglund / SwiftPrivilegedHelper

Example application using a privileged helper tool with authentication in an unsandboxed application written in Swift
MIT License
182 stars 34 forks source link

Failed to install helper with error: Error Domain=CFErrorDomainLaunchd Code=4 "(null)" #29

Open zjz2020 opened 3 years ago

zjz2020 commented 3 years ago

Failed to install helper with error: Error Domain=CFErrorDomainLaunchd Code=4 "(null)" How to deal with this problem

chipjarred commented 3 years ago

That error indicates SMJobBless failed. The most likely cause is that your app is sandboxed, which doesn't support privileged helper tools. So make sure you remove the sandbox capability from the host app in Xcode. That means you won't be able to put your app in the AppStore without getting special approval from Apple, which for security reasons, they are hesitant to grant.

Also check that your helper tool has actually been copied into your app's bundle.

Another thing that might help is to use the Swift script from my fork of this project instead of the shell script from this one. It does a couple of extra checks that the shell script doesn't do, but more importantly, it can actually emit error messages that show up in your build log. It's possible one of the extra checks may catch something, or that there are errors the shell script is catching but unable to emit errors for, because it's using stdout for string building. You can find it in the Scripts folder, along with its own README describing how to use it.

abstertee commented 3 years ago

@chipjarred can you explain a bit more about about how to remove the sandbox? Also, can you expand on Also check that your helper tool has actually been copied into your app's bundle. ?

chipjarred commented 3 years ago

@abstertee Regarding removing the sandbox, you do that by clicking on your project in Xcode's project navigator (side-bar on the left that shows files and folders). That will bring up your project settings in the editor view. In that view, select your main app's target, then click the "Signing & Capabilities" tab. By default a macOS app project will contain a signing section at the top followed by App Sandbox followed by Hardened Runtime. All the way on the right side of the editor view, across from App Sandbox there is little "x" you can click to remove the sandbox. Don't remove Hardened Runtime... you want that. Just remove App Sandbox. Now after rebuilding the app, it will run directly in macOS, not in a container. If there is no App Sandbox there, then its already removed.

Just as a quick reminder, if you remove the sandbox, Apple won't allow your app in the App Store, so if this not just project for your personal use, you'll have to distribute it independently.

As for checking that the helper tool has been copied to your app's bundle... As you probably already know, when you build your app, the actual executable binary is not the only thing Xcode builds. It creates a bundle, a special directory that appears as a file in the Finder, and places the executable binary inside of it along with other resources like storyboards, assets, etc... It has a specific folder structure. If your build is working correctly, your helper tool should be copied into that bundle as well.

So to check if its there, you have to open the bundle, which means finding the built app. It's buried deep inside a bunch of build subfolders inside your Library folder, so the easiest way to do that is to right-click on the app's product in Xcode's project navigator, then select Show in Finder. Then in Finder right-click on the app and select "Show Package Contents". There should be a Library Folder containing LaunchServices folder. That's where your helper should be

image

abstertee commented 3 years ago

@chipjarred is the CodeSignUpdate.swift file supposed to only work with "Mac Developer" certs or can we use a "Developer ID Application" cert?

chipjarred commented 3 years ago

@abstertee It's set up to work with "Mac Developer:" and "Apple Development:", which I think are what SMJobBless (or maybe it's launchd) needs to launch the helper tool. But I have to admit my memory of cert details are little hazy at this point. If you want to experiment with values that you have reason to believe are correct, the function to modify is

func isValidDeveloperCN(_ s: String) -> Bool
{
    let appleDeveloper = "Apple Development:"
    let macDeveloper = "Mac Developer:"

    if isValidDeveloperCN(s, withPrefix: appleDeveloper) { return true }
    if isValidDeveloperCN(s, withPrefix: macDeveloper) { return true }

    return false
}
abstertee commented 3 years ago

@chipjarred Either way I keep getting the following error with an SMJobBless check: SMJobBlessUtil.py: tool designated requirement (anchor apple generic and identifier "com.xxx.xxx.helper" 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] = S*********W)) doesn't match entry in 'SMPrivilegedExecutables' (identifier "com.xxx.xxx.helper" and anchor apple generic and certificate leaf[subject.CN] = "Developer ID Application: XXX, Inc. Enterprise (S*********W)" and certificate 1[field.1.2.840.113635.100.6.2.1] /* exists */) When the application is launched there is prompt to install the helper tool, but any attempt to install fails with the same error of this issue Failed to install helper with error: Error Domain=CFErrorDomainLaunchd Code=4 "(null)"

chipjarred commented 3 years ago

@abstertee What follows involves editing your Info plists. I recommend using a plain text editor for this particular task rather than plist editor for this (so not Xcode). I'm using BBEdit, but you could use TextEdit, vi, emacs, whatever you like, as long as its showing the raw XML of the plist.

Let's break down what you're getting from SMJobBlessUtil.py. The first thing I notice is an identifier mismatch, so we'll sort that out.

Helper Tool's Info.plist

If your project is based on this repo, this file is probably called Helper-Info.plist.

Make sure the CFBundleIdentifier and CFBundleName entries are the set to the helper tool's ID. Based on the error text, that appears to be com.xxx.xxx.helper for your project. Don't use quotes.

    <key>CFBundleIdentifier</key>
    <string>com.xxx.xxx.helper</string>

and

    <key>CFBundleName</key>
    <string>com.xxx.xxx.helper</string>

Also delete the SMAuthorizedClients entry (both <key> part and the entire <array> immediately below it. CodeSignUpdate (either shell or Swift version) will rebuild it.

So the last few lines of your file should change from this

    <key>CFBundleVersion</key>
    <string>1</string>
    <key>SMAuthorizedClients</key>
    <array>
        <string>some certificate info here</string>
    </array>
</dict>
</plist>

to this

    <key>CFBundleVersion</key>
    <string>1</string>
</dict>
</plist>

Save it

Main Info.plist file

Check that the CFBundleIdentifier value is $(PRODUCT_BUNDLE_IDENTIFIER)

    <key>CFBundleIdentifier</key>
    <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>

Delete the SMPrivilegedExecutables entry, so the last few lines of the file should change from this

    <key>NSPrincipalClass</key>
    <string>NSApplication</string>
    <dict>
        <key> com.xxx.xxx.helper </key>
        <string>some certificate info here</string>
    </dict>
</dict>
</plist>

to this

    <key>NSPrincipalClass</key>
    <string>NSApplication</string>
</dict>
</plist>

Save it

Project Settings -> Target -> Your Main App

Make sure your target's Bundle Identifier is set to the id you've chosen for your main app. This should not be the same as your helper tool's bundle identifier. Perhaps this is com.xxx.xxx.NameOfYourApp. Xcode uses this field to set its $(PRODUCT_BUNDLE_IDENTIFIER) environment variables.

Set Signing Certificate to Development. We can play with this more later. Get a working build first.

Project Settings -> Target -> Your Helper Tool

Set your helper tool's Bundle Identifier to the same one you used in the plist, so continuing my assumption about that, it would be com.xxx.xxx.helper

Set Signing Certificate to Development

Check your Run Script Build Phase

If you're using CodeSignUpdate.swift from my fork, make sure the Run Script phase for both targets looks like this (continuing with previously assumed bundle identifiers):

export MAIN_BUNDLE_ID="com.xxx.xxx.NameOfYourApp"
export HELPER_BUNDLE_ID="com.xxx.xxx.helper"
swift "${SRCROOT}"/Scripts/CodeSignUpdate.swift

Also make sure that the Run Script phase is after the Dependencies phase, but before the Compile phase.

chipjarred commented 3 years ago

@abstertee Once you've gotten a working build, if you want to use a different certificate, you'll probably need to modify CodeSignUpdate.swift to use it.

func isValidDeveloperCN(_ s: String) -> Bool
{
    let appleDeveloper = "Apple Development:"
    let macDeveloper = "Mac Developer:"
    let customDeveloper = "Developer ID Application:" // <-- Add this for you to try

    if isValidDeveloperCN(s, withPrefix: appleDeveloper) { return true }
    if isValidDeveloperCN(s, withPrefix: macDeveloper) { return true }
    if isValidDeveloperCN(s, withPrefix: customDeveloper) { return true } // <-- Add this too

    return false
}

I'll probably re-write that to use an array of prefixes to check in a loop instead of the straight sequential series of if statements.

Although I think CodeSignUpdate.swift completely replaces SMAuthorizedClients and SMPrivilegedExecutables each time it runs, I've slept since then, so I'd need to review the code to be sure. For now I recommend deleting those sections of the plists whenever you change signing certificates.

abstertee commented 3 years ago

@chipjarred Thank you for the very detailed instructions. This was very helpful. What I found in making these changes is that all works as it should when the Signing Cert selected is the "Apple Development", but we want to use the "Developer ID Application" cert. When the "Developer ID" cert is selected, nothing works and we get that same error message regarding the mismatch from SMJobBless.py.

What is the difference between the two certs and what is preventing us from using the "Developer ID Application" cert? I should add that this application is for internal use and will not be in the AppStore.

chipjarred commented 3 years ago

I'm glad the instructions were helpful. As for why one certificate works and the other doesn't, I don't know. I wouldn't think a particular certificate would be a hard requirement for SMJobBless or launchd but maybe it is. Did you try the changes to CodeSignUpdate.swift from my follow up? And of course both the tool and main app would need to use the same cert.

Of course, since the app has to run without a sandbox to use SMJobBless, it couldn't go on the AppStore anyway, but if it's for internal use, does it actually matter which cert you use? I'm sure you have your reasons. Just a question worth pondering. I would think it would matter more if you were planning to distribute it outside of your company.

trinhtranduc commented 11 months ago

Hi @abstertee ,Sorry for resurrecting this thread, but I'm facing the same issue you mentioned. Despite initially working with a Development certificate, when I switched to a Developer ID Application, I encountered the error. I intend to use a Developer ID Applicationfor notarizing the application and distributing it outside the Appstore. Have you found a solution? Please share it with me. Thanks!

Screenshot 2023-12-04 at 16 18 34