aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/ssh/SshTestGitServer.java44
-rw-r--r--org.eclipse.jgit.ssh.apache/META-INF/MANIFEST.MF1
-rw-r--r--org.eclipse.jgit.ssh.apache/resources/org/eclipse/jgit/internal/transport/sshd/SshdText.properties3
-rw-r--r--org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitClientSession.java115
-rw-r--r--org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitSshClient.java8
-rw-r--r--org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/KnownHostEntryReader.java208
-rw-r--r--org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/OpenSshServerKeyVerifier.java48
-rw-r--r--org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/ServerKeyLookup.java69
-rw-r--r--org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/SshdText.java3
-rw-r--r--org.eclipse.jgit.test/src/org/eclipse/jgit/transport/ssh/SshTestBase.java36
10 files changed, 518 insertions, 17 deletions
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
index 3c1111d242..97058e76ea 100644
--- 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
@@ -44,6 +44,8 @@ package org.eclipse.jgit.junit.ssh;
import java.io.ByteArrayInputStream;
import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
import java.nio.file.Path;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
@@ -101,6 +103,9 @@ public class SshTestGitServer {
@NonNull
private Repository repository;
+ @NonNull
+ private List<KeyPair> hostKeys = new ArrayList<>();
+
private final ExecutorService executorService = Executors
.newFixedThreadPool(2);
@@ -130,17 +135,16 @@ public class SshTestGitServer {
this.repository = repository;
server = SshServer.setUpDefaultServer();
// Set host key
+ try (ByteArrayInputStream in = new ByteArrayInputStream(hostKey)) {
+ hostKeys.add(SecurityUtils.loadKeyPairIdentity("", in, null));
+ } catch (IOException | GeneralSecurityException e) {
+ // Ignore.
+ }
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;
- }
+ return hostKeys;
}
});
@@ -220,6 +224,32 @@ public class SshTestGitServer {
}
/**
+ * Adds an additional host key to the server.
+ *
+ * @param key
+ * path to the private key file; should not be encrypted
+ * @param inFront
+ * whether to add the new key before other existing keys
+ * @throws IOException
+ * if the file denoted by the {@link Path} {@code key} cannot be
+ * read
+ * @throws GeneralSecurityException
+ * if the key contained in the file cannot be read
+ */
+ public void addHostKey(@NonNull Path key, boolean inFront)
+ throws IOException, GeneralSecurityException {
+ try (InputStream in = Files.newInputStream(key)) {
+ KeyPair pair = SecurityUtils.loadKeyPairIdentity(key.toString(), in,
+ null);
+ if (inFront) {
+ hostKeys.add(0, pair);
+ } else {
+ hostKeys.add(pair);
+ }
+ }
+ }
+
+ /**
* Starts the test server, listening on a random port.
*
* @return the port the server listens on; test clients should connect to
diff --git a/org.eclipse.jgit.ssh.apache/META-INF/MANIFEST.MF b/org.eclipse.jgit.ssh.apache/META-INF/MANIFEST.MF
index f79287d75e..987f8dcdce 100644
--- a/org.eclipse.jgit.ssh.apache/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.ssh.apache/META-INF/MANIFEST.MF
@@ -69,6 +69,7 @@ Import-Package: org.apache.sshd.agent;version="[2.0.0,2.1.0)",
org.apache.sshd.server.auth;version="[2.0.0,2.1.0)",
org.eclipse.jgit.annotations;version="[5.2.0,5.3.0)",
org.eclipse.jgit.errors;version="[5.2.0,5.3.0)",
+ org.eclipse.jgit.fnmatch;version="[5.2.0,5.3.0)",
org.eclipse.jgit.internal.storage.file;version="[5.2.0,5.3.0)",
org.eclipse.jgit.internal.transport.ssh;version="[5.2.0,5.3.0)",
org.eclipse.jgit.nls;version="[5.2.0,5.3.0)",
diff --git a/org.eclipse.jgit.ssh.apache/resources/org/eclipse/jgit/internal/transport/sshd/SshdText.properties b/org.eclipse.jgit.ssh.apache/resources/org/eclipse/jgit/internal/transport/sshd/SshdText.properties
index 0dc8ecc9a6..369c9784d0 100644
--- a/org.eclipse.jgit.ssh.apache/resources/org/eclipse/jgit/internal/transport/sshd/SshdText.properties
+++ b/org.eclipse.jgit.ssh.apache/resources/org/eclipse/jgit/internal/transport/sshd/SshdText.properties
@@ -1,7 +1,10 @@
authenticationCanceled=Authentication canceled: no password
closeListenerFailed=Ssh session close listener failed
configInvalidPath=Invalid path in ssh config key {0}: {1}
+configInvalidPattern=Invalid pattern in ssh config key {0}: {1}
configInvalidPositive=Ssh config entry {0} must be a strictly positive number but is ''{1}''
+configNoKnownHostKeyAlgorithms=No implementations for any of the algorithms ''{0}'' given in HostKeyAlgorithms in the ssh config; using the default.
+configNoRemainingHostKeyAlgorithms=Ssh config removed all host key algorithms: HostKeyAlgorithms ''{0}''
ftpCloseFailed=Closing the SFTP channel failed
gssapiFailure=GSS-API error for mechanism OID {0}
gssapiInitFailure=GSS-API initialization failure for mechanism {0}
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 2bde7e711d..3e2a1aa6df 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
@@ -42,11 +42,27 @@
*/
package org.eclipse.jgit.internal.transport.sshd;
+import static java.text.MessageFormat.format;
+
+import java.security.PublicKey;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.List;
+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;
+import org.apache.sshd.common.config.keys.KeyUtils;
import org.apache.sshd.common.io.IoSession;
+import org.eclipse.jgit.errors.InvalidPatternException;
+import org.eclipse.jgit.fnmatch.FileNameMatcher;
import org.eclipse.jgit.transport.CredentialsProvider;
+import org.eclipse.jgit.transport.SshConstants;
/**
* A {@link org.apache.sshd.client.session.ClientSession ClientSession} that can
@@ -111,4 +127,103 @@ public class JGitClientSession extends ClientSessionImpl {
return credentialsProvider;
}
+ @Override
+ protected String resolveAvailableSignaturesProposal(
+ FactoryManager manager) {
+ Set<String> defaultSignatures = new LinkedHashSet<>();
+ defaultSignatures.addAll(getSignatureFactoriesNames());
+ HostConfigEntry config = resolveAttribute(
+ JGitSshClient.HOST_CONFIG_ENTRY);
+ String hostKeyAlgorithms = config
+ .getProperty(SshConstants.HOST_KEY_ALGORITHMS);
+ if (hostKeyAlgorithms != null && !hostKeyAlgorithms.isEmpty()) {
+ char first = hostKeyAlgorithms.charAt(0);
+ if (first == '+') {
+ // Additions make not much sense -- it's either in
+ // defaultSignatures already, or we have no implementation for
+ // it. No point in proposing it.
+ return String.join(",", defaultSignatures); //$NON-NLS-1$
+ } else if (first == '-') {
+ // This takes wildcard patterns!
+ removeFromList(defaultSignatures,
+ SshConstants.HOST_KEY_ALGORITHMS,
+ hostKeyAlgorithms.substring(1));
+ if (defaultSignatures.isEmpty()) {
+ // Too bad: user config error. Warn here, and then fail
+ // later.
+ log.warn(format(
+ SshdText.get().configNoRemainingHostKeyAlgorithms,
+ hostKeyAlgorithms));
+ }
+ return String.join(",", defaultSignatures); //$NON-NLS-1$
+ } else {
+ // Default is overridden -- only accept the ones for which we do
+ // have an implementation.
+ List<String> newNames = filteredList(defaultSignatures,
+ hostKeyAlgorithms);
+ if (newNames.isEmpty()) {
+ log.warn(format(
+ SshdText.get().configNoKnownHostKeyAlgorithms,
+ hostKeyAlgorithms));
+ // Use the default instead.
+ } else {
+ return String.join(",", newNames); //$NON-NLS-1$
+ }
+ }
+ }
+ // No HostKeyAlgorithms; using default -- change order to put existing
+ // keys first.
+ ServerKeyVerifier verifier = getServerKeyVerifier();
+ if (verifier instanceof ServerKeyLookup) {
+ List<HostEntryPair> allKnownKeys = ((ServerKeyLookup) verifier)
+ .lookup(this, this.getIoSession().getRemoteAddress());
+ Set<String> reordered = new LinkedHashSet<>();
+ for (HostEntryPair h : allKnownKeys) {
+ PublicKey key = h.getServerKey();
+ if (key != null) {
+ String keyType = KeyUtils.getKeyType(key);
+ if (keyType != null) {
+ reordered.add(keyType);
+ }
+ }
+ }
+ reordered.addAll(defaultSignatures);
+ return String.join(",", reordered); //$NON-NLS-1$
+ }
+ return String.join(",", defaultSignatures); //$NON-NLS-1$
+ }
+
+ private void removeFromList(Set<String> current, String key,
+ String patterns) {
+ for (String toRemove : patterns.split("\\s*,\\s*")) { //$NON-NLS-1$
+ if (toRemove.indexOf('*') < 0 && toRemove.indexOf('?') < 0) {
+ current.remove(toRemove);
+ continue;
+ }
+ try {
+ FileNameMatcher matcher = new FileNameMatcher(toRemove, null);
+ for (Iterator<String> i = current.iterator(); i.hasNext();) {
+ matcher.reset();
+ matcher.append(i.next());
+ if (matcher.isMatch()) {
+ i.remove();
+ }
+ }
+ } catch (InvalidPatternException e) {
+ log.warn(format(SshdText.get().configInvalidPattern, key,
+ toRemove));
+ }
+ }
+ }
+
+ private List<String> filteredList(Set<String> known, String values) {
+ List<String> newNames = new ArrayList<>();
+ for (String newValue : values.split("\\s*,\\s*")) { //$NON-NLS-1$
+ if (known.contains(newValue)) {
+ newNames.add(newValue);
+ }
+ }
+ return newNames;
+ }
+
}
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 36e4486232..27cf05077a 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
@@ -84,6 +84,13 @@ import org.eclipse.jgit.transport.sshd.KeyCache;
public class JGitSshClient extends SshClient {
/**
+ * We need access to this during the constructor of the ClientSession,
+ * before setConnectAddress() can have been called. So we have to remember
+ * it in an attribute on the SshClient, from where we can then retrieve it.
+ */
+ static final AttributeKey<HostConfigEntry> HOST_CONFIG_ENTRY = new AttributeKey<>();
+
+ /**
* An attribute key for the comma-separated list of default preferred
* authentication mechanisms.
*/
@@ -124,6 +131,7 @@ public class JGitSshClient extends SshClient {
hostConfig.getProperty(SshConstants.PREFERRED_AUTHENTICATIONS,
getAttribute(PREFERRED_AUTHENTICATIONS)),
PREFERRED_AUTHS);
+ setAttribute(HOST_CONFIG_ENTRY, hostConfig);
connector.connect(address).addListener(listener);
return connectFuture;
}
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/KnownHostEntryReader.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/KnownHostEntryReader.java
new file mode 100644
index 0000000000..4db24a16b7
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/KnownHostEntryReader.java
@@ -0,0 +1,208 @@
+/*
+ * 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.internal.transport.sshd;
+
+import static java.text.MessageFormat.format;
+import static org.apache.sshd.client.config.hosts.HostPatternsHolder.NON_STANDARD_PORT_PATTERN_ENCLOSURE_END_DELIM;
+import static org.apache.sshd.client.config.hosts.HostPatternsHolder.NON_STANDARD_PORT_PATTERN_ENCLOSURE_START_DELIM;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.apache.sshd.client.config.hosts.HostPatternValue;
+import org.apache.sshd.client.config.hosts.HostPatternsHolder;
+import org.apache.sshd.client.config.hosts.KnownHostEntry;
+import org.apache.sshd.client.config.hosts.KnownHostHashValue;
+import org.apache.sshd.common.config.keys.AuthorizedKeyEntry;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Apache MINA sshd 2.0.0 KnownHostEntry cannot read a host entry line like
+ * "host:port ssh-rsa <key>"; it complains about an illegal character in the
+ * host name (correct would be "[host]:port"). The default known_hosts reader
+ * also aborts reading on the first error.
+ * <p>
+ * This reader is a bit more robust and tries to handle this case if there is
+ * only one colon (otherwise it might be an IPv6 address (without port)), and it
+ * skips and logs invalid entries, but still returns all other valid entries
+ * from the file.
+ * </p>
+ */
+public class KnownHostEntryReader {
+
+ private static final Logger LOG = LoggerFactory
+ .getLogger(KnownHostEntryReader.class);
+
+ private KnownHostEntryReader() {
+ // No instantiation
+ }
+
+ /**
+ * Reads a known_hosts file and returns all valid entries. Invalid entries
+ * are skipped (and a message is logged).
+ *
+ * @param path
+ * of the file to read
+ * @return a {@link List} of all valid entries read from the file
+ * @throws IOException
+ * if the file cannot be read.
+ */
+ public static List<KnownHostEntry> readFromFile(Path path)
+ throws IOException {
+ List<KnownHostEntry> result = new LinkedList<>();
+ try (BufferedReader r = Files.newBufferedReader(path,
+ StandardCharsets.UTF_8)) {
+ r.lines().forEachOrdered(l -> {
+ if (l == null) {
+ return;
+ }
+ String line = clean(l);
+ if (line.isEmpty()) {
+ return;
+ }
+ try {
+ KnownHostEntry entry = parseHostEntry(line);
+ if (entry != null) {
+ result.add(entry);
+ } else {
+ LOG.warn(format(SshdText.get().knownHostsInvalidLine,
+ path, line));
+ }
+ } catch (RuntimeException e) {
+ LOG.warn(format(SshdText.get().knownHostsInvalidLine, path,
+ line), e);
+ }
+ });
+ }
+ return result;
+ }
+
+ private static String clean(String line) {
+ int i = line.indexOf('#');
+ return i < 0 ? line.trim() : line.substring(0, i).trim();
+ }
+
+ private static KnownHostEntry parseHostEntry(String line) {
+ KnownHostEntry entry = new KnownHostEntry();
+ entry.setConfigLine(line);
+ String tmp = line;
+ int i = 0;
+ if (tmp.charAt(0) == KnownHostEntry.MARKER_INDICATOR) {
+ // A marker
+ i = tmp.indexOf(' ', 1);
+ if (i < 0) {
+ return null;
+ }
+ entry.setMarker(tmp.substring(1, i));
+ tmp = tmp.substring(i + 1).trim();
+ }
+ i = tmp.indexOf(' ');
+ if (i < 0) {
+ return null;
+ }
+ // Hash, or host patterns
+ if (tmp.charAt(0) == KnownHostHashValue.HASHED_HOST_DELIMITER) {
+ // Hashed host entry
+ KnownHostHashValue hash = KnownHostHashValue
+ .parse(tmp.substring(0, i));
+ if (hash == null) {
+ return null;
+ }
+ entry.setHashedEntry(hash);
+ entry.setPatterns(null);
+ } else {
+ Collection<HostPatternValue> patterns = parsePatterns(
+ tmp.substring(0, i));
+ if (patterns == null || patterns.isEmpty()) {
+ return null;
+ }
+ entry.setHashedEntry(null);
+ entry.setPatterns(patterns);
+ }
+ tmp = tmp.substring(i + 1).trim();
+ AuthorizedKeyEntry key = AuthorizedKeyEntry
+ .parseAuthorizedKeyEntry(tmp);
+ if (key == null) {
+ return null;
+ }
+ entry.setKeyEntry(key);
+ return entry;
+ }
+
+ private static Collection<HostPatternValue> parsePatterns(String text) {
+ if (text.isEmpty()) {
+ return null;
+ }
+ List<String> items = Arrays.stream(text.split(",")) //$NON-NLS-1$
+ .filter(item -> item != null && !item.isEmpty()).map(item -> {
+ if (NON_STANDARD_PORT_PATTERN_ENCLOSURE_START_DELIM == item
+ .charAt(0)) {
+ return item;
+ }
+ int firstColon = item.indexOf(':');
+ if (firstColon < 0) {
+ return item;
+ }
+ int secondColon = item.indexOf(':', firstColon + 1);
+ if (secondColon > 0) {
+ // Assume an IPv6 address (without port).
+ return item;
+ }
+ // We have "host:port", should be "[host]:port"
+ return NON_STANDARD_PORT_PATTERN_ENCLOSURE_START_DELIM
+ + item.substring(0, firstColon)
+ + NON_STANDARD_PORT_PATTERN_ENCLOSURE_END_DELIM
+ + item.substring(firstColon);
+ }).collect(Collectors.toList());
+ return items.isEmpty() ? null : HostPatternsHolder.parsePatterns(items);
+ }
+}
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/OpenSshServerKeyVerifier.java
index a20ee6bb84..e511be01d5 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/OpenSshServerKeyVerifier.java
@@ -148,7 +148,8 @@ 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 {
+public class OpenSshServerKeyVerifier
+ implements ServerKeyVerifier, ServerKeyLookup {
// TODO: GlobalKnownHostsFile? May need some kind of LRU caching; these
// files may be large!
@@ -192,12 +193,10 @@ public class OpenSshServerKeyVerifier implements ServerKeyVerifier {
this.askAboutNewFile = askAboutNewFile;
}
- @Override
- public boolean verifyServerKey(ClientSession clientSession,
- SocketAddress remoteAddress, PublicKey serverKey) {
+ private List<HostKeyFile> getFilesToUse(ClientSession session) {
List<HostKeyFile> filesToUse = defaultFiles;
- if (clientSession instanceof JGitClientSession) {
- HostConfigEntry entry = ((JGitClientSession) clientSession)
+ if (session instanceof JGitClientSession) {
+ HostConfigEntry entry = ((JGitClientSession) session)
.getHostConfigEntry();
if (entry instanceof JGitHostConfigEntry) {
// Always true!
@@ -209,6 +208,35 @@ public class OpenSshServerKeyVerifier implements ServerKeyVerifier {
}
}
}
+ 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);
+ 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);
+ break;
+ }
+ }
+ }
+ }
+ return result;
+ }
+
+ @Override
+ public boolean verifyServerKey(ClientSession clientSession,
+ SocketAddress remoteAddress, PublicKey serverKey) {
+ List<HostKeyFile> filesToUse = getFilesToUse(clientSession);
AskUser ask = new AskUser();
HostEntryPair[] modified = { null };
Path path = null;
@@ -634,8 +662,8 @@ public class OpenSshServerKeyVerifier implements ServerKeyVerifier {
private List<HostEntryPair> reload(Path path) throws IOException {
try {
- List<KnownHostEntry> rawEntries = KnownHostEntry
- .readKnownHostEntries(path);
+ List<KnownHostEntry> rawEntries = KnownHostEntryReader
+ .readFromFile(path);
updateReloadAttributes();
if (rawEntries == null || rawEntries.isEmpty()) {
return Collections.emptyList();
@@ -652,13 +680,13 @@ public class OpenSshServerKeyVerifier implements ServerKeyVerifier {
if (serverKey == null) {
LOG.warn(format(
SshdText.get().knownHostsUnknownKeyType,
- getPath(), entry.getConfigLine()));
+ path, entry.getConfigLine()));
} else {
newEntries.add(new HostEntryPair(entry, serverKey));
}
} catch (GeneralSecurityException e) {
LOG.warn(format(SshdText.get().knownHostsInvalidLine,
- getPath(), entry.getConfigLine()));
+ path, entry.getConfigLine()));
}
}
return newEntries;
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
new file mode 100644
index 0000000000..4f5f497f7f
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/ServerKeyLookup.java
@@ -0,0 +1,69 @@
+/*
+ * 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.internal.transport.sshd;
+
+import java.net.SocketAddress;
+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;
+
+/**
+ * Offers operations to retrieve server keys from known_hosts files.
+ */
+public interface ServerKeyLookup {
+
+ /**
+ * Retrieves all entries for a given remote address.
+ *
+ * @param session
+ * needed to determine the config files if specified in the ssh
+ * config
+ * @param remote
+ * to find entries for
+ * @return a possibly empty list of entries found, including revoked ones
+ */
+ @NonNull
+ List<HostEntryPair> lookup(ClientSession session, SocketAddress remote);
+}
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/SshdText.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/SshdText.java
index 865a8ebaa2..e7e5d8fcce 100644
--- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/SshdText.java
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/SshdText.java
@@ -21,7 +21,10 @@ public final class SshdText extends TranslationBundle {
/***/ public String authenticationCanceled;
/***/ public String closeListenerFailed;
/***/ public String configInvalidPath;
+ /***/ public String configInvalidPattern;
/***/ public String configInvalidPositive;
+ /***/ public String configNoKnownHostKeyAlgorithms;
+ /***/ public String configNoRemainingHostKeyAlgorithms;
/***/ public String ftpCloseFailed;
/***/ public String gssapiFailure;
/***/ public String gssapiInitFailure;
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 3b5aa5adb7..3e4493119e 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
@@ -595,6 +595,42 @@ public abstract class SshTestBase extends SshTestHarness {
"PreferredAuthentications password");
}
+ @Test
+ public void testRsaHostKeySecond() throws Exception {
+ // See https://git.eclipse.org/r/#/c/130402/ : server has EcDSA
+ // (preferred), RSA, we have RSA in known_hosts: client and server
+ // should agree on RSA.
+ File newHostKey = new File(getTemporaryDirectory(), "newhostkey");
+ copyTestResource("id_ecdsa_256", newHostKey);
+ server.addHostKey(newHostKey.toPath(), true);
+ cloneWith("ssh://git/doesntmatter", defaultCloneDir, null, //
+ "Host git", //
+ "HostName localhost", //
+ "Port " + testPort, //
+ "User " + TEST_USER, //
+ "IdentityFile " + privateKey1.getAbsolutePath());
+ }
+
+ @Test
+ public void testEcDsaHostKey() throws Exception {
+ // See https://git.eclipse.org/r/#/c/130402/ : server has RSA
+ // (preferred), EcDSA, we have EcDSA in known_hosts: client and server
+ // should agree on EcDSA.
+ File newHostKey = new File(getTemporaryDirectory(), "newhostkey");
+ copyTestResource("id_ecdsa_256", newHostKey);
+ server.addHostKey(newHostKey.toPath(), false);
+ File newHostKeyPub = new File(getTemporaryDirectory(),
+ "newhostkey.pub");
+ copyTestResource("id_ecdsa_256.pub", newHostKeyPub);
+ createKnownHostsFile(knownHosts, "localhost", testPort, newHostKeyPub);
+ cloneWith("ssh://git/doesntmatter", defaultCloneDir, null, //
+ "Host git", //
+ "HostName localhost", //
+ "Port " + testPort, //
+ "User " + TEST_USER, //
+ "IdentityFile " + privateKey1.getAbsolutePath());
+ }
+
@Theory
public void testSshKeys(String keyName) throws Exception {
// JSch fails on ECDSA 384/521 keys. Compare