The provenance, or origin of a dependency is verified via the signature that was used to sign the artifacts. This signature is generated by a public-private key pair used by the library’s author when uploading the library to a Maven Repo.

To allow for this check to occur in your Gradle builds, amend the previous Gradle task to the following

./gradlew --write-verification-metadata sha256,pgp someTaskName

When this is enabled, you’ll see a number of trusted-key elements added to your metadata file representing trusted public keys for given dependencies.

<verification-metadata>
<configuration>
<verify-metadata>true</verify-metadata>
<verify-signatures>true</verify-signatures>
</configuration>
<trusted-keys>
<trusted-key id=“47bf5922…” group="com.jakewharton.timber" name=“timber" version="5.0.1"/>
</trusted-keys>
</verification-metadata>

These trusted public keys are stored locally and tested against the .asc PGP signature files that are also present within the Maven repo for the given dependency. When the dependency is fetched from the repo, these files and associated keys are used to authenticate a particular artifact’s author.

⚠️ PLEASE NOTE: While the setup of this checksum/signature verification is quite straightforward, for it to provide total security it requires that you begin the process with complete trust in the pre-existing dependencies you have imported. Failure to first pre-verify each dependency’s checksum against a value you consider trusted renders this process useless. This process also applies to situations where you update the dependency — so while this is a far from frictionless experience, if done properly you can guarantee the integrity of your dependencies.

The Gradle documentation for dependency verification goes into great detail on these two methods. It is an excellent place to find out much more information about how to secure your dependencies and a must-read for any security-conscious developer! ?

Repository Filtering

Another approach to using dependencies in a more secure way is to use repository filtering. This is the process of creating allow/deny rules for specific repositories surrounding the dependencies they are allowed to fetch. The APIs available for this give developers plenty of scope for creating simple or more complex rules and is something that can be implemented relatively easily through modifications to your existing repositories block.

In the example below, we allow all dependencies with the group com.example and the specific dependency com.example:foo to be fetched from JitPack, while excluding all dependencies from dev.spght.*³

repositories {
maven {
url "https://jitpack.io"
content {
// Fetch dependencies
includeGroup "com.example"
includeModule("com.example", "foo")
// Exclude dependencies
excludeGroupByRegex("dev\\.spght\\..*")
}
}
}

By defining your rules correctly (i.e. in a mutually exclusive way), you can completely guarantee dependencies are downloaded from the source you wish them to be.

However, it should be noted that while this might add some degree of trust, this approach does not verify the dependencies themselves are legitimate like the checksum or signature checks discussed previously. So certainly consider using repository filtering in combination with other methods. ?

Gradle Wrapper Verification

The Gradle Wrapper is a tool we use often with little thought. For most of us, the gradlew script in our projects is commonly the entry point we use to run specific tasks relating to our projects, such as running tests, building artifacts and everything in between. We might give it no attention, but behind the scenes, the wrapper has an important role in ensuring the “correct version” of Gradle is downloaded and executed for your project.

Part of this process is running the gradle-wrapper.jar , a Java executable that is likely checked into your version control. As this file is commonly executed, it is certainly a potential security risk should it be maliciously modified.

In fact, in late 2022 a supply chain attack on the wrapper .jar file was first observed in the wild within the codebase of an incredibly popular Minecraft Server. A malicious Minecraft plugin, in conjunction with the wrapper JAR executable, was responsible for accessing user data and granting particular users admin/root-level access to the game as well as the underlying tech stack used to serve it.

On the back of this, Gradle has released a GitHub Action that can be used as part of your build pipeline to verify that the Wrapper’s JAR is legitimate. It uses the known wrapper checksums to ensure the wrapper has not been tampered with and also is smart enough to check files named gradle-wrapper.jar with homoglyph variants (i.e. where an attacker uses a Unicode character that looks similar to an ASCII one in order to deceive developers into using the wrong file).

However, should you not be using GitHub for CI and require a solution you can run both locally and remotely, I also created a simple script to verify the Wrapper’s JAR

Sadly no official solution exists for verifying the gradlew script, but a GitHub issue exists as a feature request. It may be worth keeping an eye on… ?

Gradle Distribution Verification

Finally, it is also possible to verify the actual Gradle distribution that the wrapper downloads. You may have seen the distributionUrl property within your $PROJ_ROOT/gradle/wrapper/gradle-wrapper.properties file, which contains the URL of the distribution of Gradle to fetch. However, how can we guarantee that what is downloaded is legitimate?

By adding a distributionSha256Sum property to this file with a SHA256 checksum, when the distribution is downloaded the checksum of that file will be compared with the string in the supplied property. These distribution checksums are known and therefore can be added to your project easily.

It is also possible to add the distributionSha256Sum property dynamically when updating your Gradle wrapper by using the gradle-distribution-sha256-sum flag

./gradlew wrapper --gradle-version=7.5 \
--gradle-distribution-sha256-sum=cb87f222c…

Should you download something that doesn’t match the distributionSha256Sum checksum, your builds will fail and you can begin to investigate. You never know, this might just save you one day!

Phew, that’s it. Thanks for sticking to the end ?

In conclusion, while a supply-chain attack on your Gradle tooling might not be as common as other forms of attacks, it is still a viable attack vector that has been proven to occur many times over.

The mechanisms Gradle supplies to thwart such attacks are more than simple enough to integrate into any project relatively quickly. Fingers crossed this post helps you make a more informed choice and perhaps look into adding one or more of these to your project.

For more information, check out spght.dev/talks and catch my talk “How to stop the Gradle Snatchers: Securing your builds from baddies” at Droidcon

Stay safe out there! ?

Thanks as always for reading! I hope you found this post interesting, please feel free to contact me with any feedback at @Sp4ghettiCode and don’t forget to clap, like, share, star etc

Source link