Valid XHTML 1.0! Valid CSS! Get Firefox!

Code snippets

XPI signing tutorial

Some years ago, I had to apply a digital signature to a (proprietary) Firefox XPI extension at work. At the time (Firefox was pre-1.0 at the time), I had to dig and search to find half complete documentation. With some extrapolation and by joining the info I gathered here and there, I managed to get a signed XPI file. Obviously, my boss asked that I document the process for him so we could sign new versions with less hassle, and that I did.

I figured that with time (about three years have passed), the Mozilla folks would have better documented the process, and I sort of forgot about the sparse documentation. A couple of days ago, a discussion on Slashdot mentionned the still pitiful state of the Mozilla documentation regarding XPI signing. So I went and re-dug up the documentation I could find, and made this tutorial.

I have executed all the following steps on a Fedora Core 6 installation, all with software and libraries that come with the distro. Therefore, this tutorial currently assumes you are running Linux. Instructions for Windows users will be provided in a future version of this tutorial.

What you need to have before beginning :

If you are running Linux, you can download this file which automates the first few steps, greatly simplifying the process. If you use the above file, unpack it where you want to work from and it will create a directory named XPI_signing. cd into that directory in a console, execute the build.sh script and skip to step 5.

Step 1: In a console, create a directory named XPI_signing, and cd into it. Then create a sub-directory named certs. This is where we'll put our certificates. Create another subdirectory, which we'll call XPI. As you might have guessed, this is where we'll unzip the extension to be signed.

user@host:~$ mkdir XPI_signing
user@host:~$ cd XPI_signing
user@host:~/XPI_signing$ mkdir certs XPI

Step 2: Get the Mozilla NSS tools' source code. The easiest way to do that is through CVS, with the following commands:

user@host:~/XPI_signing$ cvs -d :pserver:anonymous@cvs-mirror.mozilla.org:/cvsroot co -r NSPR_4_6_4_RTM mozilla/nsprpub
user@host:~/XPI_signing$ cvs -d :pserver:anonymous@cvs-mirror.mozilla.org:/cvsroot co -r NSS_3_11_4_RTM mozilla/dbm mozilla/security/dbm
user@host:~/XPI_signing$ cvs -d :pserver:anonymous@cvs-mirror.mozilla.org:/cvsroot co -r NSS_3_11_4_RTM mozilla/security/coreconf mozilla/security/nss

Step 3: Build NSS. cd into the mozilla/security/nss subdirectory and build the thing with gmake nss_build_all

user@host:~/XPI_signing$ cd mozilla/security/nss
user@host:~/XPI_signing/mozilla/security/nss$ gmake nss_build_all

Step 4: Copy the newly built signtool executable in the XPI_signing directory.

user@host:~/XPI_signing/mozilla/security/nss$ cd ../../../
user@host:~/XPI_signing$ cp `find ./ -name signtool | grep /bin/signtool` ./

Step 5: Unzip your XPI extension in the XPI sub-directory created on step 1. Make sure to put in that directory only the content of the XPI extension, and not the extension itself.

user@host:~/XPI_signing$ unzip ~/MyExtension.xpi -d XPI

Step 6: Import your code signing certificate into Firefox. To do so, launch Firefox, and open the Preferences dialog from the Edit menu. In the dialog, go to the Advanced tab, and click on the View Certificates button to open the Certificate Manager dialog.

View Certificates
In the Certificate Manager, go to the Your Certificates tab and click on the Import button, then select your .pfx certificate file.
Certificate Manager
Certificate imported
Notice the "Purposes" column in the last screenshot? That column says what your certificate can do. <Unknown> means the certificate can't do much. What you want is an "Object Signer" certificate. If your certificate has an <Unknown> purpose, read about the Free Certificate Authority tutorial.

Step 7: We're done with Firefox for now, so head back to your console. You'll want to copy two files from your Firefox profile directory into your certs directory. Those files are cert8.db and key3.db. Those two files contain the certificate information you need to sign a directory with the signtool we built and copied on step 4.

user@host:~/XPI_signing$ cp ~/.mozilla/firefox/2zue3f71.default/cert8.db ./certs/
user@host:~/XPI_signing$ cp ~/.mozilla/firefox/2zue3f71.default/key3.db ./certs/
Before signing anything, let's make sure that everything we've done so far is ok by checking if signtool finds a code signing certificate in the database files we just copied.
user@host:~/XPI_signing$ ./signtool -d certs -L
using certificate directory: certs

S Certificates
- ------------
  c.live.com
  c.msn.com #2
* 10223188607c054dac3c4bf8062587cc_4dfc69f9-fbd1-48a6-8fa1-7de8c989d2f0
  Thawte Time Stamping CA
  Thawte Personal Basic CA
  Thawte Personal Premium CA
  Thawte Personal Freemail CA
- ------------
Certificates that can be used to sign objects have *'s to their left.
As it is mentionned at the end of the command output, a * by a certificate's name means the certificate can be used to sign objects (code). If all is well, you should have at least one such certificate. If not, something went wrong and you'll need to restart some or all of the previous steps. Don't worry if your certificate doesn't have a nice looking name like "Thawte Personal Basic CA". Code signing certificates can also be refered to by a long serial number, like the 10223188607… shown above.

Step 8: Now that we have confirmed we have a valid signing certificate, we can use signtool to sign the content of the XPI directory. In the command below, replace «certificate_id» with the name of the certificate as we have discovered it in the previous step (10223188607… in my case).

user@host:~/XPI_signing$ ./signtool -d certs -k «certificate_id» XPI
using certificate directory: certs
Generating XPI/META-INF/manifest.mf file..
--> chrome/content/about.xul
--> chrome/content/contents.rdf
<snip>
--> chrome.manifest
--> install.rdf
Generating zigbert.sf file..
tree "XPI" signed successfully
The last line (tree "XPI" signed succesfully) indicates that the signing procedure succeeded.

Step 9: The content of the XPI directory is signed. All we need to do now is to package it into a usable Firefox extension. While packaging an unsigned extension simply means zipping the whole XPI directory's content into a file, that is not how it is done when dealing with signed extensions. This last part is very simple but very badly documented, and this is why most people fail to sign extensions. Here's the trick though. The order in which the files are included in the zipped file matters, and the very first file that needs to be included is META-INF/zigbert.rsa. Once this file is added to the zipped file, you can add (not replace) all the other files.

user@host:~/XPI_signing$ cd XPI
user@host:~/XPI_signing/XPI$ zip ../MyExtension.xpi META-INF/zigbert.rsa
  adding: META-INF/zigbert.rsa (deflated 29%)
user@host:~/XPI_signing/XPI$ zip -r -D ../MyExtension.xpi * -x META-INF/zigbert.rsa
  adding: chrome/content/about.xul (deflated 69%)
  adding: chrome/content/contents.rdf (deflated 54%)
<snip>
  adding: chrome.manifest (deflated 81%)
  adding: install.rdf (deflated 62%)
  adding: META-INF/manifest.mf (deflated 67%)
  adding: META-INF/zigbert.sf (deflated 67%)

Step 10: There ya go! You should now have a signed extension on your hands. Install your newly created extension in Firefox and gaze at the beauty of a signature.

Signed extension

Step 10 (b): If you have been using a free certificate authority to get your signing certificate, make sure you are aware of the possible consequences for your users. See the caveat section of the Free Certificate Authority tutorial.