summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--WORKSPACE2
-rw-r--r--org.eclipse.jgit.lfs/.settings/.api_filters12
-rw-r--r--org.eclipse.jgit.packaging/org.eclipse.jgit.feature/feature.xml4
-rw-r--r--org.eclipse.jgit.packaging/org.eclipse.jgit.http.apache.feature/feature.xml4
-rw-r--r--org.eclipse.jgit.packaging/org.eclipse.jgit.junit.feature/feature.xml4
-rw-r--r--org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/feature.xml4
-rw-r--r--org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.feature/feature.xml4
-rw-r--r--org.eclipse.jgit.packaging/org.eclipse.jgit.source.feature/feature.xml4
-rw-r--r--org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.feature/feature.xml4
-rw-r--r--org.eclipse.jgit.ssh.apache.test/META-INF/MANIFEST.MF10
-rw-r--r--org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/transport/sshd/ApacheSshTest.java53
-rw-r--r--org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/transport/sshd/NoFilesSshTest.java221
-rw-r--r--org.eclipse.jgit.ssh.apache/.settings/.api_filters28
-rw-r--r--org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitClientSession.java6
-rw-r--r--org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitServerKeyVerifier.java190
-rw-r--r--org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitSshClient.java4
-rw-r--r--org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitSshConfig.java16
-rw-r--r--org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/OpenSshServerKeyDatabase.java (renamed from org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/OpenSshServerKeyVerifier.java)420
-rw-r--r--org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/ServerKeyLookup.java6
-rw-r--r--org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/DefaultProxyDataFactory.java4
-rw-r--r--org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/ServerKeyDatabase.java177
-rw-r--r--org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/SshdSessionFactory.java53
-rw-r--r--org.eclipse.jgit.test/src/org/eclipse/jgit/transport/ssh/SshTestBase.java2
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/BatchRefUpdateTest.java29
-rw-r--r--org.eclipse.jgit/.settings/.api_filters161
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackedBatchRefUpdate.java118
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/SshConstants.java7
27 files changed, 1064 insertions, 483 deletions
diff --git a/WORKSPACE b/WORKSPACE
index 97bf81e27f..c5e4ef8361 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -15,7 +15,7 @@ versions.check(minimum_bazel_version = "0.26.1")
load("//tools:bazlets.bzl", "load_bazlets")
-load_bazlets(commit = "8528a0df69dadf6311d8d3f81c1b693afda8bcf1")
+load_bazlets(commit = "09a035e98077dce549d5f6a7472d06c4b8f792d2")
load(
"@com_googlesource_gerrit_bazlets//tools:maven_jar.bzl",
diff --git a/org.eclipse.jgit.lfs/.settings/.api_filters b/org.eclipse.jgit.lfs/.settings/.api_filters
deleted file mode 100644
index 9747df8aa2..0000000000
--- a/org.eclipse.jgit.lfs/.settings/.api_filters
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<component id="org.eclipse.jgit.lfs" version="2">
- <resource path="src/org/eclipse/jgit/lfs/lib/AnyLongObjectId.java" type="org.eclipse.jgit.lfs.lib.AnyLongObjectId">
- <filter id="1141899266">
- <message_arguments>
- <message_argument value="5.4"/>
- <message_argument value="5.5"/>
- <message_argument value="isEqual(AnyLongObjectId, AnyLongObjectId)"/>
- </message_arguments>
- </filter>
- </resource>
-</component>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.feature/feature.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.feature/feature.xml
index 57ffc3f447..fb36ac0abc 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.feature/feature.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.feature/feature.xml
@@ -18,8 +18,8 @@
</license>
<url>
- <update label="%updateSiteName" url="http://download.eclipse.org/egit/updates"/>
- <discovery label="%updateSiteName" url="http://download.eclipse.org/egit/updates"/>
+ <update label="%updateSiteName" url="https://download.eclipse.org/egit/updates"/>
+ <discovery label="%updateSiteName" url="https://download.eclipse.org/egit/updates"/>
</url>
<plugin
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.http.apache.feature/feature.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.http.apache.feature/feature.xml
index 4f77da534e..de604cd6e0 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.http.apache.feature/feature.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.http.apache.feature/feature.xml
@@ -18,8 +18,8 @@
</license>
<url>
- <update label="%updateSiteName" url="http://download.eclipse.org/egit/updates"/>
- <discovery label="%updateSiteName" url="http://download.eclipse.org/egit/updates"/>
+ <update label="%updateSiteName" url="https://download.eclipse.org/egit/updates"/>
+ <discovery label="%updateSiteName" url="https://download.eclipse.org/egit/updates"/>
</url>
<requires>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.junit.feature/feature.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.junit.feature/feature.xml
index 104e837eb9..4604b4f8a0 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.junit.feature/feature.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.junit.feature/feature.xml
@@ -18,8 +18,8 @@
</license>
<url>
- <update label="%updateSiteName" url="http://download.eclipse.org/egit/updates"/>
- <discovery label="%updateSiteName" url="http://download.eclipse.org/egit/updates"/>
+ <update label="%updateSiteName" url="https://download.eclipse.org/egit/updates"/>
+ <discovery label="%updateSiteName" url="https://download.eclipse.org/egit/updates"/>
</url>
<requires>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/feature.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/feature.xml
index 003aa8d596..1b2897d04c 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/feature.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/feature.xml
@@ -18,8 +18,8 @@
</license>
<url>
- <update label="%updateSiteName" url="http://download.eclipse.org/egit/updates"/>
- <discovery label="%updateSiteName" url="http://download.eclipse.org/egit/updates"/>
+ <update label="%updateSiteName" url="https://download.eclipse.org/egit/updates"/>
+ <discovery label="%updateSiteName" url="https://download.eclipse.org/egit/updates"/>
</url>
<requires>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.feature/feature.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.feature/feature.xml
index 33a738dd8b..dd662c9f28 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.feature/feature.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.feature/feature.xml
@@ -18,8 +18,8 @@
</license>
<url>
- <update label="%updateSiteName" url="http://download.eclipse.org/egit/updates"/>
- <discovery label="%updateSiteName" url="http://download.eclipse.org/egit/updates"/>
+ <update label="%updateSiteName" url="https://download.eclipse.org/egit/updates"/>
+ <discovery label="%updateSiteName" url="https://download.eclipse.org/egit/updates"/>
</url>
<includes
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.source.feature/feature.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.source.feature/feature.xml
index 9e6f5af41b..d4828e47c3 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.source.feature/feature.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.source.feature/feature.xml
@@ -18,8 +18,8 @@
</license>
<url>
- <update label="%updateSiteName" url="http://download.eclipse.org/egit/updates"/>
- <discovery label="%updateSiteName" url="http://download.eclipse.org/egit/updates"/>
+ <update label="%updateSiteName" url="https://download.eclipse.org/egit/updates"/>
+ <discovery label="%updateSiteName" url="https://download.eclipse.org/egit/updates"/>
</url>
<requires>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.feature/feature.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.feature/feature.xml
index 8ff596075b..2582a77e77 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.feature/feature.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.feature/feature.xml
@@ -18,8 +18,8 @@
</license>
<url>
- <update label="%updateSiteName" url="http://download.eclipse.org/egit/updates"/>
- <discovery label="%updateSiteName" url="http://download.eclipse.org/egit/updates"/>
+ <update label="%updateSiteName" url="https://download.eclipse.org/egit/updates"/>
+ <discovery label="%updateSiteName" url="https://download.eclipse.org/egit/updates"/>
</url>
<requires>
diff --git a/org.eclipse.jgit.ssh.apache.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.ssh.apache.test/META-INF/MANIFEST.MF
index b863bf3fe9..4bae3d9b6e 100644
--- a/org.eclipse.jgit.ssh.apache.test/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.ssh.apache.test/META-INF/MANIFEST.MF
@@ -7,7 +7,15 @@ Bundle-Version: 5.6.0.qualifier
Bundle-Vendor: %Bundle-Vendor
Bundle-Localization: plugin
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
-Import-Package: org.eclipse.jgit.api.errors;version="[5.6.0,5.7.0)",
+Import-Package: org.apache.sshd.client.config.hosts;version="[2.2.0,2.3.0)",
+ org.apache.sshd.common;version="[2.2.0,2.3.0)",
+ org.apache.sshd.common.auth;version="[2.2.0,2.3.0)",
+ org.apache.sshd.common.config.keys;version="[2.2.0,2.3.0)",
+ org.apache.sshd.common.keyprovider;version="[2.2.0,2.3.0)",
+ org.apache.sshd.common.session;version="[2.2.0,2.3.0)",
+ org.apache.sshd.common.util.net;version="[2.2.0,2.3.0)",
+ org.apache.sshd.common.util.security;version="[2.2.0,2.3.0)",
+ org.eclipse.jgit.api.errors;version="[5.6.0,5.7.0)",
org.eclipse.jgit.internal.transport.sshd.proxy;version="[5.6.0,5.7.0)",
org.eclipse.jgit.junit;version="[5.6.0,5.7.0)",
org.eclipse.jgit.junit.ssh;version="[5.6.0,5.7.0)",
diff --git a/org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/transport/sshd/ApacheSshTest.java b/org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/transport/sshd/ApacheSshTest.java
index df0b832a0f..b9b7353d3e 100644
--- a/org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/transport/sshd/ApacheSshTest.java
+++ b/org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/transport/sshd/ApacheSshTest.java
@@ -42,16 +42,22 @@
*/
package org.eclipse.jgit.transport.sshd;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+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 java.util.List;
+import java.util.stream.Collectors;
+import org.apache.sshd.client.config.hosts.KnownHostEntry;
import org.eclipse.jgit.api.errors.TransportException;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.transport.SshSessionFactory;
import org.eclipse.jgit.transport.ssh.SshTestBase;
-import org.eclipse.jgit.transport.sshd.SshdSessionFactory;
import org.eclipse.jgit.util.FS;
import org.junit.Test;
import org.junit.experimental.theories.Theories;
@@ -102,6 +108,51 @@ public class ApacheSshTest extends SshTestBase {
}
@Test
+ public void testHashedKnownHosts() throws Exception {
+ assertTrue("Failed to delete known_hosts", knownHosts.delete());
+ // The provider will answer "yes" to all questions, so we should be able
+ // to connect and end up with a new known_hosts file with the host key.
+ TestCredentialsProvider provider = new TestCredentialsProvider();
+ cloneWith("ssh://localhost/doesntmatter", defaultCloneDir, provider, //
+ "HashKnownHosts yes", //
+ "Host localhost", //
+ "HostName localhost", //
+ "Port " + testPort, //
+ "User " + TEST_USER, //
+ "IdentityFile " + privateKey1.getAbsolutePath());
+ List<LogEntry> messages = provider.getLog();
+ assertFalse("Expected user interaction", messages.isEmpty());
+ assertEquals(
+ "Expected to be asked about the key, and the file creation", 2,
+ messages.size());
+ assertTrue("~/.ssh/known_hosts should exist now", knownHosts.exists());
+ // Let's clone again without provider. If it works, the server host key
+ // was written correctly.
+ File clonedAgain = new File(getTemporaryDirectory(), "cloned2");
+ cloneWith("ssh://localhost/doesntmatter", clonedAgain, null, //
+ "Host localhost", //
+ "HostName localhost", //
+ "Port " + testPort, //
+ "User " + TEST_USER, //
+ "IdentityFile " + privateKey1.getAbsolutePath());
+ // Check that the first line contains neither "localhost" nor
+ // "127.0.0.1", but does contain the expected hash.
+ List<String> lines = Files.readAllLines(knownHosts.toPath()).stream()
+ .filter(s -> s != null && s.length() >= 1 && s.charAt(0) != '#'
+ && !s.trim().isEmpty())
+ .collect(Collectors.toList());
+ assertEquals("Unexpected number of known_hosts lines", 1, lines.size());
+ String line = lines.get(0);
+ assertFalse("Found host in line", line.contains("localhost"));
+ assertFalse("Found IP in line", line.contains("127.0.0.1"));
+ assertTrue("Hash not found", line.contains("|"));
+ KnownHostEntry entry = KnownHostEntry.parseKnownHostEntry(line);
+ assertTrue("Hash doesn't match localhost",
+ entry.isHostMatch("localhost", testPort)
+ || entry.isHostMatch("127.0.0.1", testPort));
+ }
+
+ @Test
public void testPreamble() throws Exception {
// Test that the client can deal with strange lines being sent before
// the server identification string.
diff --git a/org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/transport/sshd/NoFilesSshTest.java b/org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/transport/sshd/NoFilesSshTest.java
new file mode 100644
index 0000000000..c0dc2cf20e
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/transport/sshd/NoFilesSshTest.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2019 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.sshd;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UncheckedIOException;
+import java.net.InetSocketAddress;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.security.PublicKey;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.sshd.common.NamedResource;
+import org.apache.sshd.common.config.keys.KeyUtils;
+import org.apache.sshd.common.keyprovider.KeyIdentityProvider;
+import org.apache.sshd.common.session.SessionContext;
+import org.apache.sshd.common.util.net.SshdSocketAddress;
+import org.apache.sshd.common.util.security.SecurityUtils;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.transport.CredentialsProvider;
+import org.eclipse.jgit.transport.SshSessionFactory;
+import org.eclipse.jgit.transport.ssh.SshTestHarness;
+import org.eclipse.jgit.util.FS;
+import org.junit.After;
+import org.junit.Test;
+
+/**
+ * Test for using the SshdSessionFactory without files in ~/.ssh but with an
+ * in-memory setup.
+ */
+public class NoFilesSshTest extends SshTestHarness {
+
+
+ private PublicKey testServerKey;
+
+ private KeyPair testUserKey;
+
+ @Override
+ protected SshSessionFactory createSessionFactory() {
+ SshdSessionFactory result = new SshdSessionFactory(new JGitKeyCache(),
+ null) {
+
+ @Override
+ protected File getSshConfig(File dir) {
+ return null;
+ }
+
+ @Override
+ protected ServerKeyDatabase getServerKeyDatabase(File homeDir,
+ File dir) {
+ return new ServerKeyDatabase() {
+
+ @Override
+ public List<PublicKey> lookup(String connectAddress,
+ InetSocketAddress remoteAddress,
+ Configuration config) {
+ return Collections.singletonList(testServerKey);
+ }
+
+ @Override
+ public boolean accept(String connectAddress,
+ InetSocketAddress remoteAddress,
+ PublicKey serverKey, Configuration config,
+ CredentialsProvider provider) {
+ return KeyUtils.compareKeys(serverKey, testServerKey);
+ }
+
+ };
+ }
+
+ @Override
+ protected Iterable<KeyPair> getDefaultKeys(File dir) {
+ // This would work for this simple test case:
+ // return Collections.singletonList(testUserKey);
+ // But let's see if we can check the host and username that's used.
+ // For that, we need access to the sshd SessionContext:
+ return new KeyAuthenticator();
+ }
+
+ @Override
+ protected String getDefaultPreferredAuthentications() {
+ return "publickey";
+ }
+ };
+
+ // The home directory is mocked at this point!
+ result.setHomeDirectory(FS.DETECTED.userHome());
+ result.setSshDirectory(sshDir);
+ return result;
+ }
+
+ private class KeyAuthenticator implements KeyIdentityProvider, Iterable<KeyPair> {
+
+ @Override
+ public Iterator<KeyPair> iterator() {
+ // Should not be called. The use of the Iterable interface in
+ // SshdSessionFactory.getDefaultKeys() made sense in sshd 2.0.0,
+ // but sshd 2.2.0 added the SessionContext, which although good
+ // (without it we couldn't check here) breaks the Iterable analogy.
+ // But we're stuck now with that interface for getDefaultKeys, and
+ // so this override throwing an exception is unfortunately needed.
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Iterable<KeyPair> loadKeys(SessionContext session)
+ throws IOException, GeneralSecurityException {
+ if (!TEST_USER.equals(session.getUsername())) {
+ return Collections.emptyList();
+ }
+ SshdSocketAddress remoteAddress = SshdSocketAddress
+ .toSshdSocketAddress(session.getRemoteAddress());
+ switch (remoteAddress.getHostName()) {
+ case "localhost":
+ case "127.0.0.1":
+ return Collections.singletonList(testUserKey);
+ default:
+ return Collections.emptyList();
+ }
+ }
+ }
+
+ @After
+ public void cleanUp() {
+ testServerKey = null;
+ testUserKey = null;
+ }
+
+ @Override
+ protected void installConfig(String... config) {
+ File configFile = new File(sshDir, Constants.CONFIG);
+ if (config != null) {
+ try {
+ Files.write(configFile.toPath(), Arrays.asList(config));
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+ }
+
+ private KeyPair load(Path path) throws Exception {
+ try (InputStream in = Files.newInputStream(path)) {
+ return SecurityUtils
+ .loadKeyPairIdentities(null,
+ NamedResource.ofName(path.toString()), in, null)
+ .iterator().next();
+ }
+ }
+
+ @Test
+ public void testCloneWithBuiltInKeys() throws Exception {
+ // This test should fail unless our in-memory setup is taken: no
+ // known_hosts file, and a config that specifies a non-existing key.
+ File newHostKey = new File(getTemporaryDirectory(), "newhostkey");
+ copyTestResource("id_ed25519", newHostKey);
+ server.addHostKey(newHostKey.toPath(), true);
+ testServerKey = load(newHostKey.toPath()).getPublic();
+ assertTrue(newHostKey.delete());
+ testUserKey = load(privateKey1.getAbsoluteFile().toPath());
+ assertNotNull(testServerKey);
+ assertNotNull(testUserKey);
+ cloneWith(
+ "ssh://" + TEST_USER + "@localhost:" + testPort
+ + "/doesntmatter",
+ new File(getTemporaryDirectory(), "cloned"), null, //
+ "Host localhost", //
+ "IdentityFile "
+ + new File(sshDir, "does_not_exist").getAbsolutePath());
+ }
+
+}
diff --git a/org.eclipse.jgit.ssh.apache/.settings/.api_filters b/org.eclipse.jgit.ssh.apache/.settings/.api_filters
new file mode 100644
index 0000000000..a0e469509d
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/.settings/.api_filters
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<component id="org.eclipse.jgit.ssh.apache" version="2">
+ <resource path="src/org/eclipse/jgit/transport/sshd/ServerKeyDatabase.java" type="org.eclipse.jgit.transport.sshd.ServerKeyDatabase">
+ <filter id="1108344834">
+ <message_arguments>
+ <message_argument value="5.5"/>
+ <message_argument value="5.6"/>
+ <message_argument value="org.eclipse.jgit.transport.sshd.ServerKeyDatabase"/>
+ </message_arguments>
+ </filter>
+ </resource>
+ <resource path="src/org/eclipse/jgit/transport/sshd/SshdSessionFactory.java" type="org.eclipse.jgit.transport.sshd.SshdSessionFactory">
+ <filter id="1141899266">
+ <message_arguments>
+ <message_argument value="5.5"/>
+ <message_argument value="5.6"/>
+ <message_argument value="getServerKeyDatabase(File, File)"/>
+ </message_arguments>
+ </filter>
+ <filter id="1141899266">
+ <message_arguments>
+ <message_argument value="5.5"/>
+ <message_argument value="5.6"/>
+ <message_argument value="getSshConfig(File)"/>
+ </message_arguments>
+ </filter>
+ </resource>
+</component>
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitClientSession.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitClientSession.java
index 4ce4f6aade..1954abc75b 100644
--- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitClientSession.java
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitClientSession.java
@@ -57,7 +57,6 @@ import java.util.Set;
import org.apache.sshd.client.ClientFactoryManager;
import org.apache.sshd.client.config.hosts.HostConfigEntry;
-import org.apache.sshd.client.keyverifier.KnownHostsServerKeyVerifier.HostEntryPair;
import org.apache.sshd.client.keyverifier.ServerKeyVerifier;
import org.apache.sshd.client.session.ClientSessionImpl;
import org.apache.sshd.common.FactoryManager;
@@ -293,11 +292,10 @@ public class JGitClientSession extends ClientSessionImpl {
if (verifier instanceof ServerKeyLookup) {
SocketAddress remoteAddress = resolvePeerAddress(
resolveAttribute(JGitSshClient.ORIGINAL_REMOTE_ADDRESS));
- List<HostEntryPair> allKnownKeys = ((ServerKeyLookup) verifier)
+ List<PublicKey> allKnownKeys = ((ServerKeyLookup) verifier)
.lookup(this, remoteAddress);
Set<String> reordered = new LinkedHashSet<>();
- for (HostEntryPair h : allKnownKeys) {
- PublicKey key = h.getServerKey();
+ for (PublicKey key : allKnownKeys) {
if (key != null) {
String keyType = KeyUtils.getKeyType(key);
if (keyType != null) {
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitServerKeyVerifier.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitServerKeyVerifier.java
new file mode 100644
index 0000000000..b94515c157
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitServerKeyVerifier.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2019 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.internal.transport.sshd;
+
+import static org.eclipse.jgit.internal.transport.ssh.OpenSshConfigFile.flag;
+
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.security.PublicKey;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+
+import org.apache.sshd.client.config.hosts.HostConfigEntry;
+import org.apache.sshd.client.config.hosts.KnownHostHashValue;
+import org.apache.sshd.client.keyverifier.ServerKeyVerifier;
+import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.common.util.net.SshdSocketAddress;
+import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.transport.CredentialsProvider;
+import org.eclipse.jgit.transport.SshConstants;
+import org.eclipse.jgit.transport.sshd.ServerKeyDatabase;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A bridge between the {@link ServerKeyVerifier} from Apache MINA sshd and our
+ * {@link ServerKeyDatabase}.
+ */
+public class JGitServerKeyVerifier
+ implements ServerKeyVerifier, ServerKeyLookup {
+
+ private static final Logger LOG = LoggerFactory
+ .getLogger(JGitServerKeyVerifier.class);
+
+ private final @NonNull ServerKeyDatabase database;
+
+ /**
+ * Creates a new {@link JGitServerKeyVerifier} using the given
+ * {@link ServerKeyDatabase}.
+ *
+ * @param database
+ * to use
+ */
+ public JGitServerKeyVerifier(@NonNull ServerKeyDatabase database) {
+ this.database = database;
+ }
+
+ @Override
+ public List<PublicKey> lookup(ClientSession session,
+ SocketAddress remoteAddress) {
+ if (!(session instanceof JGitClientSession)) {
+ LOG.warn("Internal error: wrong session kind: " //$NON-NLS-1$
+ + session.getClass().getName());
+ return Collections.emptyList();
+ }
+ if (!(remoteAddress instanceof InetSocketAddress)) {
+ return Collections.emptyList();
+ }
+ SessionConfig config = new SessionConfig((JGitClientSession) session);
+ SshdSocketAddress connectAddress = SshdSocketAddress
+ .toSshdSocketAddress(session.getConnectAddress());
+ String connect = KnownHostHashValue.createHostPattern(
+ connectAddress.getHostName(), connectAddress.getPort());
+ return database.lookup(connect, (InetSocketAddress) remoteAddress,
+ config);
+ }
+
+ @Override
+ public boolean verifyServerKey(ClientSession session,
+ SocketAddress remoteAddress, PublicKey serverKey) {
+ if (!(session instanceof JGitClientSession)) {
+ LOG.warn("Internal error: wrong session kind: " //$NON-NLS-1$
+ + session.getClass().getName());
+ return false;
+ }
+ if (!(remoteAddress instanceof InetSocketAddress)) {
+ return false;
+ }
+ SessionConfig config = new SessionConfig((JGitClientSession) session);
+ SshdSocketAddress connectAddress = SshdSocketAddress
+ .toSshdSocketAddress(session.getConnectAddress());
+ String connect = KnownHostHashValue.createHostPattern(
+ connectAddress.getHostName(), connectAddress.getPort());
+ CredentialsProvider provider = ((JGitClientSession) session)
+ .getCredentialsProvider();
+ return database.accept(connect, (InetSocketAddress) remoteAddress,
+ serverKey, config, provider);
+ }
+
+ private static class SessionConfig
+ implements ServerKeyDatabase.Configuration {
+
+ private final JGitClientSession session;
+
+ public SessionConfig(JGitClientSession session) {
+ this.session = session;
+ }
+
+ private List<String> get(String key) {
+ HostConfigEntry entry = session.getHostConfigEntry();
+ if (entry instanceof JGitHostConfigEntry) {
+ // Always true!
+ return ((JGitHostConfigEntry) entry).getMultiValuedOptions()
+ .get(key);
+ }
+ return Collections.emptyList();
+ }
+
+ @Override
+ public List<String> getUserKnownHostsFiles() {
+ return get(SshConstants.USER_KNOWN_HOSTS_FILE);
+ }
+
+ @Override
+ public List<String> getGlobalKnownHostsFiles() {
+ return get(SshConstants.GLOBAL_KNOWN_HOSTS_FILE);
+ }
+
+ @Override
+ public StrictHostKeyChecking getStrictHostKeyChecking() {
+ HostConfigEntry entry = session.getHostConfigEntry();
+ String value = entry
+ .getProperty(SshConstants.STRICT_HOST_KEY_CHECKING, "ask"); //$NON-NLS-1$
+ switch (value.toLowerCase(Locale.ROOT)) {
+ case SshConstants.YES:
+ case SshConstants.ON:
+ return StrictHostKeyChecking.REQUIRE_MATCH;
+ case SshConstants.NO:
+ case SshConstants.OFF:
+ return StrictHostKeyChecking.ACCEPT_ANY;
+ case "accept-new": //$NON-NLS-1$
+ return StrictHostKeyChecking.ACCEPT_NEW;
+ default:
+ return StrictHostKeyChecking.ASK;
+ }
+ }
+
+ @Override
+ public boolean getHashKnownHosts() {
+ HostConfigEntry entry = session.getHostConfigEntry();
+ return flag(entry.getProperty(SshConstants.HASH_KNOWN_HOSTS));
+ }
+
+ @Override
+ public String getUsername() {
+ return session.getUsername();
+ }
+ }
+}
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitSshClient.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitSshClient.java
index 98e71dfe4b..377eabb00a 100644
--- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitSshClient.java
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitSshClient.java
@@ -177,6 +177,10 @@ public class JGitSshClient extends SshClient {
return remoteAddress;
}
InetSocketAddress address = (InetSocketAddress) proxy.address();
+ if (address.isUnresolved()) {
+ address = new InetSocketAddress(address.getHostName(),
+ address.getPort());
+ }
switch (proxy.type()) {
case HTTP:
setClientProxyConnector(
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitSshConfig.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitSshConfig.java
index 6468b3e276..54a2a052a7 100644
--- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitSshConfig.java
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitSshConfig.java
@@ -83,7 +83,9 @@ import org.eclipse.jgit.transport.SshConstants;
*/
public class JGitSshConfig implements HostConfigEntryResolver {
- private OpenSshConfigFile configFile;
+ private final OpenSshConfigFile configFile;
+
+ private final String localUserName;
/**
* Creates a new {@link OpenSshConfigFile} that will read the config from
@@ -92,20 +94,22 @@ public class JGitSshConfig implements HostConfigEntryResolver {
* @param home
* user's home directory for the purpose of ~ replacement
* @param config
- * file to load.
+ * file to load; may be {@code null} if no ssh config file
+ * handling is desired
* @param localUserName
* user name of the current user on the local host OS
*/
- public JGitSshConfig(@NonNull File home, @NonNull File config,
+ public JGitSshConfig(@NonNull File home, File config,
@NonNull String localUserName) {
- configFile = new OpenSshConfigFile(home, config, localUserName);
+ this.localUserName = localUserName;
+ configFile = config == null ? null : new OpenSshConfigFile(home, config, localUserName);
}
@Override
public HostConfigEntry resolveEffectiveHost(String host, int port,
SocketAddress localAddress, String username,
AttributeRepository attributes) throws IOException {
- HostEntry entry = configFile.lookup(host, port, username);
+ HostEntry entry = configFile == null ? new HostEntry() : configFile.lookup(host, port, username);
JGitHostConfigEntry config = new JGitHostConfigEntry();
// Apache MINA conflates all keys, even multi-valued ones, in one map
// and puts multiple values separated by commas in one string. See
@@ -131,7 +135,7 @@ public class JGitSshConfig implements HostConfigEntryResolver {
String user = username != null && !username.isEmpty() ? username
: entry.getValue(SshConstants.USER);
if (user == null || user.isEmpty()) {
- user = configFile.getLocalUserName();
+ user = localUserName;
}
config.setUsername(user);
config.setProperty(SshConstants.USER, user);
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/OpenSshServerKeyVerifier.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/OpenSshServerKeyDatabase.java
index 381f7cfc22..f4849ce4a3 100644
--- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/OpenSshServerKeyVerifier.java
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/OpenSshServerKeyDatabase.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch>
+ * Copyright (C) 2018, 2019 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
@@ -47,7 +47,6 @@ import static java.text.MessageFormat.format;
import java.io.BufferedReader;
import java.io.BufferedWriter;
-import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStreamWriter;
@@ -59,34 +58,39 @@ import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.GeneralSecurityException;
import java.security.PublicKey;
+import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
-import java.util.Locale;
import java.util.Map;
+import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Supplier;
-import org.apache.sshd.client.config.hosts.HostConfigEntry;
+import org.apache.sshd.client.config.hosts.HostPatternsHolder;
+import org.apache.sshd.client.config.hosts.KnownHostDigest;
import org.apache.sshd.client.config.hosts.KnownHostEntry;
-import org.apache.sshd.client.keyverifier.KnownHostsServerKeyVerifier;
+import org.apache.sshd.client.config.hosts.KnownHostHashValue;
import org.apache.sshd.client.keyverifier.KnownHostsServerKeyVerifier.HostEntryPair;
-import org.apache.sshd.client.keyverifier.ServerKeyVerifier;
import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.common.NamedFactory;
import org.apache.sshd.common.config.keys.AuthorizedKeyEntry;
import org.apache.sshd.common.config.keys.KeyUtils;
+import org.apache.sshd.common.config.keys.PublicKeyEntry;
import org.apache.sshd.common.config.keys.PublicKeyEntryResolver;
import org.apache.sshd.common.digest.BuiltinDigests;
+import org.apache.sshd.common.mac.Mac;
import org.apache.sshd.common.util.io.ModifiableFileWatcher;
import org.apache.sshd.common.util.net.SshdSocketAddress;
+import org.eclipse.jgit.annotations.NonNull;
import org.eclipse.jgit.internal.storage.file.LockFile;
import org.eclipse.jgit.transport.CredentialItem;
import org.eclipse.jgit.transport.CredentialsProvider;
-import org.eclipse.jgit.transport.SshConstants;
import org.eclipse.jgit.transport.URIish;
+import org.eclipse.jgit.transport.sshd.ServerKeyDatabase;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -148,14 +152,14 @@ import org.slf4j.LoggerFactory;
* @see <a href="http://man.openbsd.org/OpenBSD-current/man5/ssh_config.5">man
* ssh-config</a>
*/
-public class OpenSshServerKeyVerifier
- implements ServerKeyVerifier, ServerKeyLookup {
+public class OpenSshServerKeyDatabase
+ implements ServerKeyDatabase {
// TODO: GlobalKnownHostsFile? May need some kind of LRU caching; these
// files may be large!
private static final Logger LOG = LoggerFactory
- .getLogger(OpenSshServerKeyVerifier.class);
+ .getLogger(OpenSshServerKeyDatabase.class);
/** Can be used to mark revoked known host lines. */
private static final String MARKER_REVOKED = "revoked"; //$NON-NLS-1$
@@ -166,12 +170,8 @@ public class OpenSshServerKeyVerifier
private final List<HostKeyFile> defaultFiles = new ArrayList<>();
- private enum ModifiedKeyHandling {
- DENY, ALLOW, ALLOW_AND_STORE
- }
-
/**
- * Creates a new {@link OpenSshServerKeyVerifier}.
+ * Creates a new {@link OpenSshServerKeyDatabase}.
*
* @param askAboutNewFile
* whether to ask the user, if possible, about creating a new
@@ -181,7 +181,7 @@ public class OpenSshServerKeyVerifier
* empty or {@code null}, in which case no default files are
* installed. The files need not exist.
*/
- public OpenSshServerKeyVerifier(boolean askAboutNewFile,
+ public OpenSshServerKeyDatabase(boolean askAboutNewFile,
List<Path> defaultFiles) {
if (defaultFiles != null) {
for (Path file : defaultFiles) {
@@ -193,38 +193,30 @@ public class OpenSshServerKeyVerifier
this.askAboutNewFile = askAboutNewFile;
}
- private List<HostKeyFile> getFilesToUse(ClientSession session) {
+ private List<HostKeyFile> getFilesToUse(@NonNull Configuration config) {
List<HostKeyFile> filesToUse = defaultFiles;
- if (session instanceof JGitClientSession) {
- HostConfigEntry entry = ((JGitClientSession) session)
- .getHostConfigEntry();
- if (entry instanceof JGitHostConfigEntry) {
- // Always true!
- List<HostKeyFile> userFiles = addUserHostKeyFiles(
- ((JGitHostConfigEntry) entry).getMultiValuedOptions()
- .get(SshConstants.USER_KNOWN_HOSTS_FILE));
- if (!userFiles.isEmpty()) {
- filesToUse = userFiles;
- }
- }
+ List<HostKeyFile> userFiles = addUserHostKeyFiles(
+ config.getUserKnownHostsFiles());
+ if (!userFiles.isEmpty()) {
+ filesToUse = userFiles;
}
return filesToUse;
}
@Override
- public List<HostEntryPair> lookup(ClientSession session,
- SocketAddress remote) {
- List<HostKeyFile> filesToUse = getFilesToUse(session);
- HostKeyHelper helper = new HostKeyHelper();
- List<HostEntryPair> result = new ArrayList<>();
- Collection<SshdSocketAddress> candidates = helper
- .resolveHostNetworkIdentities(session, remote);
+ public List<PublicKey> lookup(@NonNull String connectAddress,
+ @NonNull InetSocketAddress remoteAddress,
+ @NonNull Configuration config) {
+ List<HostKeyFile> filesToUse = getFilesToUse(config);
+ List<PublicKey> result = new ArrayList<>();
+ Collection<SshdSocketAddress> candidates = getCandidates(
+ connectAddress, remoteAddress);
for (HostKeyFile file : filesToUse) {
for (HostEntryPair current : file.get()) {
KnownHostEntry entry = current.getHostEntry();
for (SshdSocketAddress host : candidates) {
if (entry.isHostMatch(host.getHostName(), host.getPort())) {
- result.add(current);
+ result.add(current.getServerKey());
break;
}
}
@@ -234,22 +226,23 @@ public class OpenSshServerKeyVerifier
}
@Override
- public boolean verifyServerKey(ClientSession clientSession,
- SocketAddress remoteAddress, PublicKey serverKey) {
- List<HostKeyFile> filesToUse = getFilesToUse(clientSession);
- AskUser ask = new AskUser();
+ public boolean accept(@NonNull String connectAddress,
+ @NonNull InetSocketAddress remoteAddress,
+ @NonNull PublicKey serverKey,
+ @NonNull Configuration config, CredentialsProvider provider) {
+ List<HostKeyFile> filesToUse = getFilesToUse(config);
+ AskUser ask = new AskUser(config, provider);
HostEntryPair[] modified = { null };
Path path = null;
- HostKeyHelper helper = new HostKeyHelper();
+ Collection<SshdSocketAddress> candidates = getCandidates(connectAddress,
+ remoteAddress);
for (HostKeyFile file : filesToUse) {
try {
- if (find(clientSession, remoteAddress, serverKey, file.get(),
- modified, helper)) {
+ if (find(candidates, serverKey, file.get(), modified)) {
return true;
}
} catch (RevokedKeyException e) {
- ask.revokedKey(clientSession, remoteAddress, serverKey,
- file.getPath());
+ ask.revokedKey(remoteAddress, serverKey, file.getPath());
return false;
}
if (path == null && modified[0] != null) {
@@ -260,20 +253,19 @@ public class OpenSshServerKeyVerifier
}
if (modified[0] != null) {
// We found an entry, but with a different key
- ModifiedKeyHandling toDo = ask.acceptModifiedServerKey(
- clientSession, remoteAddress, modified[0].getServerKey(),
+ AskUser.ModifiedKeyHandling toDo = ask.acceptModifiedServerKey(
+ remoteAddress, modified[0].getServerKey(),
serverKey, path);
- if (toDo == ModifiedKeyHandling.ALLOW_AND_STORE) {
+ if (toDo == AskUser.ModifiedKeyHandling.ALLOW_AND_STORE) {
try {
- updateModifiedServerKey(clientSession, remoteAddress,
- serverKey, modified[0], path, helper);
+ updateModifiedServerKey(serverKey, modified[0], path);
knownHostsFiles.get(path).resetReloadAttributes();
} catch (IOException e) {
LOG.warn(format(SshdText.get().knownHostsCouldNotUpdate,
path));
}
}
- if (toDo == ModifiedKeyHandling.DENY) {
+ if (toDo == AskUser.ModifiedKeyHandling.DENY) {
return false;
}
// TODO: OpenSsh disables password and keyboard-interactive
@@ -281,18 +273,20 @@ public class OpenSshServerKeyVerifier
// are switched off. (Plus a few other things such as X11 forwarding
// that are of no interest to a git client.)
return true;
- } else if (ask.acceptUnknownKey(clientSession, remoteAddress,
- serverKey)) {
+ } else if (ask.acceptUnknownKey(remoteAddress, serverKey)) {
if (!filesToUse.isEmpty()) {
HostKeyFile toUpdate = filesToUse.get(0);
path = toUpdate.getPath();
try {
- updateKnownHostsFile(clientSession, remoteAddress,
- serverKey, path, helper);
- toUpdate.resetReloadAttributes();
- } catch (IOException e) {
+ if (Files.exists(path) || !askAboutNewFile
+ || ask.createNewFile(path)) {
+ updateKnownHostsFile(candidates, serverKey, path,
+ config);
+ toUpdate.resetReloadAttributes();
+ }
+ } catch (Exception e) {
LOG.warn(format(SshdText.get().knownHostsCouldNotUpdate,
- path));
+ path), e);
}
}
return true;
@@ -304,12 +298,9 @@ public class OpenSshServerKeyVerifier
private static final long serialVersionUID = 1L;
}
- private boolean find(ClientSession clientSession,
- SocketAddress remoteAddress, PublicKey serverKey,
- List<HostEntryPair> entries, HostEntryPair[] modified,
- HostKeyHelper helper) throws RevokedKeyException {
- Collection<SshdSocketAddress> candidates = helper
- .resolveHostNetworkIdentities(clientSession, remoteAddress);
+ private boolean find(Collection<SshdSocketAddress> candidates,
+ PublicKey serverKey, List<HostEntryPair> entries,
+ HostEntryPair[] modified) throws RevokedKeyException {
for (HostEntryPair current : entries) {
KnownHostEntry entry = current.getHostEntry();
for (SshdSocketAddress host : candidates) {
@@ -355,33 +346,13 @@ public class OpenSshServerKeyVerifier
return userFiles;
}
- private void updateKnownHostsFile(ClientSession clientSession,
- SocketAddress remoteAddress, PublicKey serverKey, Path path,
- HostKeyHelper updater)
- throws IOException {
- KnownHostEntry entry = updater.prepareKnownHostEntry(clientSession,
- remoteAddress, serverKey);
- if (entry == null) {
+ private void updateKnownHostsFile(Collection<SshdSocketAddress> candidates,
+ PublicKey serverKey, Path path, Configuration config)
+ throws Exception {
+ String newEntry = createHostKeyLine(candidates, serverKey, config);
+ if (newEntry == null) {
return;
}
- if (!Files.exists(path)) {
- if (askAboutNewFile) {
- CredentialsProvider provider = getCredentialsProvider(
- clientSession);
- if (provider == null) {
- // We can't ask, so don't create the file
- return;
- }
- URIish uri = new URIish().setPath(path.toString());
- if (!askUser(provider, uri, //
- format(SshdText.get().knownHostsUserAskCreationPrompt,
- path), //
- format(SshdText.get().knownHostsUserAskCreationMsg,
- path))) {
- return;
- }
- }
- }
LockFile lock = new LockFile(path.toFile());
if (lock.lockForAppend()) {
try {
@@ -389,7 +360,7 @@ public class OpenSshServerKeyVerifier
new OutputStreamWriter(lock.getOutputStream(),
UTF_8))) {
writer.newLine();
- writer.write(entry.getConfigLine());
+ writer.write(newEntry);
writer.newLine();
}
lock.commit();
@@ -403,15 +374,12 @@ public class OpenSshServerKeyVerifier
}
}
- private void updateModifiedServerKey(ClientSession clientSession,
- SocketAddress remoteAddress, PublicKey serverKey,
- HostEntryPair entry, Path path, HostKeyHelper helper)
+ private void updateModifiedServerKey(PublicKey serverKey,
+ HostEntryPair entry, Path path)
throws IOException {
KnownHostEntry hostEntry = entry.getHostEntry();
String oldLine = hostEntry.getConfigLine();
- String newLine = helper.prepareModifiedServerKeyLine(clientSession,
- remoteAddress, hostEntry, oldLine, entry.getServerKey(),
- serverKey);
+ String newLine = updateHostKeyLine(oldLine, serverKey);
if (newLine == null || newLine.isEmpty()) {
return;
}
@@ -454,78 +422,65 @@ public class OpenSshServerKeyVerifier
}
}
- private static CredentialsProvider getCredentialsProvider(
- ClientSession session) {
- if (session instanceof JGitClientSession) {
- return ((JGitClientSession) session).getCredentialsProvider();
- }
- return null;
- }
+ private static class AskUser {
- private static boolean askUser(CredentialsProvider provider, URIish uri,
- String prompt, String... messages) {
- List<CredentialItem> items = new ArrayList<>(messages.length + 1);
- for (String message : messages) {
- items.add(new CredentialItem.InformationalMessage(message));
- }
- if (prompt != null) {
- CredentialItem.YesNoType answer = new CredentialItem.YesNoType(
- prompt);
- items.add(answer);
- return provider.get(uri, items) && answer.getValue();
- } else {
- return provider.get(uri, items);
+ public enum ModifiedKeyHandling {
+ DENY, ALLOW, ALLOW_AND_STORE
}
- }
-
- private static class AskUser {
private enum Check {
ASK, DENY, ALLOW;
}
- @SuppressWarnings("nls")
- private Check checkMode(ClientSession session,
- SocketAddress remoteAddress, boolean changed) {
+ private final @NonNull Configuration config;
+
+ private final CredentialsProvider provider;
+
+ public AskUser(@NonNull Configuration config,
+ CredentialsProvider provider) {
+ this.config = config;
+ this.provider = provider;
+ }
+
+ private static boolean askUser(CredentialsProvider provider, URIish uri,
+ String prompt, String... messages) {
+ List<CredentialItem> items = new ArrayList<>(messages.length + 1);
+ for (String message : messages) {
+ items.add(new CredentialItem.InformationalMessage(message));
+ }
+ if (prompt != null) {
+ CredentialItem.YesNoType answer = new CredentialItem.YesNoType(
+ prompt);
+ items.add(answer);
+ return provider.get(uri, items) && answer.getValue();
+ } else {
+ return provider.get(uri, items);
+ }
+ }
+
+ private Check checkMode(SocketAddress remoteAddress, boolean changed) {
if (!(remoteAddress instanceof InetSocketAddress)) {
return Check.DENY;
}
- if (session instanceof JGitClientSession) {
- HostConfigEntry entry = ((JGitClientSession) session)
- .getHostConfigEntry();
- String value = entry.getProperty(
- SshConstants.STRICT_HOST_KEY_CHECKING, "ask");
- switch (value.toLowerCase(Locale.ROOT)) {
- case SshConstants.YES:
- case SshConstants.ON:
- return Check.DENY;
- case SshConstants.NO:
- case SshConstants.OFF:
- return Check.ALLOW;
- case "accept-new":
- return changed ? Check.DENY : Check.ALLOW;
- default:
- break;
- }
- }
- if (getCredentialsProvider(session) == null) {
- // This is called only for new, unknown hosts. If we have no way
- // to interact with the user, the fallback mode is to deny the
- // key.
+ switch (config.getStrictHostKeyChecking()) {
+ case REQUIRE_MATCH:
return Check.DENY;
+ case ACCEPT_ANY:
+ return Check.ALLOW;
+ case ACCEPT_NEW:
+ return changed ? Check.DENY : Check.ALLOW;
+ default:
+ return provider == null ? Check.DENY : Check.ASK;
}
- return Check.ASK;
}
- public void revokedKey(ClientSession clientSession,
- SocketAddress remoteAddress, PublicKey serverKey, Path path) {
- CredentialsProvider provider = getCredentialsProvider(
- clientSession);
+ public void revokedKey(SocketAddress remoteAddress, PublicKey serverKey,
+ Path path) {
if (provider == null) {
return;
}
InetSocketAddress remote = (InetSocketAddress) remoteAddress;
- URIish uri = JGitUserInteraction.toURI(clientSession.getUsername(),
+ URIish uri = JGitUserInteraction.toURI(config.getUsername(),
remote);
String sha256 = KeyUtils.getFingerPrint(BuiltinDigests.sha256,
serverKey);
@@ -539,14 +494,12 @@ public class OpenSshServerKeyVerifier
md5, sha256);
}
- public boolean acceptUnknownKey(ClientSession clientSession,
- SocketAddress remoteAddress, PublicKey serverKey) {
- Check check = checkMode(clientSession, remoteAddress, false);
+ public boolean acceptUnknownKey(SocketAddress remoteAddress,
+ PublicKey serverKey) {
+ Check check = checkMode(remoteAddress, false);
if (check != Check.ASK) {
return check == Check.ALLOW;
}
- CredentialsProvider provider = getCredentialsProvider(
- clientSession);
InetSocketAddress remote = (InetSocketAddress) remoteAddress;
// Ask the user
String sha256 = KeyUtils.getFingerPrint(BuiltinDigests.sha256,
@@ -554,7 +507,7 @@ public class OpenSshServerKeyVerifier
String md5 = KeyUtils.getFingerPrint(BuiltinDigests.md5, serverKey);
String keyAlgorithm = serverKey.getAlgorithm();
String remoteHost = remote.getHostString();
- URIish uri = JGitUserInteraction.toURI(clientSession.getUsername(),
+ URIish uri = JGitUserInteraction.toURI(config.getUsername(),
remote);
String prompt = SshdText.get().knownHostsUnknownKeyPrompt;
return askUser(provider, uri, prompt, //
@@ -566,19 +519,17 @@ public class OpenSshServerKeyVerifier
}
public ModifiedKeyHandling acceptModifiedServerKey(
- ClientSession clientSession,
- SocketAddress remoteAddress, PublicKey expected,
+ InetSocketAddress remoteAddress, PublicKey expected,
PublicKey actual, Path path) {
- Check check = checkMode(clientSession, remoteAddress, true);
+ Check check = checkMode(remoteAddress, true);
if (check == Check.ALLOW) {
// Never auto-store on CHECK.ALLOW
return ModifiedKeyHandling.ALLOW;
}
- InetSocketAddress remote = (InetSocketAddress) remoteAddress;
String keyAlgorithm = actual.getAlgorithm();
- String remoteHost = remote.getHostString();
- URIish uri = JGitUserInteraction.toURI(clientSession.getUsername(),
- remote);
+ String remoteHost = remoteAddress.getHostString();
+ URIish uri = JGitUserInteraction.toURI(config.getUsername(),
+ remoteAddress);
List<String> messages = new ArrayList<>();
String warning = format(
SshdText.get().knownHostsModifiedKeyWarning,
@@ -589,8 +540,6 @@ public class OpenSshServerKeyVerifier
KeyUtils.getFingerPrint(BuiltinDigests.sha256, actual));
messages.addAll(Arrays.asList(warning.split("\n"))); //$NON-NLS-1$
- CredentialsProvider provider = getCredentialsProvider(
- clientSession);
if (check == Check.DENY) {
if (provider != null) {
messages.add(format(
@@ -618,6 +567,17 @@ public class OpenSshServerKeyVerifier
return ModifiedKeyHandling.DENY;
}
+ public boolean createNewFile(Path path) {
+ if (provider == null) {
+ // We can't ask, so don't create the file
+ return false;
+ }
+ URIish uri = new URIish().setPath(path.toString());
+ return askUser(provider, uri, //
+ format(SshdText.get().knownHostsUserAskCreationPrompt,
+ path), //
+ format(SshdText.get().knownHostsUserAskCreationMsg, path));
+ }
}
private static class HostKeyFile extends ModifiableFileWatcher
@@ -694,50 +654,108 @@ public class OpenSshServerKeyVerifier
}
}
- // The stuff below is just a hack to avoid having to copy a lot of code from
- // KnownHostsServerKeyVerifier
-
- private static class HostKeyHelper extends KnownHostsServerKeyVerifier {
-
- public HostKeyHelper() {
- // These two arguments will never be used in any way.
- super((c, r, s) -> false, new File(".").toPath()); //$NON-NLS-1$
+ private int parsePort(String s) {
+ try {
+ return Integer.parseInt(s);
+ } catch (NumberFormatException e) {
+ return -1;
}
+ }
- @Override
- protected KnownHostEntry prepareKnownHostEntry(
- ClientSession clientSession, SocketAddress remoteAddress,
- PublicKey serverKey) throws IOException {
- // Make this method accessible
- try {
- return super.prepareKnownHostEntry(clientSession, remoteAddress,
- serverKey);
- } catch (Exception e) {
- throw new IOException(e.getMessage(), e);
+ private SshdSocketAddress toSshdSocketAddress(@NonNull String address) {
+ String host = null;
+ int port = 0;
+ if (HostPatternsHolder.NON_STANDARD_PORT_PATTERN_ENCLOSURE_START_DELIM == address
+ .charAt(0)) {
+ int end = address.indexOf(
+ HostPatternsHolder.NON_STANDARD_PORT_PATTERN_ENCLOSURE_END_DELIM);
+ if (end <= 1) {
+ return null; // Invalid
}
+ host = address.substring(1, end);
+ if (end < address.length() - 1
+ && HostPatternsHolder.PORT_VALUE_DELIMITER == address
+ .charAt(end + 1)) {
+ port = parsePort(address.substring(end + 2));
+ }
+ } else {
+ int i = address
+ .lastIndexOf(HostPatternsHolder.PORT_VALUE_DELIMITER);
+ if (i > 0) {
+ port = parsePort(address.substring(i + 1));
+ host = address.substring(0, i);
+ } else {
+ host = address;
+ }
+ }
+ if (port < 0 || port > 65535) {
+ return null;
}
+ return new SshdSocketAddress(host, port);
+ }
- @Override
- protected String prepareModifiedServerKeyLine(
- ClientSession clientSession, SocketAddress remoteAddress,
- KnownHostEntry entry, String curLine, PublicKey expected,
- PublicKey actual) throws IOException {
- // Make this method accessible
- try {
- return super.prepareModifiedServerKeyLine(clientSession,
- remoteAddress, entry, curLine, expected, actual);
- } catch (Exception e) {
- throw new IOException(e.getMessage(), e);
+ private Collection<SshdSocketAddress> getCandidates(
+ @NonNull String connectAddress,
+ @NonNull InetSocketAddress remoteAddress) {
+ Collection<SshdSocketAddress> candidates = new TreeSet<>(
+ SshdSocketAddress.BY_HOST_AND_PORT);
+ candidates.add(SshdSocketAddress.toSshdSocketAddress(remoteAddress));
+ SshdSocketAddress address = toSshdSocketAddress(connectAddress);
+ if (address != null) {
+ candidates.add(address);
+ }
+ return candidates;
+ }
+
+ private String createHostKeyLine(Collection<SshdSocketAddress> patterns,
+ PublicKey key, Configuration config) throws Exception {
+ StringBuilder result = new StringBuilder();
+ if (config.getHashKnownHosts()) {
+ // SHA1 is the only algorithm for host name hashing known to OpenSSH
+ // or to Apache MINA sshd.
+ NamedFactory<Mac> digester = KnownHostDigest.SHA1;
+ Mac mac = digester.create();
+ SecureRandom prng = new SecureRandom();
+ byte[] salt = new byte[mac.getDefaultBlockSize()];
+ for (SshdSocketAddress address : patterns) {
+ if (result.length() > 0) {
+ result.append(',');
+ }
+ prng.nextBytes(salt);
+ KnownHostHashValue.append(result, digester, salt,
+ KnownHostHashValue.calculateHashValue(
+ address.getHostName(), address.getPort(), mac,
+ salt));
+ }
+ } else {
+ for (SshdSocketAddress address : patterns) {
+ if (result.length() > 0) {
+ result.append(',');
+ }
+ KnownHostHashValue.appendHostPattern(result,
+ address.getHostName(), address.getPort());
}
}
+ result.append(' ');
+ PublicKeyEntry.appendPublicKeyEntry(result, key);
+ return result.toString();
+ }
- @Override
- protected Collection<SshdSocketAddress> resolveHostNetworkIdentities(
- ClientSession clientSession, SocketAddress remoteAddress) {
- // Make this method accessible
- return super.resolveHostNetworkIdentities(clientSession,
- remoteAddress);
+ private String updateHostKeyLine(String line, PublicKey newKey)
+ throws IOException {
+ // Replaces an existing public key by the new key
+ int pos = line.indexOf(' ');
+ if (pos > 0 && line.charAt(0) == KnownHostEntry.MARKER_INDICATOR) {
+ // We're at the end of the marker. Skip ahead to the next blank.
+ pos = line.indexOf(' ', pos + 1);
+ }
+ if (pos < 0) {
+ // Don't update if bogus format
+ return null;
}
+ StringBuilder result = new StringBuilder(line.substring(0, pos + 1));
+ PublicKeyEntry.appendPublicKeyEntry(result, newKey);
+ return result.toString();
}
}
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/ServerKeyLookup.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/ServerKeyLookup.java
index 4f5f497f7f..2baeb28871 100644
--- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/ServerKeyLookup.java
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/ServerKeyLookup.java
@@ -43,9 +43,9 @@
package org.eclipse.jgit.internal.transport.sshd;
import java.net.SocketAddress;
+import java.security.PublicKey;
import java.util.List;
-import org.apache.sshd.client.keyverifier.KnownHostsServerKeyVerifier.HostEntryPair;
import org.apache.sshd.client.session.ClientSession;
import org.eclipse.jgit.annotations.NonNull;
@@ -55,7 +55,7 @@ import org.eclipse.jgit.annotations.NonNull;
public interface ServerKeyLookup {
/**
- * Retrieves all entries for a given remote address.
+ * Retrieves all public keys known for a given remote.
*
* @param session
* needed to determine the config files if specified in the ssh
@@ -65,5 +65,5 @@ public interface ServerKeyLookup {
* @return a possibly empty list of entries found, including revoked ones
*/
@NonNull
- List<HostEntryPair> lookup(ClientSession session, SocketAddress remote);
+ List<PublicKey> lookup(ClientSession session, SocketAddress remote);
}
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/DefaultProxyDataFactory.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/DefaultProxyDataFactory.java
index 97e0da0428..66e595c6cc 100644
--- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/DefaultProxyDataFactory.java
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/DefaultProxyDataFactory.java
@@ -62,8 +62,8 @@ public class DefaultProxyDataFactory implements ProxyDataFactory {
public ProxyData get(InetSocketAddress remoteAddress) {
try {
List<Proxy> proxies = ProxySelector.getDefault()
- .select(new URI(Proxy.Type.SOCKS.name(),
- "//" + remoteAddress.getHostString(), null)); //$NON-NLS-1$
+ .select(new URI(
+ "socket://" + remoteAddress.getHostString())); //$NON-NLS-1$
ProxyData data = getData(proxies, Proxy.Type.SOCKS);
if (data == null) {
proxies = ProxySelector.getDefault()
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/ServerKeyDatabase.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/ServerKeyDatabase.java
new file mode 100644
index 0000000000..bdfb96d0c7
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/ServerKeyDatabase.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2019 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.sshd;
+
+import java.net.InetSocketAddress;
+import java.security.PublicKey;
+import java.util.List;
+
+import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.transport.CredentialsProvider;
+
+/**
+ * An interface for a database of known server keys, supporting finding all
+ * known keys and also deciding whether a server key is to be accepted.
+ * <p>
+ * Connection addresses are given as strings of the format
+ * {@code [hostName]:port} if using a non-standard port (i.e., not port 22),
+ * otherwise just {@code hostname}.
+ * </p>
+ *
+ * @since 5.5
+ */
+public interface ServerKeyDatabase {
+
+ /**
+ * Retrieves all known host keys for the given addresses.
+ *
+ * @param connectAddress
+ * IP address the session tried to connect to
+ * @param remoteAddress
+ * IP address as reported for the remote end point
+ * @param config
+ * giving access to potentially interesting configuration
+ * settings
+ * @return the list of known keys for the given addresses
+ */
+ @NonNull
+ List<PublicKey> lookup(@NonNull String connectAddress,
+ @NonNull InetSocketAddress remoteAddress,
+ @NonNull Configuration config);
+
+ /**
+ * Determines whether to accept a received server host key.
+ *
+ * @param connectAddress
+ * IP address the session tried to connect to
+ * @param remoteAddress
+ * IP address as reported for the remote end point
+ * @param serverKey
+ * received from the remote end
+ * @param config
+ * giving access to potentially interesting configuration
+ * settings
+ * @param provider
+ * for interacting with the user, if required; may be
+ * {@code null}
+ * @return {@code true} if the serverKey is accepted, {@code false}
+ * otherwise
+ */
+ boolean accept(@NonNull String connectAddress,
+ @NonNull InetSocketAddress remoteAddress,
+ @NonNull PublicKey serverKey,
+ @NonNull Configuration config, CredentialsProvider provider);
+
+ /**
+ * A simple provider for ssh config settings related to host key checking.
+ * An instance is created by the JGit sshd framework and passed into
+ * {@link ServerKeyDatabase#lookup(String, InetSocketAddress, Configuration)}
+ * and
+ * {@link ServerKeyDatabase#accept(String, InetSocketAddress, PublicKey, Configuration, CredentialsProvider)}.
+ */
+ interface Configuration {
+
+ /**
+ * Retrieves the list of file names from the "UserKnownHostsFile" ssh
+ * config.
+ *
+ * @return the list as configured, with ~ already replaced
+ */
+ List<String> getUserKnownHostsFiles();
+
+ /**
+ * Retrieves the list of file names from the "GlobalKnownHostsFile" ssh
+ * config.
+ *
+ * @return the list as configured, with ~ already replaced
+ */
+ List<String> getGlobalKnownHostsFiles();
+
+ /**
+ * The possible values for the "StrictHostKeyChecking" ssh config.
+ */
+ enum StrictHostKeyChecking {
+ /**
+ * "ask"; default: ask the user whether to accept (and store) a new
+ * or mismatched key.
+ */
+ ASK,
+ /**
+ * "yes", "on": never accept new or mismatched keys.
+ */
+ REQUIRE_MATCH,
+ /**
+ * "no", "off": always accept new or mismatched keys.
+ */
+ ACCEPT_ANY,
+ /**
+ * "accept-new": accept new keys, but never accept modified keys.
+ */
+ ACCEPT_NEW
+ }
+
+ /**
+ * Obtains the value of the "StrictHostKeyChecking" ssh config.
+ *
+ * @return the {@link StrictHostKeyChecking}
+ */
+ @NonNull
+ StrictHostKeyChecking getStrictHostKeyChecking();
+
+ /**
+ * Obtains the value of the "HashKnownHosts" ssh config.
+ *
+ * @return {@code true} if new entries should be stored with hashed host
+ * information, {@code false} otherwise
+ */
+ boolean getHashKnownHosts();
+
+ /**
+ * Obtains the user name used in the connection attempt.
+ *
+ * @return the user name
+ */
+ @NonNull
+ String getUsername();
+ }
+}
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/SshdSessionFactory.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/SshdSessionFactory.java
index 90dc8ca500..3460185d84 100644
--- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/SshdSessionFactory.java
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/SshdSessionFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch>
+ * Copyright (C) 2018, 2019 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
@@ -66,7 +66,6 @@ import org.apache.sshd.client.auth.UserAuth;
import org.apache.sshd.client.auth.keyboard.UserAuthKeyboardInteractiveFactory;
import org.apache.sshd.client.auth.pubkey.UserAuthPublicKeyFactory;
import org.apache.sshd.client.config.hosts.HostConfigEntryResolver;
-import org.apache.sshd.client.keyverifier.ServerKeyVerifier;
import org.apache.sshd.common.NamedFactory;
import org.apache.sshd.common.compression.BuiltinCompressions;
import org.apache.sshd.common.config.keys.FilePasswordProvider;
@@ -77,10 +76,11 @@ import org.eclipse.jgit.errors.TransportException;
import org.eclipse.jgit.internal.transport.sshd.CachingKeyPairProvider;
import org.eclipse.jgit.internal.transport.sshd.GssApiWithMicAuthFactory;
import org.eclipse.jgit.internal.transport.sshd.JGitPasswordAuthFactory;
+import org.eclipse.jgit.internal.transport.sshd.JGitServerKeyVerifier;
import org.eclipse.jgit.internal.transport.sshd.JGitSshClient;
import org.eclipse.jgit.internal.transport.sshd.JGitSshConfig;
import org.eclipse.jgit.internal.transport.sshd.JGitUserInteraction;
-import org.eclipse.jgit.internal.transport.sshd.OpenSshServerKeyVerifier;
+import org.eclipse.jgit.internal.transport.sshd.OpenSshServerKeyDatabase;
import org.eclipse.jgit.internal.transport.sshd.PasswordProviderWrapper;
import org.eclipse.jgit.internal.transport.sshd.SshdText;
import org.eclipse.jgit.transport.CredentialsProvider;
@@ -104,7 +104,7 @@ public class SshdSessionFactory extends SshSessionFactory implements Closeable {
private final Map<Tuple, HostConfigEntryResolver> defaultHostConfigEntryResolver = new ConcurrentHashMap<>();
- private final Map<Tuple, ServerKeyVerifier> defaultServerKeyVerifier = new ConcurrentHashMap<>();
+ private final Map<Tuple, ServerKeyDatabase> defaultServerKeyDatabase = new ConcurrentHashMap<>();
private final Map<Tuple, Iterable<KeyPair>> defaultKeys = new ConcurrentHashMap<>();
@@ -226,7 +226,8 @@ public class SshdSessionFactory extends SshSessionFactory implements Closeable {
.filePasswordProvider(
createFilePasswordProvider(passphrases))
.hostConfigEntryResolver(configFile)
- .serverKeyVerifier(getServerKeyVerifier(home, sshDir))
+ .serverKeyVerifier(new JGitServerKeyVerifier(
+ getServerKeyDatabase(home, sshDir)))
.compressionFactories(
new ArrayList<>(BuiltinCompressions.VALUES))
.build();
@@ -360,34 +361,48 @@ public class SshdSessionFactory extends SshSessionFactory implements Closeable {
@NonNull File homeDir, @NonNull File sshDir) {
return defaultHostConfigEntryResolver.computeIfAbsent(
new Tuple(new Object[] { homeDir, sshDir }),
- t -> new JGitSshConfig(homeDir,
- new File(sshDir, SshConstants.CONFIG),
+ t -> new JGitSshConfig(homeDir, getSshConfig(sshDir),
getLocalUserName()));
}
/**
- * Obtain a {@link ServerKeyVerifier} to read known_hosts files and to
- * verify server host keys. The default implementation returns a
- * {@link ServerKeyVerifier} that recognizes the two openssh standard files
- * {@code ~/.ssh/known_hosts} and {@code ~/.ssh/known_hosts2} as well as any
- * files configured via the {@code UserKnownHostsFile} option in the ssh
- * config file.
+ * Determines the ssh config file. The default implementation returns
+ * ~/.ssh/config. If the file does not exist and is created later it will be
+ * picked up. To not use a config file at all, return {@code null}.
+ *
+ * @param sshDir
+ * representing ~/.ssh/
+ * @return the file (need not exist), or {@code null} if no config file
+ * shall be used
+ * @since 5.5
+ */
+ protected File getSshConfig(@NonNull File sshDir) {
+ return new File(sshDir, SshConstants.CONFIG);
+ }
+
+ /**
+ * Obtain a {@link ServerKeyDatabase} to verify server host keys. The
+ * default implementation returns a {@link ServerKeyDatabase} that
+ * recognizes the two openssh standard files {@code ~/.ssh/known_hosts} and
+ * {@code ~/.ssh/known_hosts2} as well as any files configured via the
+ * {@code UserKnownHostsFile} option in the ssh config file.
*
* @param homeDir
* home directory to use for ~ replacement
* @param sshDir
* representing ~/.ssh/
- * @return the resolver
+ * @return the {@link ServerKeyDatabase}
+ * @since 5.5
*/
@NonNull
- private ServerKeyVerifier getServerKeyVerifier(@NonNull File homeDir,
+ protected ServerKeyDatabase getServerKeyDatabase(@NonNull File homeDir,
@NonNull File sshDir) {
- return defaultServerKeyVerifier.computeIfAbsent(
+ return defaultServerKeyDatabase.computeIfAbsent(
new Tuple(new Object[] { homeDir, sshDir }),
- t -> new OpenSshServerKeyVerifier(true,
+ t -> new OpenSshServerKeyDatabase(true,
getDefaultKnownHostsFiles(sshDir)));
- }
+ }
/**
* Gets the list of default user known hosts files. The default returns
* ~/.ssh/known_hosts and ~/.ssh/known_hosts2. The ssh config
@@ -540,7 +555,7 @@ public class SshdSessionFactory extends SshSessionFactory implements Closeable {
* the ssh config defines {@code PreferredAuthentications} the value from
* the ssh config takes precedence.
*
- * @return a comma-separated list of algorithm names, or {@code null} if
+ * @return a comma-separated list of mechanism names, or {@code null} if
* none
*/
protected String getDefaultPreferredAuthentications() {
diff --git a/org.eclipse.jgit.test/src/org/eclipse/jgit/transport/ssh/SshTestBase.java b/org.eclipse.jgit.test/src/org/eclipse/jgit/transport/ssh/SshTestBase.java
index b8c90b2a40..dab93fd071 100644
--- a/org.eclipse.jgit.test/src/org/eclipse/jgit/transport/ssh/SshTestBase.java
+++ b/org.eclipse.jgit.test/src/org/eclipse/jgit/transport/ssh/SshTestBase.java
@@ -305,7 +305,7 @@ public abstract class SshTestBase extends SshTestHarness {
// without provider. If it works, the server host key was written
// correctly.
File clonedAgain = new File(getTemporaryDirectory(), "cloned2");
- cloneWith("ssh://localhost/doesntmatter", clonedAgain, provider, //
+ cloneWith("ssh://localhost/doesntmatter", clonedAgain, null, //
"Host localhost", //
"HostName localhost", //
"Port " + testPort, //
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/BatchRefUpdateTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/BatchRefUpdateTest.java
index 501b788f89..79cf4d5aff 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/BatchRefUpdateTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/BatchRefUpdateTest.java
@@ -43,6 +43,7 @@
package org.eclipse.jgit.internal.storage.file;
+import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.concurrent.TimeUnit.NANOSECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.eclipse.jgit.internal.storage.file.BatchRefUpdateTest.Result.LOCK_FAILURE;
@@ -64,6 +65,7 @@ import static org.junit.Assume.assumeTrue;
import java.io.File;
import java.io.IOException;
+import java.nio.file.Files;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
@@ -162,6 +164,33 @@ public class BatchRefUpdateTest extends LocalDiskRepositoryTestCase {
}
@Test
+ public void packedRefsFileIsSorted() throws IOException {
+ assumeTrue(atomic);
+
+ for (int i = 0; i < 2; i++) {
+ BatchRefUpdate bu = diskRepo.getRefDatabase().newBatchUpdate();
+ String b1 = String.format("refs/heads/a%d",i);
+ String b2 = String.format("refs/heads/b%d",i);
+ bu.setAtomic(atomic);
+ ReceiveCommand c1 = new ReceiveCommand(ObjectId.zeroId(), A, b1);
+ ReceiveCommand c2 = new ReceiveCommand(ObjectId.zeroId(), B, b2);
+ bu.addCommand(c1, c2);
+ try (RevWalk rw = new RevWalk(diskRepo)) {
+ bu.execute(rw, NullProgressMonitor.INSTANCE);
+ }
+ assertEquals(c1.getResult(), ReceiveCommand.Result.OK);
+ assertEquals(c2.getResult(), ReceiveCommand.Result.OK);
+ }
+
+ File packed = new File(diskRepo.getDirectory(), "packed-refs");
+ String packedStr = new String(Files.readAllBytes(packed.toPath()), UTF_8);
+
+ int a2 = packedStr.indexOf("refs/heads/a1");
+ int b1 = packedStr.indexOf("refs/heads/b0");
+ assertTrue(a2 < b1);
+ }
+
+ @Test
public void simpleNoForce() throws IOException {
writeLooseRef("refs/heads/master", A);
writeLooseRef("refs/heads/masters", B);
diff --git a/org.eclipse.jgit/.settings/.api_filters b/org.eclipse.jgit/.settings/.api_filters
index f50ea9a6d5..e4d023d177 100644
--- a/org.eclipse.jgit/.settings/.api_filters
+++ b/org.eclipse.jgit/.settings/.api_filters
@@ -1,166 +1,11 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<component id="org.eclipse.jgit" version="2">
- <resource path="src/org/eclipse/jgit/dircache/DirCacheEntry.java" type="org.eclipse.jgit.dircache.DirCacheEntry">
- <filter id="1142947843">
- <message_arguments>
- <message_argument value="5.1.9"/>
- <message_argument value="getLastModifiedInstant()"/>
- </message_arguments>
- </filter>
- <filter id="1142947843">
- <message_arguments>
- <message_argument value="5.1.9"/>
- <message_argument value="mightBeRacilyClean(Instant)"/>
- </message_arguments>
- </filter>
- <filter id="1142947843">
- <message_arguments>
- <message_argument value="5.1.9"/>
- <message_argument value="setLastModified(Instant)"/>
- </message_arguments>
- </filter>
- </resource>
- <resource path="src/org/eclipse/jgit/lib/AnyObjectId.java" type="org.eclipse.jgit.lib.AnyObjectId">
+ <resource path="src/org/eclipse/jgit/transport/SshConstants.java" type="org.eclipse.jgit.transport.SshConstants">
<filter id="1141899266">
<message_arguments>
- <message_argument value="5.4"/>
- <message_argument value="5.5"/>
- <message_argument value="isEqual(AnyObjectId, AnyObjectId)"/>
- </message_arguments>
- </filter>
- </resource>
- <resource path="src/org/eclipse/jgit/lib/ConfigConstants.java" type="org.eclipse.jgit.lib.ConfigConstants">
- <filter id="1142947843">
- <message_arguments>
- <message_argument value="5.1.9"/>
- <message_argument value="CONFIG_FILESYSTEM_SECTION"/>
- </message_arguments>
- </filter>
- <filter id="1142947843">
- <message_arguments>
- <message_argument value="5.1.9"/>
- <message_argument value="CONFIG_KEY_MIN_RACY_THRESHOLD"/>
- </message_arguments>
- </filter>
- <filter id="1142947843">
- <message_arguments>
- <message_argument value="5.1.9"/>
- <message_argument value="CONFIG_KEY_TIMESTAMP_RESOLUTION"/>
- </message_arguments>
- </filter>
- </resource>
- <resource path="src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java" type="org.eclipse.jgit.treewalk.WorkingTreeIterator">
- <filter id="1142947843">
- <message_arguments>
- <message_argument value="5.1.9"/>
- <message_argument value="getEntryLastModifiedInstant()"/>
- </message_arguments>
- </filter>
- </resource>
- <resource path="src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java" type="org.eclipse.jgit.treewalk.WorkingTreeIterator$Entry">
- <filter id="336695337">
- <message_arguments>
- <message_argument value="org.eclipse.jgit.treewalk.WorkingTreeIterator.Entry"/>
- <message_argument value="getLastModifiedInstant()"/>
- </message_arguments>
- </filter>
- <filter id="1142947843">
- <message_arguments>
- <message_argument value="5.1.9"/>
- <message_argument value="getLastModifiedInstant()"/>
- </message_arguments>
- </filter>
- </resource>
- <resource path="src/org/eclipse/jgit/util/FS.java" type="org.eclipse.jgit.util.FS">
- <filter id="338792546">
- <message_arguments>
- <message_argument value="org.eclipse.jgit.util.FS"/>
- <message_argument value="getFsTimerResolution(Path)"/>
- </message_arguments>
- </filter>
- <filter id="1142947843">
- <message_arguments>
- <message_argument value="5.1.9"/>
- <message_argument value="getFileStoreAttributes(Path)"/>
- </message_arguments>
- </filter>
- <filter id="1142947843">
- <message_arguments>
- <message_argument value="5.1.9"/>
- <message_argument value="lastModifiedInstant(File)"/>
- </message_arguments>
- </filter>
- <filter id="1142947843">
- <message_arguments>
- <message_argument value="5.1.9"/>
- <message_argument value="lastModifiedInstant(Path)"/>
- </message_arguments>
- </filter>
- <filter id="1142947843">
- <message_arguments>
- <message_argument value="5.1.9"/>
- <message_argument value="setAsyncFileStoreAttributes(boolean)"/>
- </message_arguments>
- </filter>
- <filter id="1142947843">
- <message_arguments>
- <message_argument value="5.1.9"/>
- <message_argument value="setLastModified(Path, Instant)"/>
- </message_arguments>
- </filter>
- </resource>
- <resource path="src/org/eclipse/jgit/util/FS.java" type="org.eclipse.jgit.util.FS$Attributes">
- <filter id="1142947843">
- <message_arguments>
- <message_argument value="5.1.9"/>
- <message_argument value="getLastModifiedInstant()"/>
- </message_arguments>
- </filter>
- </resource>
- <resource path="src/org/eclipse/jgit/util/FS.java" type="org.eclipse.jgit.util.FS$FileStoreAttributes">
- <filter id="1142947843">
- <message_arguments>
- <message_argument value="5.1.9"/>
- <message_argument value="FileStoreAttributes"/>
- </message_arguments>
- </filter>
- </resource>
- <resource path="src/org/eclipse/jgit/util/References.java" type="org.eclipse.jgit.util.References">
- <filter id="1108344834">
- <message_arguments>
- <message_argument value="5.4"/>
<message_argument value="5.5"/>
- <message_argument value="org.eclipse.jgit.util.References"/>
- </message_arguments>
- </filter>
- </resource>
- <resource path="src/org/eclipse/jgit/util/SimpleLruCache.java" type="org.eclipse.jgit.util.SimpleLruCache">
- <filter id="1109393411">
- <message_arguments>
- <message_argument value="5.1.9"/>
- <message_argument value="org.eclipse.jgit.util.SimpleLruCache"/>
- </message_arguments>
- </filter>
- </resource>
- <resource path="src/org/eclipse/jgit/util/Stats.java" type="org.eclipse.jgit.util.Stats">
- <filter id="1109393411">
- <message_arguments>
- <message_argument value="5.1.9"/>
- <message_argument value="org.eclipse.jgit.util.Stats"/>
- </message_arguments>
- </filter>
- </resource>
- <resource path="src/org/eclipse/jgit/util/SystemReader.java" type="org.eclipse.jgit.util.SystemReader">
- <filter id="1142947843">
- <message_arguments>
- <message_argument value="5.1.9"/>
- <message_argument value="getSystemConfig()"/>
- </message_arguments>
- </filter>
- <filter id="1142947843">
- <message_arguments>
- <message_argument value="5.1.9"/>
- <message_argument value="getUserConfig()"/>
+ <message_argument value="5.6"/>
+ <message_argument value="HASH_KNOWN_HOSTS"/>
</message_arguments>
</filter>
</resource>
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackedBatchRefUpdate.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackedBatchRefUpdate.java
index 200c63c17a..e45b53ea68 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackedBatchRefUpdate.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackedBatchRefUpdate.java
@@ -51,10 +51,10 @@ import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_OTHER_RE
import java.io.IOException;
import java.text.MessageFormat;
-import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
-import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -364,65 +364,72 @@ class PackedBatchRefUpdate extends BatchRefUpdate {
private static RefList<Ref> applyUpdates(RevWalk walk, RefList<Ref> refs,
List<ReceiveCommand> commands) throws IOException {
- int nDeletes = 0;
- List<ReceiveCommand> adds = new ArrayList<>(commands.size());
+ // Construct a new RefList by merging the old list with the updates.
+ // This assumes that each ref occurs at most once as a ReceiveCommand.
+ Collections.sort(commands, new Comparator<ReceiveCommand>() {
+ @Override
+ public int compare(ReceiveCommand a, ReceiveCommand b) {
+ return a.getRefName().compareTo(b.getRefName());
+ }
+ });
+
+ int delta = 0;
for (ReceiveCommand c : commands) {
- if (c.getType() == ReceiveCommand.Type.CREATE) {
- adds.add(c);
- } else if (c.getType() == ReceiveCommand.Type.DELETE) {
- nDeletes++;
+ switch (c.getType()) {
+ case DELETE:
+ delta--;
+ break;
+ case CREATE:
+ delta++;
+ break;
+ default:
}
}
- int addIdx = 0;
-
- // Construct a new RefList by linearly scanning the old list, and merging in
- // any updates.
- Map<String, ReceiveCommand> byName = byName(commands);
- RefList.Builder<Ref> b =
- new RefList.Builder<>(refs.size() - nDeletes + adds.size());
- for (Ref ref : refs) {
- String name = ref.getName();
- ReceiveCommand cmd = byName.remove(name);
- if (cmd == null) {
- b.add(ref);
- continue;
- }
- if (!cmd.getOldId().equals(ref.getObjectId())) {
- lockFailure(cmd, commands);
- return null;
+
+ RefList.Builder<Ref> b = new RefList.Builder<>(refs.size() + delta);
+ int refIdx = 0;
+ int cmdIdx = 0;
+ while (refIdx < refs.size() || cmdIdx < commands.size()) {
+ Ref ref = (refIdx < refs.size()) ? refs.get(refIdx) : null;
+ ReceiveCommand cmd = (cmdIdx < commands.size())
+ ? commands.get(cmdIdx)
+ : null;
+ int cmp = 0;
+ if (ref != null && cmd != null) {
+ cmp = ref.getName().compareTo(cmd.getRefName());
+ } else if (ref == null) {
+ cmp = 1;
+ } else if (cmd == null) {
+ cmp = -1;
}
- // Consume any adds between the last and current ref.
- while (addIdx < adds.size()) {
- ReceiveCommand currAdd = adds.get(addIdx);
- if (currAdd.getRefName().compareTo(name) < 0) {
- b.add(peeledRef(walk, currAdd));
- byName.remove(currAdd.getRefName());
- } else {
- break;
+ if (cmp < 0) {
+ b.add(ref);
+ refIdx++;
+ } else if (cmp > 0) {
+ assert cmd != null;
+ if (cmd.getType() != ReceiveCommand.Type.CREATE) {
+ lockFailure(cmd, commands);
+ return null;
}
- addIdx++;
- }
- if (cmd.getType() != ReceiveCommand.Type.DELETE) {
b.add(peeledRef(walk, cmd));
- }
- }
-
- // All remaining adds are valid, since the refs didn't exist.
- while (addIdx < adds.size()) {
- ReceiveCommand cmd = adds.get(addIdx++);
- byName.remove(cmd.getRefName());
- b.add(peeledRef(walk, cmd));
- }
+ cmdIdx++;
+ } else {
+ assert cmd != null;
+ assert ref != null;
+ if (!cmd.getOldId().equals(ref.getObjectId())) {
+ lockFailure(cmd, commands);
+ return null;
+ }
- // Any remaining updates/deletes do not correspond to any existing refs, so
- // they are lock failures.
- if (!byName.isEmpty()) {
- lockFailure(byName.values().iterator().next(), commands);
- return null;
+ if (cmd.getType() != ReceiveCommand.Type.DELETE) {
+ b.add(peeledRef(walk, cmd));
+ }
+ cmdIdx++;
+ refIdx++;
+ }
}
-
return b.toRefList();
}
@@ -501,15 +508,6 @@ class PackedBatchRefUpdate extends BatchRefUpdate {
}
}
- private static Map<String, ReceiveCommand> byName(
- List<ReceiveCommand> commands) {
- Map<String, ReceiveCommand> ret = new LinkedHashMap<>();
- for (ReceiveCommand cmd : commands) {
- ret.put(cmd.getRefName(), cmd);
- }
- return ret;
- }
-
private static Ref peeledRef(RevWalk walk, ReceiveCommand cmd)
throws IOException {
ObjectId newId = cmd.getNewId().copy();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshConstants.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshConstants.java
index 2b79d7105c..efbe77704b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshConstants.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshConstants.java
@@ -101,6 +101,13 @@ public final class SshConstants {
/** Key in an ssh config file. */
public static final String GLOBAL_KNOWN_HOSTS_FILE = "GlobalKnownHostsFile";
+ /**
+ * Key in an ssh config file.
+ *
+ * @since 5.5
+ */
+ public static final String HASH_KNOWN_HOSTS = "HashKnownHosts";
+
/** Key in an ssh config file. */
public static final String HOST = "Host";