As with most of my posts, this is a message to my future self who will have forgotten everything I did today, to get my Xamarin Mac app notarized. I hope it helps other people too.

Apple rejected my app from the store … please include specific macOS features that the application will use and not aggregated content. I’ve no idea what it means, but I interpreted it as we like Siri and we don’t like Alexa and your app brings Alexa to the Mac so go away. I needed to release it outside the store, and that means Notarization.

It took most of a day, and I had many errors. This is my story.

Disclaimer: These instructions worked for me as of 29 Jul 2019. The further out into the future you are reading this, the less likely these instructions are to be relevant.

Resources

This blog post by Microsoft’s David Ortinau is the best starting point. It took me 50% of the way there.

I also found this Github issue to be useful … as of writing it is still open.

Apple’s documentation on notarization and a corresponding trouble-shooting guide are useful if you can understand them. I finally felt like I understood them after I got everything working.

Install the right version of Xamarin.Mac

Although David’s post mentions installing a specific version of Xamarin.Mac (d16-1), at this point I think that release had made it into the stable branch, because I was able to get this working using the latest stable release:

The easy part: Update the plist and csproj

Per the instructions I edited my csproj to include the UseHardenedRuntime:

  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|Mac' ">
    <Optimize>true</Optimize>
    <OutputPath>bin\Release</OutputPath>
    <DefineConstants>__UNIFIED__;__MACOS__</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
...
    <CodeSignEntitlements>Entitlements.plist</CodeSignEntitlements>
    <CodeSignProvision>Developer ID Mac PP</CodeSignProvision>
    <PlatformTarget>x86</PlatformTarget>
    <UseHardenedRuntime>true</UseHardenedRuntime>
  </PropertyGroup>

I also updated my entitlements.plist to allow jit (also Microphone access because how else are you going to talk to Alexa, and Location so that Alexa knows where you are when you ask for the time or weather, although it works if the user denies location access):

Sign your app with the appropriate cert and profile

This part was the trickiest for me. Apple’s documentation talks about using a Developer ID certificate, which I stupidly thought was just my developer certificate. no No NO. This is something different.

Creating the certificate and profile

Sign in to the Apple Developer web site, and open the Certificates, Identifiers & Profiles page and then Certificates.

Check the list of certificates you have for your Mac app. Do you see one with the TYPE called Developer ID Application?

If not, this is what you need. Tap the + at the top, and on the Create a New Certificate page in the Software section, select Developer ID Application option.

Tap continue and generate and download the certificate.

I am assuming that you have already set up an Identifier for your Mac App, since you’ve likely been running it locally to develop it already.

The final step is to create a profile which combines the Developer ID Application certificate and your App Identifier. Go to the Profiles section, tap the + in the title and select the Developer ID option in the Distribution section.

On the next page select the App Id for your app from the dropdown: On the next page select the Developer ID Certificate that you created above:

Finally give your provisioning profile a name and generate and download it:

Ensuring they are downloaded to your machine

This is the thing that screwed me up I thought that every time I downloaded the certificate or provisioning profile to my machine using Apple’s web site, that was enough. It wasn’t.

What I finally figured out was that I also needed to open Apple’s Xcode app, go to Preferences and then Accounts, and tap Download Manual Profiles (sign in first if you’ve not already done so). This makes the profiles visible to Xcode and thus Visual Studio.

Signing your app

Now that you’ve done all of the above, all being well, you should be able to open Visual Studio on your Mac, go to your Mac Project’s settings, in the Mac Signing section and select the Application Developer ID Provisioning Profile you created.

Build your app in Release mode.

Uploading the binary to Apple

That was the easy part. Next comes what I really hope will also be easy for you too. You’ll be using some Apple command-line tools that need passwords, which you can generate as an app-specific password.

In all the examples below I’ll use appspecificpassword for my password. There is also a way to store the password in the keychain, which is perfect for CI type scenarios.

To upload the binary (the .app that was generated), open a terminal and navigate to your project’s bin/Release folder where you should find your project.app, which is actually a folder.

You need to upload it for authorization. First you need to zip it. Since my app is called Voice in a Can, my .app file is VoiceInACan.app

$ zip -r VoiceInACan.zip VoiceInACan.app
  adding: VoiceInACan.app/ (stored 0%)
...
  adding: VoiceInACan.app/Contents/Info.plist (deflated 32%)
  adding: VoiceInACan.app/Contents/PkgInfo (stored 0%)
$ 

