Software projects often rely on external code libraries, known as dependencies. Package managers, such as Gradle, allow developers to reference
dependencies for their projects. These dependencies simplify development, but also introduce risk as they download and include external code based on
a project’s configuration. Integrity checking is the step of verifying that the downloaded or included dependency code is exactly what the developer
expects. Without this verification, the application cannot guarantee that the dependency is legitimate.
Failing to verify the integrity of dependencies before using them is a significant security problem. It exposes your application, and potentially
your users, to several risks. The core issue is that you are running code from an untrusted source without checking it, effectively giving an attacker
a direct pathway into your application.
This is often a key component of what is called a "supply chain attack." The attacker isn’t directly attacking your application. Instead, they are
attacking a component you use. This is an important consideration because the attack’s source is less obvious. You might diligently secure your own
code, but overlook the risk introduced by external dependencies.
Ask Yourself Whether
- Your team or company has the security policy to enforce dependency verification.
There is a risk if you answer yes to any of these questions.
Recommended Secure Coding Practices
Create a verification-metadata.xml
in the gradle
directory of your project. Use ./gradlew
--write-verification-metadata pgp,sha256 --export-keys
to bootstrap the file content with PGP key ids and SH256 checksums. The
--export-keys
option creates a keyring file containing the identities of all the dependencies publishers trust.
Verify the identity of all publisher keys exported in the local keyring. If you cannot verify publisher identities, fallback on checksum-based
integrity verification.
Enabling dependency verification in Gradle will add extra friction to your development workflow. Make sure your team is aware about this change and
has a process to maintain the verification-metadata.xml
as well as the trusted identities.
Sensitive Code Example
dependencies {
implementation("com.example:a-dependency:1.0")
}
configurations {
all {
resolutionStrategy {
disableDependencyVerification() // Sensitive: dependency verification is disabled
}
}
}
Absence of a verification-metadata.xml
file in the gradle
directory of your project will also result in the Gradle build
not verifying the integrity of the dependencies.
Compliant Solution
dependencies {
implementation("com.example:adependency:1.0")
}
<verification-metadata
xmlns="https://schema.gradle.org/dependency-verification"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://schema.gradle.org/dependency-verification https://schema.gradle.org/dependency-verification/dependency-verification-1.3.xsd">
<configuration>
<verify-metadata>true</verify-metadata>
<verify-signatures>true</verify-signatures>
<keyring-format>armored</keyring-format>
<trusted-keys>
<trusted-key id="FD8190C7D72E7DCD42582B1042677B9FC1DC2161" group="com.example" name="adependency"/>
</trusted-keys>
</configuration>
</verification-metadata>
See