How To Generate PGP Signatures With Maven

Skip to end of metadata
Go to start of metadata

If you use a tool that downloads artifacts from the Central Maven repository, you need to make sure that you are making an effort to validate that these artifacts have a valid PGP signature that can be verified against a public key server. If you don’t validate signatures, then you have no guarantee that what you are downloading is the original artifact.  One way to to verify signatures on artifacts is to use a repository manager like Nexus Professional. In Nexus Professional you can configure the procurement suite to check every downloaded artifact for a valid PGP signature and validate the signature against a public keyserver.

If you are developing software using Maven, you should generate a PGP signature for your releases. Releasing software with valid signatures means that your customers can verify that a software artifact was generated by the original author and that it hasn’t been modified by anyone in transit. Most large OSS forges like the Apache Software Foundation require all projects to be released by a release manager whose key has been signed by other members of the organization, and if you want to synchronize your software artifacts to Maven central you are required to provide pgp signatures.

In this post, I show you how to configure your Maven project to generate a valid signature using GPG. GnuPG (aka. GPG) is a freely available implementation of the OpenPGP standard. It’s available for both Windows and Unix-like computers. GPG provides you with the capability to generate a signature, manage keys, and verify signatures. In the following sections, I will introduce GPG as well as maven-gpg-plugin which provides Maven goals to generate signatures for a release.

Installing GnuPG

Download GPG from http://www.gnupg.org/download/ , follow the instructions and install it to your system. Verify your gpg installation by running gpg with the version flag:

juven@juven-ubuntu:~$ gpg --version
gpg (GnuPG) 1.4.9Copyright (C) 2008 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later

Generate a Key Pair

Before you do anything with GPG, you will need to generate a key pair for yourself. Once you have you own key pair, you can use your private key to sign artifacts, and distribute your public key to public key servers and end-users so that they can validate artifacts signed with your private key.

Generate a key pair like this:

juven@juven-ubuntu:~$ gpg --gen-key

You’ll be asked for the type, the size, and the time of validity for the key, just use the default value if you don’t have any special requirements. You will be asked to input your name, email, and comment for the key. These identifiers are essential as they will be seen by anyone downloading a software artifact and validating a signature. Finally, you can provide a passphase to protect your secret key, this is not mandatory, but I highly recommend you to do this. It is essential that you choose a secure passphrase and that you do not divulge it to any one. This passphrase and your private key are all that is needed to sign artifacts with your signature.

List Keys

Once key pair is generated, we can list them to console (along with any other keys imported to local machine).

List public keys:

juven@juven-ubuntu:~$ gpg --list-keys

/home/juven/.gnupg/pubring.gpg
------------------------------
pub 1024D/C6EED57A 2010-01-13
uid Juven Xu (Juven Xu works at Sonatype) <juven@sonatype.com>
sub 2048g/D704745C 2010-01-13

Here /home/juven/.gnupg/pubring.gpg is where my public key are stored. The line starting with pub shows the length (1204D), the keyid (C6EED57A), and the creation date (2010-01-13) of the public key. The next line shows the UID of the key, which is composed of a name, a comment, and an email. The last line shows sub-keys, we don’t need to worried about this for now.

List private keys:

juven@juven-ubuntu:~$ gpg --list-secret-keys

/home/juven/.gnupg/secring.gpg
------------------------------
sec 1024D/C6EED57A 2010-01-13
uid Juven Xu (Juven Xu works at Sonatype)
ssb 2048g/D704745C 2010-01-13

Similar to public keys, here we can see the storage location of the private key, as well key length, keyid and UID.

Sign a File

To create an ASCII formatted signature for any file, run the following gpg command:

juven@juven-ubuntu:~$ gpg -ab temp.java

The -a option tells gpg to create ASCII armored output, the -b option tells gpg to make a detached signature. If your private key has a passphrase, you will be asked for it. Without a passphrase, all someone needs to forge an artifact signature is your private key. The passphrase is an extra level of protection.

GPG will create a file like temp.java.asc, which is the signature of temp.java. You will want to distribute it along with the main file so the other can verify the main file using your public key:

$ gpg --verify temp.java.asc

Use Primary Key
Some PGP tool generates sub key and use it for signing by default, but to make Maven tools recognize the signature, you must use the primary key to sign your artifacts. So you will need to delete the sub signing key, see Delete a Sub Key for the details.

Distribute Your Public Key

Since other people need your public key to verify your files, you have to distribute your public key to a key server:

$ gpg --keyserver hkp://pool.sks-keyservers.net --send-keys C6EED57A

Here I distributed my public key to hkp://pool.sks-keyservers.net, use  --keyserver along with a key server address, and use --send-keys along with a keyid. You can get your keyid by listing the public keys.

