File 0001-Don-t-depend-on-apache-sshd-test-jars.patch of Package maven-scm
From d6ebbc2783508164f749ce4c47e74c7bb45f5b7e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fridrich=20=C5=A0trba?= <fridrich.strba@bluewin.ch>
Date: Mon, 22 Sep 2025 17:41:31 +0200
Subject: [PATCH] Don't depend on apache-sshd test-jars
---
.../maven-scm-provider-gittest/pom.xml | 12 -
.../git/BogusPasswordAuthenticator.java | 47 ++
.../provider/git/CommandExecutionHelper.java | 91 ++++
.../provider/git/CommonTestSupportUtils.java | 504 ++++++++++++++++++
.../provider/git/CoreTestSupportUtils.java | 91 ++++
.../maven/scm/provider/git/EchoShell.java | 40 ++
.../scm/provider/git/EchoShellFactory.java | 41 ++
.../maven/scm/provider/git/GitSshServer.java | 2 -
8 files changed, 814 insertions(+), 14 deletions(-)
create mode 100644 maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gittest/src/main/java/org/apache/maven/scm/provider/git/BogusPasswordAuthenticator.java
create mode 100644 maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gittest/src/main/java/org/apache/maven/scm/provider/git/CommandExecutionHelper.java
create mode 100644 maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gittest/src/main/java/org/apache/maven/scm/provider/git/CommonTestSupportUtils.java
create mode 100644 maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gittest/src/main/java/org/apache/maven/scm/provider/git/CoreTestSupportUtils.java
create mode 100644 maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gittest/src/main/java/org/apache/maven/scm/provider/git/EchoShell.java
create mode 100644 maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gittest/src/main/java/org/apache/maven/scm/provider/git/EchoShellFactory.java
diff --git a/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gittest/pom.xml b/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gittest/pom.xml
index 97a4a84bc..bd894bedb 100644
--- a/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gittest/pom.xml
+++ b/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gittest/pom.xml
@@ -50,18 +50,6 @@
<artifactId>sshd-git</artifactId>
<version>${minaSshdVersion}</version>
</dependency>
- <dependency>
- <groupId>org.apache.sshd</groupId>
- <artifactId>sshd-common</artifactId>
- <version>${minaSshdVersion}</version>
- <type>test-jar</type>
- </dependency>
- <dependency>
- <groupId>org.apache.sshd</groupId>
- <artifactId>sshd-core</artifactId>
- <version>${minaSshdVersion}</version>
- <type>test-jar</type>
- </dependency>
<!-- for creating SSH keypairs dynamically -->
<dependency>
<groupId>org.bouncycastle</groupId>
diff --git a/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gittest/src/main/java/org/apache/maven/scm/provider/git/BogusPasswordAuthenticator.java b/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gittest/src/main/java/org/apache/maven/scm/provider/git/BogusPasswordAuthenticator.java
new file mode 100644
index 000000000..1bcfa2e1d
--- /dev/null
+++ b/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gittest/src/main/java/org/apache/maven/scm/provider/git/BogusPasswordAuthenticator.java
@@ -0,0 +1,47 @@
+/*
+ * 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.apache.maven.scm.provider.git;
+
+import org.apache.sshd.common.util.logging.AbstractLoggingBean;
+import org.apache.sshd.server.auth.password.PasswordAuthenticator;
+import org.apache.sshd.server.session.ServerSession;
+
+/**
+ * A test {@link PasswordAuthenticator} that accepts an authentication attempt if the username is not {@code null} and
+ * same as password
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class BogusPasswordAuthenticator extends AbstractLoggingBean implements PasswordAuthenticator {
+ public static final BogusPasswordAuthenticator INSTANCE = new BogusPasswordAuthenticator();
+
+ public BogusPasswordAuthenticator() {
+ super();
+ }
+
+ @Override
+ public boolean authenticate(String username, String password, ServerSession session) {
+ boolean result = (username != null) && username.equals(password);
+ if (log.isDebugEnabled()) {
+ log.debug("authenticate({}) {} / {} - success={}", session, username, password, result);
+ }
+
+ return result;
+ }
+}
diff --git a/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gittest/src/main/java/org/apache/maven/scm/provider/git/CommandExecutionHelper.java b/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gittest/src/main/java/org/apache/maven/scm/provider/git/CommandExecutionHelper.java
new file mode 100644
index 000000000..75011879a
--- /dev/null
+++ b/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gittest/src/main/java/org/apache/maven/scm/provider/git/CommandExecutionHelper.java
@@ -0,0 +1,91 @@
+/*
+ * 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.apache.maven.scm.provider.git;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.InterruptedIOException;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+
+import org.apache.sshd.server.command.AbstractCommandSupport;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public abstract class CommandExecutionHelper extends AbstractCommandSupport {
+ protected CommandExecutionHelper() {
+ this(null);
+ }
+
+ protected CommandExecutionHelper(String command) {
+ super(command, null);
+ }
+
+ @Override
+ public void run() {
+ String command = getCommand();
+ try {
+ if (command == null) {
+ try (BufferedReader r =
+ new BufferedReader(new InputStreamReader(getInputStream(), StandardCharsets.UTF_8))) {
+ for (; ; ) {
+ command = r.readLine();
+ if (command == null) {
+ return;
+ }
+
+ if (!handleCommandLine(command)) {
+ return;
+ }
+ }
+ }
+ } else {
+ handleCommandLine(command);
+ }
+ } catch (InterruptedIOException e) {
+ // Ignore - signaled end
+ } catch (Exception e) {
+ String message =
+ "Failed (" + e.getClass().getSimpleName() + ") to handle '" + command + "': " + e.getMessage();
+ try {
+ OutputStream stderr = getErrorStream();
+ stderr.write(message.getBytes(StandardCharsets.US_ASCII));
+ } catch (IOException ioe) {
+ log.warn(
+ "Failed ({}) to write error message={}: {}",
+ e.getClass().getSimpleName(),
+ message,
+ ioe.getMessage());
+ } finally {
+ onExit(-1, message);
+ }
+ } finally {
+ onExit(0);
+ }
+ }
+
+ /**
+ * @param command The command line
+ * @return {@code true} if continue accepting command
+ * @throws Exception If failed to handle the command line
+ */
+ protected abstract boolean handleCommandLine(String command) throws Exception;
+}
diff --git a/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gittest/src/main/java/org/apache/maven/scm/provider/git/CommonTestSupportUtils.java b/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gittest/src/main/java/org/apache/maven/scm/provider/git/CommonTestSupportUtils.java
new file mode 100644
index 000000000..be4fdda4f
--- /dev/null
+++ b/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gittest/src/main/java/org/apache/maven/scm/provider/git/CommonTestSupportUtils.java
@@ -0,0 +1,504 @@
+/*
+ * 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.apache.maven.scm.provider.git;
+
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.security.CodeSource;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.ProtectionDomain;
+import java.security.spec.InvalidKeySpecException;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.apache.sshd.common.cipher.ECCurves;
+import org.apache.sshd.common.config.keys.KeyUtils;
+import org.apache.sshd.common.keyprovider.FileKeyPairProvider;
+import org.apache.sshd.common.keyprovider.KeyIdentityProvider;
+import org.apache.sshd.common.keyprovider.KeyPairProvider;
+import org.apache.sshd.common.keyprovider.KeyPairProviderHolder;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.ValidateUtils;
+import org.apache.sshd.common.util.security.SecurityUtils;
+import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
+
+/**
+ * TODO Add javadoc
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public final class CommonTestSupportUtils {
+ /**
+ * URL/URI scheme that refers to a file
+ */
+ public static final String FILE_URL_SCHEME = "file";
+ /**
+ * Prefix used in URL(s) that reference a file resource
+ */
+ public static final String FILE_URL_PREFIX = FILE_URL_SCHEME + ":";
+
+ /**
+ * Separator used in URL(s) that reference a resource inside a JAR to denote the sub-path inside the JAR
+ */
+ public static final char RESOURCE_SUBPATH_SEPARATOR = '!';
+
+ /**
+ * Suffix of JAR files
+ */
+ public static final String JAR_FILE_SUFFIX = ".jar";
+
+ /**
+ * URL/URI scheme that refers to a JAR
+ */
+ public static final String JAR_URL_SCHEME = "jar";
+
+ /**
+ * Prefix used in URL(s) that reference a resource inside a JAR
+ */
+ public static final String JAR_URL_PREFIX = JAR_URL_SCHEME + ":";
+
+ /**
+ * Suffix of compile Java class files
+ */
+ public static final String CLASS_FILE_SUFFIX = ".class";
+
+ public static final List<String> TARGET_FOLDER_NAMES = // NOTE: order is important
+ Collections.unmodifiableList(Arrays.asList("target" /* Maven */, "build" /* Gradle */));
+
+ public static final String DEFAULT_TEST_HOST_KEY_PROVIDER_ALGORITHM = KeyUtils.EC_ALGORITHM;
+ public static final int DEFAULT_TEST_HOST_KEY_SIZE = 256;
+ public static final String DEFAULT_TEST_HOST_KEY_TYPE =
+ ECCurves.fromCurveSize(DEFAULT_TEST_HOST_KEY_SIZE).getKeyType();
+
+ // uses a cached instance to avoid re-creating the keys as it is a time-consuming effort
+ private static final AtomicReference<KeyPairProvider> KEYPAIR_PROVIDER_HOLDER = new AtomicReference<>();
+ // uses a cached instance to avoid re-creating the keys as it is a time-consuming effort
+ private static final Map<String, FileKeyPairProvider> PROVIDERS_MAP = new ConcurrentHashMap<>();
+
+ private CommonTestSupportUtils() {
+ throw new UnsupportedOperationException("No instance allowed");
+ }
+
+ /**
+ * @param clazz A {@link Class} object
+ * @return A {@link URI} to the location of the class bytes container - e.g., the root folder,
+ * the containing JAR, etc.. Returns {@code null} if location could not be resolved
+ * @throws URISyntaxException if location is not a valid URI
+ * @see #getClassContainerLocationURL(Class)
+ */
+ public static URI getClassContainerLocationURI(Class<?> clazz) throws URISyntaxException {
+ URL url = getClassContainerLocationURL(clazz);
+ return (url == null) ? null : url.toURI();
+ }
+
+ /**
+ * @param clazz A {@link Class} object
+ * @return A {@link URL} to the location of the class bytes container - e.g., the root folder, the containing
+ * JAR, etc.. Returns {@code null} if location could not be resolved
+ */
+ public static URL getClassContainerLocationURL(Class<?> clazz) {
+ ProtectionDomain pd = clazz.getProtectionDomain();
+ CodeSource cs = (pd == null) ? null : pd.getCodeSource();
+ URL url = (cs == null) ? null : cs.getLocation();
+ if (url == null) {
+ url = getClassBytesURL(clazz);
+ if (url == null) {
+ return null;
+ }
+
+ String srcForm = getURLSource(url);
+ if (GenericUtils.isEmpty(srcForm)) {
+ return null;
+ }
+
+ try {
+ url = new URL(srcForm);
+ } catch (MalformedURLException e) {
+ throw new IllegalArgumentException("getClassContainerLocationURL(" + clazz.getName() + ")"
+ + " Failed to create URL=" + srcForm + " from " + url.toExternalForm()
+ + ": " + e.getMessage());
+ }
+ }
+
+ return url;
+ }
+
+ /**
+ * @param uri The {@link URI} value - ignored if {@code null}
+ * @return The URI(s) source path where {@link #JAR_URL_PREFIX} and any sub-resource are stripped
+ * @see #getURLSource(String)
+ */
+ public static String getURLSource(URI uri) {
+ return getURLSource((uri == null) ? null : uri.toString());
+ }
+
+ /**
+ * @param url The {@link URL} value - ignored if {@code null}
+ * @return The URL(s) source path where {@link #JAR_URL_PREFIX} and any sub-resource are stripped
+ * @see #getURLSource(String)
+ */
+ public static String getURLSource(URL url) {
+ return getURLSource((url == null) ? null : url.toExternalForm());
+ }
+
+ /**
+ * @param externalForm The {@link URL#toExternalForm()} string - ignored if {@code null}/empty
+ * @return The URL(s) source path where {@link #JAR_URL_PREFIX} and any sub-resource are stripped
+ */
+ public static String getURLSource(String externalForm) {
+ String url = externalForm;
+ if (GenericUtils.isEmpty(url)) {
+ return url;
+ }
+
+ url = stripJarURLPrefix(externalForm);
+ if (GenericUtils.isEmpty(url)) {
+ return url;
+ }
+
+ int sepPos = url.indexOf(RESOURCE_SUBPATH_SEPARATOR);
+ if (sepPos < 0) {
+ return adjustURLPathValue(url);
+ } else {
+ return adjustURLPathValue(url.substring(0, sepPos));
+ }
+ }
+
+ /**
+ * @param url A {@link URL} - ignored if {@code null}
+ * @return The path after stripping any trailing '/' provided the path is not '/' itself
+ * @see #adjustURLPathValue(String)
+ */
+ public static String adjustURLPathValue(URL url) {
+ return adjustURLPathValue((url == null) ? null : url.getPath());
+ }
+
+ /**
+ * @param path A URL path value - ignored if {@code null}/empty
+ * @return The path after stripping any trailing '/' provided the path is not '/' itself
+ */
+ public static String adjustURLPathValue(final String path) {
+ final int pathLen = (path == null) ? 0 : path.length();
+ if ((pathLen <= 1) || (path.charAt(pathLen - 1) != '/')) {
+ return path;
+ }
+
+ return path.substring(0, pathLen - 1);
+ }
+
+ public static String stripJarURLPrefix(String externalForm) {
+ String url = externalForm;
+ if (GenericUtils.isEmpty(url)) {
+ return url;
+ }
+
+ if (url.startsWith(JAR_URL_PREFIX)) {
+ return url.substring(JAR_URL_PREFIX.length());
+ }
+
+ return url;
+ }
+
+ /**
+ * @param clazz The request {@link Class}
+ * @return A {@link URL} to the location of the <code>.class</code> file - {@code null} if location could not
+ * be resolved
+ */
+ public static URL getClassBytesURL(Class<?> clazz) {
+ String className = clazz.getName();
+ int sepPos = className.indexOf('$');
+ // if this is an internal class, then need to use its parent as well
+ if (sepPos > 0) {
+ sepPos = className.lastIndexOf('.');
+ if (sepPos > 0) {
+ className = className.substring(sepPos + 1);
+ }
+ } else {
+ className = clazz.getSimpleName();
+ }
+
+ return clazz.getResource(className + CLASS_FILE_SUFFIX);
+ }
+
+ public static String getClassBytesResourceName(Class<?> clazz) {
+ return getClassBytesResourceName((clazz == null) ? null : clazz.getName());
+ }
+
+ /**
+ * @param name The fully qualified class name - ignored if {@code null}/empty
+ * @return The relative path of the class file byte-code resource
+ */
+ public static String getClassBytesResourceName(String name) {
+ if (GenericUtils.isEmpty(name)) {
+ return name;
+ } else {
+ return name.replace('.', '/') + CLASS_FILE_SUFFIX;
+ }
+ }
+
+ public static Path resolve(Path root, String... children) {
+ if (GenericUtils.isEmpty(children)) {
+ return root;
+ } else {
+ return resolve(root, Arrays.asList(children));
+ }
+ }
+
+ public static Path resolve(Path root, Collection<String> children) {
+ Path path = root;
+ if (!GenericUtils.isEmpty(children)) {
+ for (String child : children) {
+ path = path.resolve(child);
+ }
+ }
+
+ return path;
+ }
+
+ /**
+ * @param anchor An anchor {@link Class} whose container we want to use as the starting point for the
+ * "target" folder lookup up the hierarchy
+ * @return The "target" <U>folder</U> - {@code null} if not found
+ * @see #detectTargetFolder(Path)
+ */
+ public static Path detectTargetFolder(Class<?> anchor) {
+ Path path = detectTargetFolder(getClassContainerLocationPath(anchor));
+ if (path == null) {
+ String basedir = System.getProperty("basedir");
+ path = detectTargetFolder(Paths.get(basedir, "target"));
+ }
+ return path;
+ }
+
+ /**
+ * @param clazz A {@link Class} object
+ * @return A {@link Path} of the location of the class bytes container - e.g., the root
+ * folder, the containing JAR, etc.. Returns {@code null} if location could not be
+ * resolved
+ * @throws IllegalArgumentException If location is not a valid {@link Path} location
+ * @see #getClassContainerLocationURI(Class)
+ * @see #toPathSource(URI)
+ */
+ public static Path getClassContainerLocationPath(Class<?> clazz) throws IllegalArgumentException {
+ try {
+ URI uri = getClassContainerLocationURI(clazz);
+ return toPathSource(uri);
+ } catch (URISyntaxException | MalformedURLException e) {
+ throw new IllegalArgumentException(e.getClass().getSimpleName() + ": " + e.getMessage(), e);
+ }
+ }
+
+ /**
+ * Converts a {@link URL} that may refer to an internal resource to a {@link Path} representing is
+ * "source" container (e.g., if it is a resource in a JAR, then the result is the JAR's path)
+ *
+ * @param url The {@link URL} - ignored if {@code null}
+ * @return The matching {@link Path}
+ * @throws MalformedURLException If source URL does not refer to a file location
+ * @see #toPathSource(URI)
+ */
+ public static Path toPathSource(URL url) throws MalformedURLException {
+ if (url == null) {
+ return null;
+ }
+
+ try {
+ return toPathSource(url.toURI());
+ } catch (URISyntaxException e) {
+ throw new MalformedURLException("toFileSource(" + url.toExternalForm() + ")"
+ + " cannot (" + e.getClass().getSimpleName() + ")"
+ + " convert to URI: " + e.getMessage());
+ }
+ }
+
+ /**
+ * Converts a {@link URI} that may refer to an internal resource to a {@link Path} representing is
+ * "source" container (e.g., if it is a resource in a JAR, then the result is the JAR's path)
+ *
+ * @param uri The {@link URI} - ignored if {@code null}
+ * @return The matching {@link Path}
+ * @throws MalformedURLException If source URI does not refer to a file location
+ * @see #getURLSource(URI)
+ */
+ public static Path toPathSource(URI uri) throws MalformedURLException {
+ String src = getURLSource(uri);
+ if (GenericUtils.isEmpty(src)) {
+ return null;
+ }
+
+ if (!src.startsWith(FILE_URL_PREFIX)) {
+ throw new MalformedURLException("toFileSource(" + src + ") not a '" + FILE_URL_SCHEME + "' scheme");
+ }
+
+ try {
+ return Paths.get(new URI(src));
+ } catch (URISyntaxException e) {
+ throw new MalformedURLException("toFileSource(" + src + ")"
+ + " cannot (" + e.getClass().getSimpleName() + ")"
+ + " convert to URI: " + e.getMessage());
+ }
+ }
+
+ /**
+ * @param anchorFile An anchor {@link Path} we want to use as the starting point for the "target" or
+ * "build" folder lookup up the hierarchy
+ * @return The "target" <U>folder</U> - {@code null} if not found
+ */
+ public static Path detectTargetFolder(Path anchorFile) {
+ for (Path file = anchorFile; file != null; file = file.getParent()) {
+ if (!Files.isDirectory(file)) {
+ continue;
+ }
+
+ String name = Objects.toString(file.getFileName(), "");
+ if (TARGET_FOLDER_NAMES.contains(name)) {
+ return file;
+ }
+ }
+
+ return null;
+ }
+
+ public static KeyPair generateKeyPair(String algorithm, int keySize) throws GeneralSecurityException {
+ KeyPairGenerator gen = SecurityUtils.getKeyPairGenerator(algorithm);
+ if (KeyUtils.EC_ALGORITHM.equalsIgnoreCase(algorithm)) {
+ ECCurves curve = ECCurves.fromCurveSize(keySize);
+ if (curve == null) {
+ throw new InvalidKeySpecException("Unknown curve for key size=" + keySize);
+ }
+ gen.initialize(curve.getParameters());
+ } else {
+ gen.initialize(keySize);
+ }
+
+ return gen.generateKeyPair();
+ }
+
+ public static KeyPairProvider createTestHostKeyProvider(Class<?> anchor) {
+ KeyPairProvider provider = KEYPAIR_PROVIDER_HOLDER.get();
+ if (provider != null) {
+ return provider;
+ }
+
+ Path targetFolder = Objects.requireNonNull(
+ CommonTestSupportUtils.detectTargetFolder(anchor), "Failed to detect target folder");
+ Path file = targetFolder.resolve("hostkey." + DEFAULT_TEST_HOST_KEY_PROVIDER_ALGORITHM.toLowerCase());
+ provider = createTestHostKeyProvider(file);
+
+ KeyPairProvider prev = KEYPAIR_PROVIDER_HOLDER.getAndSet(provider);
+ if (prev != null) { // check if somebody else beat us to it
+ return prev;
+ } else {
+ return provider;
+ }
+ }
+
+ public static KeyPairProvider createTestHostKeyProvider(Path path) {
+ SimpleGeneratorHostKeyProvider keyProvider = new SimpleGeneratorHostKeyProvider();
+ keyProvider.setPath(Objects.requireNonNull(path, "No path"));
+ keyProvider.setAlgorithm(DEFAULT_TEST_HOST_KEY_PROVIDER_ALGORITHM);
+ keyProvider.setKeySize(DEFAULT_TEST_HOST_KEY_SIZE);
+ return validateKeyPairProvider(keyProvider);
+ }
+
+ public static KeyPair getFirstKeyPair(KeyPairProviderHolder holder) {
+ return getFirstKeyPair(Objects.requireNonNull(holder, "No holder").getKeyPairProvider());
+ }
+
+ public static KeyPair getFirstKeyPair(KeyIdentityProvider provider) {
+ Objects.requireNonNull(provider, "No key pair provider");
+ Iterable<KeyPair> pairs;
+ try {
+ pairs = Objects.requireNonNull(provider.loadKeys(null), "No loaded keys");
+ } catch (IOException | GeneralSecurityException e) {
+ throw new RuntimeException(
+ "Unexpected " + e.getClass().getSimpleName() + ")" + " keys loading exception: " + e.getMessage(),
+ e);
+ }
+
+ Iterator<KeyPair> iter = Objects.requireNonNull(pairs.iterator(), "No keys iterator");
+ ValidateUtils.checkTrue(iter.hasNext(), "Empty loaded kyes iterator");
+ return Objects.requireNonNull(iter.next(), "No key pair in iterator");
+ }
+
+ private static Path getFile(String resource) {
+ URL url = CommonTestSupportUtils.class.getClassLoader().getResource(resource);
+ try {
+ return Paths.get(url.toURI());
+ } catch (URISyntaxException e) {
+ return Paths.get(url.getPath());
+ }
+ }
+
+ public static FileKeyPairProvider createTestKeyPairProvider(String resource) {
+ Path file = getFile(resource);
+ file = file.toAbsolutePath();
+ String filePath = Objects.toString(file, "");
+ FileKeyPairProvider provider = PROVIDERS_MAP.get(filePath);
+ if (provider != null) {
+ return provider;
+ }
+
+ provider = new FileKeyPairProvider();
+ provider.setPaths(Collections.singletonList(file));
+ provider = validateKeyPairProvider(provider);
+
+ FileKeyPairProvider prev = PROVIDERS_MAP.put(filePath, provider);
+ if (prev != null) { // check if somebody else beat us to it
+ return prev;
+ } else {
+ return provider;
+ }
+ }
+
+ public static <P extends KeyIdentityProvider> P validateKeyPairProvider(P provider) {
+ Objects.requireNonNull(provider, "No provider");
+
+ // get the I/O out of the way
+ Iterable<KeyPair> keys;
+ try {
+ keys = Objects.requireNonNull(provider.loadKeys(null), "No keys loaded");
+ } catch (IOException | GeneralSecurityException e) {
+ throw new RuntimeException(
+ "Unexpected " + e.getClass().getSimpleName() + ")" + " keys loading exception: " + e.getMessage(),
+ e);
+ }
+
+ if (keys instanceof Collection<?>) {
+ ValidateUtils.checkNotNullAndNotEmpty((Collection<?>) keys, "Empty keys loaded");
+ }
+
+ return provider;
+ }
+}
diff --git a/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gittest/src/main/java/org/apache/maven/scm/provider/git/CoreTestSupportUtils.java b/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gittest/src/main/java/org/apache/maven/scm/provider/git/CoreTestSupportUtils.java
new file mode 100644
index 000000000..9ed5963a8
--- /dev/null
+++ b/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gittest/src/main/java/org/apache/maven/scm/provider/git/CoreTestSupportUtils.java
@@ -0,0 +1,91 @@
+/*
+ * 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.apache.maven.scm.provider.git;
+
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.ServerSocket;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.sshd.common.NamedFactory;
+import org.apache.sshd.common.cipher.BuiltinCiphers;
+import org.apache.sshd.common.cipher.Cipher;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.core.CoreModuleProperties;
+import org.apache.sshd.server.SshServer;
+import org.apache.sshd.server.auth.pubkey.AcceptAllPublickeyAuthenticator;
+import org.apache.sshd.server.shell.UnknownCommandFactory;
+
+public final class CoreTestSupportUtils {
+ public static final Duration READ_TIMEOUT = getTimeout("read.nio2", Duration.ofSeconds(60));
+
+ private CoreTestSupportUtils() {
+ throw new UnsupportedOperationException("No instance");
+ }
+
+ public static int getFreePort() throws Exception {
+ try (ServerSocket s = new ServerSocket()) {
+ s.setReuseAddress(true);
+ s.bind(new InetSocketAddress((InetAddress) null, 0));
+ return s.getLocalPort();
+ }
+ }
+
+ public static SshServer setupTestServer(Class<?> anchor) {
+ return setupTestServer(SshServer.setUpDefaultServer(), anchor);
+ }
+
+ public static <S extends SshServer> S setupTestServer(S sshd, Class<?> anchor) {
+ List<NamedFactory<Cipher>> cipherFactories = new ArrayList<>(sshd.getCipherFactories());
+ cipherFactories.add(BuiltinCiphers.aes128cbc);
+ sshd.setCipherFactories(cipherFactories);
+ sshd.setKeyPairProvider(CommonTestSupportUtils.createTestHostKeyProvider(anchor));
+ sshd.setPasswordAuthenticator(BogusPasswordAuthenticator.INSTANCE);
+ sshd.setPublickeyAuthenticator(AcceptAllPublickeyAuthenticator.INSTANCE);
+ sshd.setShellFactory(EchoShellFactory.INSTANCE);
+ sshd.setCommandFactory(UnknownCommandFactory.INSTANCE);
+ CoreModuleProperties.NIO2_READ_TIMEOUT.set(sshd, READ_TIMEOUT);
+ return sshd;
+ }
+
+ public static Duration getTimeout(String property, Duration defaultValue) {
+ // Do we have a specific timeout value ?
+ String str = System.getProperty("org.apache.sshd.test.timeout." + property);
+ if (GenericUtils.isNotEmpty(str)) {
+ return Duration.ofMillis(Long.parseLong(str));
+ }
+
+ // Do we have a specific factor ?
+ str = System.getProperty("org.apache.sshd.test.timeout.factor." + property);
+ if (GenericUtils.isEmpty(str)) {
+ // Do we have a global factor ?
+ str = System.getProperty("org.apache.sshd.test.timeout.factor");
+ }
+
+ if (GenericUtils.isNotEmpty(str)) {
+ double factor = Double.parseDouble(str);
+ long dur = Math.round(defaultValue.toMillis() * factor);
+ return Duration.ofMillis(dur);
+ }
+
+ return defaultValue;
+ }
+}
diff --git a/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gittest/src/main/java/org/apache/maven/scm/provider/git/EchoShell.java b/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gittest/src/main/java/org/apache/maven/scm/provider/git/EchoShell.java
new file mode 100644
index 000000000..65771a451
--- /dev/null
+++ b/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gittest/src/main/java/org/apache/maven/scm/provider/git/EchoShell.java
@@ -0,0 +1,40 @@
+/*
+ * 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.apache.maven.scm.provider.git;
+
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class EchoShell extends CommandExecutionHelper {
+ public EchoShell() {
+ super();
+ }
+
+ @Override
+ protected boolean handleCommandLine(String command) throws Exception {
+ OutputStream out = getOutputStream();
+ out.write((command + "\n").getBytes(StandardCharsets.UTF_8));
+ out.flush();
+
+ return !"exit".equals(command);
+ }
+}
diff --git a/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gittest/src/main/java/org/apache/maven/scm/provider/git/EchoShellFactory.java b/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gittest/src/main/java/org/apache/maven/scm/provider/git/EchoShellFactory.java
new file mode 100644
index 000000000..2a8b162ee
--- /dev/null
+++ b/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gittest/src/main/java/org/apache/maven/scm/provider/git/EchoShellFactory.java
@@ -0,0 +1,41 @@
+/*
+ * 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.apache.maven.scm.provider.git;
+
+import org.apache.sshd.server.channel.ChannelSession;
+import org.apache.sshd.server.command.Command;
+import org.apache.sshd.server.shell.ShellFactory;
+
+/**
+ * TODO Add javadoc
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class EchoShellFactory implements ShellFactory {
+ public static final EchoShellFactory INSTANCE = new EchoShellFactory();
+
+ public EchoShellFactory() {
+ super();
+ }
+
+ @Override
+ public Command createShell(ChannelSession channel) {
+ return new EchoShell();
+ }
+}
diff --git a/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gittest/src/main/java/org/apache/maven/scm/provider/git/GitSshServer.java b/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gittest/src/main/java/org/apache/maven/scm/provider/git/GitSshServer.java
index a58525907..054990fd3 100644
--- a/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gittest/src/main/java/org/apache/maven/scm/provider/git/GitSshServer.java
+++ b/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gittest/src/main/java/org/apache/maven/scm/provider/git/GitSshServer.java
@@ -40,8 +40,6 @@
import org.apache.sshd.server.auth.pubkey.KeySetPublickeyAuthenticator;
import org.apache.sshd.server.auth.pubkey.PublickeyAuthenticator;
import org.apache.sshd.server.session.ServerSession;
-import org.apache.sshd.util.test.CommonTestSupportUtils;
-import org.apache.sshd.util.test.CoreTestSupportUtils;
import org.bouncycastle.openssl.PKCS8Generator;
import org.bouncycastle.openssl.jcajce.JcaPEMWriter;
import org.bouncycastle.openssl.jcajce.JcaPKCS8Generator;
--
2.51.0