Sandboxing Entitlements MacOS App Store

Deploying to Mac OS? Ask Mac OS specific questions here.

Moderators: FourthWorld, heatherlaine, Klaus, kevinmiller, robinmiller

Post Reply
benco5000
Posts: 11
Joined: Fri Mar 05, 2010 9:51 pm

Sandboxing Entitlements MacOS App Store

Post by benco5000 » Fri May 08, 2020 6:30 pm

........

This discussion is about:

...... 1. Sandboxing an app for the macOS app store
...... 2. Configuring the plist files
...... 3. Signing the App
...... 4. Submitting the pkg to the app store

In my case I need something called bookmarks to enable persistant security permissions

I've put down here how far I have been able to get in the process but am stuck in this
particular section about bookmarks.

The response I received from Apple about my App not working after sandboxing
was essentially to point me to a document about

Security-Scoped Bookmarks and Persistent Resource Access

----------------------------------------------------------------------------------------
Hopefully this will help newcomers to the App store process
There may be some typos and some confusion, I'll do my best to update based on feedback
----------------------------------------------------------------------------------------

More information can be found at this link with is the 'App Sandbox in Depth'
https://developer.apple.com/library/arc ... 83-CH3-SW6

I also found this post about working this in Xcode:
https://stackoverflow.com/questions/479 ... mac-os-app


I still need to figure out how to do this in LiveCode but maybe someone here can elaborate.
--------------------------------------------------------------------------------------------


-------------------------------------------------------------------------
-- Mac developers know about a text file with a .plist extension.
-------------------------------------------------------------------------

...... This plist file holds information about an app or about how it is to be compiled. Think signing the app.
...... It can be a text or binary file and must be setup for the target environment.
...... It holds information about the icons the app uses, app names for the app store, and a whole lot of other information.

...... There is another key plist file that is needed to put your app up on the mac app store.
...... That is a plist file which I will call 'entitlements.plist' that holds specific information about how your app is to be used.

...... Xcode can read the plist files and so can other text editors. There are specific plist editors that claim they
...... can read both binary and text (XML) and are listed on the mac app store.

...... NOTE: If I inadvertantly refer to the 'app store' I am actually referring to the 'mac app store'.


----------------------------------------------------------------------------------------
-- To have an app listed on the App store - your macOS app must be sandboxed.
----------------------------------------------------------------------------------------

...... To make it sandboxed, the app must be codesigned with specific entries in the entitlements file.
...... This entitlements file will be used to codesign your app.
...... The codesign command with the entitlements file is in the shell script.

...... Here is an example of an entitlements XML file with the sandbox option enabled for the macOS:

Code: Select all

	<?xml version="1.0" encoding="UTF-8"?>
	<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
	<plist version="1.0">
	<dict>
		<key>com.apple.security.app-sandbox</key>
		<true/>
	</dict>
	</plist>

...... When your app is sandboxed the macOS creates a container for your app.
...... That container is currently located in the home users Library directory (or folder if you prefer) and has the path like so:

...... /Users/<username>/Library/Containers/<com.yourCompanyName.yourAppName/Data...

...... ----------------------------------------------------------------------------------------

...... In order to read or write to the macOS Filesystem outside of your sandbox environment,
...... You have to buid the app to use

...... ...... 1. A sandboxed environment
...... ...... 2. Have read/write permissions.

...... The entitlements.plist file will look like this:

Code: Select all

	<?xml version="1.0" encoding="UTF-8"?>
	<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
	<plist version="1.0">
	<dict>
		<key>com.apple.security.app-sandbox</key>
		<true/>
		<key>com.apple.security.files.user-selected.read-write</key>
		<true/>
	</dict>
	</plist>
	
...... This will allow the app to read/write outside of the sandboxed environment but only for one session.

...... If the app is closed, the permissions will reset and the user will need to be prompted again for access.
...... If the user is not prompted for access the program will have appeared to the user to have become corrupted.

...... To enable what apple calls 'persistant access' one must use a security-scoped bookmark.


