Sunday, March 3, 2013

Signing and Promoting your Clojure libraries on Clojars

Phil Hagelberg, the creator and primary maintainer of Leiningen, has been advocating that Clojurians sign their Clojure libraries for the releases repository in Clojars. By itself, this isn't sufficient to provide security to avoid malicious code from causing havoc with public code repositories, but it is a necessary first step. Phil has talked about his ideas on how to get to a more complete model of security in a couple of places:


/* ---[ Signing your Clojure libraries ]--- */

My first experience deploying a signed jar to Clojars was a little rocky, so I'm providing this how-to report to help others (including future me).

I have only done this on (Xubuntu) Linux, but I imagine it will work fairly similarly on Macs. Not sure about Windows, as I seem to have constant trouble getting Clojure and Windows to play nicely together. I have used GPG on Windows that comes with mysysgit, so that will probably work with these instructions as well, but I haven't tried it.


/* ---[ STEP 1: Generate GPG Keys ]--- */

Clojars security is based on PGP keys, so you need to a have a PGP public/private keyset. GnuPG (GPG) is the generally recommended tool for that.

If you already have GPG installed and can't remember if you've already created a keyset, try this first:

gpg --list-keys

If you see your name and email in the list, then you have. If not, generate them with:

gpg --gen-key

Accepting the defaults you are prompted with is fine. See this article for details on this step. When completed this will create your public key ring and secret/private key ring:

$ ls ~/.gnupg
pubring.gpg  pubring.gpg~  random_seed  secring.gpg  trustdb.gpg


/* ---[ STEP 2: Publish your public GPG key to a keyserver ]--- */

By publishing your public key, others can download it and verify that your signed library is in fact signed by you.

To publish your key you will need to get its ID.

$ gpg --list-keys
/home/midpeter444/.gnupg/pubring.gpg
------------------------------------
pub   2048R/5414B325 2012-11-12
uid                  Michael Peterson <myemail@fubar.com>

The 8 characters after the '/' on the "pub" row of your key is your key's ID. Now publish it:

$ gpg --send-key 5414B325

If you don't specify a key server it will choose the GnuPG keyserver. If you want to target a specify keyserver use the --keyserver option as shown here.


/* ---[ STEP 3: Add your GPG key to your Clojars account ]--- */

When you sign up for Clojars there is a section in your Profile to add two keys: 1) an SSH public key and a PGP public key. The SSH key is for secure transport of the library from your system to the Clojars repo via scp. It is not related to signing your jars.

Your library will be signed with your PGP private key that resides only on your system. That signature indicates that the owner of the private key (the one paired with the public key you just published) signed this code artifact. It allows someone else to know who signed it and whether the code artifact has been changed since it was signed and deployed.

By having your PGP public key on Clojars, you allow Clojars to verify that one of its members signed the artifact. This check happens when you promote your release to the release repo (more on that below).

Note: Clojars is not a keyserver, so putting it there will not allow others to verify the signature. That is why in step 2 we published it to a public keyserver.

To add your public key to Clojars you create an "ASCII-armored" version of the binary public key, which you generate with:

gpg --armor --export your@email.here code

Once you have it, what exactly do you paste into the Clojars text box? The BEGIN and END delimiter lines and everything in between, like so:

-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: GnuPG v1.4.11 (GNU/Linux)

