Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MAS code signing and distribution guide request #6338

Open
tankakatan opened this issue Dec 16, 2017 · 15 comments

Comments

Projects
None yet
5 participants
@tankakatan
Copy link

commented Dec 16, 2017

Hello!

Thank you very much for nw.js!

Me and my colleagues are currently trying to prepare our nwjs app for Mac App Store distribution. But there is a lack of information on how to do it properly.

We use NWJS 0.27.1 on Mac OS 10.13.2. The documentation suggests to use MAS helper script, but it seems to be designed for nwjs 0.19.5, whereas our project uses latest nwjs features such as importNWBin.

Still, we've tried to sign our app with that script with entitlements relevant to our app features. Unfortunately the application signed with build_mas.py silently crashes upon start regardless of signing identity set in the configuration file. Here is a crash log that was captured via OS X Console app:
app-signed-via-build_mas-crash-log.txt

We have tested build_mas.py with these kinds of identity:

  • 3rd Party Mac Developer Application
  • Developer ID Application
  • Mac Developer

All the necessary certificates and provision profiles were properly installed on the system that has been used for a code signing.

We have also tried signing our product with the following self-made script:

#!/bin/sh

APP_IDENTITY="Mac Developer: Our Identity (...)"
ENTITLEMENTS_CHILD="entitlements-child.plist"
ENTITLEMENTS_PARENT="entitlements-parent.plist"
APP="Our App Name.app"

sign () {
    OBJECT=$1
    ENTITLEMENTS=$2

    codesign --force --verbose --verify --sign "$APP_IDENTITY" --entitlements "$ENTITLEMENTS" --deep "$OBJECT"
    codesign --verify --deep --strict --verbose=2 "$OBJECT"
}

sign "$APP/Contents/Versions/63.0.3239.84/nwjs Framework.framework/libnode.dylib" "$ENTITLEMENTS_CHILD"
sign "$APP/Contents/Versions/63.0.3239.84/nwjs Framework.framework/Helpers/crashpad_handler" "$ENTITLEMENTS_CHILD"
sign "$APP/Contents/Versions/63.0.3239.84/nwjs Framework.framework/XPCServices/AlertNotificationService.xpc" "$ENTITLEMENTS_CHILD"
sign "$APP/Contents/Versions/63.0.3239.84/nwjs Framework.framework/nwjs Framework" "$ENTITLEMENTS_CHILD"
# sign "$APP/Contents/Versions/63.0.3239.84/nwjs Framework.framework" "$ENTITLEMENTS_CHILD"

sign "$APP/Contents/Versions/63.0.3239.84/nwjs Helper.app" "$ENTITLEMENTS_CHILD"
sign "$APP/Contents/Versions/63.0.3239.84/libffmpeg.dylib" "$ENTITLEMENTS_CHILD"

sign "$APP" "$ENTITLEMENTS_PARENT"

The instructions listed above work correctly and the codesign validation shows valid on disk / satisfies its Designated Requirement status for every resource it is applied to (except nwjs Framework.framework that will be mentioned further). However the application signed with this script crashes in the same way as described earlier. This time the crash log differs from the one attached above, but it also has a lot in common:
app-signed-for-development-crash-log.txt

It is also worth to note that both of the above scripts (build_mas.py and the one we created ourselves) have displayed a following error after the attempt to sign nwjs Framework.framework

Our App Name.app/Contents/Versions/63.0.3239.84/nwjs Framework.framework: unsealed contents present in the root directory of an embedded framework

I suppose that the reason of this error is that the nwjs Framework.framework itself consists of a large number of resources each of which should be signed on it's own as described in the apple documentation https://developer.apple.com/library/content/documentation/Security/Conceptual/CodeSigningGuide/Procedures/Procedures.html.

Additionally we have tried to manually sync our app's bundle identifier and display name with PlistBuddy utility but it doesn't seem to solve our problem as well.

BUNDLE_ID="our.app.bundle.id"
APP_NAME="Our App Name"

sed -i '' "s#(CFBundleDisplayName[^\"]\")[^\"](\")#\1$APP_NAME\2#g" "$APP/Contents/Resources/en.lproj/InfoPlist.strings"

/usr/libexec/PlistBuddy -c "Set :CFBundleIdentifier $BUNDLE_ID" "$APP/Contents/Info.plist"
/usr/libexec/PlistBuddy -c "Set :CFBundleDisplayName $APP_NAME" "$APP/Contents/Info.plist"
/usr/libexec/PlistBuddy -c "Set :CFBundleIdentifier $BUNDLE_ID.helper" "$NWJS_HELPER/Contents/Info.plist"
/usr/libexec/PlistBuddy -c "Set :CFBundleDisplayName $APP_NAME Helper" "$NWJS_HELPER/Contents/Info.plist"
/usr/libexec/PlistBuddy -c "Set :com.apple.security.application-groups $BUNDLE_ID" "$ENTITLEMENTS_PARENT"

