Clojure, Seesaw and Java Web Start with Leiningen

Here’s a quick tutorial about how you can put your Clojure + Seesaw applications on the Web to be started using Java Web Start. I will be using Leiningen build tool.

Sample Clojure + Seesaw application using Leiningen

Let’s make a sample app first.

$ lein new com.icyrock.clojure.csjws
Created new project in: /home/icyrock.com/clojure/com.icyrock.clojure.csjws
Look over project.clj and start coding in com/icyrock/clojure/csjws/core.clj

Edit your project.clj to this:

(defproject com.icyrock.clojure.csjws "1.0.0-SNAPSHOT"
  :description "com.icyrock.clojure.csjws"
  :dependencies [[org.clojure/clojure "1.3.0"]
                 [seesaw "1.4.0"]]
  :main com.icyrock.clojure.csjws.core)

Run lein deps to fetch the dependencies.

Edit the only file in src subtree:

src/com/icyrock/clojure/csjws/core.clj

to the following:

(ns com.icyrock.clojure.csjws.core
  (:gen-class)
  (:use [seesaw core mig]))

(defn make-button [row col]
  (button :text (format "(%d, %d)" row col)
          :listen [:action
                   (fn [e] (alert (format "Hi, you clicked on (%d, %d)!" row col)))]))

(defn make-content []
  (mig-panel
   :constraints ["fill"]
   :items
   (let [rows 4 cols 4]
     (for [row (range rows) col (range cols)]
       [(make-button row col)
        (if (and (< row (dec rows)) (= col (dec cols))) "grow, wrap" "grow")]))))

(defn -main [& args]
  (native!)
  (-> (frame :title "com.icyrock.clojure.csjws.core"
             :content (make-content)
             :width 400
             :height 400
             :on-close :exit)
      show!))

Run it with lein run – this will give you a 4×4 button grid such as this:

Signing

Seesaw is using reflection, for which you need security permissions to run. To be able to grant these, jar needs to be signed. Here are the steps needed for a self-signed jar:

  • Generate public / primary key pair
  • Sign jar given that certificate

The first step is needed only if you don’t have the certificate (or want to use a new one). All you need is contained in the JDK itself.

Generate public / primary key pair

$ keytool -genkeypair -alias icyrock.com \
-keystore icyrock.com.jks -validity 365 \
-storepass password -keypass password \
-dname "CN=icyrock.com,O=icyrock.om,C=US"

This will give you a file called icyrock.com.jks, protected by password “password”, which contains a key pair with alias “icyrock.com” valid for 365 days and protected by password “password”. You can list this:

$ keytool -list -keystore icyrock.com.jks -storepass password -keypass password -v

Keystore type: JKS
Keystore provider: SUN

Your keystore contains 1 entry

Alias name: icyrock.com
Creation date: Apr 11, 2012
Entry type: PrivateKeyEntry
Certificate chain length: 1
Certificate[1]:
Owner: CN=icyrock.com, O=icyrock.om, C=US
Issuer: CN=icyrock.com, O=icyrock.om, C=US
Serial number: 4f864b76
Valid from: Wed Apr 11 23:26:46 EDT 2012 until: Thu Apr 11 23:26:46 EDT 2013
Certificate fingerprints:
     MD5:  34:8E:22:BF:D8:1A:C8:6F:40:ED:8B:B5:97:5A:65:75
     SHA1: 2E:49:87:1D:14:11:C6:C3:F1:AE:FC:6A:60:82:10:38:8E:B4:67:FA
     Signature algorithm name: SHA1withDSA
     Version: 3

Note that the above is actually a X.509 certificate (see keytool docs for more info). This certificate can be used to sign jars with jarsigner, which is all we need.

Security note

The file that keytool generated (icyrock.com.jks) is called JKS – Java Key Store – for a very good reason. It stores key information. This line from the above is important:

Entry type: PrivateKeyEntry

That should be sufficient for anyone to consider this file very important from security standpoint. It contains information that allows anyone in its possesion to create signed files. Those files will bear that certificate details, but the content might be vastly different. In real life, this is called identity theft. In the same way you would not give your house key to a random person, you should not give your .jks files to random people.

On the other hand, you can give out your certificate – this can be used by other people to verify your jar. To export the certificate, you do this:

$ keytool -export -keystore icyrock.com.jks -storepass password -keypass password -file icyrock.com.crt -alias icyrock.com

You can now give this cert file, which others can import into their .jks keystore like this:

$ keytool -importcert -file icyrock.com.crt -keystore icyrock.com-cert.jks -storepass password -keypass password -alias icyrock.com-cert

The certificate itself is different than the key, here’s the output:

$ keytool -list -keystore icyrock.com-cert.jks -storepass password -keypass password -v
Keystore type: JKS
Keystore provider: SUN

Your keystore contains 1 entry

Alias name: icyrock.com-cert
Creation date: Jul 14, 2012
Entry type: trustedCertEntry

