Signing Artifacts for Publishing to Maven Central Repository
IntroductionPermalink
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 JCenter and Bintray decommissioned, there’re not many hosting options around. There’s jitpack.io, and it’s much easier to publish there than to Maven Central, but their relaxed requirements can also be seen as a problem. For example, JitPack builds from source, so, the artifacts aren’t signed. I publish to JitPack for hobby projects, but for serious work, I prefer Maven Central.
Problem StatementPermalink
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 the signing process is a real pain in the neck. 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.
Security ConsiderationsPermalink
Using a private key in a CI/CD enviornment like GitHub workflow requires extreme caution to avoid unintentional exposure. Here are a few best practices:
-
Key Cleanup: Always delete the imported key at the end of the job, regardless of the job outcome, to prevent accidental leaks.
-
Log Hygiene: Ensure that the key is never printed to the job logs, as this can expose it. If using GitHub Secrets, this is automatically taken care of by GitHub; you can also mask it yourself using the add-mask workflow command.
-
Temporary Files: If your workflow uses temporary files to handle keys, make sure they are securely deleted.
RequirementsPermalink
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.
StepsPermalink
- Run an Ubuntu container.
docker run --rm -it -v $(pwd)/.gnupg:/root/.gnupg -e GPG_TTY=/dev/console ubuntu bash
- Install GnuPG, which is an implementation of the Open PGP standard.
apt update && apt install -y gnupg
- 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
- At the prompt, specify the kind of key you want, or press Enter to accept the default RSA and RSA.
- Enter the desired key size. I recommend at least 4096 bits.
- Enter the length of time the key should be valid. Press Enter to accept the default selection, indicating that the key does not expire.
- Confirm that the key does not expire by entering ‘y’.
- Enter your full name.
- Enter your email address.
- Enter a comment, or press Enter to skip.
- Verify that your selections are correct. Enter ‘O’ (Upper case O) to proceed.
- Type a secure passphrase. If you enter a weak passphrase, you will be asked to confirm it.
- Retype the passphrase.
- 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 afterrsa3072
,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]
To list the public keys in the keyring, run with
--list-keys
instead of--list-secret-keys
. Note that the key id is the same for both. If you need to retrive the public key later, you can search for it using the key id or the email provided in step 9 above.gpg --keyserver <keyserver> --search-key <key id> gpg --auto-key-locate <keyserver> --locate-keys <email>
The keyserver must include the protocol
hkp
. - Upload the public key to a key server; Maven Central uses hkp://keyserver.ubuntu.com.
gpg --keyserver keyserver.ubuntu.com --send-keys <key id>
Optionally, convert the secret key to a Base64 encoded format with no line breaks. This may be required because a lot of public CI servers 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 Base64 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
andsigningPassword
are project properties that are set from the corresponding environment properties.If you’ve previously saved the keys to files, here are the commands to import them.
gpg --import </path/to/public/key/file> gpg --allow-secret-key-import --import </path/to/private/key/file>
- As for the actual upload to Maven Central, the Gradle Maven Publish Plugin doesn’t support publishing to the new Sonatype Central Publishing Portal. There’s an open issue for Gradle to support publishing to Central, but they seem to be reluctant in doing so. There’re a bunch of third-party plugins that support publishing to Central, among which
JReleaser
andvanniktech/gradle-maven-publish-plugin
seem to be the most popular and actively maintained. I personally couldn’t get JReleaser to sign the artifacts successfully, and went with the vanniktech plugin instead.
ConclusionPermalink
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.
Leave a comment