Is there a way to make it work? We would really appreciate if someone shared some experience or advice on how to do MAS code signing properly. We will be happy for any help. Thanks in advance!

@tankakatan

This comment has been minimized.

Copy link
Author

commented Dec 16, 2017

Good news everyone!

The reason of the crash was the usage of the apple sandbox entitlement. If it is set to false app starts normally.

However it would be nice to find out the problem that leads our app to crash while running in the sandbox. The log attached to the issue says much about sandbox errors. So any ideas on how to solve them are welcome!

@tankakatan

This comment has been minimized.

Copy link
Author

commented Dec 23, 2017

This issue is obviously related to #6041

@tankakatan

This comment has been minimized.

Copy link
Author

commented Dec 23, 2017

Ok, I've found a fix to the unsealed contents error. It is caused by a libnode.dylib placed in the root folder of nwjs.app/Contents/Versions/63.0.3239.108/nwjs Framework.framework/.

According to Apple documentation:

...if a bundle has a Contents or Versions directory at its top level, there may be no other files or directories alongside them. The one exception is that alongside Versions, there may be symlinks to files in Versions/Current.

So the way to fix the problem is to move libnode.dylib to a nwjs.app/Contents/Versions/63.0.3239.108/nwjs Framework.framework/Versions/A/ folder and make a symbolic link to it in nwjs Framework.framework so its content will look like this:

drwxr-xr-x@ 8 user  group  256 Dec 23 20:36 .
drwxr-xr-x@ 5 user  group  160 Dec 23 20:36 ..
lrwxr-xr-x  1 user  group   24 Dec 16 20:47 Helpers -> Versions/Current/Helpers
lrwxr-xr-x  1 user  group   26 Dec 16 20:47 Resources -> Versions/Current/Resources
drwxr-xr-x@ 4 user  group  128 Dec 16 20:47 Versions
lrwxr-xr-x  1 user  group   28 Dec 16 20:47 XPCServices -> Versions/Current/XPCServices
lrwxr-xr-x  1 user  group   24 Dec 23 20:36 libnode.dylib -> Versions/A/libnode.dylib
lrwxr-xr-x  1 user  group   31 Dec 16 20:47 nwjs Framework -> Versions/Current/nwjs Framework

This fix helps to sign the application for a MAS distribution properly that can be proved via spctl utility:

spctl --assess --type execute Your app name.app
spctl --assess --verbose=4 Your app name.app

But unfortunately the application still can not be submitted to Mac App Store because the spctl will reject it whenever you replace an app bundle id with your own (keeping in mind that Info.plist can be edited strictly before the app will be signed).

Also the solution described above doesn't help to add the app sandbox capability. The application keeps crashing on start every time the sandbox option appears in entitlements.plist.

Looking forward for any help or advice.

@tankakatan

This comment has been minimized.

Copy link
Author

commented Dec 24, 2017

Yesterday I have finally gained success with getting spctl accepted status for our application. Here's a very useful application that helped me to discover some more unsealed contents in the Resources folder inside the application.

But the final fix was to add our team id to the application bundle id in the app entitlements.

BTW the entitlements-parent.plist file distributed with build_mas.py has a little bit incorrect format. According to apple docs the com.apple.security.application-groups key should have a value of type array of strings even if there is only one string.

The value for this key must be of type array, and must contain one or more string values, each of which must consist of your development team ID, followed by a period, followed by an arbitrary name chosen by your development team.

So the correct format for this entitlement is

	<key>com.apple.security.application-groups</key>
	<array>
		<string>TEAM_ID.your.app.bundle.id</string>
	</array>

Unfortunately the issue with app sandboxed mode still remains unsolved. Still waiting for any help or feedback on this subject.

Thanks everyone for the attention. Merry xmas and sorry for my awful english.

@Arti3DPlayer

This comment has been minimized.

Copy link

commented Jan 18, 2018

@tankakatan thank you, I will try your solution. Spent a lot of hours with unsealed contents. Could you add commands that you do to fix problem. It will be nice

Also, did you build app with nw-builder?

@tankakatan

This comment has been minimized.

Copy link
Author

commented Jan 21, 2018

@Arti3DPlayer here's my script for that:

# Set PATH_TO_YOUR_APP variable first
VERSION_NUMBER=`ls "${PATH_TO_YOUR_APP}/Contents/Versions/"`
NWJS_FRAMEWORK="$PATH_TO_YOUR_APP/Contents/Versions/$VERSION_NUMBER/nwjs Framework.framework"
LIBNODE_DYLIB="libnode.dylib"
LIBNODE_LINK_TO="Versions/A/$LIBNODE_DYLIB"

