From 7b9214f0a8c3adc9f286cb01fa8d81dc0d91b2bd Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Sat, 12 Oct 2024 18:04:04 +0200 Subject: [PATCH] [MRESOLVER-599] Sigstore Artifact Generator (#573) This is similar to Gpg Artifact Generator, signs installed/deployed artifacts as "batch" at Resolver level. This, combined with "deployAtEnd" means all deployed artifacts are signed in one go, no need OIDC auth per module and 10 minute window may process more (as all artifacts are present when "deployAtend"). Still, the loop may need more, as if many artifacts being deployed, can the 10 minute window still be broken? --- https://issues.apache.org/jira/browse/MRESOLVER-599 --- maven-resolver-generator-gnupg/pom.xml | 4 - maven-resolver-generator-sigstore/pom.xml | 127 +++++++++++++ .../sigstore/SigstoreConfigurationKeys.java | 57 ++++++ .../SigstoreSignatureArtifactGenerator.java | 147 +++++++++++++++ ...toreSignatureArtifactGeneratorFactory.java | 78 ++++++++ .../sigstore/internal/FulcioOidHelper.java | 92 +++++++++ .../src/site/site.xml | 37 ++++ .../sigstore/SigstoreSignerFactoryTest.java | 143 ++++++++++++++ .../src/test/resources/artifact.txt | 1 + pom.xml | 18 ++ src/site/markdown/configuration.md | 176 +++++++++--------- 11 files changed, 789 insertions(+), 91 deletions(-) create mode 100644 maven-resolver-generator-sigstore/pom.xml create mode 100644 maven-resolver-generator-sigstore/src/main/java/org/eclipse/aether/generator/sigstore/SigstoreConfigurationKeys.java create mode 100644 maven-resolver-generator-sigstore/src/main/java/org/eclipse/aether/generator/sigstore/SigstoreSignatureArtifactGenerator.java create mode 100644 maven-resolver-generator-sigstore/src/main/java/org/eclipse/aether/generator/sigstore/SigstoreSignatureArtifactGeneratorFactory.java create mode 100644 maven-resolver-generator-sigstore/src/main/java/org/eclipse/aether/generator/sigstore/internal/FulcioOidHelper.java create mode 100644 maven-resolver-generator-sigstore/src/site/site.xml create mode 100644 maven-resolver-generator-sigstore/src/test/java/org/eclipse/aether/generator/sigstore/SigstoreSignerFactoryTest.java create mode 100644 maven-resolver-generator-sigstore/src/test/resources/artifact.txt diff --git a/maven-resolver-generator-gnupg/pom.xml b/maven-resolver-generator-gnupg/pom.xml index 9959397ab..24b0fc04d 100644 --- a/maven-resolver-generator-gnupg/pom.xml +++ b/maven-resolver-generator-gnupg/pom.xml @@ -33,7 +33,6 @@ 17 - 1.78.1 @@ -69,17 +68,14 @@ org.bouncycastle bcpg-jdk18on - ${bouncycastleVersion} org.bouncycastle bcprov-jdk18on - ${bouncycastleVersion} org.bouncycastle bcutil-jdk18on - ${bouncycastleVersion} diff --git a/maven-resolver-generator-sigstore/pom.xml b/maven-resolver-generator-sigstore/pom.xml new file mode 100644 index 000000000..3c4ea6076 --- /dev/null +++ b/maven-resolver-generator-sigstore/pom.xml @@ -0,0 +1,127 @@ + + + + 4.0.0 + + + org.apache.maven.resolver + maven-resolver + 2.0.2-SNAPSHOT + + + maven-resolver-generator-sigstore + + Maven Artifact Resolver Sigstore Signer Generator + A generator implementation for Sigstore signatures. + + + 17 + 1.0.0 + + + + + org.apache.maven.resolver + maven-resolver-api + + + org.apache.maven.resolver + maven-resolver-spi + + + org.apache.maven.resolver + maven-resolver-util + + + org.slf4j + slf4j-api + + + javax.inject + javax.inject + provided + true + + + org.eclipse.sisu + org.eclipse.sisu.inject + provided + true + + + + dev.sigstore + sigstore-java + ${sigstoreVersion} + + + + org.bouncycastle + bcpg-jdk18on + + + org.bouncycastle + bcprov-jdk18on + + + org.bouncycastle + bcutil-jdk18on + + + + org.junit.jupiter + junit-jupiter-api + test + + + org.apache.maven.resolver + maven-resolver-test-util + test + + + org.slf4j + slf4j-simple + test + + + org.mockito + mockito-core + test + + + + + + + org.apache.rat + apache-rat-plugin + + + src/test/resources/gpg-signing/** + + + + + org.eclipse.sisu + sisu-maven-plugin + + + + diff --git a/maven-resolver-generator-sigstore/src/main/java/org/eclipse/aether/generator/sigstore/SigstoreConfigurationKeys.java b/maven-resolver-generator-sigstore/src/main/java/org/eclipse/aether/generator/sigstore/SigstoreConfigurationKeys.java new file mode 100644 index 000000000..497346c1e --- /dev/null +++ b/maven-resolver-generator-sigstore/src/main/java/org/eclipse/aether/generator/sigstore/SigstoreConfigurationKeys.java @@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.eclipse.aether.generator.sigstore; + +import org.eclipse.aether.ConfigurationProperties; +import org.eclipse.aether.RepositorySystemSession; + +/** + * Configuration for Sigstore Signer. + * + * @since 2.0.2 + */ +public final class SigstoreConfigurationKeys { + private SigstoreConfigurationKeys() {} + + static final String NAME = "sigstore"; + + static final String CONFIG_PROPS_PREFIX = ConfigurationProperties.PREFIX_GENERATOR + NAME + "."; + + /** + * Whether Sigstore signer is enabled. + * + * @configurationSource {@link RepositorySystemSession#getConfigProperties()} + * @configurationType {@link Boolean} + * @configurationDefaultValue {@link #DEFAULT_ENABLED} + */ + public static final String CONFIG_PROP_ENABLED = CONFIG_PROPS_PREFIX + "enabled"; + + public static final boolean DEFAULT_ENABLED = false; + + /** + * Whether Sigstore should use public staging {@code sigstage.dev} instead of public default {@code sigstore.dev}. + * + * @configurationSource {@link RepositorySystemSession#getConfigProperties()} + * @configurationType {@link Boolean} + * @configurationDefaultValue {@link #DEFAULT_PUBLIC_STAGING} + */ + public static final String CONFIG_PROP_PUBLIC_STAGING = CONFIG_PROPS_PREFIX + "publicStaging"; + + public static final boolean DEFAULT_PUBLIC_STAGING = false; +} diff --git a/maven-resolver-generator-sigstore/src/main/java/org/eclipse/aether/generator/sigstore/SigstoreSignatureArtifactGenerator.java b/maven-resolver-generator-sigstore/src/main/java/org/eclipse/aether/generator/sigstore/SigstoreSignatureArtifactGenerator.java new file mode 100644 index 000000000..c40c02e22 --- /dev/null +++ b/maven-resolver-generator-sigstore/src/main/java/org/eclipse/aether/generator/sigstore/SigstoreSignatureArtifactGenerator.java @@ -0,0 +1,147 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.eclipse.aether.generator.sigstore; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.GeneralSecurityException; +import java.security.cert.X509Certificate; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.function.Predicate; + +import dev.sigstore.KeylessSigner; +import dev.sigstore.KeylessSignerException; +import dev.sigstore.bundle.Bundle; +import dev.sigstore.encryption.certificates.Certificates; +import org.eclipse.aether.artifact.Artifact; +import org.eclipse.aether.generator.sigstore.internal.FulcioOidHelper; +import org.eclipse.aether.spi.artifact.generator.ArtifactGenerator; +import org.eclipse.aether.util.FileUtils; +import org.eclipse.aether.util.artifact.SubArtifact; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +final class SigstoreSignatureArtifactGenerator implements ArtifactGenerator { + private static final String ARTIFACT_EXTENSION = ".sigstore.json"; + private final Logger logger = LoggerFactory.getLogger(getClass()); + private final ArrayList artifacts; + private final Predicate signableArtifactPredicate; + private final boolean publicStaging; + private final ArrayList signatureTempFiles; + + SigstoreSignatureArtifactGenerator( + Collection artifacts, Predicate signableArtifactPredicate, boolean publicStaging) { + this.artifacts = new ArrayList<>(artifacts); + this.signableArtifactPredicate = signableArtifactPredicate; + this.publicStaging = publicStaging; + this.signatureTempFiles = new ArrayList<>(); + logger.debug("Created sigstore generator (publicStaging={})", publicStaging); + } + + @Override + public String generatorId() { + return SigstoreSignatureArtifactGeneratorFactory.NAME; + } + + @Override + public Collection generate(Collection generatedArtifacts) { + try { + artifacts.addAll(generatedArtifacts); + + // back out if Sigstore signatures found among artifacts + if (artifacts.stream().anyMatch(a -> a.getExtension().endsWith(ARTIFACT_EXTENSION))) { + logger.debug("Sigstore signatures are present among artifacts, bailing out"); + return Collections.emptyList(); + } + + // sign relevant artifacts + ArrayList result = new ArrayList<>(); + try (KeylessSigner signer = publicStaging + ? KeylessSigner.builder().sigstoreStagingDefaults().build() + : KeylessSigner.builder().sigstorePublicDefaults().build()) { + for (Artifact artifact : artifacts) { + if (signableArtifactPredicate.test(artifact)) { + Path fileToSign = artifact.getPath(); + Path signatureTempFile = Files.createTempFile("signer-sigstore", "tmp"); + signatureTempFiles.add(signatureTempFile); + + logger.debug("Signing " + artifact); + long start = System.currentTimeMillis(); + Bundle bundle = signer.signFile(fileToSign); + + X509Certificate cert = (X509Certificate) + bundle.getCertPath().getCertificates().get(0); + long durationMinutes = Certificates.validity(cert, ChronoUnit.MINUTES); + + logger.debug(" Fulcio certificate (valid for " + + durationMinutes + + " m) obtained for " + + cert.getSubjectAlternativeNames() + .iterator() + .next() + .get(1) + + " (by " + + FulcioOidHelper.getIssuerV2(cert) + + " IdP)"); + + FileUtils.writeFile(signatureTempFile, p -> Files.writeString(p, bundle.toJson())); + + long duration = System.currentTimeMillis() - start; + logger.debug(" > Rekor entry " + + bundle.getEntries().get(0).getLogIndex() + + " obtained in " + + duration + + " ms, saved to " + + signatureTempFile); + + result.add(new SubArtifact( + artifact, + artifact.getClassifier(), + artifact.getExtension() + ARTIFACT_EXTENSION, + signatureTempFile.toFile())); + } + } + } + logger.info("Signed {} artifacts with Sigstore", result.size()); + return result; + } catch (GeneralSecurityException e) { + throw new IllegalArgumentException("Preparation problem", e); + } catch (KeylessSignerException e) { + throw new IllegalStateException("Processing problem", e); + } catch (IOException e) { + throw new UncheckedIOException("IO problem", e); + } + } + + @Override + public void close() { + signatureTempFiles.forEach(p -> { + try { + Files.deleteIfExists(p); + } catch (IOException e) { + p.toFile().deleteOnExit(); + } + }); + } +} diff --git a/maven-resolver-generator-sigstore/src/main/java/org/eclipse/aether/generator/sigstore/SigstoreSignatureArtifactGeneratorFactory.java b/maven-resolver-generator-sigstore/src/main/java/org/eclipse/aether/generator/sigstore/SigstoreSignatureArtifactGeneratorFactory.java new file mode 100644 index 000000000..c512eeb48 --- /dev/null +++ b/maven-resolver-generator-sigstore/src/main/java/org/eclipse/aether/generator/sigstore/SigstoreSignatureArtifactGeneratorFactory.java @@ -0,0 +1,78 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.eclipse.aether.generator.sigstore; + +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Singleton; + +import java.util.Collection; + +import org.eclipse.aether.RepositorySystemSession; +import org.eclipse.aether.artifact.Artifact; +import org.eclipse.aether.deployment.DeployRequest; +import org.eclipse.aether.installation.InstallRequest; +import org.eclipse.aether.spi.artifact.ArtifactPredicateFactory; +import org.eclipse.aether.spi.artifact.generator.ArtifactGenerator; +import org.eclipse.aether.spi.artifact.generator.ArtifactGeneratorFactory; +import org.eclipse.aether.util.ConfigUtils; + +@Singleton +@Named(SigstoreSignatureArtifactGeneratorFactory.NAME) +public final class SigstoreSignatureArtifactGeneratorFactory implements ArtifactGeneratorFactory { + + public static final String NAME = "sigstore"; + + private final ArtifactPredicateFactory artifactPredicateFactory; + + @Inject + public SigstoreSignatureArtifactGeneratorFactory(ArtifactPredicateFactory artifactPredicateFactory) { + this.artifactPredicateFactory = artifactPredicateFactory; + } + + @Override + public ArtifactGenerator newInstance(RepositorySystemSession session, InstallRequest request) { + return null; // nothing on install + } + + @Override + public ArtifactGenerator newInstance(RepositorySystemSession session, DeployRequest request) { + return newInstance(session, request.getArtifacts()); // generate on deploy + } + + private ArtifactGenerator newInstance(RepositorySystemSession session, Collection artifacts) { + final boolean enabled = ConfigUtils.getBoolean( + session, SigstoreConfigurationKeys.DEFAULT_ENABLED, SigstoreConfigurationKeys.CONFIG_PROP_ENABLED); + if (!enabled) { + return null; + } + final boolean publicStaging = ConfigUtils.getBoolean( + session, + SigstoreConfigurationKeys.DEFAULT_PUBLIC_STAGING, + SigstoreConfigurationKeys.CONFIG_PROP_PUBLIC_STAGING); + + return new SigstoreSignatureArtifactGenerator( + artifacts, artifactPredicateFactory.newInstance(session)::hasChecksums, publicStaging); + } + + @Override + public float getPriority() { + return 150; + } +} diff --git a/maven-resolver-generator-sigstore/src/main/java/org/eclipse/aether/generator/sigstore/internal/FulcioOidHelper.java b/maven-resolver-generator-sigstore/src/main/java/org/eclipse/aether/generator/sigstore/internal/FulcioOidHelper.java new file mode 100644 index 000000000..84feefd63 --- /dev/null +++ b/maven-resolver-generator-sigstore/src/main/java/org/eclipse/aether/generator/sigstore/internal/FulcioOidHelper.java @@ -0,0 +1,92 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.eclipse.aether.generator.sigstore.internal; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.security.cert.X509Certificate; + +import org.bouncycastle.asn1.ASN1Primitive; +import org.bouncycastle.asn1.ASN1Sequence; +import org.bouncycastle.asn1.ASN1String; +import org.bouncycastle.asn1.DEROctetString; + +/** + * Helper to decode Fulcio OID data, see Sigstore OID + * information. + */ +public class FulcioOidHelper { + private static final String SIGSTORE_OID_ROOT = "1.3.6.1.4.1.57264"; + private static final String FULCIO_OID_ROOT = SIGSTORE_OID_ROOT + ".1"; + + @Deprecated + private static final String FULCIO_ISSUER_OID = FULCIO_OID_ROOT + ".1"; + + private static final String FULCIO_ISSUER_V2_OID = FULCIO_OID_ROOT + ".8"; + + public static String getIssuer(X509Certificate cert) { + String issuerV2 = getIssuerV2(cert); + if (issuerV2 == null) { + return getIssuerV1(cert); + } + return issuerV2; + } + + @Deprecated + public static String getIssuerV1(X509Certificate cert) { + return getExtensionValue(cert, FULCIO_ISSUER_OID, true); + } + + public static String getIssuerV2(X509Certificate cert) { + return getExtensionValue(cert, FULCIO_ISSUER_V2_OID, false); + } + + /* Extracts the octets from an extension value and converts to utf-8 directly, it does NOT + * account for any ASN1 encoded value. If the extension value is an ASN1 object (like an + * ASN1 encoded string), you need to write a new extraction helper. */ + private static String getExtensionValue(X509Certificate cert, String oid, boolean rawUtf8) { + byte[] extensionValue = cert.getExtensionValue(oid); + + if (extensionValue == null) { + return null; + } + try { + ASN1Primitive derObject = ASN1Sequence.fromByteArray(cert.getExtensionValue(oid)); + if (derObject instanceof DEROctetString) { + DEROctetString derOctetString = (DEROctetString) derObject; + if (rawUtf8) { + // this is unusual, but the octet is a raw utf8 string in fulcio land (no prefix of type) + // and not an ASN1 object. + return new String(derOctetString.getOctets(), StandardCharsets.UTF_8); + } + + derObject = ASN1Sequence.fromByteArray(derOctetString.getOctets()); + if (derObject instanceof ASN1String) { + ASN1String s = (ASN1String) derObject; + return s.getString(); + } + } + throw new RuntimeException( + "Could not parse extension " + oid + " in certificate because it was not an octet string"); + } catch (IOException ioe) { + throw new RuntimeException("Could not parse extension " + oid + " in certificate", ioe); + } + } +} diff --git a/maven-resolver-generator-sigstore/src/site/site.xml b/maven-resolver-generator-sigstore/src/site/site.xml new file mode 100644 index 000000000..e19e490a4 --- /dev/null +++ b/maven-resolver-generator-sigstore/src/site/site.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/maven-resolver-generator-sigstore/src/test/java/org/eclipse/aether/generator/sigstore/SigstoreSignerFactoryTest.java b/maven-resolver-generator-sigstore/src/test/java/org/eclipse/aether/generator/sigstore/SigstoreSignerFactoryTest.java new file mode 100644 index 000000000..b8f6badac --- /dev/null +++ b/maven-resolver-generator-sigstore/src/test/java/org/eclipse/aether/generator/sigstore/SigstoreSignerFactoryTest.java @@ -0,0 +1,143 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.eclipse.aether.generator.sigstore; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; + +import org.eclipse.aether.DefaultRepositorySystemSession; +import org.eclipse.aether.RepositorySystemSession; +import org.eclipse.aether.artifact.Artifact; +import org.eclipse.aether.artifact.DefaultArtifact; +import org.eclipse.aether.deployment.DeployRequest; +import org.eclipse.aether.internal.test.util.TestUtils; +import org.eclipse.aether.spi.artifact.ArtifactPredicate; +import org.eclipse.aether.spi.artifact.ArtifactPredicateFactory; +import org.eclipse.aether.spi.artifact.generator.ArtifactGenerator; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class SigstoreSignerFactoryTest { + private SigstoreSignatureArtifactGeneratorFactory createFactory() throws Exception { + Files.createDirectories(Paths.get(System.getProperty("java.io.tmpdir"))); // hack for Surefire + + ArtifactPredicate artifactPredicate = mock(ArtifactPredicate.class); + when(artifactPredicate.hasChecksums(any(Artifact.class))).thenAnswer(a -> { + Artifact artifact = (Artifact) a.getArguments()[0]; + return artifact != null + && !artifact.getExtension().endsWith(".sha1") + && !artifact.getExtension().endsWith(".md5"); + }); + ArtifactPredicateFactory artifactPredicateFactory = mock(ArtifactPredicateFactory.class); + when(artifactPredicateFactory.newInstance(any(RepositorySystemSession.class))) + .thenReturn(artifactPredicate); + + return new SigstoreSignatureArtifactGeneratorFactory(artifactPredicateFactory); + } + + private RepositorySystemSession createSession() { + DefaultRepositorySystemSession session = TestUtils.newSession(); + session.setConfigProperty(SigstoreConfigurationKeys.CONFIG_PROP_ENABLED, Boolean.TRUE); + return session; + } + + @Test + void doNotSignNonRelevantArtifacts() throws Exception { + SigstoreSignatureArtifactGeneratorFactory factory = createFactory(); + try (ArtifactGenerator signer = factory.newInstance(createSession(), new DeployRequest())) { + assertNotNull(signer); + + Collection signatures = signer.generate(Arrays.asList( + new DefaultArtifact("org.apache.maven.resolver:test:jar.sha1:1.0"), + new DefaultArtifact("org.apache.maven.resolver:test:jar.md5:1.0"))); + + assertEquals(0, signatures.size()); + } + } + + @Disabled("needs login! Remove this and run it to try out") + @Test + void doSignAllRelevantArtifacts() throws Exception { + SigstoreSignatureArtifactGeneratorFactory factory = createFactory(); + try (ArtifactGenerator signer = factory.newInstance(createSession(), new DeployRequest())) { + assertNotNull(signer); + Path irrelevant = Paths.get("src/test/resources/artifact.txt"); + + Collection signatures = signer.generate(Arrays.asList( + new DefaultArtifact("org.apache.maven.resolver:test:jar:1.0").setPath(irrelevant), + new DefaultArtifact("org.apache.maven.resolver:test:jar.sha1:1.0").setPath(irrelevant), + new DefaultArtifact("org.apache.maven.resolver:test:jar:source:1.0").setPath(irrelevant), + new DefaultArtifact("org.apache.maven.resolver:test:jar.md5:source:1.0").setPath(irrelevant), + new DefaultArtifact("org.apache.maven.resolver:test:foo:1.0").setPath(irrelevant))); + + assertEquals(3, signatures.size()); + + assertTrue(signatures.stream() + .anyMatch(a -> "".equals(a.getClassifier()) && "jar.asc".equals(a.getExtension()))); + assertTrue(signatures.stream() + .anyMatch(a -> "source".equals(a.getClassifier()) && "jar.asc".equals(a.getExtension()))); + assertTrue(signatures.stream() + .anyMatch(a -> "".equals(a.getClassifier()) && "foo.asc".equals(a.getExtension()))); + } + } + + @Test + void doNotSignIfSignatureAlreadyPresent() throws Exception { + SigstoreSignatureArtifactGeneratorFactory factory = createFactory(); + try (ArtifactGenerator signer = factory.newInstance(createSession(), new DeployRequest())) { + assertNotNull(signer); + + Collection signatures = signer.generate(Arrays.asList( + new DefaultArtifact("org.apache.maven.resolver:test:jar:1.0"), + new DefaultArtifact("org.apache.maven.resolver:test:jar.sigstore.json:1.0"))); + + assertEquals(0, signatures.size()); + } + } + + @Disabled("needs login! Remove this and run it to try out") + @Test + void doSign() throws Exception { + SigstoreSignatureArtifactGeneratorFactory factory = createFactory(); + try (ArtifactGenerator signer = factory.newInstance(createSession(), new DeployRequest())) { + assertNotNull(signer); + Path artifactPath = Paths.get("src/test/resources/artifact.txt"); + Collection signatures = signer.generate(Collections.singleton( + new DefaultArtifact("org.apache.maven.resolver:test:1.0").setPath(artifactPath))); + + // one signature expected for one relevant artifact + assertEquals(1, signatures.size()); + Path signaturePath = signatures.iterator().next().getPath(); + + // cannot assert file size due OS differences, so just count the lines instead: those should be same + assertEquals(8, Files.lines(signaturePath).count()); + + // TODO: validate the signature + } + } +} diff --git a/maven-resolver-generator-sigstore/src/test/resources/artifact.txt b/maven-resolver-generator-sigstore/src/test/resources/artifact.txt new file mode 100644 index 000000000..a4f7cbd4e --- /dev/null +++ b/maven-resolver-generator-sigstore/src/test/resources/artifact.txt @@ -0,0 +1 @@ +This is the content diff --git a/pom.xml b/pom.xml index b9f94606e..3c3b90f9b 100644 --- a/pom.xml +++ b/pom.xml @@ -64,6 +64,7 @@ maven-resolver-transport-wagon maven-resolver-transport-minio maven-resolver-generator-gnupg + maven-resolver-generator-sigstore maven-resolver-supplier-mvn3 maven-resolver-supplier-mvn4 maven-resolver-demos @@ -104,6 +105,7 @@ 6.0.0 2.0.16 1.20.2 + 1.78.1 5.11.2 @@ -296,6 +298,22 @@ 1.3.0 + + org.bouncycastle + bcpg-jdk18on + ${bouncycastleVersion} + + + org.bouncycastle + bcprov-jdk18on + ${bouncycastleVersion} + + + org.bouncycastle + bcutil-jdk18on + ${bouncycastleVersion} + + com.google.code.gson gson diff --git a/src/site/markdown/configuration.md b/src/site/markdown/configuration.md index 6c8f65eaa..48abf66df 100644 --- a/src/site/markdown/configuration.md +++ b/src/site/markdown/configuration.md @@ -58,93 +58,95 @@ under the License. | 31. | `"aether.generator.gpg.keyFilePath"` | `String` | The path to the OpenPGP transferable secret key file. If relative, is resolved from local repository root. | `"maven-signing-key.key"` | 2.0.0 | No | Session Configuration | | 32. | `"aether.generator.gpg.keyFingerprint"` | `String` | The PGP Key fingerprint as hex string (40 characters long), optional. If not set, first secret key found will be used. | - | 2.0.0 | No | Session Configuration | | 33. | `"aether.generator.gpg.useAgent"` | `Boolean` | Whether GnuPG agent should be used. | `true` | 2.0.0 | No | Session Configuration | -| 34. | `"aether.interactive"` | `Boolean` | A flag indicating whether interaction with the user is allowed. | `false` | | No | Session Configuration | -| 35. | `"aether.layout.maven2.checksumAlgorithms"` | `String` | Comma-separated list of checksum algorithms with which checksums are validated (downloaded) and generated (uploaded) with this layout. Resolver by default supports following algorithms: MD5, SHA-1, SHA-256 and SHA-512. New algorithms can be added by implementing ChecksumAlgorithmFactory component. | `"SHA-1,MD5"` | 1.8.0 | Yes | Session Configuration | -| 36. | `"aether.lrm.enhanced.localPrefix"` | `String` | The prefix to use for locally installed artifacts. | `"installed"` | 1.8.1 | No | Session Configuration | -| 37. | `"aether.lrm.enhanced.releasesPrefix"` | `String` | The prefix to use for release artifacts. | `"releases"` | 1.8.1 | No | Session Configuration | -| 38. | `"aether.lrm.enhanced.remotePrefix"` | `String` | The prefix to use for remotely cached artifacts. | `"cached"` | 1.8.1 | No | Session Configuration | -| 39. | `"aether.lrm.enhanced.snapshotsPrefix"` | `String` | The prefix to use for snapshot artifacts. | `"snapshots"` | 1.8.1 | No | Session Configuration | -| 40. | `"aether.lrm.enhanced.split"` | `Boolean` | Whether LRM should split local and remote artifacts. | `false` | 1.8.1 | No | Session Configuration | -| 41. | `"aether.lrm.enhanced.splitLocal"` | `Boolean` | Whether locally installed artifacts should be split by version (release/snapshot). | `false` | 1.8.1 | No | Session Configuration | -| 42. | `"aether.lrm.enhanced.splitRemote"` | `Boolean` | Whether cached artifacts should be split by version (release/snapshot). | `false` | 1.8.1 | No | Session Configuration | -| 43. | `"aether.lrm.enhanced.splitRemoteRepository"` | `Boolean` | Whether cached artifacts should be split by origin repository (repository ID). | `false` | 1.8.1 | No | Session Configuration | -| 44. | `"aether.lrm.enhanced.splitRemoteRepositoryLast"` | `Boolean` | For cached artifacts, if both splitRemote and splitRemoteRepository are set to true sets the splitting order: by default it is repositoryId/version (false) or version/repositoryId (true) | `false` | 1.8.1 | No | Session Configuration | -| 45. | `"aether.lrm.enhanced.trackingFilename"` | `String` | Filename of the file in which to track the remote repositories. | `"_remote.repositories"` | | No | Session Configuration | -| 46. | `"aether.metadataResolver.threads"` | `Integer` | Number of threads to use in parallel for resolving metadata. | `4` | 0.9.0.M4 | No | Session Configuration | -| 47. | `"aether.named.diagnostic.enabled"` | `Boolean` | System property key to enable locking diagnostic collection. | `false` | 1.9.11 | No | Java System Properties | -| 48. | `"aether.named.file-lock.attempts"` | `Integer` | Tweak: on Windows, the presence of StandardOpenOption#DELETE_ON_CLOSE causes concurrency issues. This flag allows to implement similar fix as referenced JDK bug report: retry and hope the best. Default value is 5 attempts (will retry 4 times). | `5` | 1.7.3 | No | Java System Properties | -| 49. | `"aether.named.file-lock.deleteLockFiles"` | `Boolean` | Tweak: on Windows, the presence of StandardOpenOption#DELETE_ON_CLOSE causes concurrency issues. This flag allows to have it removed from effective flags, at the cost that lockfile directory becomes crowded with 0 byte sized lock files that are never cleaned up. Default value is on non-Windows OS. See JDK-8252883 for Windows related bug. Users on Windows can still force "delete on close" by explicitly setting this property to . | `true` | 1.7.3 | No | Java System Properties | -| 50. | `"aether.named.file-lock.sleepMillis"` | `Long` | Tweak: When used, the amount of milliseconds to sleep between subsequent retries. Default value is 50 milliseconds. | `50` | 1.7.3 | No | Java System Properties | -| 51. | `"aether.named.ipc.debug"` | `Boolean` | Should the IPC server log debug messages? (i.e. for testing purposes) | `false` | 2.0.1 | No | Java System Properties | -| 52. | `"aether.named.ipc.family"` | `String` | IPC socket family to use. | `"unix"` | 2.0.1 | No | Java System Properties | -| 53. | `"aether.named.ipc.idleTimeout"` | `Integer` | IPC idle timeout in seconds. If there is no IPC request during idle time, it will stop. | `60` | 2.0.1 | No | Java System Properties | -| 54. | `"aether.named.ipc.nativeName"` | `String` | The name if the IPC server native executable (without file extension like ".exe") | `"ipc-sync"` | 2.0.1 | No | Java System Properties | -| 55. | `"aether.named.ipc.nofork"` | `Boolean` | Should the IPC server not fork? (i.e. for testing purposes) | `false` | 2.0.1 | No | Java System Properties | -| 56. | `"aether.named.ipc.nonative"` | `Boolean` | Should the IPC server not use native executable? | `true` | 2.0.1 | No | Java System Properties | -| 57. | `"aether.offline.hosts"` | `String` | Comma-separated list of hosts which are supposed to be resolved offline. | - | | No | Session Configuration | -| 58. | `"aether.offline.protocols"` | `String` | Comma-separated list of protocols which are supposed to be resolved offline. | - | | No | Session Configuration | -| 59. | `"aether.priority.cached"` | `Boolean` | A flag indicating whether the created ordered components should be cached in session. | `true` | 2.0.0 | No | Session Configuration | -| 60. | `"aether.priority.implicit"` | `Boolean` | A flag indicating whether the priorities of pluggable extensions are implicitly given by their iteration order such that the first extension has the highest priority. If set, an extension's built-in priority as well as any corresponding configuration properties are ignored when searching for a suitable implementation among the available extensions. This priority mode is meant for cases where the application will present/inject extensions in the desired search order. | `false` | | No | Session Configuration | -| 61. | `"aether.remoteRepositoryFilter.groupId"` | `Boolean` | Is filter enabled? | `false` | 1.9.0 | No | Session Configuration | -| 62. | `"aether.remoteRepositoryFilter.groupId.basedir"` | `String` | The basedir where to store filter files. If path is relative, it is resolved from local repository root. | `".remoteRepositoryFilters"` | 1.9.0 | No | Session Configuration | -| 63. | `"aether.remoteRepositoryFilter.groupId.record"` | `Boolean` | Should filter go into "record" mode (and collect encountered artifacts)? | `false` | 1.9.0 | No | Session Configuration | -| 64. | `"aether.remoteRepositoryFilter.prefixes"` | `Boolean` | Is filter enabled? | `false` | 1.9.0 | No | Session Configuration | -| 65. | `"aether.remoteRepositoryFilter.prefixes.basedir"` | `String` | The basedir where to store filter files. If path is relative, it is resolved from local repository root. | `".remoteRepositoryFilters"` | 1.9.0 | No | Session Configuration | -| 66. | `"aether.snapshotFilter"` | `Boolean` | The key in the repository session's used to store a flag whether this filter should be forced to ban snapshots. By default, snapshots are only filtered if the root artifact is not a snapshot. | `false` | | No | Session Configuration | -| 67. | `"aether.syncContext.named.basedir.locksDir"` | `String` | The location of the directory toi use for locks. If relative path, it is resolved from the local repository root. | `".locks"` | 1.9.0 | No | Session Configuration | -| 68. | `"aether.syncContext.named.discriminating.discriminator"` | `String` | Configuration property to pass in discriminator, if needed. If not present, it is auto-calculated. | - | 1.7.0 | No | Session Configuration | -| 69. | `"aether.syncContext.named.discriminating.hostname"` | `String` | Configuration property to pass in hostname, if needed. If not present, hostname as reported by system will be used. | - | 1.7.0 | No | Session Configuration | -| 70. | `"aether.syncContext.named.factory"` | `String` | Name of the lock factory to use in session. | `"file-lock"` | 1.9.1 | No | Session Configuration | -| 71. | `"aether.syncContext.named.hashing.depth"` | `Integer` | The depth how many levels should adapter create. Acceptable values are 0-4 (inclusive). | `2` | 1.9.0 | No | Session Configuration | -| 72. | `"aether.syncContext.named.nameMapper"` | `String` | Name of the name mapper to use in session. Out of the box supported ones are "static", "gav", "file-gav", "file-hgav", "file-static" and "discriminating". | `"file-gav"` | 1.9.1 | No | Session Configuration | -| 73. | `"aether.syncContext.named.redisson.address"` | `String` | Address of the Redis instance. Optional. | `"redis://localhost:6379"` | 2.0.0 | No | Java System Properties | -| 74. | `"aether.syncContext.named.redisson.configFile"` | `String` | Path to a Redisson configuration file in YAML format. Read official documentation for details. | - | 1.7.0 | No | Java System Properties | -| 75. | `"aether.syncContext.named.retry"` | `Integer` | The amount of retries on time-out. | `1` | 1.7.0 | No | Session Configuration | -| 76. | `"aether.syncContext.named.retry.wait"` | `Long` | The amount of milliseconds to wait between retries on time-out. | `200l` | 1.7.0 | No | Session Configuration | -| 77. | `"aether.syncContext.named.time"` | `Long` | The maximum of time amount to be blocked to obtain lock. | `30l` | 1.7.0 | No | Session Configuration | -| 78. | `"aether.syncContext.named.time.unit"` | `String` | The unit of maximum time amount to be blocked to obtain lock. Use TimeUnit enum names. | `"SECONDS"` | 1.7.0 | No | Session Configuration | -| 79. | `"aether.system.dependencyVisitor"` | `String` | A flag indicating which visitor should be used to "flatten" the dependency graph into list. Default is same as in older resolver versions "preOrder", while it can accept values like "postOrder" and "levelOrder". | `"preOrder"` | 2.0.0 | No | Session Configuration | -| 80. | `"aether.transport.apache.https.cipherSuites"` | `String` | Comma-separated list of Cipher Suites which are enabled for HTTPS connections. | - | 2.0.0 | No | Session Configuration | -| 81. | `"aether.transport.apache.https.protocols"` | `String` | Comma-separated list of Protocols which are enabled for HTTPS connections. | - | 2.0.0 | No | Session Configuration | -| 82. | `"aether.transport.apache.retryHandler.name"` | `String` | The name of retryHandler, supported values are “standard”, that obeys RFC-2616, regarding idempotent methods, and “default” that considers requests w/o payload as idempotent. | `"standard"` | 2.0.0 | Yes | Session Configuration | -| 83. | `"aether.transport.apache.retryHandler.requestSentEnabled"` | `Boolean` | Set to true if it is acceptable to retry non-idempotent requests, that have been sent. | `false` | 2.0.0 | Yes | Session Configuration | -| 84. | `"aether.transport.apache.useSystemProperties"` | `Boolean` | If enabled, underlying Apache HttpClient will use system properties as well to configure itself (typically used to set up HTTP Proxy via Java system properties). See HttpClientBuilder for used properties. This mode is not recommended, better use documented ways of configuration instead. | `false` | 2.0.0 | Yes | Session Configuration | -| 85. | `"aether.transport.classpath.loader"` | `ClassLoader` | The key in the repository session's used to store a from which resources should be retrieved. If unspecified, the of the current thread will be used. | - | | No | Session Configuration | -| 86. | `"aether.transport.http.connectTimeout"` | `Integer` | The maximum amount of time (in milliseconds) to wait for a successful connection to a remote server. Non-positive values indicate no timeout. | `10000` | | Yes | Session Configuration | -| 87. | `"aether.transport.http.connectionMaxTtl"` | `Integer` | Total time to live in seconds for an HTTP connection, after that time, the connection will be dropped (no matter for how long it was idle). | `300` | 1.9.8 | Yes | Session Configuration | -| 88. | `"aether.transport.http.credentialEncoding"` | `String` | The encoding/charset to use when exchanging credentials with HTTP servers. Besides this general key, clients may also specify the encoding for a specific remote repository by appending the suffix to this key when storing the charset name. | `"ISO-8859-1"` | | Yes | Session Configuration | -| 89. | `"aether.transport.http.expectContinue"` | `Boolean` | Boolean flag should the HTTP transport use expect-continue handshake for PUT requests. Not all transport support this option. This option may be needed for some broken HTTP servers. Default value corresponds to given transport default one (resolver does not override those), but if configuration IS given, it will replace given transport own default value. | - | 1.9.17 | Yes | Session Configuration | -| 90. | `"aether.transport.http.headers"` | `java.util.Map` | The request headers to use for HTTP-based repository connectors. The headers are specified using a , mapping a header name to its value. Besides this general key, clients may also specify headers for a specific remote repository by appending the suffix to this key when storing the headers map. The repository-specific headers map is supposed to be complete, i.e. is not merged with the general headers map. | - | | Yes | Session Configuration | -| 91. | `"aether.transport.http.localAddress"` | `String` | The local address (interface) to use with HTTP transport. Not all transport supports this option. | - | 2.0.0 | Yes | Session Configuration | -| 92. | `"aether.transport.http.maxConnectionsPerRoute"` | `Integer` | The maximum concurrent connections per route HTTP client is allowed to use. | `50` | 1.9.8 | Yes | Session Configuration | -| 93. | `"aether.transport.http.preemptiveAuth"` | `Boolean` | Should HTTP client use preemptive-authentication for all HTTP verbs (works only w/ BASIC). By default, is disabled, as it is considered less secure. | `false` | 1.9.6 | Yes | Session Configuration | -| 94. | `"aether.transport.http.preemptivePutAuth"` | `Boolean` | Boolean flag should the HTTP transport use preemptive-auth for PUT requests. Not all transport support this option. | `true` | 2.0.0 (moved out from maven-resolver-transport-http). | Yes | Session Configuration | -| 95. | `"aether.transport.http.requestTimeout"` | `Integer` | The maximum amount of time (in milliseconds) to wait for remaining data to arrive from a remote server. Note that this timeout does not restrict the overall duration of a request, it only restricts the duration of inactivity between consecutive data packets. Non-positive values indicate no timeout. | `1800000` | | Yes | Session Configuration | -| 96. | `"aether.transport.http.retryHandler.count"` | `Integer` | The maximum number of times a request to a remote server should be retried in case of an error. | `3` | 1.9.6 | Yes | Session Configuration | -| 97. | `"aether.transport.http.retryHandler.interval"` | `Long` | The initial retry interval in millis of request to a remote server should be waited in case of "too many requests" (HTTP codes 429 and 503). Accepts long as milliseconds. This value is used if remote server does not use header, in which case Server value is obeyed. | `5000l` | 1.9.16 | Yes | Session Configuration | -| 98. | `"aether.transport.http.retryHandler.intervalMax"` | `Long` | The maximum retry interval in millis of request to a remote server above which the request should be aborted instead. In theory, a malicious server could tell Maven "come back after 100 years" that would stall the build for some. Using this parameter Maven will fail the request instead, if interval is above this value. | `300000l` | 1.9.16 | Yes | Session Configuration | -| 99. | `"aether.transport.http.retryHandler.serviceUnavailable"` | `String` | The HTTP codes of remote server responses that should be handled as "too many requests" (examples: HTTP codes 429 and 503). Accepts comma separated list of HTTP response codes. | `"429,503"` | 1.9.16 | Yes | Session Configuration | -| 100. | `"aether.transport.http.reuseConnections"` | `Boolean` | Should HTTP client reuse connections (in other words, pool connections) or not? | `true` | 1.9.8 | Yes | Session Configuration | -| 101. | `"aether.transport.http.supportWebDav"` | `Boolean` | Boolean flag should the HTTP transport support WebDAV remote. Not all transport support this option. | `false` | 2.0.0 (moved out from maven-resolver-transport-http). | Yes | Session Configuration | -| 102. | `"aether.transport.http.userAgent"` | `String` | The user agent that repository connectors should report to servers. | `"Aether"` | | No | Session Configuration | -| 103. | `"aether.transport.https.securityMode"` | `String` | The mode that sets HTTPS transport "security mode": to ignore any SSL errors (certificate validity checks, hostname verification). The default value is . | `"default"` | 1.9.6 | Yes | Session Configuration | -| 104. | `"aether.transport.jdk.httpVersion"` | `String` | Use string representation of HttpClient version enum "HTTP_2" or "HTTP_1_1" to set default HTTP protocol to use. | `"HTTP_2"` | 2.0.0 | Yes | Session Configuration | -| 105. | `"aether.transport.jdk.maxConcurrentRequests"` | `Integer` | The hard limit of maximum concurrent requests JDK transport can do. This is a workaround for the fact, that in HTTP/2 mode, JDK HttpClient initializes this value to Integer.MAX_VALUE (!) and lowers it on first response from the remote server (but it may be too late). See JDK bug JDK-8225647 for details. | `100` | 2.0.0 | Yes | Session Configuration | -| 106. | `"aether.transport.jetty.followRedirects"` | `Boolean` | If enabled, Jetty client will follow HTTP redirects. | `true` | 2.0.1 | Yes | Session Configuration | -| 107. | `"aether.transport.jetty.maxRedirects"` | `Integer` | The max redirect count to follow. | `5` | 2.0.1 | Yes | Session Configuration | -| 108. | `"aether.transport.minio.fixedBucketName"` | `String` | The fixed bucket name to use. | `"maven"` | 2.0.2 | Yes | Session Configuration | -| 109. | `"aether.transport.minio.objectNameMapper"` | `String` | Object name mapper to use. | `"fixedBucket"` | 2.0.2 | Yes | Session Configuration | -| 110. | `"aether.transport.wagon.config"` | `Object` | The configuration to use for the Wagon provider. | - | | Yes | Session Configuration | -| 111. | `"aether.transport.wagon.perms.dirMode"` | `String` | Octal numerical notation of permissions to set for newly created directories. Only considered by certain Wagon providers. | - | | Yes | Session Configuration | -| 112. | `"aether.transport.wagon.perms.fileMode"` | `String` | Octal numerical notation of permissions to set for newly created files. Only considered by certain Wagon providers. | - | | Yes | Session Configuration | -| 113. | `"aether.transport.wagon.perms.group"` | `String` | Group which should own newly created directories/files. Only considered by certain Wagon providers. | - | | Yes | Session Configuration | -| 114. | `"aether.trustedChecksumsSource.sparseDirectory"` | `Boolean` | Is checksum source enabled? | `false` | 1.9.0 | No | Session Configuration | -| 115. | `"aether.trustedChecksumsSource.sparseDirectory.basedir"` | `String` | The basedir where checksums are. If relative, is resolved from local repository root. | `".checksums"` | 1.9.0 | No | Session Configuration | -| 116. | `"aether.trustedChecksumsSource.sparseDirectory.originAware"` | `Boolean` | Is source origin aware? | `true` | 1.9.0 | No | Session Configuration | -| 117. | `"aether.trustedChecksumsSource.summaryFile"` | `Boolean` | Is checksum source enabled? | `false` | 1.9.0 | No | Session Configuration | -| 118. | `"aether.trustedChecksumsSource.summaryFile.basedir"` | `String` | The basedir where checksums are. If relative, is resolved from local repository root. | `".checksums"` | 1.9.0 | No | Session Configuration | -| 119. | `"aether.trustedChecksumsSource.summaryFile.originAware"` | `Boolean` | Is source origin aware? | `true` | 1.9.0 | No | Session Configuration | -| 120. | `"aether.updateCheckManager.sessionState"` | `String` | Manages the session state, i.e. influences if the same download requests to artifacts/metadata will happen multiple times within the same RepositorySystemSession. If "enabled" will enable the session state. If "bypass" will enable bypassing (i.e. store all artifact ids/metadata ids which have been updates but not evaluating those). All other values lead to disabling the session state completely. | `"enabled"` | | No | Session Configuration | +| 34. | `"aether.generator.sigstore.enabled"` | `Boolean` | Whether Sigstore signer is enabled. | `false` | 2.0.2 | No | Session Configuration | +| 35. | `"aether.generator.sigstore.publicStaging"` | `Boolean` | Whether Sigstore should use public staging instead of public default . | `false` | 2.0.2 | No | Session Configuration | +| 36. | `"aether.interactive"` | `Boolean` | A flag indicating whether interaction with the user is allowed. | `false` | | No | Session Configuration | +| 37. | `"aether.layout.maven2.checksumAlgorithms"` | `String` | Comma-separated list of checksum algorithms with which checksums are validated (downloaded) and generated (uploaded) with this layout. Resolver by default supports following algorithms: MD5, SHA-1, SHA-256 and SHA-512. New algorithms can be added by implementing ChecksumAlgorithmFactory component. | `"SHA-1,MD5"` | 1.8.0 | Yes | Session Configuration | +| 38. | `"aether.lrm.enhanced.localPrefix"` | `String` | The prefix to use for locally installed artifacts. | `"installed"` | 1.8.1 | No | Session Configuration | +| 39. | `"aether.lrm.enhanced.releasesPrefix"` | `String` | The prefix to use for release artifacts. | `"releases"` | 1.8.1 | No | Session Configuration | +| 40. | `"aether.lrm.enhanced.remotePrefix"` | `String` | The prefix to use for remotely cached artifacts. | `"cached"` | 1.8.1 | No | Session Configuration | +| 41. | `"aether.lrm.enhanced.snapshotsPrefix"` | `String` | The prefix to use for snapshot artifacts. | `"snapshots"` | 1.8.1 | No | Session Configuration | +| 42. | `"aether.lrm.enhanced.split"` | `Boolean` | Whether LRM should split local and remote artifacts. | `false` | 1.8.1 | No | Session Configuration | +| 43. | `"aether.lrm.enhanced.splitLocal"` | `Boolean` | Whether locally installed artifacts should be split by version (release/snapshot). | `false` | 1.8.1 | No | Session Configuration | +| 44. | `"aether.lrm.enhanced.splitRemote"` | `Boolean` | Whether cached artifacts should be split by version (release/snapshot). | `false` | 1.8.1 | No | Session Configuration | +| 45. | `"aether.lrm.enhanced.splitRemoteRepository"` | `Boolean` | Whether cached artifacts should be split by origin repository (repository ID). | `false` | 1.8.1 | No | Session Configuration | +| 46. | `"aether.lrm.enhanced.splitRemoteRepositoryLast"` | `Boolean` | For cached artifacts, if both splitRemote and splitRemoteRepository are set to true sets the splitting order: by default it is repositoryId/version (false) or version/repositoryId (true) | `false` | 1.8.1 | No | Session Configuration | +| 47. | `"aether.lrm.enhanced.trackingFilename"` | `String` | Filename of the file in which to track the remote repositories. | `"_remote.repositories"` | | No | Session Configuration | +| 48. | `"aether.metadataResolver.threads"` | `Integer` | Number of threads to use in parallel for resolving metadata. | `4` | 0.9.0.M4 | No | Session Configuration | +| 49. | `"aether.named.diagnostic.enabled"` | `Boolean` | System property key to enable locking diagnostic collection. | `false` | 1.9.11 | No | Java System Properties | +| 50. | `"aether.named.file-lock.attempts"` | `Integer` | Tweak: on Windows, the presence of StandardOpenOption#DELETE_ON_CLOSE causes concurrency issues. This flag allows to implement similar fix as referenced JDK bug report: retry and hope the best. Default value is 5 attempts (will retry 4 times). | `5` | 1.7.3 | No | Java System Properties | +| 51. | `"aether.named.file-lock.deleteLockFiles"` | `Boolean` | Tweak: on Windows, the presence of StandardOpenOption#DELETE_ON_CLOSE causes concurrency issues. This flag allows to have it removed from effective flags, at the cost that lockfile directory becomes crowded with 0 byte sized lock files that are never cleaned up. Default value is on non-Windows OS. See JDK-8252883 for Windows related bug. Users on Windows can still force "delete on close" by explicitly setting this property to . | `true` | 1.7.3 | No | Java System Properties | +| 52. | `"aether.named.file-lock.sleepMillis"` | `Long` | Tweak: When used, the amount of milliseconds to sleep between subsequent retries. Default value is 50 milliseconds. | `50` | 1.7.3 | No | Java System Properties | +| 53. | `"aether.named.ipc.debug"` | `Boolean` | Should the IPC server log debug messages? (i.e. for testing purposes) | `false` | 2.0.1 | No | Java System Properties | +| 54. | `"aether.named.ipc.family"` | `String` | IPC socket family to use. | `"unix"` | 2.0.1 | No | Java System Properties | +| 55. | `"aether.named.ipc.idleTimeout"` | `Integer` | IPC idle timeout in seconds. If there is no IPC request during idle time, it will stop. | `60` | 2.0.1 | No | Java System Properties | +| 56. | `"aether.named.ipc.nativeName"` | `String` | The name if the IPC server native executable (without file extension like ".exe") | `"ipc-sync"` | 2.0.1 | No | Java System Properties | +| 57. | `"aether.named.ipc.nofork"` | `Boolean` | Should the IPC server not fork? (i.e. for testing purposes) | `false` | 2.0.1 | No | Java System Properties | +| 58. | `"aether.named.ipc.nonative"` | `Boolean` | Should the IPC server not use native executable? | `true` | 2.0.1 | No | Java System Properties | +| 59. | `"aether.offline.hosts"` | `String` | Comma-separated list of hosts which are supposed to be resolved offline. | - | | No | Session Configuration | +| 60. | `"aether.offline.protocols"` | `String` | Comma-separated list of protocols which are supposed to be resolved offline. | - | | No | Session Configuration | +| 61. | `"aether.priority.cached"` | `Boolean` | A flag indicating whether the created ordered components should be cached in session. | `true` | 2.0.0 | No | Session Configuration | +| 62. | `"aether.priority.implicit"` | `Boolean` | A flag indicating whether the priorities of pluggable extensions are implicitly given by their iteration order such that the first extension has the highest priority. If set, an extension's built-in priority as well as any corresponding configuration properties are ignored when searching for a suitable implementation among the available extensions. This priority mode is meant for cases where the application will present/inject extensions in the desired search order. | `false` | | No | Session Configuration | +| 63. | `"aether.remoteRepositoryFilter.groupId"` | `Boolean` | Is filter enabled? | `false` | 1.9.0 | No | Session Configuration | +| 64. | `"aether.remoteRepositoryFilter.groupId.basedir"` | `String` | The basedir where to store filter files. If path is relative, it is resolved from local repository root. | `".remoteRepositoryFilters"` | 1.9.0 | No | Session Configuration | +| 65. | `"aether.remoteRepositoryFilter.groupId.record"` | `Boolean` | Should filter go into "record" mode (and collect encountered artifacts)? | `false` | 1.9.0 | No | Session Configuration | +| 66. | `"aether.remoteRepositoryFilter.prefixes"` | `Boolean` | Is filter enabled? | `false` | 1.9.0 | No | Session Configuration | +| 67. | `"aether.remoteRepositoryFilter.prefixes.basedir"` | `String` | The basedir where to store filter files. If path is relative, it is resolved from local repository root. | `".remoteRepositoryFilters"` | 1.9.0 | No | Session Configuration | +| 68. | `"aether.snapshotFilter"` | `Boolean` | The key in the repository session's used to store a flag whether this filter should be forced to ban snapshots. By default, snapshots are only filtered if the root artifact is not a snapshot. | `false` | | No | Session Configuration | +| 69. | `"aether.syncContext.named.basedir.locksDir"` | `String` | The location of the directory toi use for locks. If relative path, it is resolved from the local repository root. | `".locks"` | 1.9.0 | No | Session Configuration | +| 70. | `"aether.syncContext.named.discriminating.discriminator"` | `String` | Configuration property to pass in discriminator, if needed. If not present, it is auto-calculated. | - | 1.7.0 | No | Session Configuration | +| 71. | `"aether.syncContext.named.discriminating.hostname"` | `String` | Configuration property to pass in hostname, if needed. If not present, hostname as reported by system will be used. | - | 1.7.0 | No | Session Configuration | +| 72. | `"aether.syncContext.named.factory"` | `String` | Name of the lock factory to use in session. | `"file-lock"` | 1.9.1 | No | Session Configuration | +| 73. | `"aether.syncContext.named.hashing.depth"` | `Integer` | The depth how many levels should adapter create. Acceptable values are 0-4 (inclusive). | `2` | 1.9.0 | No | Session Configuration | +| 74. | `"aether.syncContext.named.nameMapper"` | `String` | Name of the name mapper to use in session. Out of the box supported ones are "static", "gav", "file-gav", "file-hgav", "file-static" and "discriminating". | `"file-gav"` | 1.9.1 | No | Session Configuration | +| 75. | `"aether.syncContext.named.redisson.address"` | `String` | Address of the Redis instance. Optional. | `"redis://localhost:6379"` | 2.0.0 | No | Java System Properties | +| 76. | `"aether.syncContext.named.redisson.configFile"` | `String` | Path to a Redisson configuration file in YAML format. Read official documentation for details. | - | 1.7.0 | No | Java System Properties | +| 77. | `"aether.syncContext.named.retry"` | `Integer` | The amount of retries on time-out. | `1` | 1.7.0 | No | Session Configuration | +| 78. | `"aether.syncContext.named.retry.wait"` | `Long` | The amount of milliseconds to wait between retries on time-out. | `200l` | 1.7.0 | No | Session Configuration | +| 79. | `"aether.syncContext.named.time"` | `Long` | The maximum of time amount to be blocked to obtain lock. | `30l` | 1.7.0 | No | Session Configuration | +| 80. | `"aether.syncContext.named.time.unit"` | `String` | The unit of maximum time amount to be blocked to obtain lock. Use TimeUnit enum names. | `"SECONDS"` | 1.7.0 | No | Session Configuration | +| 81. | `"aether.system.dependencyVisitor"` | `String` | A flag indicating which visitor should be used to "flatten" the dependency graph into list. Default is same as in older resolver versions "preOrder", while it can accept values like "postOrder" and "levelOrder". | `"preOrder"` | 2.0.0 | No | Session Configuration | +| 82. | `"aether.transport.apache.https.cipherSuites"` | `String` | Comma-separated list of Cipher Suites which are enabled for HTTPS connections. | - | 2.0.0 | No | Session Configuration | +| 83. | `"aether.transport.apache.https.protocols"` | `String` | Comma-separated list of Protocols which are enabled for HTTPS connections. | - | 2.0.0 | No | Session Configuration | +| 84. | `"aether.transport.apache.retryHandler.name"` | `String` | The name of retryHandler, supported values are “standard”, that obeys RFC-2616, regarding idempotent methods, and “default” that considers requests w/o payload as idempotent. | `"standard"` | 2.0.0 | Yes | Session Configuration | +| 85. | `"aether.transport.apache.retryHandler.requestSentEnabled"` | `Boolean` | Set to true if it is acceptable to retry non-idempotent requests, that have been sent. | `false` | 2.0.0 | Yes | Session Configuration | +| 86. | `"aether.transport.apache.useSystemProperties"` | `Boolean` | If enabled, underlying Apache HttpClient will use system properties as well to configure itself (typically used to set up HTTP Proxy via Java system properties). See HttpClientBuilder for used properties. This mode is not recommended, better use documented ways of configuration instead. | `false` | 2.0.0 | Yes | Session Configuration | +| 87. | `"aether.transport.classpath.loader"` | `ClassLoader` | The key in the repository session's used to store a from which resources should be retrieved. If unspecified, the of the current thread will be used. | - | | No | Session Configuration | +| 88. | `"aether.transport.http.connectTimeout"` | `Integer` | The maximum amount of time (in milliseconds) to wait for a successful connection to a remote server. Non-positive values indicate no timeout. | `10000` | | Yes | Session Configuration | +| 89. | `"aether.transport.http.connectionMaxTtl"` | `Integer` | Total time to live in seconds for an HTTP connection, after that time, the connection will be dropped (no matter for how long it was idle). | `300` | 1.9.8 | Yes | Session Configuration | +| 90. | `"aether.transport.http.credentialEncoding"` | `String` | The encoding/charset to use when exchanging credentials with HTTP servers. Besides this general key, clients may also specify the encoding for a specific remote repository by appending the suffix to this key when storing the charset name. | `"ISO-8859-1"` | | Yes | Session Configuration | +| 91. | `"aether.transport.http.expectContinue"` | `Boolean` | Boolean flag should the HTTP transport use expect-continue handshake for PUT requests. Not all transport support this option. This option may be needed for some broken HTTP servers. Default value corresponds to given transport default one (resolver does not override those), but if configuration IS given, it will replace given transport own default value. | - | 1.9.17 | Yes | Session Configuration | +| 92. | `"aether.transport.http.headers"` | `java.util.Map` | The request headers to use for HTTP-based repository connectors. The headers are specified using a , mapping a header name to its value. Besides this general key, clients may also specify headers for a specific remote repository by appending the suffix to this key when storing the headers map. The repository-specific headers map is supposed to be complete, i.e. is not merged with the general headers map. | - | | Yes | Session Configuration | +| 93. | `"aether.transport.http.localAddress"` | `String` | The local address (interface) to use with HTTP transport. Not all transport supports this option. | - | 2.0.0 | Yes | Session Configuration | +| 94. | `"aether.transport.http.maxConnectionsPerRoute"` | `Integer` | The maximum concurrent connections per route HTTP client is allowed to use. | `50` | 1.9.8 | Yes | Session Configuration | +| 95. | `"aether.transport.http.preemptiveAuth"` | `Boolean` | Should HTTP client use preemptive-authentication for all HTTP verbs (works only w/ BASIC). By default, is disabled, as it is considered less secure. | `false` | 1.9.6 | Yes | Session Configuration | +| 96. | `"aether.transport.http.preemptivePutAuth"` | `Boolean` | Boolean flag should the HTTP transport use preemptive-auth for PUT requests. Not all transport support this option. | `true` | 2.0.0 (moved out from maven-resolver-transport-http). | Yes | Session Configuration | +| 97. | `"aether.transport.http.requestTimeout"` | `Integer` | The maximum amount of time (in milliseconds) to wait for remaining data to arrive from a remote server. Note that this timeout does not restrict the overall duration of a request, it only restricts the duration of inactivity between consecutive data packets. Non-positive values indicate no timeout. | `1800000` | | Yes | Session Configuration | +| 98. | `"aether.transport.http.retryHandler.count"` | `Integer` | The maximum number of times a request to a remote server should be retried in case of an error. | `3` | 1.9.6 | Yes | Session Configuration | +| 99. | `"aether.transport.http.retryHandler.interval"` | `Long` | The initial retry interval in millis of request to a remote server should be waited in case of "too many requests" (HTTP codes 429 and 503). Accepts long as milliseconds. This value is used if remote server does not use header, in which case Server value is obeyed. | `5000l` | 1.9.16 | Yes | Session Configuration | +| 100. | `"aether.transport.http.retryHandler.intervalMax"` | `Long` | The maximum retry interval in millis of request to a remote server above which the request should be aborted instead. In theory, a malicious server could tell Maven "come back after 100 years" that would stall the build for some. Using this parameter Maven will fail the request instead, if interval is above this value. | `300000l` | 1.9.16 | Yes | Session Configuration | +| 101. | `"aether.transport.http.retryHandler.serviceUnavailable"` | `String` | The HTTP codes of remote server responses that should be handled as "too many requests" (examples: HTTP codes 429 and 503). Accepts comma separated list of HTTP response codes. | `"429,503"` | 1.9.16 | Yes | Session Configuration | +| 102. | `"aether.transport.http.reuseConnections"` | `Boolean` | Should HTTP client reuse connections (in other words, pool connections) or not? | `true` | 1.9.8 | Yes | Session Configuration | +| 103. | `"aether.transport.http.supportWebDav"` | `Boolean` | Boolean flag should the HTTP transport support WebDAV remote. Not all transport support this option. | `false` | 2.0.0 (moved out from maven-resolver-transport-http). | Yes | Session Configuration | +| 104. | `"aether.transport.http.userAgent"` | `String` | The user agent that repository connectors should report to servers. | `"Aether"` | | No | Session Configuration | +| 105. | `"aether.transport.https.securityMode"` | `String` | The mode that sets HTTPS transport "security mode": to ignore any SSL errors (certificate validity checks, hostname verification). The default value is . | `"default"` | 1.9.6 | Yes | Session Configuration | +| 106. | `"aether.transport.jdk.httpVersion"` | `String` | Use string representation of HttpClient version enum "HTTP_2" or "HTTP_1_1" to set default HTTP protocol to use. | `"HTTP_2"` | 2.0.0 | Yes | Session Configuration | +| 107. | `"aether.transport.jdk.maxConcurrentRequests"` | `Integer` | The hard limit of maximum concurrent requests JDK transport can do. This is a workaround for the fact, that in HTTP/2 mode, JDK HttpClient initializes this value to Integer.MAX_VALUE (!) and lowers it on first response from the remote server (but it may be too late). See JDK bug JDK-8225647 for details. | `100` | 2.0.0 | Yes | Session Configuration | +| 108. | `"aether.transport.jetty.followRedirects"` | `Boolean` | If enabled, Jetty client will follow HTTP redirects. | `true` | 2.0.1 | Yes | Session Configuration | +| 109. | `"aether.transport.jetty.maxRedirects"` | `Integer` | The max redirect count to follow. | `5` | 2.0.1 | Yes | Session Configuration | +| 110. | `"aether.transport.minio.fixedBucketName"` | `String` | The fixed bucket name to use. | `"maven"` | 2.0.2 | Yes | Session Configuration | +| 111. | `"aether.transport.minio.objectNameMapper"` | `String` | Object name mapper to use. | `"fixedBucket"` | 2.0.2 | Yes | Session Configuration | +| 112. | `"aether.transport.wagon.config"` | `Object` | The configuration to use for the Wagon provider. | - | | Yes | Session Configuration | +| 113. | `"aether.transport.wagon.perms.dirMode"` | `String` | Octal numerical notation of permissions to set for newly created directories. Only considered by certain Wagon providers. | - | | Yes | Session Configuration | +| 114. | `"aether.transport.wagon.perms.fileMode"` | `String` | Octal numerical notation of permissions to set for newly created files. Only considered by certain Wagon providers. | - | | Yes | Session Configuration | +| 115. | `"aether.transport.wagon.perms.group"` | `String` | Group which should own newly created directories/files. Only considered by certain Wagon providers. | - | | Yes | Session Configuration | +| 116. | `"aether.trustedChecksumsSource.sparseDirectory"` | `Boolean` | Is checksum source enabled? | `false` | 1.9.0 | No | Session Configuration | +| 117. | `"aether.trustedChecksumsSource.sparseDirectory.basedir"` | `String` | The basedir where checksums are. If relative, is resolved from local repository root. | `".checksums"` | 1.9.0 | No | Session Configuration | +| 118. | `"aether.trustedChecksumsSource.sparseDirectory.originAware"` | `Boolean` | Is source origin aware? | `true` | 1.9.0 | No | Session Configuration | +| 119. | `"aether.trustedChecksumsSource.summaryFile"` | `Boolean` | Is checksum source enabled? | `false` | 1.9.0 | No | Session Configuration | +| 120. | `"aether.trustedChecksumsSource.summaryFile.basedir"` | `String` | The basedir where checksums are. If relative, is resolved from local repository root. | `".checksums"` | 1.9.0 | No | Session Configuration | +| 121. | `"aether.trustedChecksumsSource.summaryFile.originAware"` | `Boolean` | Is source origin aware? | `true` | 1.9.0 | No | Session Configuration | +| 122. | `"aether.updateCheckManager.sessionState"` | `String` | Manages the session state, i.e. influences if the same download requests to artifacts/metadata will happen multiple times within the same RepositorySystemSession. If "enabled" will enable the session state. If "bypass" will enable bypassing (i.e. store all artifact ids/metadata ids which have been updates but not evaluating those). All other values lead to disabling the session state completely. | `"enabled"` | | No | Session Configuration | All properties which have `yes` in the column `Supports Repo ID Suffix` can be optionally configured specifically for a repository id. In that case the configuration property needs to be suffixed with a period followed by the repository id of the repository to configure, e.g. `aether.connector.http.headers.central` for repository with id `central`.