------------------------------------------------------------------------------------------------
-- Security-scoped bookmarks, available starting in macOS 10.7.3, support two distinct use cases:
------------------------------------------------------------------------------------------------
1. An app-scoped bookmark provides your sandboxed app with persistent access to a user-specified file or folder.
2. A document-scoped bookmark provides a specific document with persistent access to a file.

...... To use app-scoped bookmarks in a target

...... ...... Turn on com.apple.security.files.bookmarks.app-scope

...... This is the entitlement needed to turn on bookmarking so that later bookmarks can be added, modified, retrieved, and deleted.
...... It is also the part I have yet to figure out or be told how to do in 3rd party apps like livecode.
...... Here is the third key added to the entitlements.plist file.

Code: Select all

	<?xml version="1.0" encoding="UTF-8"?>
	<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
	<plist version="1.0">
	<dict>
		<key>com.apple.security.app-sandbox</key>
		<true/>
		<key>com.apple.security.files.user-selected.read-write</key>
		<true/>
		<key>com.apple.security.files.bookmarks.app-scope</key>
		<true/>
	</dict>
	</plist>
	
...... To Sign your app, you must be an Apple Developer. To do that just sign up, at this time it is about $100 US dollars per year.
...... You must then download and certify your computer system(s) that you will be developing on.
...... I should probably post about my experiance on all of that.
...... It took me several weeks to figure out all the details, and some are still a bit confusing. In a nutshell:

...... 1. Create the necessary certificate requests / download / import them into your 'keychain.app'
.......... Note there is a limit as I have seen some complaints on other blogs. Although Apple can fix that if you have issues.
...... 2. Create an Identifier for your app. This is something like com.yourCompany.yourApp and is used in your plist files
...... 3. Devices for a single developer may not be needed - but basically you identify a computer for beta testing
...... 4. Profiles for a single developer may only be needed for a wildcard scoped development appID.

...... Your situation may vary.

...... Set the LiveCode 'Stand Alone Applicaton settings' under macOS for:
...... ...... icons, AppName, VersionNumbers, Info String and most importantly:

...... ...... Bundle Identifier: com.yourCompany.yourApp

...... After this I use a shell script to build the app changing the appropriate entries in the app.plist file and entitlements.plist file

...... The codesign step (second to last step in the shell script) is the step that 'sandboxes' your app

...... The shell script listed next is a how-to to get you started.

