diff options
author | Matthias Sohn <matthias.sohn@sap.com> | 2019-09-04 02:42:51 +0200 |
---|---|---|
committer | Matthias Sohn <matthias.sohn@sap.com> | 2019-09-04 02:43:03 +0200 |
commit | 4d7821567383ea5e431dca623ab865ae96fffa4c (patch) | |
tree | 37d897bc7baf7cea7eeef41f95988879bbcc040e /org.eclipse.jgit.ssh.apache | |
parent | 2f751c34e132cf86d51271255f92cdbc105c18d2 (diff) | |
parent | 8f742b9d3058cc5d4266194bf9a8bf21e7b34e16 (diff) | |
download | jgit-4d7821567383ea5e431dca623ab865ae96fffa4c.tar.gz jgit-4d7821567383ea5e431dca623ab865ae96fffa4c.zip |
Merge branch 'stable-5.5'
* stable-5.5:
Prepare 5.4.4-SNAPSHOT builds
JGit v5.4.3.201909031940-r
Prepare 5.3.6-SNAPSHOT builds
JGit v5.3.5.201909031855-r
Prepare 5.1.12-SNAPSHOT builds
JGit v5.1.11.201909031202-r
Prepare 4.11.10-SNAPSHOT builds
JGit v4.11.9.201909030838-r
Bazel: Update bazlets to the latest master revision
Bazel: Remove FileTreeIteratorWithTimeControl from BUILD file
BatchRefUpdate: repro racy atomic update, and fix it
Delete unused FileTreeIteratorWithTimeControl
Fix RacyGitTests#testRacyGitDetection
Change RacyGitTests to create a racy git situation in a stable way
Silence API warnings
sshd: fix proxy connections with the DefaultProxyDataFactory
sshd: support the HashKnownHosts configuration
sshd: configurable server key verification
sshd: allow setting a null ssh config
sshd: simplify OpenSshServerKeyVerifier
sshd: simplify ServerKeyLookup interface
Use https in update site URLs
Change-Id: Icd21a8fcccffd56bfedbd037e48028308db6d13b
Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
Diffstat (limited to 'org.eclipse.jgit.ssh.apache')
10 files changed, 669 insertions, 235 deletions
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() { |