Owner: CN=icyrock.com, O=icyrock.om, C=US
Issuer: CN=icyrock.com, O=icyrock.om, C=US
Serial number: 4f864b76
Valid from: Wed Apr 11 23:26:46 EDT 2012 until: Thu Apr 11 23:26:46 EDT 2013
Certificate fingerprints:
     MD5:  34:8E:22:BF:D8:1A:C8:6F:40:ED:8B:B5:97:5A:65:75
     SHA1: 2E:49:87:1D:14:11:C6:C3:F1:AE:FC:6A:60:82:10:38:8E:B4:67:FA
     SHA256: 2D:63:93:C0:26:8D:8C:5C:16:AD:22:D9:31:42:05:F2:9D:B8:9C:75:BC:68:F1:40:D6:8B:95:7C:61:D2:79:2B
     Signature algorithm name: SHA1withDSA
     Version: 3

The important line here is:

Entry type: trustedCertEntry

which says it contains the certificate only. To compare to the house analogy, this would be as if you gave someone your address. They could then come to your house and confirm that’s really your house, but without the key that only you have they could not come in. In a similar way, this certificate can be used to verify .jar files, but not sign them.

Creating uberjar and signing it

Leiningen has uberjar command which creates a jar that contains your code plus all dependencies packed into one jar. Run that and you should get a jar called com.icyrock.clojure.csjws-1.0.0-standalone.jar in the root folder of the project.

Let’s sign that jar with the certificate we created above:

$ jarsigner -keystore icyrock.com.jks \
-storepass password -keypass password \
-signedjar com.icyrock.clojure.csjws-1.0.0-SNAPSHOT-standalone-signed.jar \
com.icyrock.clojure.csjws-1.0.0-SNAPSHOT-standalone.jar icyrock.com

This will generate a new file:

com.icyrock.clojure.csjws-1.0.0-SNAPSHOT-standalone-signed.jar

that is now signed. You can verify this file:

$ jarsigner -verify \
com.icyrock.clojure.csjws-1.0.0-SNAPSHOT-standalone-signed.jar 
jar verified.

Warning: 
This jar contains entries whose certificate chain is not validated.

Re-run with the -verbose and -certs options for more details.

Due to our certifiacte being self-signed, the above warning is printed. Now the certificate file we generated above comes into play:

$ jarsigner -keystore icyrock.com-cert.jks -verify \
com.icyrock.clojure.csjws-1.0.0-SNAPSHOT-standalone-signed.jar 
jar verified.

In this case, jarsigner used the provided certificate to confirm that all contents of the .jar file actually were signed with the key corresponding to that certificate.

You can start this using regular java -jar command:

$ java -jar \
com.icyrock.clojure.csjws-1.0.0-SNAPSHOT-standalone-signed.jar 

Java Web Start

Now that you have a signed file, you can put it on some Web server to be picked up by javaws. For this, you first need to make a .jnlp file. Say you have a local Web server accessible via http://localhost and you want to put this under http://localhost/clojure/. Here’s a JNLP file that would work for that arrangement:

<?xml version="1.0" encoding="utf-8"?>
<jnlp spec="1.0+" 
      codebase="http://localhost/clojure/"
      href="csjws.jnlp">
  <information>
    <title>com.icyrock.clojure.csjws.core</title>
    <vendor>icyrock.com</vendor>
    <icon href="com.icyrock.clojure.csjws.png"/>
    <offline-allowed/>
  </information>
  <security>
      <all-permissions/>
  </security>
  <resources>
    <j2se version="1.6.0+"/>
    <jar href="com.icyrock.clojure.csjws-1.0.0-SNAPSHOT-standalone-signed.jar"/>
  </resources>
  <application-desc main-class="com.icyrock.clojure.clsjws.core"/>
</jnlp> 

Now if you put this JNLP and the signed jar file (com.icyrock.clojure.csjws-1.0.0-SNAPSHOT-standalone-signed.jar) so they are accessible via http://localhost/clojure/, you should be able to then do:

$ javaws http://localhost/clojure/com.icyrock.clojure.csjws.jnlp

Of course, you can do that with any browser that has Java plug-in enabled by going to the same location:

http://localhost/clojure/com.icyrock.clojure.csjws.jnlp

This should launch Java Web Start. It will download the file and ask you to acknowledge the certificate (as it is self-signed, not by a trusted CA authority). To confirm this is the right certificate, you can compare the fingerprints presented by javaws and the certificate itself. In general, only if these match and only if you trust the provider of the certificate should you proceed – otherwise, you can potentially allow malicious code to execute without security restrictions, which is not a desirable result.

This may look like this:

When you click on “More information…”, you get this:

When you click on “Certificate details…”, you get this:

Here you can see the SHA1 fingerprint. Take this and compare to the certificate. Usually, for self-signed certificates, this can be given e.g. on the site for users to compare. If these match, you can be pretty sure the code was signed by that certificate and all is good. The best way is to give the certificate (icyrock.com-cert.jks or icyrock.com.crt) to your users, which can then import it and the browser will automatically do the verification for them. You can close these two and click Run on the original dialog – the app will launch.

Be Sociable, Share!

Leave a Reply


4 + seven =