Code: Select all

	## For each external component
	# pseudo code do this for each external app used like revdb, sqlite, tsnet, etc...
	# this must be done from the inside out so if A depends upon B which depends upon C then 
	# start with C then, B, then A

		# remove attributes
			xattr -cr ./revdb.bundle
		
		# remove 32 bit code for macOS Catalina
			lipo ./revdb -remove i386 -output ./revdb64 
			RESULT=$?
			if [ $RESULT -eq 0 ]; then
				rm ./revdb && mv ./revdb64 ./revdb
			fi

		# take ownership using your bundle identifer (cAsE matters or if it doesn't it proabably will in the future):
		plutil -replace CFBundleIdentifier -string "com.YourCompany.YourApp" ./Info.plist

		# sign
		sudo codesign --force --sign "Apple Distribution: Your Name (your ID)" ./revdb.bundle

    Finally do the same for your app
    Copy any additional files you may need like entitlements.plist 

    # this is where I do a version increment number from the source directory
		FILE=app.version
		if [ -f "$FILE" ]; then
			## file exists read the version number 
			version=`cat app.version` 
			versionField1=$(echo $version | cut -d . -f 1 | tr -d " ")
			# -- gets '"0"'  and then cuts the quotes
			versionField2=$(echo $version | cut -d . -f 2 | tr -d " ")
			# -- gets '"4"' and then cuts the quotes
			versionField3=$(echo $version | cut -d . -f 3 | tr -d " ")
			echo "app.version file exists, Version ""$versionField1"".""$versionField2"".""$versionField3""\n\n"
		else
			## file does not exist so read from plist and create the file
			versionField1=$(plutil -p Info.plist | grep CFBundleShortVersion | cut -d \> -f 2 | tr -d '"' | cut -d . -f 1 | tr -d " ")
			# -- gets '"0"'  and then cuts the quotes
			versionField2=$(plutil -p Info.plist | grep CFBundleShortVersion | cut -d . -f 2 | tr -d '"')
			# -- gets '"4"' and then cuts the quotes
			versionField3=$(plutil -p Info.plist | grep CFBundleShortVersion | cut -d . -f 3 | tr -d '"')
			echo "$versionField1"".""$versionField2"".""$versionField3" > app.version
			echo "Created file app.version with version number ""$versionField1"".""$versionField2"".""$versionField3""\n\n"
		fi

		##versionField3New="$versionField3" 
		##Write the new version number into the source directory
		versionField3New=$(($versionField3+1))
		echo "current version is $versionField1"".""$versionField2"".""$versionField3"
		echo "incrementing version number to ""$versionField1"".""$versionField2"".""$versionField3New"
		echo "$versionField1"".""$versionField2"".""$versionField3New" > app.version
		echo "current version is now ""$versionField1"".""$versionField2"".""$versionField3New""\n\n"

		newVersion="$versionField1"".""$versionField2"".""$versionField3New"
		newLongVersion="app ""$versionField1"".""$versionField2"".""$versionField3New"

		## --------------------------------------------------
		## -- write new version to directory for reference
		## -- tells us which version this is w/out looking
		## -- into the pList file
		## --------------------------------------------------
		cd /Users/Ben/Desktop/app
		echo "Version ""$newLongVersion" > "Version ""$newLongVersion"


		## --------------------------------------------------
		## -- update Info.plist with the new version number
		## --------------------------------------------------
		cd app/Contents
		# write the new value into CFBundleLongVersionString
		cmd="plutil -replace CFBundleLongVersionString -string "'"'"$newLongVersion"'"'" ./Info.plist"
		eval "$cmd"

		# write the new value into CFBundleShortVersionString
		cmd="plutil -replace CFBundleShortVersionString -string "'"'"$newVersion"'"'" ./Info.plist"
		eval "$cmd"

		# write the new value into CFBundleVersion
		cmd="plutil -replace CFBundleVersion -string "'"'"$newVersion"'"'" ./Info.plist"
		eval "$cmd"

		## --------------------------------------------------
		## -- update Info.plist CFBundleIdentifier
		## --------------------------------------------------
		# write the new value into CFBundleIdentifier
		plutil -replace CFBundleIdentifier -string "com.yourCompany.yourAppName" ./Info.plist

		## --------------------------------------------------
		## -- update Info.plist CFBundleGetInfoString
		## --------------------------------------------------
		# write the new value into CFBundleGetInfoString
		plutil -replace CFBundleGetInfoString -string "com.yourCompany.yourAppName" ./Info.plist


		## The following was required for my app - I'll save you some time - took me a while

		## --------------------------------------------------
		## -- update Info.plist LSApplicationCategoryType
		## --------------------------------------------------
		plutil -replace LSApplicationCategoryType -string "public.app-category.utilities" ./Info.plist


		## --------------------------------------------------
		## -- update Info.plist LSHandlerRank
		## --------------------------------------------------
		plutil -replace LSEnvironment.LSHandlerRank -string "None" ./Info.plist


		## -------------------------------------------------------------------
		## -- update Info.plist CFBundleDocumentTypes.CFBundleTypeExtensions
		## -- I did not need this one
		## -------------------------------------------------------------------
		##plutil -replace CFBundleDocumentTypes.CFBundleTypeExtensions -string ".###" ./Info.plist


		## -----------------------------------------------------------------
		## -- update Info.plist CFBundleDocumentTypes.CFBundleTypeIconFile
		## -----------------------------------------------------------------
		plutil -replace CFBundleDocumentTypes.CFBundleTypeIconFile -string "app.icns" ./Info.plist


		## -------------------------------------------------------------
		## -- update Info.plist CFBundleDocumentTypes.CFBundleTypeName
		## -- Case matters
		## -------------------------------------------------------------
		plutil -replace CFBundleDocumentTypes.CFBundleTypeName -string "None" ./Info.plist



		## -------------------------------------------------------------
		## -- update Info.plist CFBundleDocumentTypes.CFBundleTypeRole
		## -- Case matters
		## -------------------------------------------------------------
		plutil -replace CFBundleDocumentTypes.CFBundleTypeRole -string "None" ./Info.plist



		## --------------------------------------------------
		## -- codesign entire app with entitlements
		## --------------------------------------------------
		cd to dir

		## works but no sandboxing (so no macOS APP Store)
		## sudo codesign --force --deep --sign "Apple Distribution: YourName (Your ID)" ./app.app

		## does not work in my situation
		## sudo codesign --force --options runtime --deep --sign "Apple Distribution: YourName (Your ID)" ./app.app

		## --------------------------------------------------
		## -- works for me
		## --------------------------------------------------
		## sudo codesign --force --deep --entitlements ./appEntitlements.plist --sign "Apple Distribution: YourName (Your ID)" ./app.app


		## -----------------------------------------------------------------------------------------------
		## -- package the app for the app store
		## -- this app can be sent via Transporter
		## -----------------------------------------------------------------------------------------------
		cd /app/dir
		productbuild --component ./app.app /Applications ./app.pkg --sign "3rd Party Mac Developer Installer: YourName (Your ID)"


		## -----------------------------------------------------------------------------------------------
		## -- Transporter is an app that you can drag and drop your app.Pkg onto and it will upload it.
		## -- After that you can right click on the elipses (...) and click "Verify"
		## -- This will run your app through a verification check and report back to you any errors.
		## -- Do this before clicking the submit button and you can keep uploading and overwriting 
		## -- your app until you are completely satisfied that it will verify properly.
		## -- Finally after it verifies properly, then submit it for final evaluation to the app store.
		## -----------------------------------------------------------------------------------------------

		

