This page looks best with JavaScript enabled

Simpler and safer artifact signing on CI server with Gradle

 ·  ☕ 4 min read

Check out how to simplify your Gradle configuration for artifact signing (and releasing) on a CI server (and along the way avoid CVE-2020-13165 in Gradle <6.5).

Releasing from a CI server

Testing, build and releasing (deploying) from CI servers has been more and more popular in the recent years. In many organizations this is something natural. Also multiple FOSS project has started to publish their artifacts to Maven Central (aka The Central Repository) directly from a CI server. This is very nice, as it reduces time to market (or time to release), makes the process less error-prone and removes all the release-related legwork (git commit -m "Trigger release" -m "[#DO_RELEASE]" --allow-empty && git push in many cases can be just enough!).

However, there are also drawbacks of that approach. The CI server - especially in non enterprise environments - is very often a place which has access to all secrets needed to sign and release the software (e.g. the private signing PGP key or the artifacts repository credentials). A data breach from one of the popular SaaS CI servers could open an opportunity to inject malicious code into various popular FOSS projects… Luckily, usually it works fine (or is not actively exploited), however, I cannot resist to mention the vulnerability I have found in Travis back in 2016 which was causing - in some circumstances - to have the build secrets available to anybody in plain text :-/.

There are also some other possible vectors of attack, however, in that article I would like to focus on mitigating the risk of revealing secrets in the system logs, which very often - especially in the FOSS projects - are publicly available.

Gradle and security

Recently, it is pretty visible that the Gradle team takes security very seriously. In January 2020, the HTTP access for Gradle services has been disabled. In February, the verification of Gradle Wrapper was added as an official action for GitHub Actions. A few weeks later, Gradle 6.2 introduced a built-in ability to verify the checksums (and signatures) of the artifacts used in the build process. Starting with Gradle 6.4, using the DEBUG log statement clearly states that it may reveal some secrets and shouldn’t be used in non private builds.

Just released Gradle 6.5 fixes another vulnerability (CVE-2020-13165 ) which I discovered and reported to the Gradle team recently. The earlier Gradle versions might reveal the passphrase for a PGP key used to sign published artifacts, if gpg2 was used together with the INFO log level. This mostly affects CI servers, especially those with the limited (and faulty) secret masking.

I will use CVE-2020-13165 as an excuse to present the simpler artifact signing in Gradle, designed especially for CI servers, which unfortunately is very little-known (an order of magnitude less results on GitHub that a regular useGpgCmd() for GnuPG 2).

PGP artifact signing in Gradle

Originally, Gradle was using the Bouncy Castle library to deal with keys stored in the GnuPG 1 format. However, GnuPG 2 changed the way how keys are kept and their handling has been delegated to a gpg2 process spawn during the Gradle build (which also made it possible to use a system defined gpg-agent program). It was not very convenient on a CI server, where the key usually had to be kept in the encrypted way in the code repository, decrypted with some shell magic during a build using the provided secret and imported into the locally stoped keyring. The procedure was not fully CI server agnostic. In addition, that file could persist on a file system, which especially combined with not using a passphrase could generate some security risk.

Simpler and safer artifact signing

In April 2019, Gradle 5.4 introduced a very nice feature which unfortunately was not even mentioned in the release notes :-(. Marc Philipp (who you may know from his work in JUnit 4/5) implemented a PGP artifact signing optimized for CI servers. Having that in place remote artifact signing is a piece of cake ;-).

  1. Export the private OpenPGP key intended for the CI environment to the ascii-armored form:
$ gpg --export-secret-keys --armor KEY_ID > private-ci.key
  1. Create two secret environment variables on a CI server:
ORG_GRADLE_PROJECT_signingKey = <content of exported private key file>
ORG_GRADLE_PROJECT_signingPassword = <key passphrase>
  1. Enable in-memory signatory provider:
signing {
    useInMemoryPgpKeys(findProperty("signingKey"), findProperty("signingPassword"))
    sign ...

And that’s all. Isn’t is simple?

Btw, that mechanism supports also using subkeys (which provide some benefits ).


As a bonus to simplicity of using the in-memory signatory provider, that method prevents aforementioned passphrase revealing, even in Gradle <6.5. Therefore, it hard to find a good argument against migration :).

Lead photo by Steve Buissinne, published in Pixabay, Pixabay License.
Share on

Marcin Zajączkowski
Marcin Zajączkowski
Software Craftsman & Solution Architect
An experienced architect aiming for high quality solutions. Very engaged in evangelising Software Craftsmanship, Clean Code and Test-Driven Development as a conference speaker and a trainer. A specialist in Continuous Delivery and Continuous Inspection of Code Quality. An enthusiast of Reactive Systems and broadly defined concurrency.

Besides, open source author and contributor, a proud Linux user.

Don't want to use the Utterance bot? Comments can be also placed directly on GitHub.
What's on this Page