summaryrefslogtreecommitdiffstats
path: root/org.eclipse.jgit.ssh.apache/src
diff options
context:
space:
mode:
authorThomas Wolf <thomas.wolf@paranor.ch>2019-06-20 19:43:49 +0200
committerThomas Wolf <thomas.wolf@paranor.ch>2019-09-02 21:30:25 +0200
commit124fbbc33a05c177767c5f4233717765acb1ab4d (patch)
tree2386a51734cfb8b5918b93a4ead05fb34ebe31e1 /org.eclipse.jgit.ssh.apache/src
parent8c74a543155ceff5828bbc5c3c72366729ea23c5 (diff)
downloadjgit-124fbbc33a05c177767c5f4233717765acb1ab4d.tar.gz
jgit-124fbbc33a05c177767c5f4233717765acb1ab4d.zip
sshd: configurable server key verification
Provide a wrapper interface and change the implementation such that a client can substitute its own database of known hosts keys instead of the default file-based mechanism. Bug: 547619 Change-Id: Ifc25a4519fa5bcf7bb8541b9f3e2de15215e3d66 Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>
Diffstat (limited to 'org.eclipse.jgit.ssh.apache/src')
-rw-r--r--org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitServerKeyVerifier.java182
-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)161
-rw-r--r--org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/ServerKeyDatabase.java169
-rw-r--r--org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/SshdSessionFactory.java35
4 files changed, 459 insertions, 88 deletions
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..c1d10bd44a
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitServerKeyVerifier.java
@@ -0,0 +1,182 @@
+/*
+ * 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 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 String getUsername() {
+ return session.getUsername();
+ }
+ }
+}
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 40da9559c9..e433109687 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
@@ -64,17 +64,15 @@ 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.KnownHostEntry;
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.config.keys.AuthorizedKeyEntry;
import org.apache.sshd.common.config.keys.KeyUtils;
@@ -83,11 +81,12 @@ import org.apache.sshd.common.config.keys.PublicKeyEntryResolver;
import org.apache.sshd.common.digest.BuiltinDigests;
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;
@@ -149,14 +148,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$
@@ -168,7 +167,7 @@ public class OpenSshServerKeyVerifier
private final List<HostKeyFile> defaultFiles = new ArrayList<>();
/**
- * Creates a new {@link OpenSshServerKeyVerifier}.
+ * Creates a new {@link OpenSshServerKeyDatabase}.
*
* @param askAboutNewFile
* whether to ask the user, if possible, about creating a new
@@ -178,7 +177,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) {
@@ -190,31 +189,24 @@ 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<PublicKey> lookup(ClientSession session,
- SocketAddress remote) {
- List<HostKeyFile> filesToUse = getFilesToUse(session);
+ 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(
- session.getConnectAddress(), remote);
+ connectAddress, remoteAddress);
for (HostKeyFile file : filesToUse) {
for (HostEntryPair current : file.get()) {
KnownHostEntry entry = current.getHostEntry();
@@ -230,14 +222,16 @@ public class OpenSshServerKeyVerifier
}
@Override
- public boolean verifyServerKey(ClientSession clientSession,
- SocketAddress remoteAddress, PublicKey serverKey) {
- List<HostKeyFile> filesToUse = getFilesToUse(clientSession);
- AskUser ask = new AskUser(clientSession);
+ 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;
- Collection<SshdSocketAddress> candidates = getCandidates(
- clientSession.getConnectAddress(), remoteAddress);
+ Collection<SshdSocketAddress> candidates = getCandidates(connectAddress,
+ remoteAddress);
for (HostKeyFile file : filesToUse) {
try {
if (find(candidates, serverKey, file.get(), modified)) {
@@ -433,16 +427,14 @@ public class OpenSshServerKeyVerifier
ASK, DENY, ALLOW;
}
- private final JGitClientSession session;
+ private final @NonNull Configuration config;
- public AskUser(ClientSession clientSession) {
- session = (clientSession instanceof JGitClientSession)
- ? (JGitClientSession) clientSession
- : null;
- }
+ private final CredentialsProvider provider;
- private CredentialsProvider getCredentialsProvider() {
- return session == null ? null : session.getCredentialsProvider();
+ public AskUser(@NonNull Configuration config,
+ CredentialsProvider provider) {
+ this.config = config;
+ this.provider = provider;
}
private static boolean askUser(CredentialsProvider provider, URIish uri,
@@ -465,38 +457,25 @@ public class OpenSshServerKeyVerifier
if (!(remoteAddress instanceof InetSocketAddress)) {
return Check.DENY;
}
- 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:
+ switch (config.getStrictHostKeyChecking()) {
+ case REQUIRE_MATCH:
return Check.DENY;
- case SshConstants.NO:
- case SshConstants.OFF:
+ case ACCEPT_ANY:
return Check.ALLOW;
- case "accept-new": //$NON-NLS-1$
+ case ACCEPT_NEW:
return changed ? Check.DENY : Check.ALLOW;
default:
- break;
- }
- if (getCredentialsProvider() == 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.
- return Check.DENY;
+ return provider == null ? Check.DENY : Check.ASK;
}
- return Check.ASK;
}
public void revokedKey(SocketAddress remoteAddress, PublicKey serverKey,
Path path) {
- CredentialsProvider provider = getCredentialsProvider();
if (provider == null) {
return;
}
InetSocketAddress remote = (InetSocketAddress) remoteAddress;
- URIish uri = JGitUserInteraction.toURI(session.getUsername(),
+ URIish uri = JGitUserInteraction.toURI(config.getUsername(),
remote);
String sha256 = KeyUtils.getFingerPrint(BuiltinDigests.sha256,
serverKey);
@@ -516,7 +495,6 @@ public class OpenSshServerKeyVerifier
if (check != Check.ASK) {
return check == Check.ALLOW;
}
- CredentialsProvider provider = getCredentialsProvider();
InetSocketAddress remote = (InetSocketAddress) remoteAddress;
// Ask the user
String sha256 = KeyUtils.getFingerPrint(BuiltinDigests.sha256,
@@ -524,7 +502,7 @@ public class OpenSshServerKeyVerifier
String md5 = KeyUtils.getFingerPrint(BuiltinDigests.md5, serverKey);
String keyAlgorithm = serverKey.getAlgorithm();
String remoteHost = remote.getHostString();
- URIish uri = JGitUserInteraction.toURI(session.getUsername(),
+ URIish uri = JGitUserInteraction.toURI(config.getUsername(),
remote);
String prompt = SshdText.get().knownHostsUnknownKeyPrompt;
return askUser(provider, uri, prompt, //
@@ -536,18 +514,17 @@ public class OpenSshServerKeyVerifier
}
public ModifiedKeyHandling acceptModifiedServerKey(
- SocketAddress remoteAddress, PublicKey expected,
+ InetSocketAddress remoteAddress, PublicKey expected,
PublicKey actual, Path path) {
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(session.getUsername(),
- remote);
+ String remoteHost = remoteAddress.getHostString();
+ URIish uri = JGitUserInteraction.toURI(config.getUsername(),
+ remoteAddress);
List<String> messages = new ArrayList<>();
String warning = format(
SshdText.get().knownHostsModifiedKeyWarning,
@@ -558,7 +535,6 @@ public class OpenSshServerKeyVerifier
KeyUtils.getFingerPrint(BuiltinDigests.sha256, actual));
messages.addAll(Arrays.asList(warning.split("\n"))); //$NON-NLS-1$
- CredentialsProvider provider = getCredentialsProvider();
if (check == Check.DENY) {
if (provider != null) {
messages.add(format(
@@ -587,7 +563,6 @@ public class OpenSshServerKeyVerifier
}
public boolean createNewFile(Path path) {
- CredentialsProvider provider = getCredentialsProvider();
if (provider == null) {
// We can't ask, so don't create the file
return false;
@@ -674,12 +649,56 @@ public class OpenSshServerKeyVerifier
}
}
+ private int parsePort(String s) {
+ try {
+ return Integer.parseInt(s);
+ } catch (NumberFormatException e) {
+ return -1;
+ }
+ }
+
+ 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);
+ }
+
private Collection<SshdSocketAddress> getCandidates(
- SocketAddress connectAddress, SocketAddress remoteAddress) {
+ @NonNull String connectAddress,
+ @NonNull InetSocketAddress remoteAddress) {
Collection<SshdSocketAddress> candidates = new TreeSet<>(
SshdSocketAddress.BY_HOST_AND_PORT);
candidates.add(SshdSocketAddress.toSshdSocketAddress(remoteAddress));
- candidates.add(SshdSocketAddress.toSshdSocketAddress(connectAddress));
+ SshdSocketAddress address = toSshdSocketAddress(connectAddress);
+ if (address != null) {
+ candidates.add(address);
+ }
return candidates;
}
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..ce58d9518b
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/ServerKeyDatabase.java
@@ -0,0 +1,169 @@
+/*
+ * 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 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 ea5d811557..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();
@@ -380,28 +381,28 @@ public class SshdSessionFactory extends SshSessionFactory implements Closeable {
}
/**
- * 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.
+ * 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
@@ -554,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() {