Note
Public keys are synced among key servers, but it may take a while.

Now other people can import your public key from the key server to their local machines:

$ gpg --keyserver hkp://pool.sks-keyservers.net --recv-keys C6EED57A

Maven GPG Plugin

When you release a Maven project, you have two options: you can use the manual signing technique shown above, or you can use an automated approach. Manually signing and deploying artifacts to a repository is very time-consuming. The maven-gpg-plugin is very handy in this case. With a few lines of configuration, it can help you automatically sign the maven artifacts.

Before using maven-gpg-plugin, make sure gpg is available on the search path.  Configure maven-gpg-plugin in your project’s POM like this:

<project>
  ...
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-gpg-plugin</artifactId>
        <executions>
          <execution>
            <id>sign-artifacts</id>
            <phase>verify</phase>
            <goals>
              <goal>sign</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
  ...
</project>

Then simply deploy your artifacts use normal maven command:

$ mvn clean deploy -Dgpg.passphrase=yourpassphrase

If you don’t specify the passphrase, you will be prompted for one.

What if you have some artifacts which have already been released but not signed? You want to sign and deploy them into repository as well. Fortunately maven-gpg-plugin has another goal for this use case:$

$ mvn gpg:sign-and-deploy-file
> -DpomFile=target/myapp-1.0.pom
> -Dfile=target/myapp-1.0.jar
> -Durl=https://oss.sonatype.org/service/local/staging/deploy/maven2/
> -DrepositoryId=sonatype-nexus-staging

The last thing I want to mention about maven-gpg-plugin is wrapping the plugin configuration with a profile. Usually when you build SNAPSHOT version, you don’t want to sign the artifacts, because it’s unnecessary and time-consuming. Artifact signing is only necessary when it’s time to release project. So, we can wrap the plugin configuration with a profile, which will only be activated on project releasing:

<profiles>
  <profile>
    <id>release-sign-artifacts</id>
    <activation>
      <property>
        <name>performRelease</name>
        <value>true</value>
      </property>
    </activation>
    <build>
      <plugins>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-gpg-plugin</artifactId>
          <version>1.1</version>
          <executions>
            <execution>
              <id>sign-artifacts</id>
              <phase>verify</phase>
              <goals>
                <goal>sign</goal>
              </goals>
            </execution>
          </executions>
        </plugin>
      </plugins>
    </build>
  </profile>
</profiles>

This profile will be activated when the value of maven property performRelease is true. When you use maven-release-plugin and run mvn release:perform, the property value will be set to true.

Note
You will want to configure <mavenExecutorId>forked-path</mavenExecutorId> for maven-release-plugin to get around a GPG plugin hanging issue
Tip
Because maven-release-plugin will start a new Maven instance, -Dgpg.passphrase=PASSPHRASE won't work in this case, instead, you should use
mvn release:perform -Darguments=-Dgpg.passphrase=PASSPHRASE

If you are releasing your artifacts on OSSRH, you need to make one more configuration change.  Because of the way the maven-release-plugin is configured in the OSS parent POM, you will have to edit the <pluginManagement> block of the <build> section of your POM so that it overrides the parent POM and honors the arguments you are passing in:

<build>
...
	<pluginManagement>
	    <plugins>
	        <plugin>
	            <groupId>org.apache.maven.plugins</groupId>
	            <artifactId>maven-release-plugin</artifactId>
	            <version>2.1</version>
	            <configuration>
	                <mavenExecutorId>forked-path</mavenExecutorId>
	                <useReleaseProfile>false</useReleaseProfile>
	                <arguments>${arguments} -Psonatype-oss-release</arguments>
	            </configuration>
	        </plugin>
	    </plugins>
	</pluginManagement>
...
</build>

That’s it! With the help of GnuPG and maven-gpg-plugin, you can have more confidence of the security of your Maven artifacts.

When My Key Expired

When you generate your PGP key, you need to specify how long the key should be valid. Let's say 1 year, now the question is, "should I create another key after 1 year?" The answer is no, you can edit your existing key to extend it's valid time.

For example, I have a key pair which expires on 2012-02-27:

$ gpg --list-keys

/Users/juven/.gnupg/pubring.gpg
-------------------------------
pub   2048R/A6BAB25C 2011-08-31 [expires: 2012-02-27]
uid                  Juven Xu (for testing) <test@juvenxu.com>
sub   2048R/DD289F64 2011-08-31 [expires: 2011-02-27]

Now edit this key with the following command: (note that you need to specify the key id as a parameter)

$ gpg --edit-key A6BAB25C

gpg (GnuPG/MacGPG2) 2.0.17; Copyright (C) 2011 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Secret key is available.