To upload the zip file you created use the xcrun altool. You’ll need to use your Apple account. The bundle-id is just a placeholder. Use what you want:

$ xcrun altool --notarize-app --primary-bundle-id "com.atadore.VoiceInACanForMac.Zip" --username damian@mehers.com --password "appspecificpassword" --file VoiceInACan.zip
2019-07-30 08:55:54.038 altool[1624:421061] No errors uploading 'VoiceInACan.zip'.
RequestUUID = 46ecfa36-4338-48de-b9ac-087d14826fad
$

This doesn’t give a response immediately. It uploads the file for processing. Note the RequestUUID that is output by the tool (in this case 46ecfa36-4338-48de-b9ac-087d14826fad). You’ll need this below to get the status of the notarization.

Always an edge-case

There is a 99.9% chance that you won’t get the error I describe here, but I did. If your Apple ID is associated with more than one developer account you will get the 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.

To find the ids associated with your accounts use the iTMSTransporter command (again use your Apple ID):

$ /Applications/Xcode.app/Contents/Applications/Application\ Loader.app/Contents/itms/bin/iTMSTransporter -m provider -u damian@mehers.com -p appspecificpassword 
...
[2019-07-30 08:50:23 CEST] <main> DBG-X:   parameter Atadore SARL = AtadoreSARL
 
Provider listing:
   - Long Name -  - Short Name -
1  Atadore SARL   AtadoreSARL
...
$

After much grinding and processing this will eventually spit out a Provider listing with a long name and a Short Name. Take a note of the Short Name for the account you wish to use, and in every xcrun command append -itc_provider "The Short Name"

For example in my case the above command became:

$ xcrun altool --notarize-app --primary-bundle-id "com.atadore.VoiceInACanForMac.Zip" --username damian@mehers.com --password "appspecificpassword" --file VoiceInACan.zip -itc_provider "AtadoreSARL"
$

Getting the status

Once you’ve uploaded the app for notarization you will have to wait for it to be processed. Continually checking the status will definitely speed things up. To do so, use this command:

$ xcrun altool --notarization-info edab1ebc-29be-45f1-8bca-98b89066196f --username "damian@mehers.com" --password "appspecificpassword"  -itc_provider "AtadoreSARL" 
2019-07-30 09:05:53.314 altool[1799:441307] No errors getting notarization info.

   RequestUUID: edab1ebc-29be-45f1-8bca-98b89066196f
          Date: 2019-07-30 07:05:28 +0000
        Status: in progress
    LogFileURL: (null)
$ 

The GUID (edab1…) that is passed as a parameter is the one returned when you uploaded the zip file above.

After a while of repeating this, you will eventually get the processing response. And it will fail. It always fails the first time.

$ xcrun altool --notarization-info edab1ebc-29be-45f1-8bca-98b89066196f --username "damian@mehers.com" --password "appspecificpassword"  -itc_provider "AtadoreSARL" 
2019-07-30 09:07:14.468 altool[1803:445374] No errors getting notarization info.

   RequestUUID: edab1ebc-29be-45f1-8bca-98b89066196f
          Date: 2019-07-30 07:05:28 +0000
        Status: invalid
    LogFileURL: https://osxapps-ssl.itunes.apple.com/itunes-assets/Enigma123/v4/b2/f3/ed/b...d/developer_log.json?accessKey=1...D
   Status Code: 2
Status Message: Package Invalid
$ 

To get the details, use the curl command to show the contents of the LogFileURL:

$ curl https://osxapps-ssl.itunes.apple.com/itunes-assets/Enigma123/v4/b2/f3/ed/b...d/developer_log.json?accessKey=1...D
{
  "logFormatVersion": 1,
  "jobId": "e...f",
  "status": "Invalid",
  "statusSummary": "Archive contains critical validation errors",
  "statusCode": 4000,
  "archiveFilename": "VoiceInACan.zip",
  "uploadDate": "2019-07-30T07:05:28Z",
  "sha256": "c...3",
  "ticketContents": null,
  "issues": [
    {
      "severity": "error",
      "code": null,
      "path": "VoiceInACan.zip/VoiceInACan.app/Contents/MacOS/VoiceInACan",
      "message": "The binary is not signed with a valid Developer ID certificate.",
      "docUrl": null,
      "architecture": "x86_64"
    },
    {
      "severity": "error",
      "code": null,
      "path": "VoiceInACan.zip/VoiceInACan.app/Contents/MonoBundle/libMonoPosixHelper.dylib",
      "message": "The binary is not signed with a valid Developer ID certificate.",
      "docUrl": null,
      "architecture": "x86_64"
    },
    {
      "severity": "error",
      "code": null,
      "path": "VoiceInACan.zip/VoiceInACan.app/Contents/MonoBundle/libmono-native.dylib",
      "message": "The binary is not signed with a valid Developer ID certificate.",
      "docUrl": null,
      "architecture": "x86_64"
    }
  ]
}
$ 

