iOS applications are distributed in .ipa archive files. These files are regular zip files which contain application resources and executable-s. To protect them from unauthorized modifications and to provide identification of their sources, the content of the archives is signed. The signature is included in the application executable of an.ipa archive and protects the executable file itself and the associated resource files. Apple provides native Mac OS tools for signing iOS executable-s (which are actually generic Mach-O code signing tools), but these tools are not generally available on other platforms. To provide a multi-platform development environment for JavaFX based iOS applications, we ported iOS signing and packaging to Java and created a dedicated ipack tool for it.
The iPack tool can be used as a last step of creating .ipa package on various operating systems. Prototype has been tested by creating a final distributable for JavaFX application that runs on iPad, all done on Windows 7.
Source Code
The source code of iPac tool is in OpenJFX project repository. You can find it in:
<openjfx root>/rt/tools/ios/Maven/ipack
To build the iPack tool use:
rt/tools/ios/Maven/ipack$ mvn package
After building, you can run the tool:
java -jar <path to ipack.jar> <arguments>
Signing keystore
The tool uses a java key store to read the signing certificate and the associated private key. To prepare such keystore users can use keytool from JDK.
One possible scenario is to import an existing private key and the certificate from a key store used on Mac OS:
To list the content of an existing key store and identify the source alias:
keytool -list -keystore <src keystore>.p12 -storetype pkcs12 -storepass <src keystore password>
To create Java key store and import the private key with its certificate to the keys store:
keytool -importkeystore \
-destkeystore <dst keystore> -deststorepass <dst keystore password> \
-srckeystore <src keystore>.p12 -srcstorepass <src keystore password> -srcstoretype pkcs12 \
-srcalias <src alias> -destalias <dst alias> -destkeypass <dst key password>
Another scenario would be to generate a private / public key pair directly in a Java key store and create a certificate request from it. After sending the request to Apple one can then import the certificate response back to the Java key store and complete the signing certificate entry.
In both scenarios the resulting alias in the Java key store will contain only a single (leaf) certificate. This can be verified with the following command:
keytool -list -v -keystore <ipack keystore> -storepass <keystore password>
When looking at the Certificate chain length entry, the number next to it is 1.
When an executable file is signed on Mac OS, the resulting signature (in CMS format) includes the whole certificate chain up to the Apple Root CA. The ipack tool includes only the chain which is stored under the alias specified on the command line. So to have the whole chain in the signature we need to replace the single certificate entry under the alias with the corresponding full certificate chain.
To do that we need first to create the chain in a separate file. It is easy to create such chain when working with certificates in Base-64 encoded PEM format. A certificate chain can be created by concatenating PEM certificates, which should form the chain, into a single file.
For iOS signing we need the following certificates in our chain:
Apple Root CA
Apple Worldwide Developer Relations CA
Our signing leaf certificate
To convert a certificate from the binary DER format (.der, .cer) to PEM format:
keytool -importcert -noprompt -keystore temp.ks -storepass temppwd -alias tempcert -file <certificate>.cer
keytool -exportcert -keystore temp.ks -storepass temppwd -alias tempcert -rfc -file <certificate>.pem
To export the signing certificate into PEM format:
keytool -exportcert -keystore <ipack keystore> -storepass <keystore password> -alias <signing alias> -rfc -file SigningCert.pem
After constructing a chain from AppleIncRootCertificate.pem, AppleWWDRCA.pem andSigningCert.pem, it can be imported back into the keystore with:
keytool -importcert -noprompt -keystore <ipack keystore> -storepass <keystore password> -alias <signing alias> -keypass <key password> -file SigningCertChain.pem
To summarize, the following example shows the full certificate chain replacement process:
keytool -importcert -noprompt -keystore temp.ks -storepass temppwd -alias tempcert1 -file AppleIncRootCertificate.cer
keytool -exportcert -keystore temp.ks -storepass temppwd -alias tempcert1 -rfc -file AppleIncRootCertificate.pem
keytool -importcert -noprompt -keystore temp.ks -storepass temppwd -alias tempcert2 -file AppleWWDRCA.cer
keytool -exportcert -keystore temp.ks -storepass temppwd -alias tempcert2 -rfc -file AppleWWDRCA.pem
keytool -exportcert -keystore ipack.ks -storepass keystorepwd -alias mycert -rfc -file SigningCert.pem
cat SigningCert.pem AppleWWDRCA.pem AppleIncRootCertificate.pem >SigningCertChain.pem
keytool -importcert -noprompt -keystore ipack.ks -storepass keystorepwd -alias mycert -keypass keypwd -file SigningCertChain.pem
keytool -list -v -keystore ipack.ks -storepass keystorepwd
Usage
When the ipack tool is started with no arguments it prints the following usage information:
-appname MyApplication -appid com.myorg.MyApplication Usage:
ipack <archive> <signing opts> <application opts> [ <application opts> ... ]
Signing options:
-keystore <keystore> keystore to use for signing
-storepass <password> keystore password
-alias <alias> alias for the signing certificate chain and
the associated private key
-keypass <password> password for the private key
Application options:
-basedir <directory> base directory from which to derive relative paths
-appdir <directory> directory with the application executable and resources
-appname <file> name of the application executable
-appid <id> application identifier
Example:
ipack MyApplication.ipa -keystore ipack.ks -storepass keystorepwd
-alias mycert -keypass keypwd
-basedir mysources/MyApplication/dist
-appdir Payload/MyApplication.app
-appname MyApplication -appid com.myorg.MyApplication