Signing Artifacts for Publishing to Maven Central Repository

Introduction

If you are the author or maintainer of an JVM-based Open Source software, chances are you publish it to the Maven Central repository so that automated build tools can download it. With the recent decommissioning of JCenter and Bintray, Maven Central is pretty much the only game in town.

Problem Statement

Artifacts to be published to Maven Central must meet certain requirements, one of which is accompanying PGP signatures. Users of your library might want to verify these artifacts’ PGP signatures against a public key server. Several plugins are available for popular build tools like Maven, Gradle and SBT that attempt to make the publishing process less painful, but as far as I am aware, they all leave the signing part up to the user. In this article, I will lay out the exact steps needed for generating a PGP key pair, and a Gradle example of using them to sign the artifacts. Having generated the keys, they can be used by any build tool.

A PGP signature can also be used for signing commits and tags on GitHub.

Requirements

The only thing you will need other than a computer and an internet connection is Docker engine installed. For Windows and Mac, you can install Docker Desktop.

Steps

  1. Run an Ubuntu container.
    docker run --rm -it -v $(pwd)/.gnupg:/root/.gnupg -e GPG_TTY=/dev/console ubuntu bash
    
  2. Install GnuPG, which is an implementation of the Open PGP standard.
    apt update && apt install -y gnupg
    
  3. Generate a GPG key pair. Because of the Docker volume mapping, the generated keys are stored in the .gnupg directory in your home directory.
    gpg --full-generate-key
    
  4. At the prompt, specify the kind of key you want, or press Enter to accept the default RSA and RSA.
  5. Enter the desired key size. I recommend at least 4096 bits.
  6. Enter the length of time the key should be valid. Press Enter to accept the default selection, indicating that the key does not expire.
  7. Confirm that the key does not expire by entering ‘y’.
  8. Enter your full name.
  9. Enter your email address.
  10. Enter a comment, or press Enter to skip.
  11. Verify that your selections are correct. Enter ‘O’ (Upper case O) to proceed.
  12. Type a secure passphrase. If you enter a weak passphrase, you will be asked to confirm it.
  13. Retype the passphrase.
  14. Use the following command to list GPG keys for which you have both a public and private key.
    gpg --list-secret-keys --keyid-format SHORT
    

    The last few lines will be similar to the following. The key id/fingerprint is the 40-character string on the second line of sec. The last 8 characters (shown on the first line after rsa3072, 9D397642 in the example below) are sufficient to uniquely identify the key, and is referred to as the <key id> in the following steps.

    sec   rsa3072/9D397642 2021-04-04 [SC]
      C49D1269413357A989292F0BBED9F87D9D397642
    uid         [ultimate] John Doe <johndoe@nowhere.com>
    ssb   rsa3072/AD77D767 2021-04-04 [E]
    
  15. Upload the public key to a key server; Maven central uses hkp://keyserver.ubuntu.com.
    gpg --keyserver hkp://keyserver.ubuntu.com --send-keys <key id>
    

Optionally, convert the secret key to a Base64 encoded format with no line breaks. This is required because a lot of public CI servers will allow the secret key to be set as an environment variable, and the line breaks and special characters in the secret key are not compatible with an environment variable.

gpg --armor --export-secret-keys <key id> | base64 -w0

If you are using Gradle, you can use the Signing plugin along with the keys to sign the artifacts, as shown below (using Kotlin DSL):

fun base64Decode(prop: String): String? {
    return project.findProperty(prop)?.let {
        String(Base64.getDecoder().decode(it.toString())).trim()
    }
}

signing {
    useInMemoryPgpKeys(base64Decode("signingKey"), base64Decode("signingPassword"))
    sign(*publishing.publications.toTypedArray())
}

signingKey and signingPassword are project properties that are set from the corresponding environment properties.

Conclusion

And there we have it, a complete recipe for generating a PGP key pair, and a Gradle example of using them to sign the artifacts.

Comments