pub  2048R/A6BAB25C  created: 2011-08-31  expires: 2012-02-27  usage: SC
                     trust: ultimate      validity: ultimate
sub  2048R/DD289F64  created: 2011-08-31  expires: 2011-02-27  usage: E
[ultimate] (1). Juven Xu (for testing) <test@juvenxu.com>

Here is only one key to edit, so I choose 1:

gpg> 1

pub  2048R/A6BAB25C  created: 2011-08-31  expires: 2012-02-27  usage: SC
                     trust: ultimate      validity: ultimate
sub  2048R/DD289F64  created: 2011-08-31  expires: 2011-02-27  usage: E
[ultimate] (1)* Juven Xu (for testing) <test@juvenxu.com>

You will see the * after (1), which mean you've selected this key to edit.
To edit the key's expire time, enter the following command: (for a full list of editing command, see this Document)

gpg> expire

Changing expiration time for the primary key.
Please specify how long the key should be valid.
         0 = key does not expire
      <n>  = key expires in n days
      <n>w = key expires in n weeks
      <n>m = key expires in n months
      <n>y = key expires in n years

Enter what you need, for example 10m (10 months), and confirm it.
The last step about editing is saving it what you've done:

gpg> save

Now you can see your key's expires time is updated:

$ gpg --list-keys

pub   2048R/A6BAB25C 2011-08-31 [expires: 2012-06-26]
uid                  Juven Xu (for testing) <test@juvenxu.com>
sub   2048R/DD289F64 2011-08-31 [expires: 2011-02-27]

Finally, distribute your public key again:

$ gpg --keyserver hkp://pool.sks-keyservers.net --send-keys A6BAB25C

Delete a Sub Key

Some PGP tools by default generate a sub signing key and use it for signing instead of using the primary key. This is a problem if you use it to sign artifacts and deploy artifacts to the Central Repository, because Nexus could not get the primary key ID from a signature produced by sub key, thus it could not import the public key and will fail to verify the artifact. The fix is to delete the sub signing key so PGP will use the primary key for signing.

To get an idea weather you have a sub signing key, run command below with your own key ID:

$ gpg --edit-key A6BAB25C

gpg (GnuPG/MacGPG2) 2.0.17; Copyright (C) 2011 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Secret key is available.

pub  2048R/A6BAB25C  created: 2011-08-31  expires: 2012-06-26  usage: SC
                     trust: ultimate      validity: ultimate
sub  2048R/DD289F64  created: 2011-08-31  expired: 2011-09-30  usage: E
sub  2048R/8738EC86  created: 2011-12-19  expires: 2012-06-16  usage: S
[ultimate] (1). Juven Xu (for testing) <test@juvenxu.com>

As you can see from above example, this key has 2 sub keys with ID DD289F64 and 8738EC86. The output also shows the creation time and expiration time. What's important here is usage: E stands for Encryption so sub key DD289F64 is used for encryption only, S stands for Signing so sub key 8738EC86 is used for Signing only. If a primary key has a S sub key, it will use it for signing, otherwise itself will do signing job. So we want to delete the sub key 8738EC86.

First select the sub key we want to delete, since its index is 2 (indices starts with 0), we run command:

gpg> key 2

pub  2048R/A6BAB25C  created: 2011-08-31  expires: 2012-06-26  usage: SC
                     trust: ultimate      validity: ultimate
sub  2048R/DD289F64  created: 2011-08-31  expired: 2011-09-30  usage: E
sub* 2048R/8738EC86  created: 2011-12-19  expires: 2012-06-16  usage: S
[ultimate] (1). Juven Xu (for testing) <test@juvenxu.com>

As you can see from the output, the sub key 8738EC86 is marked with *. Now delete it:

gpg> delkey

Do you really want to delete this key? (y/N) y

pub  2048R/A6BAB25C  created: 2011-08-31  expires: 2012-06-26  usage: SC
                     trust: ultimate      validity: ultimate
sub  2048R/DD289F64  created: 2011-08-31  expired: 2011-09-30  usage: E
[ultimate] (1). Juven Xu (for testing) <test@juvenxu.com>
Tip
If you've already distributed your public key, it's better to revoke the sub signing key instead of deleting it, although either way you can make your primary key as the signing key. See the The GNU Privacy Handbook for the difference between deleting and revoking. To revoke a sub key, use gpg> revkey instead of gpg> delkey.

Ya! 8738EC86 is not listed any more, the final step is saving our change:

gpg> save

That's it! Now you can test the change by signing a file, and then verify it. The output should contain something like:

gpg: Signature made ************************* using *** key ID [YOUR-PRIMARY-KEY-ID]
Enter labels to add to this page:
Please wait 
Looking for a label? Just start typing.