echo fixing nwjs Framework unsealed content
pushd "$NWJS_FRAMEWORK"
mv "$LIBNODE_DYLIB" "$LIBNODE_LINK_TO"
ln -s "$LIBNODE_LINK_TO"
popd

To synchronise bundle ids you can use PlistBuddy – a very handy tool for that. So my script is something like this:

# Set BUNDLE_ID variable with your app bundle id
/usr/libexec/PlistBuddy -c "Set :CFBundleIdentifier $BUNDLE_ID" "$PATH_TO_YOUR_APP/Contents/Info.plist"

# Set PATH_TO_ENTITLEMENTS_PARENT_PLIST variable
# Set TEAM_ID variable with your team id
/usr/libexec/PlistBuddy -c "Set :com.apple.security.application-groups:0 $TEAM_ID.$BUNDLE_ID" "$PATH_TO_ENTITLEMENTS_PARENT_PLIST"

Also i suggest you to update CFBundleIdentifier in all Info.plist files you will find inside your app's package. Add the corresponding postfix to the bundle id of each resource. For example, use $BUNDLE_ID.helper in Info.plist of nwjs Helper.app.

@Arti3DPlayer

This comment has been minimized.

Copy link

commented Jan 24, 2018

@tankakatan thanks, it works! But Users still get message that app from Unknown developer. i used mas.py script

And yes still crashing problem with Sandbox=Yes that makes impossible to upload app to Appstore

@tankakatan

This comment has been minimized.

Copy link
Author

commented Jan 24, 2018

@Arti3DPlayer it is ok to see the unknown developer warning until your users do not have to manually allow gatekeeper to run your app. It means that as long as you need to go to the system security settings to open your application its code signature is incorrect.

Sadly i haven't found a solution to the sandbox issue yet. The apple console shows a number of different errors refer to crashpad_handler and nwjs Framework itself. Сurrently i'm trying to build nwjs from the sources to try to fix all that errors one by one.

@Arti3DPlayer

This comment has been minimized.

Copy link

commented Jan 24, 2018

@tankakatan I fixed my problem by set the full name of certificate in build.cfg file:

## [REQUIRED] Your Application Certificate Identity
ApplicationIdentity = Developer ID Application: MyComapny Name (TT666666)

And its weird because when I used only code from brackets in this line(TT666666) it used 3rd Party Mac Developer Application that needed for appstore, but doesn't work for outside app. For now after download my app gatekeeper only warn user that

App was downloaded from internet, do you want to open it?

I didn't test this with auto update yet. Hope I will be able to swap apps programmatically

Found a lot of useful info from electron documentation:
https://www.electron.build/code-signing

@gpetrov

This comment has been minimized.

Copy link

commented Apr 8, 2018

@tankakatan, @Arti3DPlayer and @rogerwang - does this all means that we can finally upload the latest NWJS to the MAS - or is still a special build needed?

@tankakatan

This comment has been minimized.

Copy link
Author

commented Apr 8, 2018

@gpetrov nope, i have not succeed with MAS distribution so far. I also tried to build nwjs from the sources, but alas, failed to do so.

@tankakatan

This comment has been minimized.

Copy link
Author

commented Apr 13, 2018

@Arti3DPlayer Have you successfully uploaded your app to MAS?

@denigada

This comment has been minimized.

Copy link

commented May 31, 2018

@tankakatan I have tried everything and getting this problem when trying to sign my app:

Warning: unable to build chain to self-signed root for signer

Have you found a solution to sign the app? I can't even sign the app for outside MAS use. I sign it with Developer ID and it gives unidentified developer warning, I sign with 3rd Party Mac Developer certificate and I get the above warning and the app crashes on launch.

@Arti3DPlayer

This comment has been minimized.

Copy link

commented May 31, 2018

Yes, I'm not able to upload to MAS too, but sign app works fine even with autoupdates, so I don't require mas for now

@denigada try to use https://itunes.apple.com/us/app/rb-app-checker-lite/id519421117?mt=12 to identify what problems do you have. You can open and compare some popular apps and yours. I did this with "docker" app to make sure that I have all the same certificates

@tankakatan

This comment has been minimized.

Copy link
Author

commented Jun 1, 2018

@denigada @Arti3DPlayer i have built nwjs from the sources to find out the details of the sandboxed app crash and discovered that the reason of the problem is in the chromium itself. Here's the the google groups discussion where i have described it. Unfortunately i haven't got any feedback yet. Probably we should communicate with electron devs to ask them to share their experience.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.