]> source.dussan.org Git - jgit.git/commitdiff
Ssh tests with an Apache MINA sshd test git server 79/131879/6
authorThomas Wolf <thomas.wolf@paranor.ch>
Fri, 14 Sep 2018 20:40:08 +0000 (22:40 +0200)
committerMatthias Sohn <matthias.sohn@sap.com>
Tue, 6 Nov 2018 12:17:21 +0000 (13:17 +0100)
Add a simple ssh git server based on Apache MINA sshd, and use it
in new tests that verify ssh operations and in particular a number
of bugs that had cropped up over time in JSch.

The git server supports fetching only, and sftp access.

The tests are all in an abstract base class; the concrete JschSshTest
class only provides ssh-specific test setup. So the same tests could
be run easily also with some other ssh client.

Bug: 520927
Change-Id: Ide6687b717fb497a29fc83f22b07390a26dfce1d
Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>
16 files changed:
WORKSPACE
lib/BUILD
org.eclipse.jgit.junit/BUILD
org.eclipse.jgit.junit/META-INF/MANIFEST.MF
org.eclipse.jgit.junit/pom.xml
org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/ssh/SshTestGitServer.java [new file with mode: 0644]
org.eclipse.jgit.test/BUILD
org.eclipse.jgit.test/META-INF/MANIFEST.MF
org.eclipse.jgit.test/pom.xml
org.eclipse.jgit.test/tests.bzl
org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/transport/id_dsa_test [new file with mode: 0644]
org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/transport/id_dsa_test.pub [new file with mode: 0644]
org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/JSchSshTest.java [new file with mode: 0644]
org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/OpenSshConfigTest.java
org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/SshTestBase.java [new file with mode: 0644]
pom.xml

index d327e13afdc74efff462cc9f535c6a2d13c2c62c..30fbfd671c045ffce49a0daf3d2fda4b07cc3a72 100644 (file)
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -52,6 +52,18 @@ maven_jar(
     sha1 = "a86ce739e5a7175b4b234c290a00a5fdb80957a0",
 )
 
