“The never-ending notary nuisance,” or, “infinite loop at Infinite Loop”

A couple weeks ago, Apple posted a note that says:

In an upcoming release of macOS, Gatekeeper will require Developer ID–signed software to be notarized by Apple.

Being a developer of Developer ID-signed (i.e., non-App-Store) software, I set out to vault Apple’s latest hurdle.

Xcode includes a command-line utility called altool that manages the notarization process. That seems appropriate since my app isn’t built using Xcode’s build system.

1. Validation vanquished

First, I created a simple macOS test app in Xcode 10, built it, and tried to validate it for notarization:

$ altool --validate-app --file test.app --username test@example.com
test@example.com's password: 
altool *** Error: Unable to validate archive 'test.app': (
    "Error Domain=ITunesSoftwareServiceErrorDomain Code=-21017 \"Could not find the main bundle or the Info.plist is missing a CFBundleIdentifier in 'test.app'.\" UserInfo={NSLocalizedDescription=Could not find the main bundle or the Info.plist is missing a CFBundleIdentifier in 'test.app'., NSLocalizedFailureReason=Unable to validate your application.}"
)

That’s odd, since it does have a bundle identifier:

$ grep -A1 CFBundleIdentifier test.app/Contents/Info.plist 
    <key>CFBundleIdentifier</key>
    <string>com.example.test</string>

I noticed altool has an optional --primary-bundle-id argument; maybe it needs that to figure out the bundle identifier?

$ altool --validate-app --file test.app --username test@example.com --primary-bundle-id com.example.test
test@example.com's password: 
altool *** Error: Unable to validate archive 'test.app': (
    "Error Domain=ITunesSoftwareServiceErrorDomain Code=-21017 \"Could not find the main bundle or the Info.plist is missing a CFBundleIdentifier in 'test.app'.\" UserInfo={NSLocalizedDescription=Could not find the main bundle or the Info.plist is missing a CFBundleIdentifier in 'test.app'., NSLocalizedFailureReason=Unable to validate your application.}"
)

…nope, same error as above.

Reading the altool --help text again, I see that the --validate-app description says “Validates an app archive for the App Store” (emphasis mine), so maybe it’ll help if I zip it?

$ zip -qry test.zip test.app
$ altool --validate-app --file test.zip --username test@example.com
test@example.com's password: 
altool *** Error: Unable to validate archive 'test.zip': (
    "Error Domain=ITunesSoftwareServiceErrorDomain Code=-20008 \"The Info.plist indicates an iOS app, but submitting a pkg or mpkg.\" UserInfo={NSLocalizedDescription=The Info.plist indicates an iOS app, but submitting a pkg or mpkg., NSLocalizedFailureReason=Unable to validate your application.}"
)

What! No, this is really a macOS app, not an iOS app, I promise.

$ file test.app/Contents/MacOS/test 
test.app/Contents/MacOS/test: Mach-O 64-bit x86_64 executable, flags:<NOUNDEFS|DYLDLINK|TWOLEVEL|PIE>
Update 2018.11.01: I submitted a bug report about the macOS-vs-iOS issue, and an Apple technician replied:
Please remove the key LSMinimumSystemVersion from [the app bundle’s Info.plist].

Okay, I suppose I can do that, but I was using LSMinimumSystemVersion so that if someone tries to run my app on an old macOS version that my app doesn’t support, they’ll receive a clear explanation rather than mysterious brokenness.

After removing that, I tried to validate it again, but (after resolving the login issues described in the next section) then there was this error:

altool --validate-app --file test.zip --username appstore@example.com
appstore@example.com's password: 
altool *** Error: Unable to validate archive 'test.zip': (
    "Error Domain=ITunesConnectionOperationErrorDomain Code=1190 \"No suitable application records were found. Verify your bundle identifier 'com.example.test' is correct.\" UserInfo={NSLocalizedRecoverySuggestion=No suitable application records were found. Verify your bundle identifier 'com.example.test' is correct., NSLocalizedDescription=No suitable application records were found. Verify your bundle identifier 'com.example.test' is correct., NSLocalizedFailureReason=App Store operation failed.}"
)
I guess “No suitable application records were found” means it checked App Store Connect for an app with that bundle identifier? But I do really have a bundle identifier of that name in App Store Connect… Sigh.

2. Login lossage

Maybe I don’t need to validate it; maybe I can just notarize it?

$ altool --notarize-app --file test.app --username test@example.com --primary-bundle-id com.example.test
test@example.com's password: 
altool *** Error: Is a directory The operation couldn’t be completed. Is a directory

Somewhat redundant and ungrammatical, but at least it says that the problem is. So let’s try the archive again:

$ altool --notarize-app --file test.zip --username test@example.com --primary-bundle-id com.example.test
test@example.com's password: 
Error: Unable to validate your application. Sign in with the app-specific password you generated. If you forgot the app-specific password or need to create a new one, go to appleid.apple.com

OK, so I generated an app-specific password and used that this time:

$ altool --notarize-app --file test.zip --username test@example.com --primary-bundle-id com.example.test
test@example.com's password: 
Error: Your Apple ID account is attached to other iTunes providers. You will need to specify which provider you intend to submit content to by using the -itc_provider command. Please contact us if you have questions or need help.

As a software consultant, I’m involved in App Store submissions for some of our clients, so I have multiple providers linked to my Apple ID.

