From cd9779f0a215a113312a710aa98f59de605008b9 Mon Sep 17 00:00:00 2001 From: Balint Hegyi Date: Thu, 26 Jan 2023 16:01:19 +0000 Subject: [PATCH] Trusted GPG-key should only accept 160-bit fingerprints Backport from #23667 to 7.6.1 Fixes #23910 --- ...VerificationSignatureCheckIntegTest.groovy | 128 ++++++++++++------ .../DefaultLenientConfiguration.java | 2 +- .../StartParameterResolutionOverride.java | 2 +- ...cksumAndSignatureVerificationOverride.java | 2 +- .../WriteDependencyVerificationFile.java | 2 +- .../ComponentVerificationException.java | 72 ++++++++++ .../DependencyVerificationException.java | 4 +- .../exceptions/InvalidGpgKeyIdsException.java | 70 ++++++++++ .../DependencyVerificationsXmlReader.java | 2 +- .../DependencyVerificationConfiguration.java | 17 ++- .../verifier/DependencyVerifierBuilder.java | 64 +++++++-- .../writer/PgpKeyGrouperTest.groovy | 61 +++++---- ...ependencyVerificationsXmlReaderTest.groovy | 20 +-- ...ependencyVerificationsXmlWriterTest.groovy | 18 +-- .../DependencyVerifierBuilderTest.groovy | 88 ++++++++++++ .../dependency_verification.adoc | 44 +++--- 16 files changed, 462 insertions(+), 134 deletions(-) create mode 100644 subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/verification/exceptions/ComponentVerificationException.java rename subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/verification/{ => exceptions}/DependencyVerificationException.java (89%) create mode 100644 subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/verification/exceptions/InvalidGpgKeyIdsException.java create mode 100644 subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/verification/verifier/DependencyVerifierBuilderTest.groovy diff --git a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/verification/DependencyVerificationSignatureCheckIntegTest.groovy b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/verification/DependencyVerificationSignatureCheckIntegTest.groovy index cbd8fe6ea955..ed3566465f83 100644 --- a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/verification/DependencyVerificationSignatureCheckIntegTest.groovy +++ b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/verification/DependencyVerificationSignatureCheckIntegTest.groovy @@ -32,7 +32,7 @@ import java.util.concurrent.TimeUnit import static org.gradle.security.fixtures.SigningFixtures.getValidPublicKeyLongIdHexString import static org.gradle.security.fixtures.SigningFixtures.signAsciiArmored import static org.gradle.security.fixtures.SigningFixtures.validPublicKeyHexString -import static org.gradle.security.internal.SecuritySupport.toLongIdHexString +import static org.gradle.security.internal.SecuritySupport.toHexString class DependencyVerificationSignatureCheckIntegTest extends AbstractSignatureVerificationIntegrationTest { @@ -64,34 +64,6 @@ class DependencyVerificationSignatureCheckIntegTest extends AbstractSignatureVer succeeds ":compileJava" } - def "doesn't need checksums if signature is verified and trust using long id"() { - createMetadataFile { - keyServer(keyServerFixture.uri) - verifySignatures() - addTrustedKey("org:foo:1.0", validPublicKeyLongIdHexString) - addTrustedKey("org:foo:1.0", validPublicKeyLongIdHexString, "pom", "pom") - } - - given: - javaLibrary() - uncheckedModule("org", "foo", "1.0") { - withSignature { - signAsciiArmored(it) - } - } - buildFile << """ - dependencies { - implementation "org:foo:1.0" - } - """ - - when: - serveValidKey() - - then: - succeeds ":compileJava" - } - def "if signature is verified and checksum is declared in configuration, verify checksum (terse output=#terse)"() { createMetadataFile { keyServer(keyServerFixture.uri) @@ -496,8 +468,8 @@ If the artifacts are trustworthy, you will need to update the gradle/verificatio createMetadataFile { keyServer(keyServerFixture.uri) verifySignatures() - addTrustedKeyByFileName("org:foo:1.0", "foo-1.0-classy.jar", trustedKey) - addTrustedKey("org:foo:1.0", trustedKey, "pom", "pom") + addTrustedKeyByFileName("org:foo:1.0", "foo-1.0-classy.jar", validPublicKeyHexString) + addTrustedKey("org:foo:1.0", validPublicKeyHexString, "pom", "pom") } given: @@ -527,12 +499,6 @@ If the artifacts are trustworthy, you will need to update the gradle/verificatio if (GradleContextualExecuter.isConfigCache()) { failure.assertOutputContains("Configuration cache entry discarded.") } - - where: - trustedKey << [ - validPublicKeyHexString, - validPublicKeyLongIdHexString - ] } def "reasonable error message if key server fails to answer (terse output=#terse)"() { @@ -586,7 +552,7 @@ This can indicate that a dependency has been compromised. Please carefully verif def keyring = newKeyRing() def secondServer = new KeyServer(temporaryFolder.createDir("keyserver-${UUID.randomUUID()}")) secondServer.registerPublicKey(keyring.publicKey) - def pkId = toLongIdHexString(keyring.publicKey.keyID) + def pkId = toHexString(keyring.publicKey.fingerprint) secondServer.start() createMetadataFile { keyServer(keyServerFixture.uri) @@ -1296,7 +1262,7 @@ This can indicate that a dependency has been compromised. Please carefully verif def "passes verification if an artifact is signed with multiple keys and one of them is ignored"() { def keyring = newKeyRing() keyServerFixture.registerPublicKey(keyring.publicKey) - def pkId = toLongIdHexString(keyring.publicKey.keyID) + def pkId = toHexString(keyring.publicKey.fingerprint) createMetadataFile { keyServer(keyServerFixture.uri) verifySignatures() @@ -1464,7 +1430,7 @@ If the artifacts are trustworthy, you will need to update the gradle/verificatio def "can read public keys from #keyRingFormat keyring"() { // key will not be published on the server fixture but available locally def keyring = newKeyRing() - def pkId = toLongIdHexString(keyring.publicKey.keyID) + def pkId = toHexString(keyring.publicKey.fingerprint) createMetadataFile { disableKeyServers() @@ -1687,7 +1653,7 @@ This can indicate that a dependency has been compromised. Please carefully verif @Issue("https://github.com/gradle/gradle/issues/18440") def "fails on a bad verification file change after previous successful build when key servers are disabled"() { def keyring = newKeyRing() - def pkId = toLongIdHexString(keyring.publicKey.keyID) + def pkId = toHexString(keyring.publicKey.fingerprint) createMetadataFile { disableKeyServers() @@ -1771,6 +1737,86 @@ This can indicate that a dependency has been compromised. Please carefully verif succeeds ":compileJava" } + def "fails verification if a per artifact trusted key is not a fingerprint"() { + createMetadataFile { + keyServer(keyServerFixture.uri) + verifySignatures() + addTrustedKey("org:foo:1.0", validPublicKeyHexString) + addTrustedKey("org:foo:1.0", validPublicKeyHexString, "pom", "pom") + } + + // We need to manually replace the key in the XML, as 'createMetadataFile' will already fail if we use a non-fingerprint ID + def longId = validPublicKeyHexString.substring(validPublicKeyHexString.length() - 16) + file("gradle/verification-metadata.xml").replace(validPublicKeyHexString, longId) + + given: + terseConsoleOutput(terse) + javaLibrary() + uncheckedModule("org", "foo", "1.0") { + withSignature { + signAsciiArmored(it) + } + } + buildFile << """ + dependencies { + implementation "org:foo:1.0" + } + """ + + when: + fails ":compileJava" + + then: + if (GradleContextualExecuter.isConfigCache()) { + failure.assertOutputContains("Configuration cache entry discarded.") + } + failureCauseContains("An error happened meanwhile verifying 'org:foo:1.0'") + failureCauseContains("The following trusted GPG IDs are not in a minimum 160-bit fingerprint format") + failureCauseContains("'${longId}'") + + where: + terse << [true, false] + } + + def "fails verification if a globally trusted key is not a fingerprint"() { + createMetadataFile { + keyServer(keyServerFixture.uri) + verifySignatures() + addGloballyTrustedKey(validPublicKeyHexString, "org", "foo", "1.0", "foo-1.0-classified.jar", false) + } + + // We need to manually replace the key in the XML, as 'createMetadataFile' will already fail if we use a non-fingerprint ID + def longId = validPublicKeyHexString.substring(validPublicKeyHexString.length() - 16) + file("gradle/verification-metadata.xml").replace(validPublicKeyHexString, longId) + + given: + terseConsoleOutput(terse) + javaLibrary() + uncheckedModule("org", "foo", "1.0") { + withSignature { + signAsciiArmored(it) + } + } + buildFile << """ + dependencies { + implementation "org:foo:1.0" + } + """ + + when: + fails ":compileJava" + + then: + if (GradleContextualExecuter.isConfigCache()) { + failure.assertOutputContains("Configuration cache entry discarded.") + } + failureCauseContains("The following trusted GPG IDs are not in a minimum 160-bit fingerprint format") + failureCauseContains("'${longId}'") + + where: + terse << [true, false] + } + private static void tamperWithFile(File file) { file.bytes = [0, 1, 2, 3] + file.readBytes().toList() as byte[] } diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/DefaultLenientConfiguration.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/DefaultLenientConfiguration.java index 87cc1c0d95a1..fc599eb6dbf7 100644 --- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/DefaultLenientConfiguration.java +++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/DefaultLenientConfiguration.java @@ -45,7 +45,7 @@ import org.gradle.api.internal.artifacts.ivyservice.resolveengine.oldresult.TransientConfigurationResultsLoader; import org.gradle.api.internal.artifacts.transform.ArtifactTransforms; import org.gradle.api.internal.artifacts.transform.VariantSelector; -import org.gradle.api.internal.artifacts.verification.DependencyVerificationException; +import org.gradle.api.internal.artifacts.verification.exceptions.DependencyVerificationException; import org.gradle.api.internal.attributes.AttributeContainerInternal; import org.gradle.api.internal.file.FileCollectionInternal; import org.gradle.api.internal.file.FileCollectionStructureVisitor; diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/StartParameterResolutionOverride.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/StartParameterResolutionOverride.java index cd55332c7ebd..47510860bda8 100644 --- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/StartParameterResolutionOverride.java +++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/StartParameterResolutionOverride.java @@ -27,7 +27,7 @@ import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.verification.writer.WriteDependencyVerificationFile; import org.gradle.api.internal.artifacts.ivyservice.resolutionstrategy.ExternalResourceCachePolicy; import org.gradle.api.internal.artifacts.repositories.resolver.MetadataFetchingCost; -import org.gradle.api.internal.artifacts.verification.DependencyVerificationException; +import org.gradle.api.internal.artifacts.verification.exceptions.DependencyVerificationException; import org.gradle.api.internal.artifacts.verification.signatures.BuildTreeDefinedKeys; import org.gradle.api.internal.artifacts.verification.signatures.SignatureVerificationServiceFactory; import org.gradle.api.internal.component.ArtifactType; diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/verification/ChecksumAndSignatureVerificationOverride.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/verification/ChecksumAndSignatureVerificationOverride.java index f896baa9e65f..9ccb634d6c2c 100644 --- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/verification/ChecksumAndSignatureVerificationOverride.java +++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/verification/ChecksumAndSignatureVerificationOverride.java @@ -30,7 +30,7 @@ import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleComponentRepository; import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.verification.report.DependencyVerificationReportWriter; import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.verification.report.VerificationReport; -import org.gradle.api.internal.artifacts.verification.DependencyVerificationException; +import org.gradle.api.internal.artifacts.verification.exceptions.DependencyVerificationException; import org.gradle.api.internal.artifacts.verification.serializer.DependencyVerificationsXmlReader; import org.gradle.api.internal.artifacts.verification.signatures.BuildTreeDefinedKeys; import org.gradle.api.internal.artifacts.verification.signatures.SignatureVerificationService; diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/verification/writer/WriteDependencyVerificationFile.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/verification/writer/WriteDependencyVerificationFile.java index 7d9e39cbb6b9..71fee9c2fde9 100644 --- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/verification/writer/WriteDependencyVerificationFile.java +++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/verification/writer/WriteDependencyVerificationFile.java @@ -34,7 +34,7 @@ import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.verification.DefaultKeyServers; import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.verification.DependencyVerificationOverride; import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.verification.utils.PGPUtils; -import org.gradle.api.internal.artifacts.verification.DependencyVerificationException; +import org.gradle.api.internal.artifacts.verification.exceptions.DependencyVerificationException; import org.gradle.api.internal.artifacts.verification.model.ChecksumKind; import org.gradle.api.internal.artifacts.verification.model.IgnoredKey; import org.gradle.api.internal.artifacts.verification.serializer.DependencyVerificationsXmlReader; diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/verification/exceptions/ComponentVerificationException.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/verification/exceptions/ComponentVerificationException.java new file mode 100644 index 000000000000..30e929203d5e --- /dev/null +++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/verification/exceptions/ComponentVerificationException.java @@ -0,0 +1,72 @@ +/* + * Copyright 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.api.internal.artifacts.verification.exceptions; + +import org.gradle.api.GradleException; +import org.gradle.api.artifacts.component.ModuleComponentIdentifier; +import org.gradle.internal.logging.text.TreeFormatter; + +import java.util.function.Consumer; + +public class ComponentVerificationException extends GradleException { + + private final ModuleComponentIdentifier component; + private final Consumer causeErrorFormatter; + + /** + * Creates a new exception when a component cannot be verified - because of some reason. + * + * @param component the component which failed the verification + */ + public ComponentVerificationException(String message, ModuleComponentIdentifier component) { + super(message); + this.component = component; + this.causeErrorFormatter = null; + } + + /** + * Creates a new exception when a component cannot be verified - because of some reason. + * + * @param component the component which failed the verification + * @param causeErrorFormatter a consumer, which will be called with a {@link TreeFormatter}, and can put extra details what happened + */ + public ComponentVerificationException(ModuleComponentIdentifier component, Consumer causeErrorFormatter) { + this.component = component; + this.causeErrorFormatter = causeErrorFormatter; + } + + @Override + public String getMessage() { + final TreeFormatter treeFormatter = new TreeFormatter(); + // Add our header first + treeFormatter.node( + String.format( + "An error happened meanwhile verifying '%s:%s:%s':", + component.getGroup(), component.getModule(), component.getVersion() + ) + ); + + if (this.causeErrorFormatter != null) { + treeFormatter.startChildren(); + // Let the underlying exception explain the situation + causeErrorFormatter.accept(treeFormatter); + treeFormatter.endChildren(); + + } + return treeFormatter.toString(); + } +} diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/verification/DependencyVerificationException.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/verification/exceptions/DependencyVerificationException.java similarity index 89% rename from subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/verification/DependencyVerificationException.java rename to subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/verification/exceptions/DependencyVerificationException.java index 79d0797a0309..492b8dfbb683 100644 --- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/verification/DependencyVerificationException.java +++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/verification/exceptions/DependencyVerificationException.java @@ -1,5 +1,5 @@ /* - * Copyright 2021 the original author or authors. + * Copyright 2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.gradle.api.internal.artifacts.verification; +package org.gradle.api.internal.artifacts.verification.exceptions; import org.gradle.api.GradleException; import org.gradle.internal.exceptions.Contextual; diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/verification/exceptions/InvalidGpgKeyIdsException.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/verification/exceptions/InvalidGpgKeyIdsException.java new file mode 100644 index 000000000000..a1ec04cda3cf --- /dev/null +++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/verification/exceptions/InvalidGpgKeyIdsException.java @@ -0,0 +1,70 @@ +/* + * Copyright 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.api.internal.artifacts.verification.exceptions; + +import org.gradle.api.GradleException; +import org.gradle.api.internal.DocumentationRegistry; +import org.gradle.internal.logging.text.TreeFormatter; + +import java.util.List; + +/** + * Exception class used when a GPG IDs were not correct. + * + *

