{"id":952,"date":"2020-01-15T21:10:27","date_gmt":"2020-01-15T21:10:27","guid":{"rendered":"https:\/\/psyphi.net\/blog\/?p=952"},"modified":"2020-01-15T21:16:02","modified_gmt":"2020-01-15T21:16:02","slug":"signing-macosx-apps-with-linux","status":"publish","type":"post","link":"https:\/\/psyphi.net\/blog\/2020\/01\/signing-macosx-apps-with-linux\/","title":{"rendered":"Signing MacOSX apps with Linux"},"content":{"rendered":"\n<p>Do you, like me, develop desktop applications for MacOSX? Do you, like me, do it on Linux because it makes for a much cheaper and easier to manage gitlab CI\/CD build farm? Do you still sign your apps using a MacOSX machine, or worse (yes, like me), not sign them at all, leaving ugly popups like the one below?<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"454\" src=\"https:\/\/psyphi.net\/wp-uploads\/2020\/01\/Screenshot-2020-01-15-at-19.42.19-1024x454.png\" alt=\"\" class=\"wp-image-953\" srcset=\"https:\/\/psyphi.net\/wp-uploads\/2020\/01\/Screenshot-2020-01-15-at-19.42.19-1024x454.png 1024w, https:\/\/psyphi.net\/wp-uploads\/2020\/01\/Screenshot-2020-01-15-at-19.42.19-300x133.png 300w, https:\/\/psyphi.net\/wp-uploads\/2020\/01\/Screenshot-2020-01-15-at-19.42.19.png 1136w\" sizes=\"auto, (max-width: 709px) 85vw, (max-width: 909px) 67vw, (max-width: 1362px) 62vw, 840px\" \/><\/figure>\n\n\n\n<p>With the <a href=\"https:\/\/www.engadget.com\/2019\/12\/24\/apple-will-start-enforcing-its-mac-app-security-policy-in-februa\">impending trustpocalypse<\/a> next month a lot of third-party (non-app-store) apps for MacOSX are going to start having deeper trust issues than they&#8217;ve had previously, no doubt meaning more, uglier popups than that one, or worse, not being able to run at all.<\/p>\n\n\n\n<p>I suspect this trust-tightening issue, whilst arguably a relatively good thing to do to in the war against malware, will adversely affect a huge number of open-source Mac applications where the developer\/s wish to provide Mac support for their users but may not wish to pay the annual Apple Developer tax even though it&#8217;s still relatively light, or may not even own any Apple hardware (though who knows how they do their integration testing?). In-particular this is likely to affect very many applications built with Electron or NWJS, into which group this post falls.<\/p>\n\n\n\n<p>Well, this week I&#8217;ve been looking into this issue for one of the apps I look after, and I&#8217;m pleased to say it&#8217;s at a stage where I&#8217;m comfortable writing something about it. The limitation is that you don&#8217;t sidestep paying the Apple Developer tax, as you do still need valid certs with the Apple trust root. But you <em>can<\/em> sidestep paying for more Apple hardware than you need, i.e. nothing needed in the build farm.<\/p>\n\n\n\n<p>First I should say all of the directions I used came from <a href=\"http:\/\/users.wfu.edu\/cottrell\/productsign\/productsign_linux.html\">a 2016 article, here<\/a>. Thanks very much to Allin Cottrell.<\/p>\n\n\n\n<p>Below is the (slightly-edited)  script now forming part of the build pipeline for my app. Hopefully the comments make it fairly self-explanatory. Before you say so, yes I&#8217;ve been lazy and haven&#8217;t parameterised directory and package names yet.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">#!\/bin\/bash\n\n#########\n# This is a nwjs (node) project so fish the version out of package.json\n#\nVERSION=$(jq -r .version package.json)\n\n#########\n# set up the private key for signing, if present\n#\nrm -f key.pem\nif [ \"$APPLE_PRIVATE_KEY\" != \"\" ]; then\n    echo \"$APPLE_PRIVATE_KEY\" &gt; key.pem\nfi\n\n#########\n# temporary build folder\/s for package construction\n#\nrm -rf build\nmkdir build &amp;&amp; cd build\nmkdir -p flat\/base.pkg flat\/Resources\/en.lproj\nmkdir -p root\/Applications;\n\n#########\n# stage the unsigned applicatio into the build folder\n#\ncp -pR \"..\/dist\/EPI2MEAgent\/osx64\/EPI2MEAgent.app\" root\/Applications\/\n\n#########\n# fix a permissions issue which only manifests after following cpio stage\n# nw.app seems to be built with owner-read only. no good when packaging as root\n#\nchmod go+r \"root\/Applications\/EPI2MEAgent.app\/Contents\/Resources\/app.nw\"\n\n#########\n# pack the application payload\n#\n( cd root &amp;&amp; find . | cpio -o --format odc --owner 0:80 | gzip -c ) &gt; flat\/base.pkg\/Payload\n\n#########\n# calculate a few attributes\n#\nfiles=$(find root | wc -l)\nbytes=$(du -b -s root | awk '{print $1}')\nkbytes=$(( $bytes \/ 1000 ))\n\n#########\n# template the Installer PackageInfo\n#\ncat &lt;&lt;EOT &gt; flat\/base.pkg\/PackageInfo\n&lt;pkg-info format-version=\"2\" identifier=\"com.metrichor.agent.base.pkg\" version=\"$VERSION\" install-location=\"\/\" auth=\"root\"&gt;\n  &lt;payload installKBytes=\"$kbytes\" numberOfFiles=\"$files\"\/&gt;\n  &lt;scripts&gt;\n    &lt;postinstall file=\".\/postinstall\"\/&gt;\n  &lt;\/scripts&gt;\n  &lt;bundle-version&gt;\n    &lt;bundle id=\"com.metrichor.agent\" CFBundleIdentifier=\"com.nw-builder.epimeagent\" path=\".\/Applications\/EPI2MEAgent.app\" CFBundleVersion=\"$VERSION\"\/&gt;\n  &lt;\/bundle-version&gt;\n&lt;\/pkg-info&gt;\nEOT\n\n#########\n# configure the optional post-install script with a popup dialog\n#\nmkdir -p scripts\ncat &lt;&lt;EOT &gt; scripts\/postinstall\n#!\/bin\/bash\n\nosascript -e 'tell app \"Finder\" to activate'\nosascript -e 'tell app \"Finder\" to display dialog \"To get the most of EPI2ME please also explore the Nanopore Community https:\/\/community.nanoporetech.com\/ .\"'\nEOT\n\nchmod +x scripts\/postinstall\n\n#########\n# pack the postinstall payload\n#\n( cd scripts &amp;&amp; find . | cpio -o --format odc --owner 0:80 | gzip -c ) &gt; flat\/base.pkg\/Scripts\nmkbom -u 0 -g 80 root flat\/base.pkg\/Bom\n\n#########\n# Template the flat-package Distribution file together with a MacOS version check\n#\ncat &lt;&lt;EOT &gt; flat\/Distribution\n&lt;?xml version=\"1.0\" encoding=\"utf-8\"?&gt;\n&lt;installer-script minSpecVersion=\"1.000000\" authoringTool=\"com.apple.PackageMaker\" authoringToolVersion=\"3.0.3\" authoringToolBuild=\"174\"&gt;\n    &lt;title&gt;EPI2MEAgent $VERSION&lt;\/title&gt;\n    &lt;options customize=\"never\" allow-external-scripts=\"no\"\/&gt;\n    &lt;domains enable_anywhere=\"true\"\/&gt;\n    &lt;installation-check script=\"pm_install_check();\"\/&gt;\n    &lt;script&gt;\nfunction pm_install_check() {\n  if(!(system.compareVersions(system.version.ProductVersion,'10.12') &gt;= 0)) {\n    my.result.title = 'Failure';\n    my.result.message = 'You need at least Mac OS X 10.12 to install EPI2MEAgent.';\n    my.result.type = 'Fatal';\n    return false;\n  }\n  return true;\n}\n    &lt;\/script&gt;\n    &lt;choices-outline&gt;\n        &lt;line choice=\"choice1\"\/&gt;\n    &lt;\/choices-outline&gt;\n    &lt;choice id=\"choice1\" title=\"base\"&gt;\n        &lt;pkg-ref id=\"com.metrichor.agent.base.pkg\"\/&gt;\n    &lt;\/choice&gt;\n    &lt;pkg-ref id=\"com.metrichor.agent.base.pkg\" installKBytes=\"$kbytes\" version=\"$VERSION\" auth=\"Root\"&gt;#base.pkg&lt;\/pkg-ref&gt;\n&lt;\/installer-script&gt;\nEOT\n\n#########\n# pack the Installer\n#\n( cd flat &amp;&amp; xar --compression none -cf \"..\/EPI2MEAgent $VERSION Installer.pkg\" * )\n\n#########\n# check if we have a key for signing\n#\nif [ ! -f ..\/key.pem ]; then\n    echo \"not signing\"\n    exit\nfi\n\n#########\n# calculate attribute\n: | openssl dgst -sign ..\/key.pem -binary | wc -c &gt; siglen.txt\n\n#########\n# xar the Installer package\n#\nxar --sign -f \"EPI2MEAgent $VERSION Installer.pkg\" \\\n    --digestinfo-to-sign digestinfo.dat --sig-size $(cat siglen.txt) \\\n    --cert-loc ..\/dist\/tools\/mac\/certs\/cert00 --cert-loc ..\/dist\/tools\/mac\/certs\/cert01 --cert-loc ..\/dist\/tools\/mac\/certs\/cert02\n\n#########\n# construct the signature\n#\nopenssl rsautl -sign -inkey ..\/key.pem -in digestinfo.dat \\\n        -out signature.dat\n\n#########\n# add the signature to the installer\n#\nxar --inject-sig signature.dat -f \"EPI2MEAgent $VERSION Installer.pkg\"\n\n#########\n# clean up\n#\nrm -f signature.dat digestinfo.dat siglen.txt key.pem<\/pre>\n\n\n\n<p>With all that you still need a few assets. I built and published (internally) corresponding debs for <a href=\"https:\/\/github.com\/mackyle\/xar\/releases\">xar v1.6.1<\/a> and <a href=\"https:\/\/github.com\/hogliux\/bomutils\/releases\">bomutils 0.2<\/a>. You might want to compile &amp; install those from source &#8211; they&#8217;re pretty straightforward builds.<\/p>\n\n\n\n<p>Next, you need a signing identity. I used XCode (Preferences => Accounts => Apple ID => Manage Certificates) to add a new Mac Installer Distribution certificate. Then used that to sign my .app <strong>once<\/strong> on MacOS in order to fish out the Apple cert chain (there are probably better ways to do this)<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">productsign --sign LJXXXXXX58 \\\n        build\/EPI2MEAgent\\ 2020.1.14\\ Installer.pkg \\\n        EPI2MEAgent\\ 2020.1.14\\ Installer.pkg<\/pre>\n\n\n\n<p>Then fish out the certs<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">xar -f EPI2MEAgent\\ 2020.1.14\\ Installer.pkg \\\n        --extract-certs certs\nmac:~\/agent rmp$ ls -l certs\/\ntotal 24\n-rw-r--r--  1 rmp  Users  1494 15 Jan 12:06 cert00\n-rw-r--r--  1 rmp  Users  1062 15 Jan 12:06 cert01\n-rw-r--r--  1 rmp  Users  1215 15 Jan 12:06 cert02<\/pre>\n\n\n\n<p>Next use Keychain to export the .p12 private key for the &#8220;3rd Party Mac Developer Installer&#8221; key. Then openssl it a bit to convert to a pem.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">openssl pkcs12 -in certs.p12 -nodes | openssl rsa -out key.pem<\/pre>\n\n\n\n<p>I set this up the contents of key.pem as a gitlab CI\/CD Environment Variable APPLE_PRIVATE_KEY so it&#8217;s never committed to the project source tree.<\/p>\n\n\n\n<p>Once all that&#8217;s in place it should be possible to run the script (paths-permitting, obviously yours will be different) and end up with an installer looking something like this. Look for the closed padlock in the top-right, and the fully validated chain of certificate trust.<\/p>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-large is-resized\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/psyphi.net\/wp-uploads\/2020\/01\/Screenshot-2020-01-15-at-20.41.02-1024x744.png\" alt=\"\" class=\"wp-image-956\" width=\"512\" height=\"372\" srcset=\"https:\/\/psyphi.net\/wp-uploads\/2020\/01\/Screenshot-2020-01-15-at-20.41.02-1024x744.png 1024w, https:\/\/psyphi.net\/wp-uploads\/2020\/01\/Screenshot-2020-01-15-at-20.41.02-300x218.png 300w, https:\/\/psyphi.net\/wp-uploads\/2020\/01\/Screenshot-2020-01-15-at-20.41.02-1200x872.png 1200w, https:\/\/psyphi.net\/wp-uploads\/2020\/01\/Screenshot-2020-01-15-at-20.41.02.png 1346w\" sizes=\"auto, (max-width: 512px) 85vw, 512px\" \/><\/figure><\/div>\n\n\n\n<p>In conclusion, the cross-platform application nwjs builds (Mac, Windows, Linux) all run using <a href=\"https:\/\/www.npmjs.com\/package\/nw-builder\">nw-builder<\/a> on ubuntu:18.04, and the Mac (and Windows, using <a href=\"https:\/\/github.com\/develar\/osslsigncode\">osslsigncode<\/a>, maybe more on that later) also all run on ubuntu:18.04. Meaning one docker image for the Linux-based Gitlab CI\/CD build farm. Nice!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Do you, like me, develop desktop applications for MacOSX? Do you, like me, do it on Linux because it makes for a much cheaper and easier to manage gitlab CI\/CD build farm? Do you still sign your apps using a MacOSX machine, or worse (yes, like me), not sign them at all, leaving ugly popups &hellip; <a href=\"https:\/\/psyphi.net\/blog\/2020\/01\/signing-macosx-apps-with-linux\/\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &#8220;Signing MacOSX apps with Linux&#8221;<\/span><\/a><\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"om_disable_all_campaigns":false,"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"_uf_show_specific_survey":0,"_uf_disable_surveys":false,"footnotes":""},"categories":[11,17],"tags":[1109,1113,199,318,1114,184,1112],"class_list":["post-952","post","type-post","status-publish","format-standard","hentry","category-programming","category-sysadmin","tag-ci-cd","tag-codesigning","tag-linux","tag-mac","tag-nwjs","tag-osx","tag-signing"],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/psyphi.net\/blog\/wp-json\/wp\/v2\/posts\/952","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/psyphi.net\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/psyphi.net\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/psyphi.net\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/psyphi.net\/blog\/wp-json\/wp\/v2\/comments?post=952"}],"version-history":[{"count":16,"href":"https:\/\/psyphi.net\/blog\/wp-json\/wp\/v2\/posts\/952\/revisions"}],"predecessor-version":[{"id":970,"href":"https:\/\/psyphi.net\/blog\/wp-json\/wp\/v2\/posts\/952\/revisions\/970"}],"wp:attachment":[{"href":"https:\/\/psyphi.net\/blog\/wp-json\/wp\/v2\/media?parent=952"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/psyphi.net\/blog\/wp-json\/wp\/v2\/categories?post=952"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/psyphi.net\/blog\/wp-json\/wp\/v2\/tags?post=952"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}