I tried using the suggested -itc_provider flag, but altool doesn’t recognize that flag. However, there is --asc-provider, whose description (“Required … when a user account is associated with multiple providers.”) sounds appropriate, so I tried that. It doesn’t say what format it expects that argument to have, so I took a guess:

$ altool --notarize-app --file test.zip --username test@example.com --primary-bundle-id com.example.test --asc-provider example
test@example.com's password: 
Unable to find an iTunes Connect user for username test@example.com and provider example.

Maybe it wants reverse-DNS?

$ altool --notarize-app --file test.zip --username test@example.com --primary-bundle-id com.example.test --asc-provider com.example
test@example.com's password: 
Unable to find an iTunes Connect user for username test@example.com and provider com.example.

Nope. Hmm. I vaguely recall there being some long string of numbers and letters associated with my company’s App Store account; maybe it wants me to use that? I found the code under App Store Connect > Users and Access > me > Team ID.

$ altool --notarize-app --file test.zip --username test@example.com --primary-bundle-id com.example.test --asc-provider J1550PCNAR
test@example.com's password: 
Unable to find an iTunes Connect user for username test@example.com and provider J1550PCNAR.

Since it doesn’t seem to like my account, maybe I can create a new user associated with my company’s App Store account, just for the purpose of notarization. I did that, and:

$ altool --notarize-app --file test.zip --username appstore@example.com --primary-bundle-id com.example.test
appstore@example.com's password: 
 
…half an hour later…
 
altool No errors uploading 'test.zip'.
RequestUUID = 4b6f03d1-865a-4453-9133-4b64bba5c381

Whee!

Oh wait, then I got an email that says this:

The Mac software that you uploaded (bundle identifier com.example.test) was not notarized. Please address the issues listed below and upload your software again.

Status: Invalid
Error #2 - Package Invalid

If you have any questions, sign in to your developer account and submit a Technical Support Incident. Make sure to include these error numbers in the description you provide.

That’s not very informative. Well, I do have a spare Technical Support Incident ($49.50 USD), so I guess I’ll ask them what this vague error message means.

3. Infinite loop at Infinite Loop

3 days later I got a response:

Thank you for contacting Apple Developer Technical Support (DTS). We provide support for code-level questions on hardware & software development, and are unable to help you with your question.

If you are not able to find the information you need in the App Store Connect Help and you are still not able to resolve your issue, please direct your inquiry through the Contact Us page:

So, I did that.

The next day:

Thank you for contacting us in regard to signing your app.

Because we provide administrative level support, I am able to refer your to our resources available in the Technical Notes:

macOS Code Signing In Depth
Entitlements Troubleshooting

If you still need assistance with this request after reviewing the above mentioned resources, the Developer Technical Support team specializes in code-level assistance for Apple frameworks, APIs, and tools. Please visit our website to learn how to contact them or access free technical support resources.

A visual representation of this situation: A directed graph demonstrating the infinite loop of Apple Support

That’s an infinite loop.

4. Closing in on it

In the altool --help text, I found another option that looked like it might be able to provide more info:

$ altool --notarization-info 4b6f03d1-865a-4453-9133-4b64bba5c381 -u appstore@example.com
appstore@example.com's password: 
altool No errors getting notarization info.
 
   RequestUUID: 4b6f03d1-865a-4453-9133-4b64bba5c381
          Date: 2018-10-20 02:44:34
        Status: invalid
    LogFileURL: https://osxapps-ssl.itunes.apple.com/itunes-assets/Enigma118/v4/[redacted]/developer_log.json?accessKey=[redacted]
   Status Code: 2
Status Message: Package Invalid

Same Status Message as the email I received above, but what’s at that LogFileURL? A JSON blob that contains a list of issues, including:

The executable does not have the hardened runtime enabled.

Ah, that’s helpful. OK, I enabled hardened runtime — Xcode > Target > Capabilities > Hardened Runtime: On — and re-submitted the app.

I eventually received another “Package Invalid” email, then ran altool --notarization-info again, and viewed the LogFileUrl JSON and found this:

The executable requests the com.apple.security.get-task-allow entitlement.

The com.apple.security.get-task-allow entitlement allows attaching a debugger to the process, so I guess that means you aren’t allowed to do that to notarized apps. After disabling that, I re-submitted it. Next error:

The signature does not include a secure timestamp.

When Xcode signs the app’s main executable, it passes codesign the --timestamp=none (perhaps that’s intended to facilitate offline development?). I couldn’t find a way to turn that off in the Xcode GUI, so I re-signed the app using the codesign command line utility:

$ codesign --sign Example --options runtime --entitlements test/test.entitlements --force build/Release/test.app
build/Release/test.app: replacing existing signature

…and then re-submitted it for notarization.

And now, finally:

Your Mac software has been notarized. You can now export this software and distribute it directly to users.

5. Pressing the Easy Button

Now that Apple has notarized this wonderful do-nothing app, we can download proof of that and attach it to the app, using the stapler command line utility:

$ stapler staple test.app
Processing: test.app
Processing: test.app
The staple and validate action worked!

It sounds so surprised!

In conclusion, due to the inaccurate error messages and general lack of documentation, this process took a lot longer than it seems like it ought to have.


Steve Mokris is a developer at Kosada, Inc.