mQENBFChf/ABCAC/2nK75NwOsg7nkI5NNTCqBMk5DMX0JWu17EZoii/6vH88KlTm
0xeIHwv3leMZbtjqTNzFPfGh5xQo7zH+Y2CBPG8gq9QKv9aB587vuzwCtN/uaP6Z
mjQlafc5HK8gn5PMULWJC0V46g+y39g8bDSEZDInGFFWF7kCOXMcsJnNuoXWbajz
WwV8lcr56EKeenRS3lV4GWd/W+aSjCkaq1SM+9XP3qZYC9lOuaYfkzxfTsf5hpvG
wfTJVOaaPDtfhefgzrK6+znvMC1TsKMKU8bpX7u9WaHn9jD24UE6idzSn84uPuNK
5Jms4r7r6y+kfMSrWK0KUH+Gp0Bs+6kVu6S1ABEBAAG0J01pY2hhZWwgUGV0ZXJz
b24gPG1wZXRlcnNvbjJAZ21haWwuY29tPokBOAQTAQIAIgUCUKF/8AIbAwYLCQgH
AwIGFQgCCQoLBBYCAwECHgECF4AACgkQeBe9+DhXuBiGzAf/aC43wc/TrNSMeWkN
6X92YpPu8SYh1bcDOEm7FvBSWZg/NSf4VBNqP6TXjobIGfSX8hFGrgrkB/ZDMY6N
Ec9UxpnhVC2gOn9TZzOCNfbvN4SAcBWm9vfABEQxIcXsOXEGxLWsW3FSeK2fp5Iv
S19eQ6Z6N2jw/H6xLpd5Zrvw4vAROOVKiYNkQKkqU95hqJQz+9xPOBwDIIL2isQ2
qd0fgDryue7D31XJ/Qrwxa++I70ew4u3TqYboUAL6aTIAxSGmMlbk2CDvVusRUw5
lrN7qxWejq61Qlhx+l9xEEBcq5HflQZpFENn95xT6l6IiLiiEWT4Gju4EwZz+CUO
pe99QrkBDQRQoX/wAQgAq6SDCbXHh6GKFnb1as0zzlngwv6MiA5eaY+83qOgeXov
UVOZBQU8vBmVuF/3Pd5Q7asTOy+40sBYcuCwsMvtXPX0s7A0pdSn5A7DFelVRM5y
oQheASDCtlnp1xpL/8GTr/YuYlQSgC5zqcv23FatrKQ5ljPDV+tbe40T0HQ1491x
g+QPmnS4jofcOGBJ/AcAPAXU17zEiip/JmDOGfpvAf+igRNW5nyGfCkkrHeAaovR
tkqMMtq3YZBBrfgYIuGOZYzIz/lOCDyVb6QP2B/rn6ub5UeB0oYJa98uW7Zmx1vn
ZIPgtbDFRoAj2NV1JEAgmZABcYYQVVpRuvyEC+94FwARAQABiQEfBBgBAgAJBQJQ
oX/wAhsMAAoJEHgXvfg4V7gYfcMH/3hiqNPHlb9FxY4p8gIj6JWdj++CXXjRg4Re
4QWP/JvRH5v4z8DLstcJmezgerHyFqSb7ylo108qONW+x9Q1tNRe+ey9YOeg4581
tdXLMPaGjU0jz5aCKnKQR7LJjOTS4SPPU4dYURDUUkmKgU4tmbQVdkXyT45rCh6b
tB655w1aYSLbA93E3DKkdqoN1gCTlwzsiayLsu1kiWSUopOlPKcwLjyo1OpRC2ph
3T7RuF+whq/NQ8SYSz6GgWh8tSMt/SDpJ5/YOveyH7iAuwcL4pNgGYSjAPklSolp
UZwJPsLOqDSxnlc7RKwX9hsdDL7tybYAX2P7BOGpoNDeN1ZMIEA=
=Kyc/
-----END PGP PUBLIC KEY BLOCK-----


/* ---[ STEP 4: Prepare your project and its metadata ]--- */

With Clojars you can publish SNAPSHOTS or releases. The latter can be "promoted" if you meet all the criteria in your project.clj, which are:

  • you cannot have the word SNAPSHOT in your version
  • you should have your license filled in
  • you need to have the :scm section filled in
    • you can either do this manually, as in the example below
    • or lein in theory can automatically do this for you if you are using GitHub and its remote "ID" is origin (though I've had issues even in that case)

Here is an example project.clj:


 (defproject thornydev/go-lightly "0.4.0"
   :description "Clojure library to facilitate CSP concurrent programming based on Go concurrency constructs"
   :url "https://github.com/midpeter444/go-lightly"
   :license {:name "Eclipse Public License"
             :url "http://www.eclipse.org/legal/epl-v10.html"}
   :profiles {:dev {:dependencies [[criterium "0.3.1"]]}}
   :dependencies [[org.clojure/clojure "1.5.0"]]
   :scm {:name "git"
         :url "https://github.com/midpeter444/go-lightly"})


/* ---[ STEP 5: Commit your code ]--- */

Make sure you have committed all your changes into Git (or Hg, SVN or whatever SCM you are using). Tag the release if you are so inclined and (optional) push it to GitHub or your remote or central hosting server.


/* ---[ STEP 6: Deploy to Clojars ]--- */

From the top of your project directory, enter:

lein deploy clojars

In my case, my gpg-agent prompted me twice for my GPG passphrase and then the deploy happened.

When you do this lein will create a pom and a jar and upload those to Clojars. That pom.xml should include SCM information that looks like this:


  <scm>
    <tag>12f653361a88c4df14</tag>
    <url>https://github.com/midpeter444/go-lightly</url>
  </scm>

The tag there should be the SHA1 of the last commit (in the case of Git). Note: Don't confuse it with a "tag" that you create with "git tag".

If the deploy was successful, your jar should be signed and (possibly) ready for Promotion.


/* ---[ STEP 7: Check whether your jar was signed ]--- */

Create a new lein project and make your deployed library one of its dependencies. Then in that new project run:

$ lein deps :verify
:signed [criterium "0.3.1"]
:unsigned [enlive "1.0.1"]
:signed [org.clojure/tools.macro "0.1.1"]
:signed [org.clojure/clojure "1.5.0"]
:bad-signature [thornydev/go-lightly "0.4.0"]

You see that some are signed and some are not. Obviously, you want yours to say :signed. If it has unsigned then you are probably either using Lein 1 or you didn't generate your GPG keys. If it has :bad-signature then something got corrupted on the Clojars server. In my case above, I promoted and tried to redeploy, which uncovered a bug in lein/clojars that caused some files to get overwritten when they shouldn't. This issue should be fixed soon. If you do have that problem, delete your local copy from your ~/.m2 directory and contact someone on the #leiningen IRC channel.


/* ---[ Optional STEP 8: Promote to release status ]--- */

If you are eligible to promote to release status, you will see a "Promote" button on your Clojars page. If you are not, you may be missing SCM information, which is what happened to me recently.

Note that once you promote you can no longer deploy to that version again, so make sure you're ready to make it immutable. After that, you can only add new versions.