How to sign your Eclipse project with Tycho

I am sure, every user of Eclipse has seen this message before:

Security Warning: Unsigned Content

As it turns out, it’s surprisingy easy to spare your users this worrying message – at least if you are building an Eclipse project rather than just a project that uses Eclipse. Thanks to Apache Maven, Eclipse Tycho and the Common Build Infrastructure effort of the Eclipse Foundation, properly signed bundles and features are just a few lines of XML away. Of course, as with all things Maven, finding the right XML incantation is not always that easy, so here’s the solution for you – ready to be copied & pasted into your project’s pom.xml.

Requirements

But before we get to the mindless copying and pasting, let’s take a step back and ask ourselves: What are we actually trying to accomplish?

Well, in an ideal world, every Eclipse update site contains only signed bundles and features. Moreover, in such a world, every bundle is accompanied by a smaller, pack200-packed bundle (.jar.pack.gz) that saves the user time and bandwidth. And in an ideal Open Source world, you want the make available the sources of your bundles and features as well – all signed, of course.

A Parent POM for Bundles

So much for the requirements. Now, how does our bundles’ parent POM look like?

<project>
 …
 <build>
  <plugins>
   <!-- 1. -->
   <plugin>
    <groupId>org.eclipse.tycho</groupId>
    <artifactId>tycho-source-plugin</artifactId>
    <executions>
     <execution>
      <id>attach-source</id>
      <goals>
       <goal>plugin-source</goal>
      </goals>
     </execution>
    </executions>
   </plugin>
  </plugins>
 </build>
 <profiles>
  <!-- 2. -->
  <profile>
   <id>build-server</id>
   <build>
    <plugins>
     <!-- 3.a -->
     <plugin>
      <groupId>org.eclipse.tycho.extras</groupId>
      <artifactId>tycho-pack200a-plugin</artifactId>
      <executions>
       <execution>
        <id>pack200-normalize</id>
        <goals>
         <goal>normalize</goal>
        </goals>
       </execution>
      </executions>
     </plugin>
     <plugin>
      <groupId>org.eclipse.cbi.maven.plugins</groupId>
      <artifactId>eclipse-jarsigner-plugin</artifactId>
      <executions>
       <execution>
        <id>sign</id>
        <goals>
         <goal>sign</goal>
        </goals>
       </execution>
      </executions>
     </plugin>
     <!-- 3.b -->
     <plugin>
      <groupId>org.eclipse.tycho.extras</groupId>
      <artifactId>tycho-pack200b-plugin</artifactId>
      <executions>
       <execution>
        <id>pack200-pack</id>
        <goals>
         <goal>pack</goal>
        </goals>
       </execution>
      </executions>
     </plugin>
     <!-- 4. -->
     <plugin>
      <groupId>org.eclipse.tycho</groupId>
      <artifactId>tycho-p2-plugin</artifactId>
      <executions>
       <execution>
        <id>attach-p2-metadata</id>
        <phase>package</phase>
        <goals>
         <goal>p2-metadata</goal>
        </goals>
       </execution>
      </executions>
      <configuration>
       <defaultP2Metadata>false</defaultP2Metadata>
      </configuration>
     </plugin>
    </plugins>
   </build>
  </profile>
 </profiles>
 …
</project>