+ * An example is using short/long IDs instead of fingerprints when trusting keys + */ +public class InvalidGpgKeyIdsException extends GradleException { + private final List wrongKeys; + + /** + * Creates a new exception with a list of incorrect keys. + * + * @param wrongKeys the list of incorrect IDs, which will be nicely formatted as part of the exception messages so the user can find them + */ + public InvalidGpgKeyIdsException(List wrongKeys) { + this.wrongKeys = wrongKeys; + } + + /** + * Formats a nice error message by using a {@link TreeFormatter}. + * + *

+ * Idea for this method is that you can pass a higher-level {@link TreeFormatter} into here, and get a coherent, nice error message printed out - so the user will see a nice chain of causes. + */ + public void formatMessage(TreeFormatter formatter) { + final DocumentationRegistry documentationRegistry = new DocumentationRegistry(); + final String documentLink = documentationRegistry.getDocumentationFor("dependency_verification", "sec:understanding-signature-verification"); + + formatter.node( + String.format("The following trusted GPG IDs are not in a minimum 160-bit fingerprint format (see: %s):", documentLink) + ); + formatter.startChildren(); + wrongKeys + .stream() + .map(key -> String.format("'%s'", key)) + .forEach(formatter::node); + formatter.endChildren(); + } + + @Override + public String getMessage() { + final TreeFormatter treeFormatter = new TreeFormatter(); + formatMessage(treeFormatter); + return treeFormatter.toString(); + } +} diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/verification/serializer/DependencyVerificationsXmlReader.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/verification/serializer/DependencyVerificationsXmlReader.java index 5c0b74a76532..4283872bbab3 100644 --- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/verification/serializer/DependencyVerificationsXmlReader.java +++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/verification/serializer/DependencyVerificationsXmlReader.java @@ -20,7 +20,7 @@ import org.gradle.api.artifacts.ModuleIdentifier; import org.gradle.api.artifacts.component.ModuleComponentIdentifier; import org.gradle.api.internal.artifacts.DefaultModuleIdentifier; -import org.gradle.api.internal.artifacts.verification.DependencyVerificationException; +import org.gradle.api.internal.artifacts.verification.exceptions.DependencyVerificationException; import org.gradle.api.internal.artifacts.verification.model.ChecksumKind; import org.gradle.api.internal.artifacts.verification.model.IgnoredKey; import org.gradle.api.internal.artifacts.verification.verifier.DependencyVerifier; diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/verification/verifier/DependencyVerificationConfiguration.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/verification/verifier/DependencyVerificationConfiguration.java index 8090f9f850d7..0d956f42eb5d 100644 --- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/verification/verifier/DependencyVerificationConfiguration.java +++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/verification/verifier/DependencyVerificationConfiguration.java @@ -17,11 +17,14 @@ import com.google.common.collect.ImmutableList; import org.gradle.api.artifacts.component.ModuleComponentIdentifier; +import org.gradle.api.internal.artifacts.verification.exceptions.InvalidGpgKeyIdsException; import org.gradle.api.internal.artifacts.verification.model.IgnoredKey; import org.gradle.internal.component.external.model.ModuleComponentArtifactIdentifier; import javax.annotation.Nullable; import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.Set; @@ -205,7 +208,19 @@ public static class TrustedKey extends TrustCoordinates implements Comparable byArtifact = Maps.newHashMap(); - private ComponentVerificationsBuilder(ModuleComponentIdentifier component) { + protected ComponentVerificationsBuilder(ModuleComponentIdentifier component) { this.component = component; } @@ -165,7 +168,7 @@ void addIgnoredKey(ModuleComponentArtifactIdentifier artifact, IgnoredKey key) { byArtifact.computeIfAbsent(artifact.getFileName(), id -> new ArtifactVerificationBuilder()).addIgnoredKey(key); } - private static ArtifactVerificationMetadata toArtifactVerification(Map.Entry entry) { + private static ArtifactVerificationMetadata toArtifactVerification(Map.Entry entry) throws InvalidGpgKeyIdsException { String key = entry.getKey(); ArtifactVerificationBuilder value = entry.getValue(); return new ImmutableArtifactVerificationMetadata( @@ -176,17 +179,21 @@ private static ArtifactVerificationMetadata toArtifactVerification(Map.Entry builder = Maps.newEnumMap(ChecksumKind.class); private final Set pgpKeys = Sets.newLinkedHashSet(); private final Set ignoredPgpKeys = Sets.newLinkedHashSet(); @@ -215,8 +222,37 @@ public void addIgnoredKey(IgnoredKey key) { ignoredPgpKeys.add(key); } - public Set buildTrustedPgpKeys() { - return pgpKeys; + /** + * Builds the list of trusted GPG keys. + *

+ * This method will verify if all the trusted keys are in 160-bit fingerprint format. + * We do not accept either short or long formats, as they can be vulnerable to collision attacks. + * + *

+ * Note: the fingerprints' formatting is not verified (i.e. if it's true base32 or not) at this stage. + * It will happen when these fingerprints will be converted to {@link org.gradle.security.internal.Fingerprint}. + * + * @return a set of trusted GPG keys + * @throws InvalidGpgKeyIdsException if keys not fitting the requirements were found + */ + public Set buildTrustedPgpKeys() throws InvalidGpgKeyIdsException { + final List wrongPgpKeys = pgpKeys + .stream() + // The key is 160 bits long, encoded in base32 (case-insensitive characters). + // + // Base32 gives us 4 bits per character, so the whole fingerprint will be: + // (160 bits) / (4 bits / character) = 40 characters + // + // By getting ASCII bytes (aka. strictly 1 byte per character, no variable-length magic) + // we can safely check if the fingerprint is of the correct length. + .filter(key -> key.getBytes(StandardCharsets.US_ASCII).length < 40) + .collect(Collectors.toList()); + + if (wrongPgpKeys.isEmpty()) { + return pgpKeys; + } else { + throw new InvalidGpgKeyIdsException(wrongPgpKeys); + } } public Set buildIgnoredPgpKeys() { diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/verification/writer/PgpKeyGrouperTest.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/verification/writer/PgpKeyGrouperTest.groovy index 9aaeb5af495d..5d98ab6d3d75 100644 --- a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/verification/writer/PgpKeyGrouperTest.groovy +++ b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/verification/writer/PgpKeyGrouperTest.groovy @@ -30,6 +30,9 @@ class PgpKeyGrouperTest extends Specification { private DependencyVerifier verifier private PgpKeyGrouper pgpKeyGrouper + private static final String KEY_1 = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + private static final String KEY_2 = 'BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB' + def "common prefix for groups #groups == #expected"() { expect: PgpKeyGrouper.tryComputeCommonPrefixes(groups) == expected @@ -65,39 +68,39 @@ class PgpKeyGrouperTest extends Specification { def "groups entries which have the same module component id"() { grouper { - entry("org", "foo", "1.0", "foo-1.0.jar").addVerifiedKey("key1") - entry("org", "foo", "1.0", "foo-1.0.pom").addVerifiedKey("key1") + entry("org", "foo", "1.0", "foo-1.0.jar").addVerifiedKey(KEY_1) + entry("org", "foo", "1.0", "foo-1.0.pom").addVerifiedKey(KEY_1) } when: executeGrouping() then: - verifier.configuration.trustedKeys*.keyId == ["key1"] + verifier.configuration.trustedKeys*.keyId == [KEY_1] verifier.verificationMetadata.empty } def "groups entries which have the same module id"() { grouper { - entry("org", "foo", "1.0", "foo-1.0.jar").addVerifiedKey("key1") - entry("org", "foo", "1.0", "foo-1.0.pom").addVerifiedKey("key1") - entry("org", "foo", "1.1", "foo-1.1.jar").addVerifiedKey("key1") - entry("org", "foo", "1.1", "foo-1.1.pom").addVerifiedKey("key1") + entry("org", "foo", "1.0", "foo-1.0.jar").addVerifiedKey(KEY_1) + entry("org", "foo", "1.0", "foo-1.0.pom").addVerifiedKey(KEY_1) + entry("org", "foo", "1.1", "foo-1.1.jar").addVerifiedKey(KEY_1) + entry("org", "foo", "1.1", "foo-1.1.pom").addVerifiedKey(KEY_1) } when: executeGrouping() then: - verifier.configuration.trustedKeys*.keyId == ["key1"] + verifier.configuration.trustedKeys*.keyId == [KEY_1] } def "doesn't group entries which have the same module id but different keys"() { grouper { - entry("org", "foo", "1.0", "foo-1.0.jar").addVerifiedKey("key1") - entry("org", "foo", "1.0", "foo-1.0.pom").addVerifiedKey("key1") - entry("org", "foo", "1.1", "foo-1.1.jar").addVerifiedKey("key2") - entry("org", "foo", "1.1", "foo-1.1.pom").addVerifiedKey("key2") + entry("org", "foo", "1.0", "foo-1.0.jar").addVerifiedKey(KEY_1) + entry("org", "foo", "1.0", "foo-1.0.pom").addVerifiedKey(KEY_1) + entry("org", "foo", "1.1", "foo-1.1.jar").addVerifiedKey(KEY_2) + entry("org", "foo", "1.1", "foo-1.1.pom").addVerifiedKey(KEY_2) } when: @@ -105,17 +108,17 @@ class PgpKeyGrouperTest extends Specification { then: def keys = verifier.configuration.trustedKeys - keys*.keyId == ["key1", "key2"] + keys*.keyId == [KEY_1, KEY_2] keys[0].version == '1.0' keys[1].version == '1.1' } def "groups entries which have the same group id"() { grouper { - entry("org", "foo", "1.0", "foo-1.0.jar").addVerifiedKey("key1") - entry("org", "foo", "1.0", "foo-1.0.pom").addVerifiedKey("key1") - entry("org", "bar", "1.1", "bar-1.1.jar").addVerifiedKey("key1") - entry("org", "bar", "1.1", "bar-1.1.pom").addVerifiedKey("key1") + entry("org", "foo", "1.0", "foo-1.0.jar").addVerifiedKey(KEY_1) + entry("org", "foo", "1.0", "foo-1.0.pom").addVerifiedKey(KEY_1) + entry("org", "bar", "1.1", "bar-1.1.jar").addVerifiedKey(KEY_1) + entry("org", "bar", "1.1", "bar-1.1.pom").addVerifiedKey(KEY_1) } when: @@ -123,7 +126,7 @@ class PgpKeyGrouperTest extends Specification { then: def keys = verifier.configuration.trustedKeys - keys*.keyId == ["key1"] + keys*.keyId == [KEY_1] keys[0].group == 'org' keys[0].name == null keys[0].version == null @@ -132,10 +135,10 @@ class PgpKeyGrouperTest extends Specification { def "groups entries which have a common group prefix"() { grouper { - entry("org.group.a", "foo", "1.0", "foo-1.0.jar").addVerifiedKey("key1") - entry("org.group.a", "foo", "1.0", "foo-1.0.pom").addVerifiedKey("key1") - entry("org.group.b", "bar", "1.1", "bar-1.1.jar").addVerifiedKey("key1") - entry("org.group.b", "bar", "1.1", "bar-1.1.pom").addVerifiedKey("key1") + entry("org.group.a", "foo", "1.0", "foo-1.0.jar").addVerifiedKey(KEY_1) + entry("org.group.a", "foo", "1.0", "foo-1.0.pom").addVerifiedKey(KEY_1) + entry("org.group.b", "bar", "1.1", "bar-1.1.jar").addVerifiedKey(KEY_1) + entry("org.group.b", "bar", "1.1", "bar-1.1.pom").addVerifiedKey(KEY_1) } when: @@ -143,7 +146,7 @@ class PgpKeyGrouperTest extends Specification { then: def keys = verifier.configuration.trustedKeys - keys*.keyId == ["key1"] + keys*.keyId == [KEY_1] keys[0].group == '^org[.]group($|([.].*))' keys[0].name == null keys[0].version == null @@ -152,14 +155,14 @@ class PgpKeyGrouperTest extends Specification { } def "does not attempt grouping when it exists already"() { - def trustedKey = new DependencyVerificationConfiguration.TrustedKey("key1", "org.*", null, null, null, true) - builder.addTrustedKey("key1", "org.*", null, null, null, true) + def trustedKey = new DependencyVerificationConfiguration.TrustedKey(KEY_1, "org.*", null, null, null, true) + builder.addTrustedKey(KEY_1, "org.*", null, null, null, true) grouper { - entry("org.group.a", "foo", "1.0", "foo-1.0.jar").addVerifiedKey("key1") - entry("org.group.a", "foo", "1.0", "foo-1.0.pom").addVerifiedKey("key1") - entry("org.group.b", "bar", "1.1", "bar-1.1.jar").addVerifiedKey("key1") - entry("org.group.b", "bar", "1.1", "bar-1.1.pom").addVerifiedKey("key1") + entry("org.group.a", "foo", "1.0", "foo-1.0.jar").addVerifiedKey(KEY_1) + entry("org.group.a", "foo", "1.0", "foo-1.0.pom").addVerifiedKey(KEY_1) + entry("org.group.b", "bar", "1.1", "bar-1.1.jar").addVerifiedKey(KEY_1) + entry("org.group.b", "bar", "1.1", "bar-1.1.pom").addVerifiedKey(KEY_1) } when: diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/verification/serializer/DependencyVerificationsXmlReaderTest.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/verification/serializer/DependencyVerificationsXmlReaderTest.groovy index 333c0ef35233..de029dfc96d0 100644 --- a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/verification/serializer/DependencyVerificationsXmlReaderTest.groovy +++ b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/verification/serializer/DependencyVerificationsXmlReaderTest.groovy @@ -17,7 +17,7 @@ package org.gradle.api.internal.artifacts.verification.serializer -import org.gradle.api.internal.artifacts.verification.DependencyVerificationException +import org.gradle.api.internal.artifacts.verification.exceptions.DependencyVerificationException import org.gradle.api.internal.artifacts.verification.model.ChecksumKind import org.gradle.api.internal.artifacts.verification.model.IgnoredKey import org.gradle.api.internal.artifacts.verification.verifier.DependencyVerifier @@ -196,13 +196,13 @@ class DependencyVerificationsXmlReaderTest extends Specification { true false - - + + - - + + @@ -213,35 +213,35 @@ class DependencyVerificationsXmlReaderTest extends Specification { def trustedKeys = verifier.configuration.trustedKeys trustedKeys.size() == 5 - trustedKeys[0].keyId == "012345" + trustedKeys[0].keyId == "A000000000000000000000000000000000000000" trustedKeys[0].group == "g2" trustedKeys[0].name == "m1" trustedKeys[0].version == null trustedKeys[0].fileName == "file.jar" trustedKeys[0].regex == true - trustedKeys[1].keyId == "456DEF" + trustedKeys[1].keyId == "B000000000000000000000000000000000000000" trustedKeys[1].group == null trustedKeys[1].name == "m3" trustedKeys[1].version == "1.4" trustedKeys[1].fileName == "file.zip" trustedKeys[1].regex == false - trustedKeys[2].keyId == "456DEF" + trustedKeys[2].keyId == "B000000000000000000000000000000000000000" trustedKeys[2].group == null trustedKeys[2].name == "m4" trustedKeys[2].version == null trustedKeys[2].fileName == "other-file.zip" trustedKeys[2].regex == true - trustedKeys[3].keyId == "ABC123" + trustedKeys[3].keyId == "C000000000000000000000000000000000000000" trustedKeys[3].group == "g3" trustedKeys[3].name == "m2" trustedKeys[3].version == "1.0" trustedKeys[3].fileName == null trustedKeys[3].regex == true - trustedKeys[4].keyId == "ABCDEF" + trustedKeys[4].keyId == "D000000000000000000000000000000000000000" trustedKeys[4].group == "g1" trustedKeys[4].name == null trustedKeys[4].version == null diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/verification/serializer/DependencyVerificationsXmlWriterTest.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/verification/serializer/DependencyVerificationsXmlWriterTest.groovy index 3cccf1fbb6e8..3bb5ca17a5a1 100644 --- a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/verification/serializer/DependencyVerificationsXmlWriterTest.groovy +++ b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/verification/serializer/DependencyVerificationsXmlWriterTest.groovy @@ -164,11 +164,11 @@ on two lines --> def "can declare trusted keys"() { when: - builder.addTrustedKey("ABCDEF", "g1", null, null, null, false) - builder.addTrustedKey("012345", "g2", "m1", null, "file.jar", true) - builder.addTrustedKey("ABC123", "g3", "m2", "1.0", null, true) - builder.addTrustedKey("456DEF", null, "m3", "1.4", "file.zip", false) - builder.addTrustedKey("456DEF", null, "m4", null, "other-file.zip", true) + builder.addTrustedKey("A000000000000000000000000000000000000000", "g1", null, null, null, false) + builder.addTrustedKey("B000000000000000000000000000000000000000", "g2", "m1", null, "file.jar", true) + builder.addTrustedKey("C000000000000000000000000000000000000000", "g3", "m2", "1.0", null, true) + builder.addTrustedKey("D000000000000000000000000000000000000000", null, "m3", "1.4", "file.zip", false) + builder.addTrustedKey("D000000000000000000000000000000000000000", null, "m4", null, "other-file.zip", true) serialize() then: @@ -178,13 +178,13 @@ on two lines --> true false - - + + + + - - diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/verification/verifier/DependencyVerifierBuilderTest.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/verification/verifier/DependencyVerifierBuilderTest.groovy new file mode 100644 index 000000000000..aab71f6d70b1 --- /dev/null +++ b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/verification/verifier/DependencyVerifierBuilderTest.groovy @@ -0,0 +1,88 @@ +/* + * Copyright 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.api.internal.artifacts.verification.verifier + + +import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier +import org.gradle.api.internal.artifacts.verification.exceptions.ComponentVerificationException +import org.gradle.api.internal.artifacts.verification.exceptions.InvalidGpgKeyIdsException +import org.gradle.internal.component.external.model.DefaultModuleComponentArtifactIdentifier +import org.gradle.internal.component.external.model.DefaultModuleComponentIdentifier +import spock.lang.Specification + +class DependencyVerifierBuilderTest extends Specification { + + def "ComponentVerificationsBuilder should fail if trusted GPG key is not a fingerprint but a #name"() { + given: + def moduleComponentIdentifier = DefaultModuleComponentIdentifier::newId( + DefaultModuleVersionIdentifier::newId("test.group", "test-module", "0.0.0") + ) + def componentArtifactIdentified = new DefaultModuleComponentArtifactIdentifier( + moduleComponentIdentifier, "artifact", "jar", ".jar" + ) + + def dependencyVerifier = new DependencyVerifierBuilder.ComponentVerificationsBuilder(moduleComponentIdentifier); + keyIds.forEach { + dependencyVerifier.addTrustedKey(componentArtifactIdentified, it) + } + + when: + dependencyVerifier.build() + + then: + def ex = thrown(ComponentVerificationException) + println(ex.getMessage()) + + where: + name | keyIds + "short id" | ["AAAAAAA"] + "long id" | ["AAAAAAAAAAAAAA"] + "mixed short/long id" | ["AAAAAAAA", "AAAAAAAAAAAAAA"] + } + + def "ArtifactVerificationBuilder should fail if trusted GPG key is not a fingerprint but a #name"() { + given: + def verificationBuilder = new DependencyVerifierBuilder.ArtifactVerificationBuilder() + keyIds.forEach(verificationBuilder::addTrustedKey) + + when: + verificationBuilder.buildTrustedPgpKeys(); + + then: + def ex = thrown(InvalidGpgKeyIdsException) + println(ex.getMessage()) + + where: + name | keyIds + "short id" | ["AAAAAAA"] + "long id" | ["AAAAAAAAAAAAAA"] + "mixed short/long id" | ["AAAAAAAA", "AAAAAAAAAAAAAA"] + } + + def "ArtifactVerificationBuilder should succeed if trusted GPG key is a fingerprint"() { + given: + def verificationBuilder = new DependencyVerifierBuilder.ArtifactVerificationBuilder() + verificationBuilder.addTrustedKey("d7bf96a169f77b28c934ab1614f53f0824875d73") + + when: + verificationBuilder.buildTrustedPgpKeys(); + + then: + noExceptionThrown() + } + +} diff --git a/subprojects/docs/src/docs/userguide/dep-man/01-core-dependency-management/dependency_verification.adoc b/subprojects/docs/src/docs/userguide/dep-man/01-core-dependency-management/dependency_verification.adoc index 056566cf1fd9..5c4d0fd4905f 100644 --- a/subprojects/docs/src/docs/userguide/dep-man/01-core-dependency-management/dependency_verification.adoc +++ b/subprojects/docs/src/docs/userguide/dep-man/01-core-dependency-management/dependency_verification.adoc @@ -115,7 +115,6 @@ Therefore, it's recommended to always use the same parameters once you started b The dependency verification file can be generated with the following CLI instructions: - ---- gradle --write-verification-metadata sha256 help ---- @@ -174,7 +173,6 @@ There are situations where you would just want to _see_ what the generated verif For this purpose, you can just add `--dry-run`: - ---- gradle --write-verification-metadata sha256 help --dry-run ---- @@ -272,7 +270,6 @@ Changing the `origin` gives users a sense of how trustworthy your build it. Interestingly, using `pdfbox` will require _much more_ than those 2 artifacts, because it will also bring in transitive dependencies. If the dependency verification file only included the checksums for the main artifacts you used, the build would fail with an error like this one: - ---- Execution failed for task ':compileJava'. > Dependency verification failed for configuration ':compileClasspath': @@ -301,7 +298,8 @@ For example, the following configuration would check both the `md5` and `sha1` c There are multiple reasons why you'd like to do so: -1. an official site doesn't publish _secure_ checksums (SHA-256, SHA-512) but publishes multiple insecure ones (MD5, SHA1). While it's easy to fake a MD5 checksum and hard but possible to fake a SHA1 checksum, it's harder to fake both of them for the same artifact. +1. an official site doesn't publish _secure_ checksums (SHA-256, SHA-512) but publishes multiple insecure ones (MD5, SHA1). +While it's easy to fake a MD5 checksum and hard but possible to fake a SHA1 checksum, it's harder to fake both of them for the same artifact. 2. you might want to add generated checksums to the list above 3. when _updating_ dependency verification file with more secure checksums, you don't want to accidentally erase checksums @@ -384,10 +382,14 @@ In practice, it means you need to list the keys that you trust for each artifact ---- -[TIP] +[WARNING] ==== -Gradle supports both full fingerprint ids or long (64-bit) key ids in `pgp`, `trusted-key` and `ignore-key` elements. -For maximum security, you should use full fingerprints as it's possible to have collisions for long key ids. +For the `pgp` and `trusted-key` elements, Gradle _requires_ full fingerprint IDs (e.g. `b801e2f8ef035068ec1139cc29579f18fa8fd93b` instead of a long ID `29579f18fa8fd93b`) . +This minimizes the chance of a https://en.wikipedia.org/wiki/Collision_attack[collision attack]. + +At the time, https://www.rfc-editor.org/rfc/rfc4880#section-12.2[V4 key fingerprints] are of 160-bit (40 characters) length. We accept longer keys to be future-proof in case a longer key fingerprint is introduced. + +In `ignore-key` elements, either fingerprints or long (64-bit) IDs can be used. A shorter ID can only result in a bigger range of exclusion, therefore, it's safe to use. ==== This effectively means that you trust `com.github.javaparser:javaparser-core:3.6.11` if it's signed with the key `8756c4f765c9ac3cb6b85d62379ce192d401ab61`. @@ -421,14 +423,14 @@ If this is the case, you can move the trusted key from the artifact level to the true true - + ---- -The configuration above means that for any artifact belonging to the group `com.github.javaparser`, we trust it if it's signed with the `379ce192d401ab61`. +The configuration above means that for any artifact belonging to the group `com.github.javaparser`, we trust it if it's signed with the `8756c4f765c9ac3cb6b85d62379ce192d401ab61` fingerprint. The `trusted-key` element works similarly to the <> element: @@ -518,14 +520,14 @@ You can generate the binary version using GPG, for example issuing the following [source,bash] ---- -$ gpg --no-default-keyring --keyring gradle/verification-keyring.gpg --recv-keys 379ce192d401ab61 +$ gpg --no-default-keyring --keyring gradle/verification-keyring.gpg --recv-keys 8756c4f765c9ac3cb6b85d62379ce192d401ab61 gpg: keybox 'gradle/verification-keyring.gpg' created gpg: key 379CE192D401AB61: public key "Bintray (by JFrog) <****>" imported gpg: Total number processed: 1 gpg: imported: 1 -$ gpg --no-default-keyring --keyring gradle/verification-keyring.gpg --recv-keys 6a0975f8b1127b83 +$ gpg --no-default-keyring --keyring gradle/verification-keyring.gpg --recv-keys 6f538074ccebf35f28af9b066a0975f8b1127b83 gpg: key 0729A0AFF8999A87: public key "Kotlin Release <****>" imported gpg: Total number processed: 1 @@ -539,7 +541,7 @@ In the example above, you could amend an existing KEYS file by issuing the follo [source,bash] ---- -$ gpg --no-default-keyring --keyring /tmp/keyring.gpg --recv-keys 379ce192d401ab61 +$ gpg --no-default-keyring --keyring /tmp/keyring.gpg --recv-keys 8756c4f765c9ac3cb6b85d62379ce192d401ab61 gpg: keybox '/tmp/keyring.gpg' created gpg: key 379CE192D401AB61: public key "Bintray (by JFrog) <****>" imported @@ -547,10 +549,10 @@ gpg: Total number processed: 1 gpg: imported: 1 # First let's add a header so that we can recognize the added key -$ gpg --keyring /tmp/keyring.gpg --list-sigs 379CE192D401AB61 > gradle/verification-keyring.keys +$ gpg --keyring /tmp/keyring.gpg --list-sigs 8756c4f765c9ac3cb6b85d62379ce192d401ab61 > gradle/verification-keyring.keys # Then write its ASCII armored version -$ gpg --keyring /tmp/keyring.gpg --export --armor 379CE192D401AB61 > gradle/verification-keyring.keys +$ gpg --keyring /tmp/keyring.gpg --export --armor 8756c4f765c9ac3cb6b85d62379ce192d401ab61 > gradle/verification-keyring.keys ---- Or, alternatively, you can _ask Gradle to export all keys it used for verification of this build to the keyring_ during bootstrapping: @@ -644,14 +646,14 @@ This is the case for example if you use < Dependency verification failed for configuration ':compileClasspath': - On artifact commons-logging-1.2.jar (commons-logging:commons-logging:1.2) in repository 'MavenRepo': checksum is missing from verification metadata. ---- -- the missing module group is `commons-logging`, it's artifact name is `commons-logging` and its version is `1.2`. The corresponding artifact is `commons-logging-1.2.jar` so you need to add the following entry to the verification file: +- the missing module group is `commons-logging`, it's artifact name is `commons-logging` and its version is `1.2`. +The corresponding artifact is `commons-logging-1.2.jar` so you need to add the following entry to the verification file: [source,xml] ---- @@ -668,7 +670,6 @@ Alternatively, you can ask Gradle to generate the missing information by using t A more problematic issue is when the actual checksum verification fails: - ---- Execution failed for task ':compileJava'. > Dependency verification failed for configuration ':compileClasspath': @@ -681,7 +682,8 @@ Such a failure indicates that a **dependency may have been compromised**. At this stage, you **must** perform manual verification and check what happens. Several things can happen: -* a dependency was tampered in the local dependency cache of Gradle. This is usually harmless: erase the file from the cache and Gradle would redownload the dependency. +* a dependency was tampered in the local dependency cache of Gradle. +This is usually harmless: erase the file from the cache and Gradle would redownload the dependency. * a dependency is available in multiple sources with slightly different binaries (additional whitespace, ...) ** please inform the maintainers of the library that they have such an issue ** you can use <<#sec:trusting-several-checksums,`also-trust`>> to accept the additional checksums @@ -695,7 +697,6 @@ Note that a variation of a compromised library is often _name squatting_, when a If you have signature verification enabled, Gradle will perform verification of the signatures but will not trust them automatically: - ---- > Dependency verification failed for configuration ':compileClasspath': - On artifact javaparser-core-3.6.11.jar (com.github.javaparser:javaparser-core:3.6.11) in repository 'MavenRepo': Artifact was signed with key '379ce192d401ab61' (Bintray (by JFrog) <****>) and passed verification but the key isn't in your trusted keys list. @@ -780,7 +781,7 @@ $ curl https://my-company-mirror.com/repo/com/google/j2objc/j2objc-annotations/1 Then we can use the key information provided in the error message to import the key locally: ---- -$ gpg --recv-keys 29579f18fa8fd93b +$ gpg --recv-keys B801E2F8EF035068EC1139CC29579F18FA8FD93B ---- And perform verification: @@ -867,14 +868,12 @@ The verification errors will be displayed during the build without causing a bui All those modes can be activated on the CLI using the `--dependency-verification` flag, for example: - ---- ./gradlew --dependency-verification lenient build ---- Alternatively, you can set the `org.gradle.dependency.verification` system property, either on the CLI: - ---- ./gradlew -Dorg.gradle.dependency.verification=lenient build ---- @@ -997,7 +996,6 @@ Gradle caches missing keys for 24 hours, meaning it will not attempt to re-downl If you want to retry immediately, you can run with the `--refresh-keys` CLI flag: - ---- ./gradlew build --refresh-keys ----