I deliberately signed the app with the wrong provisioning profile to get this error. Remember you must use the profile that incorporates the Developer ID Application certificate.

To add insult to injury I also received a taunting email Your Mac software was not notarized..

You may have other errors. Perhaps you didn’t enable the hardened runtime, or allow JIT, or you use need to enable an entitlement.

This is the Live, Die, Repeat section where you keep on making changes, building, uploading, waiting for it to be processed, reading the reason why it failed, and trying again. For me it was just the certificate.

Once I used Developer ID Application certificate and generated a profile, and crucially used Xcode to Download Manual Profiles I was good:

$ xcrun altool --notarization-info 5f56223e-3d13-4e47-9d01-cc572ba1ca04 --username "damian@mehers.com" --password "fappspecificpassword" 
2019-07-30 09:24:15.210 altool[1953:476339] No errors getting notarization info.

   RequestUUID: 5f56223e-3d13-4e47-9d01-cc572ba1ca04
          Date: 2019-07-30 07:21:39 +0000
        Status: success
    LogFileURL: https://osxapps-ssl.itunes.apple.com/itunes-assets/Enigma123/v4/bd/7c/98/b...D
   Status Code: 0
Status Message: Package Approved
$ 

When it finally worked, I couldn’t believe it. I felt like someone who’d won the lottery and could not believe their luck, obsessively comparing the numbers on their ticket with the ones on the screen. But sure enough I received the celebratory email too:

I used the curl to check the log for warnings but all was good. My situation was actually trickier than I let on above because my app contains another app embedded inside it to allow my app be to launched at startup if the user wishes:

$ curl https://osxapps-ssl.itunes.apple.com/itunes-assets/Enigma123/v4/bd/7c/98/b...D
{
  "logFormatVersion": 1,
  "jobId": "5..4",
  "status": "Accepted",
  "statusSummary": "Ready for distribution",
  "statusCode": 0,
  "archiveFilename": "VoiceInACan.zip",
  "uploadDate": "2019-07-30T07:21:39Z",
  "sha256": "6..f",
  "ticketContents": [
    {
      "path": "VoiceInACan.zip/VoiceInACan.app/Contents/Library/LoginItems/AppleMacLoginHelper.app",
      "digestAlgorithm": "SHA-256",
      "cdhash": "5...1",
      "arch": "x86_64"
    },
    {
      "path": "VoiceInACan.zip/VoiceInACan.app",
      "digestAlgorithm": "SHA-256",
      "cdhash": "d...b",
      "arch": "x86_64"
    },
   ...
    {
      "path": "VoiceInACan.zip/VoiceInACan.app/Contents/MonoBundle/libmono-native.dylib",
      "digestAlgorithm": "SHA-256",
      "cdhash": "f...9",
      "arch": "x86_64"
    }
  ],
  "issues": null
}
$ 

Staple

The final step is to staple the notarization information to the app. You do this using the staple command:

$ xcrun stapler staple VoiceInACan.app
Processing: /Users/damian/Projects/AppleWatchAlexa/VoiceInACan.AppleMac/bin/Release/VoiceInACan.app
Processing: /Users/damian/Projects/AppleWatchAlexa/VoiceInACan.AppleMac/bin/Release/VoiceInACan.app
The staple and validate action worked!
$ 

You can now make your app available for download, and it should run on anyone’s Mac without them having to do anything special to their security settings.

Conclusions

Whilst the arrival of Catalyst is causing many questions, including the value of creating Xamarin Mac apps as opposed to simply bringing over an iPad app, there are some situations where native Mac apps make sense.

My app sits in the toolbar. It has a login helper to launch it at login. Two things that are likely not possible with Catalyst.

It is perhaps an edge-case, but in my experience everyone is an edge case, just in different ways. Maintaining the ability to create Xamarin Mac apps gives developers more flexibility and a chance to do something that otherwise would not be possible. Although I would understand if Microsoft dropped Xamarin Mac support, I’m hoping they won’t.

If you’d like to try out the Mac app, it is available here. And guess what? It is notarized!