+maven_jar(
+    name = "sshd-core",
+    artifact = "org.apache.sshd:sshd-core:2.0.0",
+    sha1 = "f4275079a2463cfd2bf1548a80e1683288a8e86b",
+)
+
+maven_jar(
+    name = "sshd-sftp",
+    artifact = "org.apache.sshd:sshd-sftp:2.0.0",
+    sha1 = "a12d64dc2d5d23271a4dc58075e55f9c64a68494",
+)
+
 maven_jar(
     name = "commons-codec",
     artifact = "commons-codec:commons-codec:1.10",
index 7cd420a5266560e1f852e923321a0e5c2b39ad36..35cb79eb3981ebde5400dc3584870abcbd0e419e 100644 (file)
--- a/lib/BUILD
+++ b/lib/BUILD
@@ -58,6 +58,24 @@ java_library(
     exports = ["@httpcore//jar"],
 )
 
+java_library(
+    name = "sshd-core",
+    visibility = [
+        "//org.eclipse.jgit.junit:__pkg__",
+        "//org.eclipse.jgit.test:__pkg__",
+    ],
+    exports = ["@sshd-core//jar"],
+)
+
+java_library(
+    name = "sshd-sftp",
+    visibility = [
+        "//org.eclipse.jgit.junit:__pkg__",
+        "//org.eclipse.jgit.test:__pkg__",
+    ],
+    exports = ["@sshd-sftp//jar"],
+)
+
 java_library(
     name = "javaewah",
     visibility = ["//visibility:public"],
index 74498fdf62bdcd2578fecf678a07ada7770e7544..cba2318df4c55b3a168a801d4cf0044518efc35d 100644 (file)
@@ -8,6 +8,8 @@ java_library(
     resources = glob(["resources/**"]),
     deps = [
         "//lib:junit",
+        "//lib:sshd-core",
+        "//lib:sshd-sftp",
         # We want these deps to be provided_deps
         "//org.eclipse.jgit:jgit",
     ],
index 9721c42e50e7ce53897a259e9640430ac355a552..e44ee0301e1503a7acdeaa63e28fe89ed69232e9 100644 (file)
@@ -8,7 +8,21 @@ Bundle-Localization: plugin
 Bundle-Vendor: %provider_name
 Bundle-ActivationPolicy: lazy
 Bundle-RequiredExecutionEnvironment: JavaSE-1.8
-Import-Package: org.eclipse.jgit.api;version="[5.2.0,5.3.0)",
+Import-Package:  org.apache.sshd.common;version="[2.0.0,2.1.0)",
+ org.apache.sshd.common.config.keys;version="[2.0.0,2.1.0)",
+ org.apache.sshd.common.file.virtualfs;version="[2.0.0,2.1.0)",
+ org.apache.sshd.common.helpers;version="[2.0.0,2.1.0)",
+ org.apache.sshd.common.kex;version="[2.0.0,2.1.0)",
+ org.apache.sshd.common.keyprovider;version="[2.0.0,2.1.0)",
+ org.apache.sshd.common.session;version="[2.0.0,2.1.0)",
+ org.apache.sshd.common.util.logging;version="[2.0.0,2.1.0)",
+ org.apache.sshd.common.util.security;version="[2.0.0,2.1.0)",
+ org.apache.sshd.server;version="[2.0.0,2.1.0)",
+ org.apache.sshd.server.command;version="[2.0.0,2.1.0)",
+ org.apache.sshd.server.shell;version="[2.0.0,2.1.0)",
+ org.apache.sshd.server.subsystem.sftp;version="[2.0.0,2.1.0)",
+ org.eclipse.jgit.annotations;version="[5.2.0,5.3.0)",
+ org.eclipse.jgit.api;version="[5.2.0,5.3.0)",
  org.eclipse.jgit.api.errors;version="[5.2.0,5.3.0)",
  org.eclipse.jgit.dircache;version="[5.2.0,5.3.0)",
  org.eclipse.jgit.errors;version="[5.2.0,5.3.0)",
@@ -18,6 +32,7 @@ Import-Package: org.eclipse.jgit.api;version="[5.2.0,5.3.0)",
  org.eclipse.jgit.merge;version="[5.2.0,5.3.0)",
  org.eclipse.jgit.revwalk;version="[5.2.0,5.3.0)",
  org.eclipse.jgit.storage.file;version="[5.2.0,5.3.0)",
+ org.eclipse.jgit.transport;version="5.2.0",
  org.eclipse.jgit.treewalk;version="[5.2.0,5.3.0)",
  org.eclipse.jgit.treewalk.filter;version="[5.2.0,5.3.0)",
  org.eclipse.jgit.util;version="[5.2.0,5.3.0)",
@@ -26,7 +41,8 @@ Import-Package: org.eclipse.jgit.api;version="[5.2.0,5.3.0)",
  org.junit;version="[4.12,5.0.0)",
  org.junit.rules;version="[4.12,5.0.0)",
  org.junit.runner;version="[4.12,5.0.0)",
- org.junit.runners.model;version="[4.12,5.0.0)"
+ org.junit.runners.model;version="[4.12,5.0.0)",
+ org.slf4j;version="[1.7.0,2.0.0)"
 Export-Package: org.eclipse.jgit.junit;version="5.2.0";
   uses:="org.eclipse.jgit.dircache,
    org.eclipse.jgit.lib,
@@ -35,5 +51,10 @@ Export-Package: org.eclipse.jgit.junit;version="5.2.0";
    org.eclipse.jgit.treewalk,
    org.eclipse.jgit.util,
    org.eclipse.jgit.storage.file,
-   org.eclipse.jgit.api",
- org.eclipse.jgit.junit.time;version="5.2.0"
+   org.eclipse.jgit.api,
+   org.junit.rules,
+   org.junit.runners.model,
+   org.junit.runner,
+   org.eclipse.jgit.util.time",
+ org.eclipse.jgit.junit.ssh;version="5.2.0",
+ org.eclipse.jgit.junit.time;version="5.2.0";uses:="org.eclipse.jgit.util.time"
index 24e2c71f9b9d0b195476e864748fc5645a43d311..112c73f6527493a05b31716fe559ef9bb211b4eb 100644 (file)
       <version>${project.version}</version>
     </dependency>
 
+    <dependency>
+      <groupId>org.apache.sshd</groupId>
+      <artifactId>sshd-core</artifactId>
+      <version>${apache-sshd-version}</version>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.sshd</groupId>
+      <artifactId>sshd-sftp</artifactId>
+      <version>${apache-sshd-version}</version>
+    </dependency>
+
     <dependency>
       <groupId>junit</groupId>
       <artifactId>junit</artifactId>
diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/ssh/SshTestGitServer.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/ssh/SshTestGitServer.java
new file mode 100644 (file)
index 0000000..675a115
--- /dev/null
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.eclipse.jgit.junit.ssh;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.security.PublicKey;
+import java.text.MessageFormat;
+import java.util.Collections;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import org.apache.sshd.common.config.keys.IdentityUtils;
+import org.apache.sshd.common.config.keys.KeyUtils;
+import org.apache.sshd.common.file.virtualfs.VirtualFileSystemFactory;
+import org.apache.sshd.common.keyprovider.KeyPairProvider;
+import org.apache.sshd.common.session.Session;
+import org.apache.sshd.common.util.security.SecurityUtils;
+import org.apache.sshd.server.SshServer;
+import org.apache.sshd.server.command.AbstractCommandSupport;
+import org.apache.sshd.server.shell.UnknownCommand;
+import org.apache.sshd.server.subsystem.sftp.SftpSubsystemFactory;
+import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.transport.UploadPack;
+
+/**
+ * A simple ssh/sftp git <em>test</em> server based on Apache MINA sshd.
+ * <p>
+ * Supports only a single repository. Authenticates only the given test user
+ * against his given test public key. ssh is limited to fetching (upload-pack).
+ * </p>
+ *
+ * @since 5.2
+ */
+public class SshTestGitServer {
+
+       @NonNull
+       private String testUser;
+
+       @NonNull
+       private PublicKey testKey;
+
+       @NonNull
+       private Repository repository;
+
+       private final ExecutorService executorService = Executors
+                       .newFixedThreadPool(2);
+
+       private final SshServer server;
+
+       /**
+        * Creates a ssh git <em>test</em> server. It serves one single repository,
+        * and accepts public-key authentication for exactly one test user.
+        *
+        * @param testUser
+        *            user name of the test user
+        * @param testKey
+        *            <em>private</em> key file of the test user; the server will
+        *            only user the public key from it
+        * @param repository
+        *            to serve
+        * @param hostKey
+        *            the unencrypted private key to use as host key
+        * @throws IOException
+        * @throws GeneralSecurityException
+        */
+       public SshTestGitServer(@NonNull String testUser, @NonNull Path testKey,
+                       @NonNull Repository repository, @NonNull byte[] hostKey)
+                       throws IOException, GeneralSecurityException {
+               this.testUser = testUser;
+               this.testKey = IdentityUtils
+                               .loadIdentities(Collections.singletonMap("A", testKey), null)
+                               .get("A").getPublic();
+               this.repository = repository;
+               server = SshServer.setUpDefaultServer();
+               // Set host key
+               server.setKeyPairProvider(new KeyPairProvider() {
+
+                       @Override
+                       public Iterable<KeyPair> loadKeys() {
+                               try (ByteArrayInputStream in = new ByteArrayInputStream(
+                                               hostKey)) {
+                                       return Collections.singletonList(
+                                                       SecurityUtils.loadKeyPairIdentity("", in, null));
+                               } catch (IOException | GeneralSecurityException e) {
+                                       return null;
+                               }
+                       }
+
+               });
+               // SFTP.
+               server.setFileSystemFactory(new VirtualFileSystemFactory() {
+
+                       @Override
+                       protected Path computeRootDir(Session session) throws IOException {
+                               return SshTestGitServer.this.repository.getDirectory()
+                                               .getParentFile().getAbsoluteFile().toPath();
+                       }
+               });
+               server.setSubsystemFactories(Collections
+                               .singletonList((new SftpSubsystemFactory.Builder()).build()));
+               // No shell
+               server.setShellFactory(null);
+               // Disable some authentications
+               server.setPasswordAuthenticator(null);
+               server.setKeyboardInteractiveAuthenticator(null);
+               server.setGSSAuthenticator(null);
+               server.setHostBasedAuthenticator(null);
+               // Accept only the test user/public key
+               server.setPublickeyAuthenticator((userName, publicKey, session) -> {
+                       return SshTestGitServer.this.testUser.equals(userName) && KeyUtils
+                                       .compareKeys(SshTestGitServer.this.testKey, publicKey);
+               });
+               server.setCommandFactory(command -> {
+                       if (command.startsWith("git-upload-pack")
+                                       || command.startsWith("git upload-pack")) {
+                               return new GitUploadPackCommand(command, executorService);
+                       }
+                       return new UnknownCommand(command);
+               });
+       }
+
+       /**
+        * Starts the test server, listening on a random port.
+        *
+        * @return the port the server listens on; test clients should connect to
+        *         that port
+        * @throws IOException
+        */
+       public int start() throws IOException {
+               server.start();
+               return server.getPort();
+       }
+
+       /**
+        * Stops the test server.
+        *
+        * @throws IOException
+        */
+       public void stop() throws IOException {
+               executorService.shutdownNow();
+               server.stop(true);
+       }
+
+       private class GitUploadPackCommand extends AbstractCommandSupport {
+
+               protected GitUploadPackCommand(String command,
+                               ExecutorService executorService) {
+                       super(command, executorService, false);
+               }
+
+               @Override
+               public void run() {
+                       UploadPack uploadPack = new UploadPack(repository);
+                       String gitProtocol = getEnvironment().getEnv().get("GIT_PROTOCOL");
+                       if (gitProtocol != null) {
+                               uploadPack
+                                               .setExtraParameters(Collections.singleton(gitProtocol));
+                       }
+                       try {
+                               uploadPack.upload(getInputStream(), getOutputStream(),
+                                               getErrorStream());
+                               onExit(0);
+                       } catch (IOException e) {
+                               log.warn(
+                                               MessageFormat.format("Could not run {0}", getCommand()),
+                                               e);
+                               onExit(-1, e.toString());
+                       }
+               }
+
+       }
+}
index 186de25d9413e0da0acc296ba44f88a7715c54c4..07597f3251ee019e9a4046606ebc1f9c7c6b0c8b 100644 (file)
@@ -20,6 +20,7 @@ HELPERS = glob(["src/**/*.java"]) + [PKG + c for c in [
     "revwalk/RevWalkTestCase.java",
     "transport/ObjectIdMatcher.java",
     "transport/SpiTransport.java",
+    "transport/SshTestBase.java",
     "treewalk/FileTreeIteratorWithTimeControl.java",
     "treewalk/filter/AlwaysCloneTreeFilter.java",
     "test/resources/SampleDataRepositoryTestCase.java",
@@ -44,6 +45,9 @@ java_library(
     resources = DATA,
     deps = [
         "//lib:junit",
+        "//lib:jsch",
+        "//lib:sshd-core",
+        "//lib:sshd-sftp",
         "//org.eclipse.jgit:jgit",
         "//org.eclipse.jgit.junit:junit",
     ],
index 6df9e39094cd784610139290815ec7e5a96e51c8..6514277ac5add07424c7abaa20e9436f7d9eaccb 100644 (file)
@@ -10,6 +10,7 @@ Bundle-ActivationPolicy: lazy
 Bundle-RequiredExecutionEnvironment: JavaSE-1.8
 Import-Package: com.googlecode.javaewah;version="[1.1.6,2.0.0)",
  com.jcraft.jsch;version="[0.1.54,0.2.0)",
+ org.eclipse.jgit.annotations;version="[5.2.0,5.3.0)",
  org.eclipse.jgit.api;version="[5.2.0,5.3.0)",
  org.eclipse.jgit.api.errors;version="[5.2.0,5.3.0)",
  org.eclipse.jgit.attributes;version="[5.2.0,5.3.0)",
@@ -34,6 +35,7 @@ Import-Package: com.googlecode.javaewah;version="[1.1.6,2.0.0)",
  org.eclipse.jgit.internal.storage.reftree;version="[5.2.0,5.3.0)",
  org.eclipse.jgit.internal.transport.parser;version="[5.2.0,5.3.0)",
  org.eclipse.jgit.junit;version="[5.2.0,5.3.0)",
+ org.eclipse.jgit.junit.ssh;version="[5.2.0,5.3.0)",
  org.eclipse.jgit.lfs;version="[5.2.0,5.3.0)",
  org.eclipse.jgit.lib;version="[5.2.0,5.3.0)",
  org.eclipse.jgit.merge;version="[5.2.0,5.3.0)",
index 6aa34f5b76716ceb6c1cbb959fda132b55c53a0d..3d5df9a68adc4d0c39d30e1fc2c3e1bd13b3c70e 100644 (file)
       <artifactId>org.eclipse.jgit.pgm</artifactId>
       <version>${project.version}</version>
     </dependency>
+
+    <dependency>
+      <groupId>org.apache.sshd</groupId>
+      <artifactId>sshd-core</artifactId>
+      <version>${apache-sshd-version}</version>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.sshd</groupId>
+      <artifactId>sshd-sftp</artifactId>
+      <version>${apache-sshd-version}</version>
+    </dependency>
   </dependencies>
 
   <profiles>
index bc06e3ef40c21074a895e5eeb35663ead4f3e7f3..b9ad8b212645a4c7cc57f1dd3fe53b13735badcc 100644 (file)
@@ -41,7 +41,13 @@ def tests(tests):
             additional_deps = [
                 "//lib:jsch",
             ]
-
+        if src.endswith("JSchSshTest.java"):
+            additional_deps = [
+                "//lib:jsch",
+                "//lib:jzlib",
+                "//lib:sshd-core",
+                "//lib:sshd-sftp",
+            ]
         heap_size = "-Xmx256m"
         if src.endswith("HugeCommitMessageTest.java"):
             heap_size = "-Xmx512m"
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/transport/id_dsa_test b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/transport/id_dsa_test
new file mode 100644 (file)
index 0000000..cc39e8b
--- /dev/null
@@ -0,0 +1,15 @@
+-----BEGIN DSA PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: AES-128-CBC,D7B8FC3F4E304A2A22754B068767081F
+
+IewkLt6JyqtPccAsnfeLv7IMlLvgm7tqQSYK1/CLhmDE0aZXViD8sqxLA6dVjmkp
+BVyk7EBpp43PnVQYsDcMPnyM8H83vNRDtIQ6fxM1PJafiP7Rbn1k1fDh7DwA48PU
+FnT6zZ9aYEKYMto0WIdQ86j/uY+LtYygQDDoZ2ohn2NlpykeyrSp0bDRIoW6sdc5
++LlfDtq2usv3fcxMAJpO/SSN78LvBlyOK4n/JAVSkPawsW1WsIrXA52mk0iUhjYc
+aYOCuL+wA7OmHAOpfS5HUXZ4i/7qONnLBkEqeIOcgTmShh1c4oWw9TjWK1AzdSDU
+G2nkRJ/8zK/jdm5wcmrjrzuREM1VbCiXHlVoHYI0W1Z9etOgz1cj4KLz/bB8Nf+8
+shCez1Aw5ec33BzwysfwymfAKeXjYaxdKcur3j+UdXAlYRD28BRnWmTL5Jx82eUu
+NIh0U9pHkn+PjdzmjSPEUP7wzDjQQacaQTkBRf5gPyOYfv/+Mnq6YyflKaPYmkEr
+eztO22VZlpyp/hj2LzBav9wi0++teInNQGU+GxHedsWm4+YpffMhz1bz5ZUQ670A
+0WJJH3k/KnxbCY3usj4eJr+CsX+LNZhm+rKyjRDmRwA=
+-----END DSA PRIVATE KEY-----
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/transport/id_dsa_test.pub b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/transport/id_dsa_test.pub
new file mode 100644 (file)
index 0000000..0528a92
--- /dev/null
@@ -0,0 +1 @@
+ssh-dss AAAAB3NzaC1kc3MAAACBAIsXi0EUiI6GmhHqrwwjvO2wdujW46+uXM/SG2GVI3KxCSf95B2XgXBsgiKH0sy3guyqjDcP4Ph5Mctg1IxqmqugN6xf9YB6lf09bRdIbumVGU6nXW7bZDHdk9nmvWy56vurofwvhoRnQBUJ3L4n7dxxvXhIyRPOxptayOS2ZcnRAAAAFQDsgGxVxcBBM9y0Rm3kNz/R64CYEQAAAIEAgCbyCJNZb66KQBMO7B+NPxx0caSKjZ+3TpWL6pLJGTAu1pztd1wpElECNCEBhTX9p1HEypTIjOUFU2gjgaBLUcWE0JK+/4vJjjvaENvrQardH0EeRfrazhpRY+X6ytUTk0YPDuQn+ZqBhXxAoD8BA+TJMvk7oMpMUTyr6LGBuj4AAACAeXCfOrKY6wHuMkHHpa9Ix95T+7h5ZrSosrV1WO5g9X04LNiPFRXvGyMWYF17VaGqVWID5NbbGP4PqwSw0rjmw7c/xxV2DYNfJ5NFWsDHxhI6RP9AaGTKcdIEykWEkGgJDiVF/DJgjvapGCW4Lo5UB1JJRXEM4YmTiEbyUyahKqw= thomas@Arcturus
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/JSchSshTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/JSchSshTest.java
new file mode 100644 (file)
index 0000000..7b03440
--- /dev/null
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.eclipse.jgit.transport;
+
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.file.Files;
+import java.util.Arrays;
+
+import org.eclipse.jgit.errors.TransportException;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.transport.OpenSshConfig.Host;
+import org.eclipse.jgit.util.FS;
+
+import com.jcraft.jsch.JSch;
+import com.jcraft.jsch.JSchException;
+import com.jcraft.jsch.Session;
+
+public class JSchSshTest extends SshTestBase {
+
+       private class TestSshSessionFactory extends JschConfigSessionFactory {
+
+               @Override
+               protected void configure(Host hc, Session session) {
+                       // Nothing
+               }
+
+               @Override
+               public synchronized RemoteSession getSession(URIish uri,
+                               CredentialsProvider credentialsProvider, FS fs, int tms)
+                               throws TransportException {
+                       return super.getSession(uri, credentialsProvider, fs, tms);
+               }
+
+               @Override
+               protected JSch createDefaultJSch(FS fs) throws JSchException {
+                       JSch defaultJSch = super.createDefaultJSch(fs);
+                       if (knownHosts.exists()) {
+                               defaultJSch.setKnownHosts(knownHosts.getAbsolutePath());
+                       }
+                       return defaultJSch;
+               }
+       }
+
+       @Override
+       protected SshSessionFactory createSessionFactory() {
+               return new TestSshSessionFactory();
+       }
+
+       @Override
+       protected void installConfig(String... config) {
+               SshSessionFactory factory = getSessionFactory();
+               assertTrue(factory instanceof JschConfigSessionFactory);
+               JschConfigSessionFactory j = (JschConfigSessionFactory) factory;
+               try {
+                       j.setConfig(createConfig(config));
+               } catch (IOException e) {
+                       throw new UncheckedIOException(e);
+               }
+       }
+
+       private OpenSshConfig createConfig(String... content) throws IOException {
+               File configFile = new File(sshDir, Constants.CONFIG);
+               if (content != null) {
+                       Files.write(configFile.toPath(), Arrays.asList(content));
+               }
+               return new OpenSshConfig(getTemporaryDirectory(), configFile);
+       }
+
+}
index 19fcbfd7a2207b302cffef9b3d63366b88f1d490..076058576141098e945137b231c64d505674ade0 100644 (file)
@@ -67,6 +67,7 @@ import org.junit.Before;
 import org.junit.Test;
 
 import com.jcraft.jsch.ConfigRepository;
+import com.jcraft.jsch.ConfigRepository.Config;
 
 public class OpenSshConfigTest extends RepositoryTestCase {
        private File home;
@@ -163,6 +164,20 @@ public class OpenSshConfigTest extends RepositoryTestCase {
                assertEquals("bad.tld\"", osc.lookup("bad").getHostName());
        }
 
+       @Test
+       public void testCaseInsensitiveKeyLookup() throws Exception {
+               config("Host orcz\n" + "Port 29418\n"
+                               + "\tHostName repo.or.cz\nStrictHostKeyChecking yes\n");
+               final Host h = osc.lookup("orcz");
+               Config c = h.getConfig();
+               String exactCase = c.getValue("StrictHostKeyChecking");
+               assertEquals("yes", exactCase);
+               assertEquals(exactCase, c.getValue("stricthostkeychecking"));
+               assertEquals(exactCase, c.getValue("STRICTHOSTKEYCHECKING"));
+               assertEquals(exactCase, c.getValue("sTrIcThostKEYcheckING"));
+               assertNull(c.getValue("sTrIcThostKEYcheckIN"));
+       }
+
        @Test
        public void testAlias_DoesNotMatch() throws Exception {
                config("Host orcz\n" + "Port 29418\n" + "\tHostName repo.or.cz\n");
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/SshTestBase.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/SshTestBase.java
new file mode 100644 (file)
index 0000000..0d625f3
--- /dev/null
@@ -0,0 +1,466 @@
+/*
+ * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.eclipse.jgit.transport;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.jgit.api.CloneCommand;
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.errors.TransportException;
+import org.eclipse.jgit.errors.UnsupportedCredentialItem;
+import org.eclipse.jgit.junit.RepositoryTestCase;
+import org.eclipse.jgit.junit.ssh.SshTestGitServer;
+import org.eclipse.jgit.util.FS;
+import org.junit.After;
+import org.junit.Test;
+
+import com.jcraft.jsch.JSch;
+import com.jcraft.jsch.KeyPair;
+
+public abstract class SshTestBase extends RepositoryTestCase {
+
+       protected static final String TEST_USER = "testuser";
+
+       protected File sshDir;
+
+       protected File privateKey1;
+
+       protected File privateKey2;
+
+       private SshTestGitServer server;
+
+       private SshSessionFactory factory;
+
+       protected int testPort;
+
+       protected File knownHosts;
+
+       private File homeDir;
+
+       @Override
+       public void setUp() throws Exception {
+               super.setUp();
+               writeTrashFile("file.txt", "something");
+               try (Git git = new Git(db)) {
+                       git.add().addFilepattern("file.txt").call();
+                       git.commit().setMessage("Initial commit").call();
+               }
+               mockSystemReader.setProperty("user.home",
+                               getTemporaryDirectory().getAbsolutePath());
+               mockSystemReader.setProperty("HOME",
+                               getTemporaryDirectory().getAbsolutePath());
+               homeDir = FS.DETECTED.userHome();
+               FS.DETECTED.setUserHome(getTemporaryDirectory().getAbsoluteFile());
+               sshDir = new File(getTemporaryDirectory(), ".ssh");
+               assertTrue(sshDir.mkdir());
+               File serverDir = new File(getTemporaryDirectory(), "srv");
+               assertTrue(serverDir.mkdir());
+               // Create two key pairs. Let's not call them "id_rsa".
+               privateKey1 = new File(sshDir, "first_key");
+               privateKey2 = new File(sshDir, "second_key");
+               createKeyPair(privateKey1);
+               createKeyPair(privateKey2);
+               ByteArrayOutputStream publicHostKey = new ByteArrayOutputStream();
+               // Start a server with our test user and the first key.
+               server = new SshTestGitServer(TEST_USER, privateKey1.toPath(), db,
+                               createHostKey(publicHostKey));
+               testPort = server.start();
+               assertTrue(testPort > 0);
+               knownHosts = new File(sshDir, "known_hosts");
+               Files.write(knownHosts.toPath(), Collections.singleton("[localhost]:"
+                               + testPort + ' '
+                               + publicHostKey.toString(StandardCharsets.US_ASCII.name())));
+               factory = createSessionFactory();
+               SshSessionFactory.setInstance(factory);
+       }
+
+       private static void createKeyPair(File privateKeyFile) throws Exception {
+               // Found no way to do this with MINA sshd except rolling it all
+               // ourselves...
+               JSch jsch = new JSch();
+               KeyPair pair = KeyPair.genKeyPair(jsch, KeyPair.RSA, 2048);
+               try (OutputStream out = new FileOutputStream(privateKeyFile)) {
+                       pair.writePrivateKey(out);
+               }
+               File publicKeyFile = new File(privateKeyFile.getParentFile(),
+                               privateKeyFile.getName() + ".pub");
+               try (OutputStream out = new FileOutputStream(publicKeyFile)) {
+                       pair.writePublicKey(out, TEST_USER);
+               }
+       }
+
+       private static byte[] createHostKey(OutputStream publicKey)
+                       throws Exception {
+               JSch jsch = new JSch();
+               KeyPair pair = KeyPair.genKeyPair(jsch, KeyPair.RSA, 2048);
+               pair.writePublicKey(publicKey, "");
+               try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
+                       pair.writePrivateKey(out);
+                       out.flush();
+                       return out.toByteArray();
+               }
+       }
+
+       @After
+       public void shutdownServer() throws Exception {
+               if (server != null) {
+                       server.stop();
+                       server = null;
+               }
+               FS.DETECTED.setUserHome(homeDir);
+               SshSessionFactory.setInstance(null);
+               factory = null;
+       }
+
+       protected abstract SshSessionFactory createSessionFactory();
+
+       protected SshSessionFactory getSessionFactory() {
+               return factory;
+       }
+
+       protected abstract void installConfig(String... config);
+
+       @Test(expected = TransportException.class)
+       public void testSshCloneWithoutConfig() throws Exception {
+               cloneWith("ssh://" + TEST_USER + "@localhost:" + testPort
+                               + "/doesntmatter", null);
+       }
+
+       @Test
+       public void testSshCloneWithGlobalIdentity() throws Exception {
+               cloneWith(
+                               "ssh://" + TEST_USER + "@localhost:" + testPort
+                                               + "/doesntmatter",
+                               null,
+                               "IdentityFile " + privateKey1.getAbsolutePath());
+       }
+
+       @Test
+       public void testSshCloneWithDefaultIdentity() throws Exception {
+               File idRsa = new File(privateKey1.getParentFile(), "id_rsa");
+               Files.copy(privateKey1.toPath(), idRsa.toPath());
+               // We expect the session factory to pick up these keys...
+               cloneWith("ssh://" + TEST_USER + "@localhost:" + testPort
+                               + "/doesntmatter", null);
+       }
+
+       @Test
+       public void testSshCloneWithConfig() throws Exception {
+               cloneWith("ssh://localhost/doesntmatter", null, //
+                               "Host localhost", //
+                               "HostName localhost", //
+                               "Port " + testPort, //
+                               "User " + TEST_USER, //
+                               "IdentityFile " + privateKey1.getAbsolutePath());
+       }
+
+       @Test
+       public void testSshCloneWithConfigEncryptedUnusedKey() throws Exception {
+               // Copy the encrypted test key from the bundle.
+               File encryptedKey = new File(sshDir, "id_dsa");
+               try (InputStream in = SshTestBase.class
+                               .getResourceAsStream("id_dsa_test")) {
+                       Files.copy(in, encryptedKey.toPath());
+               }
+               TestCredentialsProvider provider = new TestCredentialsProvider(
+                               "testpass");
+               cloneWith("ssh://localhost/doesntmatter", provider, //
+                               "Host localhost", //
+                               "HostName localhost", //
+                               "Port " + testPort, //
+                               "User " + TEST_USER, //
+                               "IdentityFile " + privateKey1.getAbsolutePath());
+               assertEquals("CredentialsProvider should not have been called", 0,
+                               provider.getLog().size());
+       }
+
+       @Test(expected = TransportException.class)
+       public void testSshCloneWithoutKnownHosts() throws Exception {
+               assertTrue("Could not delete known_hosts", knownHosts.delete());
+               cloneWith("ssh://localhost/doesntmatter", null, //
+                               "Host localhost", //
+                               "HostName localhost", //
+                               "Port " + testPort, //
+                               "User " + TEST_USER, //
+                               "IdentityFile " + privateKey1.getAbsolutePath());
+       }
+
+       @Test
+       public void testSshCloneWithoutKnownHostsWithProvider() throws Exception {
+               File copiedHosts = new File(knownHosts.getParentFile(),
+                               "copiedKnownHosts");
+               assertTrue("Failed to rename known_hosts",
+                               knownHosts.renameTo(copiedHosts));
+               TestCredentialsProvider provider = new TestCredentialsProvider();
+               cloneWith("ssh://localhost/doesntmatter", provider, //
+                               "Host localhost", //
+                               "HostName localhost", //
+                               "Port " + testPort, //
+                               "User " + TEST_USER, //
+                               "IdentityFile " + privateKey1.getAbsolutePath());
+               Map<URIish, List<CredentialItem>> messages = provider.getLog();
+               assertFalse("Expected user iteraction", messages.isEmpty());
+       }
+
+       @Test
+       public void testSftpCloneWithConfig() throws Exception {
+               cloneWith("sftp://localhost/.git", null, //
+                               "Host localhost", //
+                               "HostName localhost", //
+                               "Port " + testPort, //
+                               "User " + TEST_USER, //
+                               "IdentityFile " + privateKey1.getAbsolutePath());
+       }
+
+       @Test(expected = TransportException.class)
+       public void testSshCloneWithConfigWrongKey() throws Exception {
+               cloneWith("ssh://localhost/doesntmatter", null, //
+                               "Host localhost", //
+                               "HostName localhost", //
+                               "Port " + testPort, //
+                               "User " + TEST_USER, //
+                               "IdentityFile " + privateKey2.getAbsolutePath());
+       }
+
+       @Test
+       public void testSshCloneWithWrongUserNameInConfig() throws Exception {
+               // Bug 526778
+               cloneWith(
+                               "ssh://" + TEST_USER + "@localhost:" + testPort
+                                               + "/doesntmatter",
+                               null, //
+                               "Host localhost", //
+                               "HostName localhost", //
+                               "User sombody_else", //
+                               "IdentityFile " + privateKey1.getAbsolutePath());
+       }
+
+       @Test
+       public void testSshCloneWithWrongPortInConfig() throws Exception {
+               // Bug 526778
+               cloneWith(
+                               "ssh://" + TEST_USER + "@localhost:" + testPort
+                                               + "/doesntmatter",
+                               null, //
+                               "Host localhost", //
+                               "HostName localhost", //
+                               "Port 22", //
+                               "User " + TEST_USER, //
+                               "IdentityFile " + privateKey1.getAbsolutePath());
+       }
+
+       @Test
+       public void testSshCloneWithAliasInConfig() throws Exception {
+               // Bug 531118
+               cloneWith("ssh://git/doesntmatter", null, //
+                               "Host git", //
+                               "HostName localhost", //
+                               "Port " + testPort, //
+                               "User " + TEST_USER, //
+                               "IdentityFile " + privateKey1.getAbsolutePath(), "", //
+                               "Host localhost", //
+                               "HostName localhost", //
+                               "Port 22", //
+                               "User someone_else", //
+                               "IdentityFile " + privateKey2.getAbsolutePath());
+       }
+
+       @Test
+       public void testSshCloneWithUnknownCiphersInConfig() throws Exception {
+               // Bug 535672
+               cloneWith("ssh://git/doesntmatter", null, //
+                               "Host git", //
+                               "HostName localhost", //
+                               "Port " + testPort, //
+                               "User " + TEST_USER, //
+                               "IdentityFile " + privateKey1.getAbsolutePath(), //
+                               "Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr");
+       }
+
+       @Test
+       public void testSshCloneWithUnknownHostKeyAlgorithmsInConfig()
+                       throws Exception {
+               // Bug 535672
+               cloneWith("ssh://git/doesntmatter", null, //
+                               "Host git", //
+                               "HostName localhost", //
+                               "Port " + testPort, //
+                               "User " + TEST_USER, //
+                               "IdentityFile " + privateKey1.getAbsolutePath(), //
+                               "HostKeyAlgorithms foobar,ssh-rsa,ssh-dss");
+       }
+
+       @Test
+       public void testSshCloneWithUnknownKexAlgorithmsInConfig()
+                       throws Exception {
+               // Bug 535672
+               cloneWith("ssh://git/doesntmatter", null, //
+                               "Host git", //
+                               "HostName localhost", //
+                               "Port " + testPort, //
+                               "User " + TEST_USER, //
+                               "IdentityFile " + privateKey1.getAbsolutePath(), //
+                               "KexAlgorithms foobar,diffie-hellman-group14-sha1,ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521");
+       }
+
+       @Test
+       public void testSshCloneWithMinimalHostKeyAlgorithmsInConfig()
+                       throws Exception {
+               // Bug 537790
+               cloneWith("ssh://git/doesntmatter", null, //
+                               "Host git", //
+                               "HostName localhost", //
+                               "Port " + testPort, //
+                               "User " + TEST_USER, //
+                               "IdentityFile " + privateKey1.getAbsolutePath(), //
+                               "HostKeyAlgorithms ssh-rsa,ssh-dss");
+       }
+
+       private void cloneWith(String uri, CredentialsProvider provider,
+                       String... config) throws Exception {
+               installConfig(config);
+               File cloned = new File(getTemporaryDirectory(), "cloned");
+               CloneCommand clone = Git.cloneRepository().setCloneAllBranches(true)
+                               .setDirectory(cloned).setURI(uri);
+               if (provider != null) {
+                       clone.setCredentialsProvider(provider);
+               }
+               try (Git git = clone.call()) {
+                       assertNotNull(git.getRepository().resolve("master"));
+                       assertNotEquals(db.getWorkTree(),
+                                       git.getRepository().getWorkTree());
+                       checkFile(new File(git.getRepository().getWorkTree(), "file.txt"),
+                                       "something");
+               }
+       }
+
+       private class TestCredentialsProvider extends CredentialsProvider {
+
+               private final List<String> stringStore;
+
+               private final Iterator<String> strings;
+
+               public TestCredentialsProvider(String... strings) {
+                       if (strings == null || strings.length == 0) {
+                               stringStore = Collections.emptyList();
+                       } else {
+                               stringStore = Arrays.asList(strings);
+                       }
+                       this.strings = stringStore.iterator();
+               }
+
+               @Override
+               public boolean isInteractive() {
+                       return true;
+               }
+
+               @Override
+               public boolean supports(CredentialItem... items) {
+                       return true;
+               }
+
+               @Override
+               public boolean get(URIish uri, CredentialItem... items)
+                               throws UnsupportedCredentialItem {
+                       System.out.println("URI: " + uri);
+                       for (CredentialItem item : items) {
+                               System.out.println(item.getClass().getSimpleName() + ' '
+                                               + item.getPromptText());
+                       }
+                       logItems(uri, items);
+                       for (CredentialItem item : items) {
+                               if (item instanceof CredentialItem.InformationalMessage) {
+                                       continue;
+                               }
+                               if (item instanceof CredentialItem.YesNoType) {
+                                       ((CredentialItem.YesNoType) item).setValue(true);
+                               } else if (item instanceof CredentialItem.CharArrayType) {
+                                       if (strings.hasNext()) {
+                                               ((CredentialItem.CharArrayType) item)
+                                                               .setValue(strings.next().toCharArray());
+                                       } else {
+                                               return false;
+                                       }
+                               } else if (item instanceof CredentialItem.StringType) {
+                                       if (strings.hasNext()) {
+                                               ((CredentialItem.StringType) item)
+                                                               .setValue(strings.next());
+                                       } else {
+                                               return false;
+                                       }
+                               } else {
+                                       return false;
+                               }
+                       }
+                       return true;
+               }
+
+               private Map<URIish, List<CredentialItem>> log = new LinkedHashMap<>();
+
+               private void logItems(URIish uri, CredentialItem... items) {
+                       log.put(uri, Arrays.asList(items));
+               }
+
+               public Map<URIish, List<CredentialItem>> getLog() {
+                       return log;
+               }
+       }
+}
diff --git a/pom.xml b/pom.xml
index 4bd9b3878d5aa71c3046bf8ca5e8662bc5fe1076..d7daae98780271f49d020cb19843c978044a2ef1 100644 (file)
--- a/pom.xml
+++ b/pom.xml
     <bundle-manifest>${project.build.directory}/META-INF/MANIFEST.MF</bundle-manifest>
 
     <jgit-last-release-version>4.11.0.201803080745-r</jgit-last-release-version>
+    <apache-sshd-version>2.0.0</apache-sshd-version>
     <jsch-version>0.1.54</jsch-version>
     <jzlib-version>1.1.1</jzlib-version>
     <javaewah-version>1.1.6</javaewah-version>