There are a few things noteworthy about the above snippet:

  1. First, we had to manually configure a plugin execution to create a source bundle (goal tycho-source:plugin-source). This was necessary simply because the necessary goal is not executed by default for projects with eclipse-plugin packaging.
  2. Next, you’ll notice that we placed everything related to signing (goal eclipse-jarsigner-plugin:sign) and packing (goals tycho-pack200a:normalize, tycho-pack200b:pack) into a profile (build-server). There are two reasons for this: One of necessity and one of practicality. The eclipse-jarsigner-plugin only works when run on an eclipse.org machine (like your project’s Hudson instance). This way, the Eclipse Foundation’s private key used for signing never travels across the Internet. Also, packing your JARs takes some time and is, in day-to-day development work, completely unnecessary; for development, plain JARs work just fine.
  3. Note also that Tycho’s pack200 plugin comes in two parts: tycho-200a-plugin and tycho-200b-plugin. The reason for this is fairly technical: You sometimes need to be able to squeeze in another plugin execution (here: signing) between the normalizing and packing of a JAR and Maven will (rightfully) complain if it sees the same plugin configured twice in a POM, once for each of the two executions. Now, the Right Thing™ would be to bind the normalize and pack goals to different phases, but the Maven lifecycle is woefully short of phases between prepare-package and package (there are none).
  4. Finally, we need to make sure that Tycho generates its p2 metadata after the JAR has been packed. To do this, we place the attach-p2-metadata execution after the pack execution. Although all executions within the profile are bound to the same phase (package), their order within the POM ensures the correct order of execution. We have, however, also to make sure that the p2 metadata attached by the default-p2-metadata-default execution (as if one “default” weren't enough), which every eclipse-plugin project uses by, well, default, does not attach its own metadata as that would happen before the execution of sign. That’s what the defaultP2Metadata configuration option is for. See the Tycho Wiki for further explanations.

A Parent POM for Features

OK, this properly signs and packs our bundles and also creates source bundles for us. Next, will see how to do the same for our project’s features.

<project>
 …
 <build>
  <plugins>
   <!-- 1. -->
   <plugin>
    <groupId>org.eclipse.tycho.extras</groupId>
    <artifactId>tycho-source-feature-plugin</artifactId>
    <executions>
     <execution>
      <id>generate-source-feature</id>
      <goals>
       <goal>source-feature</goal>
      </goals>
     </execution>
    </executions>
   </plugin>
   <plugin>
    <groupId>org.eclipse.tycho</groupId>
    <artifactId>tycho-p2-plugin</artifactId>
    <executions>
     <execution>
      <!-- 2. -->
      <id>default-p2-metadata-default</id>
      <configuration>
       <attachP2Metadata>false</attachP2Metadata>
      </configuration>
     </execution>
     <execution>
      <id>attach-p2-metadata</id>
      <phase>package</phase>
      <goals>
       <goal>p2-metadata</goal>
      </goals>
     </execution>
    </executions>
   </plugin>
  </plugins>
 </build>
 <profiles>
  <!-- 3. -->
  <profile>
   <id>build-server</id>
   <build>
    <plugins>
     <plugin>
      <groupId>org.eclipse.cbi.maven.plugins</groupId>
      <artifactId>eclipse-jarsigner-plugin</artifactId>
      <executions>
       <execution>
        <id>sign</id>
        <goals>
         <goal>sign</goal>
        </goals>
       </execution>
      </executions>
     </plugin>
     <plugin>
      <groupId>org.eclipse.tycho</groupId>
      <artifactId>tycho-p2-plugin</artifactId>
     </plugin>
    </plugins>
   </build>
  </profile>
 </profiles>
 …
 <!-- 4. -->
</project>

Again, the snippet warrants some notes:

  1. As was the case for source bundles, Tycho requires you to explicitly request source features to be generated; hence, we again have to manually configure a plugin execution (goal tycho-source-feature:source-feature).
  2. Also, we again have to deal with an overcrowded package phase: The default-p2-metadata-default execution of the tycho-p2-plugin:p2-metadata-default goal will run before generate-source-feature gets executed. Alas, the p2 metadata needs to include our newly generated source feature. Thus, perform a second execution after the source feature has been generated – and again don’t attach anything in default-p2-metadata-default.
  3. Signing again happens in our build-server profile, as it works on an eclipse.org server only. And again we need to ensure that the p2 metadata is generated after this step. To achieve this, it suffices to merely mention the tycho-p2-plugin, which attaches the p2 metadata, after the eclipse-jarsigner-plugin. This enforces the proper build order.
  4. Finally, you may have noticed that the the pack200 plugin is not to be seen anywhere in the features’ parent POM. This is because pack200 compression only works on Java classfiles. Thus, feature JARs (or source bundles) need not and will not be packed.

Conclusion

OK, I admit that this was a bit more than “just a few lines of XML.” Nevertheless, I hope this blog post explained to you how they all fit together. How can also have a look at how we at the Eclipse Code Recommenders project employ the above in our build: our bundles’ parent POM and our features’ parent POM, both of which inherit from our organisational parent POM.

All that now stands in the way of your properly signed and packed bundles and features is just a bit of “mindless” copying and pasting.

One last tip: If you want to test your build-server profile locally, i.e., not on an eclipse.org machine, you can skip any execution of the eclipse-jarsigner-plugin:sign with -Dcbi.jarsigner.skip=true. Thus, the following command executes all goals in the order the build server would and produces properly packed (but unsigned) bundles and features:

mvn clean install -Pbuild-server -Dcbi.jarsigner.skip=true

Further Reading

This post would have been impossible without the wealth of documentation already spread over Web, some which saved me lot of of frustration and other which would have – if I only had found it in time. Either way, the following articles are all worth reading if you are building anything with Maven and Tycho.

Comments

Stuart Hendren's picture

 

I would like to provide signed bundles for my eclipse plugins, but it's not an eclipse.org project.

Is this possible with this technique? I'm already using maven and tycho to build my p2 site.

 

Stuart

Andreas Sewe's picture

Hi Stuart,

have a look at the maven-jarsigner-plugin. I think it will work as a drop-in replacement of the eclipse-jar-signer-plugin. Maybe some minor tweaks to its configuration will be necessary, but at least you won’t have to fiddle around with the order of plugin executions. That can stay the same as in this post.

As an alternative, you can also try to run the signing webservice that the Eclipse Foundation and the eclipse-jarsigner-plugin use on your local machine. Have a look at the webservice’s README on how to set up things. But personally, I would try the maven-jarsigner-plugin route first.

Hope this helps.

Add new comment