Here are some Xcode additional notes I found (not sure if they are as current as the ones above)
The URL links above should help.


In Xcode:
------------
With the appropriate entitlements, you can create a security-scoped bookmark by calling the

bookmarkDataWithOptions:includingResourceValuesForKeys:relativeToURL:error: method of the NSURL class.




When you later need access to a bookmarked resource, resolve its security-scoped bookmark by calling the the

URLByResolvingBookmarkData:options:relativeToURL:bookmarkDataIsStale:error: method of the NSURL class.




In a sandboxed app, you cannot access the file-system resource that a security-scoped URL points to until you call the

startAccessingSecurityScopedResource method on the URL.




When you no longer need access to a resource that you obtained using security scope (typically, after you close the resource) you must call the

stopAccessingSecurityScopedResource method on the resource’s URL.


Calls to start and stop access are not nested. When you call the stopAccessingSecurityScopedResource method, you immediately lose access to the resource.
If you call this method on a URL whose referenced resource you do not have access to, nothing happens.

!!!! Warning !!!!: If you fail to relinquish your access to file-system resources when you no longer need them, your app leaks kernel resources. If sufficient kernel resources are leaked, your app loses its ability to add file-system locations to its sandbox, such as via Powerbox or security-scoped bookmarks, until relaunched.

For detailed descriptions of the methods, constants, and entitlements to use for implementing security-scoped bookmarks in your app, read NSURL Class Reference, and read Enabling Security-Scoped Bookmark and URL Access in Entitlement Key Reference.

Note: The Core Foundation framework also provides equivalent C functions for working with security-scoped bookmarks. For details, see the documentation for CFURLCreateBookmarkData, CFURLCreateByResolvingBookmarkData,CFURLStartAccessingSecurityScopedResource, and CFURLStopAccessingSecurityScopedResource in CFURL Reference.

Post Reply

Return to “Mac OS”