How to sign (dynamic) JNLP files for OSX and Gatekeeper
Asked Answered
F

5

21

My company produces Java Applications for Servers and delivers JNLP files to start local Applications. Since OSX 10.8.4 it is required to sign JNLP files with a Developer ID to keep Gatekeeper happy (it's actually in the release notes at the very bottom).

The question is: how to accomplish this? AFAIK you can sign Apps (we have some Java Apps signed with Developer IDs) - but JNLP - Files are just that: files.

Next: how to do this with generated JNLP files. We have to modify them as they come from a server - e.g. properties, base URL and so forth.

AFAIK Java has a certain mechanism to say JNLP files are signed via their respective JAR file (the one that holds the main class) - but: Jar files are signed with a different certificate they will not satisfy Gatekeeper as well.

I did find one reference on how to sign tools and stuff, but it does not apply the scenario of dynamic files.

What I do not want as answers: Right-Click and Open to override the Gatekeeper or change the System- or Java settings. This is not an option.

[UPDATE] Since OSX 10.9.5 you also have to sign using OSX 10.9+ and have valid version 2 signatures. How will this be done?

Fossick answered 6/6, 2013 at 9:14 Comment(0)
F
7

I think I found a solution. The only one I can currently think up. We basically need to wrap the JNLP with a custom app launcher, sign the app, make sure we can modify the JNLP on the fly on a server and then have it run.

As you may be aware, there is an app bundler project which can wrap up any JAR files into an OSX executable. This can be signed, delivered and will not fail Gatekeeper. I made a custom fork (which is up for a pull int o the main fork) that can take an JNLP file, wrap it up and you have a custom application doing just all the stuff a JNLP should do.

A Requirement is, however, that you do have a valid "Developer ID Application" certificate

  1. Head over to bitbucket.org and download the current version
  2. Run the ant task and build the appbundler package.
  3. Have a look at the documentation for an example build script that will create the app container.
    • The example does not include the JNLP into the application right now.
    • The applications signature is created in a way so that the JNLP file can be modified later.
    • The application is being put into a zip file. This is important for downloading an application since they are only directories
  4. Create your server code. Load the ZIP file, put the JNLP File into the directory <yourapp>.app/Contents/Java/
  5. Deliver the zip file.

Now, if everything went fine, the zip file should automatically be unpacked in the Download folder and you should see your application icon. If you really made no mistake, you can execute the application as if it was a normal one.

I hope this will help a lot of developers fixing the broken JNLP behavior with OSX.

[UPDATE for modifiable JNLPs] Since OSX 10.9.5 it is required to have valid version 2 signatures on your app. This means that it the trick that was previously used by the app bundler (set a resource list file) does not work anymore. Everything and anything has to be signed now and it is virtually impossible to change the signed app afterwards.

I did however find a way: Use the app bundler. Set the JNLP to a file inside the Contents/_CodeSignature directory. Do not yet copy your modifiable JNLP in there but do this e.g. using Java later on when patching the zip (you'll need some code here anyway).

Please note: this should really only be needed if you have to put another JNLP file dynamically into the app container (thats is what the questions was about)

UPDATE (08-2017)

Oracle will be releasing Java 9 by the end of September. The appbundler does not handle the java9 vm correctly. They changed a whole lot of the API and the way that javaws works. For I need to say: stick with java8 if you want to use wrapped JNLP apps.

Fossick answered 29/10, 2013 at 15:39 Comment(4)
Thanks for your work! I have question though, how does this cope with required updates on the JNLP file?Hammerless
That is the tricky part. We are using Java to deliver our content. Before delivering the ZIP file we create the JNLP file on the fly and put it into the existing ZIP file. Since modifying the JNLP file in the ZIP does not cause the APP to change its signature (part of the signing process) - it just works. But you need tools to modify the ZIP on the fly. (e.g. ZipInputStream / ZipOutputStream)Fossick
Yes, but once the JNLp/App is delivered the JNLP is never updated right?Hammerless
Ah, ok. It can be updated. Within the JNLP you can say where this JNLP file is from. The boot loader of the JNLP (that is what comes from bitbucket) will try to fetch a newer version from the URL. But it will not place the new version into the APP again, only keep it at the temporary location.Fossick
P
6

We've been able to determine that you can sign a jnlp file with codesign, using the "Developer ID Application" Certificate, like this:

codesign -f -s "Developer ID Application: " foo.jnlp

The result from this operation seems to pass Gatekeeper on the local machine. However, it seems like the signature gets stored as extended HFS attributes, and as a result, it is not transmitted if a user fetches the file from a HTTP transaction.

It might work if you took the .jnlp file, and packaged it in some kind of container, like a .dmg or maybe a .tar.gz, however, that's both a lot of work, and it provides a fairly challenging user experience.

Protocol answered 9/6, 2013 at 19:45 Comment(6)
Yeah, I figured this out as well. The problem is: we have to modify the JNLP file on the server before delivering it to the user. The server is not necessarily an OSX server.Fossick
Sure -- but you could even get around that, if you set up a Mac to do the signing -- you could even build a tiny web service which would take a .jnlp file, sign it, and return it (careful to ensure that this service is only available to you). The real problem is how to get these .jnlp files to the user, and what steps the user would then need to take to launch them. If we need to pack these into a container like a .dmg, that leads to a fairly involved process for users to launch.Protocol
Nice try ;) - I think a service like you describe is a no-no. What I thought of was an App that could be downloaded and executes an URL or something that would then load and start the JNLP which will not have to be signed then. The app itself will have to be static and "know" where it's from. Dunno if that would work.Fossick
You could build your own "launcher" like that, and even pass it a file with the exact same contents as a jnlp. It could then take those contents and launch them with javaws. As long as the file that feeds that "launcher" was not considered to be a "jnlp" or "java-web-start" file by the OS, the codesign requirement would not be in play. Of course, if you just did that without any other restrictions, you are just re-opening the door Apple's been trying to close. However, if you also added some kind of validation to the launch file (a signature), it would be in keeping with the spirit.Protocol
Yep, Been thinking the same. Nevertheless I opened a bug report with Oracle. It's their technology - they should provide a suitable way to do what I described. Lets see what comes out of that.Fossick
We also have a case open with Apple, but with WWDC this week, DTS is officially "closed" this week.Protocol
N
3

From an email thread with Apple tech support, it seems the official word is to use the xip tool to work around the reliance on HFS extended attributes with codesign:

Instead of codesign, use xip (pronounced "chip") to create a signed archive of your JNLP file. Provide your Developer ID Installer identity as the argument to the --sign option, not your Developer ID Application identity.

A xip archive is essentially a signed zip archive so it can be served over the Internet in the same way as a zip archive. It will be unarchived automatically on the client Mac.

https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man1/xip.1.html

From my experimentation the xip tool always generates an archive with the jnlp contained in a folder when unxip'd.

Nagging answered 20/6, 2013 at 19:45 Comment(1)
According to developer.apple.com/library/content/technotes/tn2206/… XIP not available for non-Apple developers starting with Sierra.Deepset
G
2

Just to summarise the discussion; currently there is no existing solution on how to come around this.

This means that end users cannot launch an application via JNLP easily. Basically one needs to tell the user to Right-Click and Open to override the Gatekeeper.

The other solution would be to make an signed Mac application and have users install that via disk image.

Gynecology answered 17/7, 2013 at 8:16 Comment(0)
D
0

Would it work to bundle a simple executable shell script called something like "myapp" in a signed .dmg which looks like this:

javaws http://path/to/my/app.jnlp

that way you can change the .jnlp however you like without changing your .dmg. I don't have an Apple Developer ID, so I can't try it myself right now.

Deepset answered 3/11, 2016 at 15:16 Comment(3)
I do not know this. But you can't modify dmg content on the fly which is a requirement. The accepted answers's concept is in place since I posted it. It has not yet failed to perform as desired ;)Fossick
Are you dynamically changing the URL of your jnlps?Deepset
Yep. The JNLP is updated on the fly by a java backend. When we package the JNLP during the build process we can not know the URLs the end user will have.Fossick

© 2022 - 2024 — McMap. All rights reserved.