You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mina.apache.org by lg...@apache.org on 2016/02/21 09:52:08 UTC
mina-sshd git commit: [SSHD-653] Use known_hosts behavior for
standalone SshClient usage
Repository: mina-sshd
Updated Branches:
refs/heads/master 512ce3a9f -> cf674e8e0
[SSHD-653] Use known_hosts behavior for standalone SshClient usage
Project: http://git-wip-us.apache.org/repos/asf/mina-sshd/repo
Commit: http://git-wip-us.apache.org/repos/asf/mina-sshd/commit/cf674e8e
Tree: http://git-wip-us.apache.org/repos/asf/mina-sshd/tree/cf674e8e
Diff: http://git-wip-us.apache.org/repos/asf/mina-sshd/diff/cf674e8e
Branch: refs/heads/master
Commit: cf674e8e0416e6ab731752fa90498f422c3805c9
Parents: 512ce3a
Author: Lyor Goldstein <ly...@gmail.com>
Authored: Sun Feb 21 10:52:51 2016 +0200
Committer: Lyor Goldstein <ly...@gmail.com>
Committed: Sun Feb 21 10:52:51 2016 +0200
----------------------------------------------------------------------
.../java/org/apache/sshd/client/SshClient.java | 64 ++++++++++++++++++++
.../KnownHostsServerKeyVerifier.java | 54 +++++++++++------
.../keyverifier/ModifiedServerKeyAcceptor.java | 47 ++++++++++++++
.../java/org/apache/sshd/common/Closeable.java | 4 +-
.../KnownHostsServerKeyVerifierTest.java | 12 ++--
5 files changed, 154 insertions(+), 27 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/cf674e8e/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java b/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java
index d159fe1..b3565c5 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java
@@ -37,6 +37,7 @@ import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
+import java.security.PublicKey;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -47,6 +48,7 @@ import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.logging.ConsoleHandler;
import java.util.logging.Formatter;
@@ -68,10 +70,14 @@ import org.apache.sshd.client.channel.ClientChannel;
import org.apache.sshd.client.channel.ClientChannelEvent;
import org.apache.sshd.client.config.hosts.HostConfigEntry;
import org.apache.sshd.client.config.hosts.HostConfigEntryResolver;
+import org.apache.sshd.client.config.hosts.KnownHostEntry;
import org.apache.sshd.client.config.keys.ClientIdentityLoader;
import org.apache.sshd.client.config.keys.DefaultClientIdentitiesWatcher;
import org.apache.sshd.client.future.ConnectFuture;
import org.apache.sshd.client.future.DefaultConnectFuture;
+import org.apache.sshd.client.keyverifier.DefaultKnownHostsServerKeyVerifier;
+import org.apache.sshd.client.keyverifier.KnownHostsServerKeyVerifier;
+import org.apache.sshd.client.keyverifier.ModifiedServerKeyAcceptor;
import org.apache.sshd.client.keyverifier.ServerKeyVerifier;
import org.apache.sshd.client.session.AbstractClientSession;
import org.apache.sshd.client.session.ClientConnectionServiceFactory;
@@ -879,6 +885,8 @@ public class SshClient extends AbstractFactoryManager implements ClientFactoryMa
}
}
+ setupServerKeyVerifier(client, options, stdin, stdout, stderr);
+
Map<String, Object> props = client.getProperties();
props.putAll(options);
@@ -938,6 +946,62 @@ public class SshClient extends AbstractFactoryManager implements ClientFactoryMa
}
}
+ public static ServerKeyVerifier setupServerKeyVerifier(ClientAuthenticationManager manager, Map<String, ?> options,
+ final BufferedReader stdin, final PrintStream stdout, final PrintStream stderr) {
+ ServerKeyVerifier current = manager.getServerKeyVerifier();
+ if (current == null) {
+ current = ClientBuilder.DEFAULT_SERVER_KEY_VERIFIER;
+ manager.setServerKeyVerifier(current);
+ }
+
+ String strictValue = Objects.toString(options.remove(KnownHostsServerKeyVerifier.STRICT_CHECKING_OPTION), "true");
+ if (!SshConfigFileReader.parseBooleanValue(strictValue)) {
+ return current;
+ }
+
+ final ModifiedServerKeyAcceptor acceptor = new ModifiedServerKeyAcceptor() {
+ @Override
+ public boolean acceptModifiedServerKey(ClientSession clientSession, SocketAddress remoteAddress,
+ KnownHostEntry entry, PublicKey expected, PublicKey actual) throws Exception {
+ stderr.append("Mismatched keys presented by ").append(Objects.toString(remoteAddress))
+ .append(" for entry=").println(entry);
+ stderr.append('\t').append("Expected=").append(KeyUtils.getKeyType(expected))
+ .append('-').println(KeyUtils.getFingerPrint(expected));
+ stderr.append('\t').append("Actual=").append(KeyUtils.getKeyType(actual))
+ .append('-').println(KeyUtils.getFingerPrint(actual));
+ stderr.flush(); // just making sure
+
+ stdout.append("Accept key and update known hosts: y/[N]");
+ stdout.flush(); // just making sure
+
+ String ans = GenericUtils.trimToEmpty(stdin.readLine());
+ return (GenericUtils.length(ans) > 0) && (Character.toLowerCase(ans.charAt(0)) == 'y');
+ }
+ };
+
+ String filePath = Objects.toString(options.remove(KnownHostsServerKeyVerifier.KNOWN_HOSTS_FILE_OPTION), null);
+ if (GenericUtils.isEmpty(filePath)) {
+ current = new DefaultKnownHostsServerKeyVerifier(current) {
+ @Override
+ public boolean acceptModifiedServerKey(ClientSession clientSession, SocketAddress remoteAddress,
+ KnownHostEntry entry, PublicKey expected, PublicKey actual) throws Exception {
+ return acceptor.acceptModifiedServerKey(clientSession, remoteAddress, entry, expected, actual);
+ }
+ };
+ } else { // if user specifies a different location than default be lenient
+ current = new DefaultKnownHostsServerKeyVerifier(current, false, Paths.get(filePath)) {
+ @Override
+ public boolean acceptModifiedServerKey(ClientSession clientSession, SocketAddress remoteAddress,
+ KnownHostEntry entry, PublicKey expected, PublicKey actual) throws Exception {
+ return acceptor.acceptModifiedServerKey(clientSession, remoteAddress, entry, expected, actual);
+ }
+ };
+ }
+
+ manager.setServerKeyVerifier(current);
+ return current;
+ }
+
public static Level resolveLoggingVerbosity(String ... args) {
return resolveLoggingVerbosity(args, GenericUtils.length(args));
}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/cf674e8e/sshd-core/src/main/java/org/apache/sshd/client/keyverifier/KnownHostsServerKeyVerifier.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/keyverifier/KnownHostsServerKeyVerifier.java b/sshd-core/src/main/java/org/apache/sshd/client/keyverifier/KnownHostsServerKeyVerifier.java
index 6961f901..5ec9323 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/keyverifier/KnownHostsServerKeyVerifier.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/keyverifier/KnownHostsServerKeyVerifier.java
@@ -60,7 +60,21 @@ import org.apache.sshd.common.util.net.SshdSocketAddress;
/**
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
*/
-public class KnownHostsServerKeyVerifier extends ModifiableFileWatcher implements ServerKeyVerifier {
+public class KnownHostsServerKeyVerifier
+ extends ModifiableFileWatcher
+ implements ServerKeyVerifier, ModifiedServerKeyAcceptor {
+
+ /**
+ * Standard option used to indicate whether to use strict host key checking or not.
+ * Values may be "yes/no", "true/false" or "on/off"
+ */
+ public static final String STRICT_CHECKING_OPTION = "StrictHostKeyChecking";
+
+ /**
+ * Standard option used to indicate alternative known hosts file location
+ */
+ public static final String KNOWN_HOSTS_FILE_OPTION = "UserKnownHostsFile";
+
/**
* Represents an entry in the internal verifier's cach
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
@@ -233,7 +247,17 @@ public class KnownHostsServerKeyVerifier extends ModifiableFileWatcher implement
return acceptKnownHostEntry(clientSession, remoteAddress, serverKey, entry);
}
- if (!acceptModifiedServerKey(clientSession, remoteAddress, match, serverKey)) {
+ try {
+ if (!acceptModifiedServerKey(clientSession, remoteAddress, entry, expected, serverKey)) {
+ return false;
+ }
+ } catch (Throwable t) {
+ log.warn("acceptKnownHostEntries({})[{}] failed ({}) to accept modified server key: {}",
+ clientSession, remoteAddress, t.getClass().getSimpleName(), t.getMessage());
+ if (log.isDebugEnabled()) {
+ log.debug("acceptKnownHostEntries(" + clientSession + ")[" + remoteAddress + "]"
+ + " modified server key acceptance failure details", t);
+ }
return false;
}
@@ -249,7 +273,7 @@ public class KnownHostsServerKeyVerifier extends ModifiableFileWatcher implement
/**
* Invoked if a matching host entry was found, but the key did not match and
- * {@link #acceptModifiedServerKey(ClientSession, SocketAddress, HostEntryPair, PublicKey)}
+ * {@link #acceptModifiedServerKey(ClientSession, SocketAddress, KnownHostEntry, PublicKey, PublicKey)}
* returned {@code true}. By default it locates the line to be updated and updates only
* its key data, marking the file for reload on next verification just to be
* on the safe side.
@@ -697,24 +721,14 @@ public class KnownHostsServerKeyVerifier extends ModifiableFileWatcher implement
return candidates;
}
- /**
- * Invoked when a matching known host key was found but it does not match
- * the presented one.
- *
- * @param clientSession The {@link ClientSession}
- * @param remoteAddress The remote host address
- * @param match The original {@link HostEntryPair} whose key did not match
- * @param actual The presented server {@link PublicKey}
- * @return {@code true} if accept the server key anyway - default={@code false}
- */
- protected boolean acceptModifiedServerKey(
- ClientSession clientSession, SocketAddress remoteAddress, HostEntryPair match, PublicKey actual) {
- KnownHostEntry entry = match.getHostEntry();
- PublicKey expected = match.getServerKey();
+ @Override
+ public boolean acceptModifiedServerKey(ClientSession clientSession, SocketAddress remoteAddress,
+ KnownHostEntry entry, PublicKey expected, PublicKey actual)
+ throws Exception {
log.warn("acceptModifiedServerKey({}) mismatched keys presented by {} for entry={}: expected={}-{}, actual={}-{}",
- clientSession, remoteAddress, entry,
- KeyUtils.getKeyType(expected), KeyUtils.getFingerPrint(expected),
- KeyUtils.getKeyType(actual), KeyUtils.getFingerPrint(actual));
+ clientSession, remoteAddress, entry,
+ KeyUtils.getKeyType(expected), KeyUtils.getFingerPrint(expected),
+ KeyUtils.getKeyType(actual), KeyUtils.getFingerPrint(actual));
return false;
}
}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/cf674e8e/sshd-core/src/main/java/org/apache/sshd/client/keyverifier/ModifiedServerKeyAcceptor.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/keyverifier/ModifiedServerKeyAcceptor.java b/sshd-core/src/main/java/org/apache/sshd/client/keyverifier/ModifiedServerKeyAcceptor.java
new file mode 100644
index 0000000..784576e
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/client/keyverifier/ModifiedServerKeyAcceptor.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.client.keyverifier;
+
+import java.net.SocketAddress;
+import java.security.PublicKey;
+
+import org.apache.sshd.client.config.hosts.KnownHostEntry;
+import org.apache.sshd.client.session.ClientSession;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface ModifiedServerKeyAcceptor {
+ /**
+ * Invoked when a matching known host key was found but it does not match
+ * the presented one.
+ *
+ * @param clientSession The {@link ClientSession}
+ * @param remoteAddress The remote host address
+ * @param entry The original {@link KnownHostEntry} whose key did not match
+ * @param expected The expected server {@link PublicKey}
+ * @param actual The presented server {@link PublicKey}
+ * @return {@code true} if accept the server key anyway
+ * @throws Exception if cannot process the request - equivalent to {@code false} return value
+ */
+ boolean acceptModifiedServerKey(ClientSession clientSession, SocketAddress remoteAddress,
+ KnownHostEntry entry, PublicKey expected, PublicKey actual)
+ throws Exception;
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/cf674e8e/sshd-core/src/main/java/org/apache/sshd/common/Closeable.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/Closeable.java b/sshd-core/src/main/java/org/apache/sshd/common/Closeable.java
index a7278fc..f86a00f 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/Closeable.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/Closeable.java
@@ -50,14 +50,14 @@ public interface Closeable extends Channel {
* resource is already closed, the listener will be invoked immediately
* and not registered for future notification
*
- * @param listener The notification {@link #SshFutureListener} - never {@code null}
+ * @param listener The notification {@link SshFutureListener} - never {@code null}
*/
void addCloseFutureListener(SshFutureListener<CloseFuture> listener);
/**
* Remove a pre-registered close event listener
*
- * @param listener The register {@link #SshFutureListener} - never {@code null}.
+ * @param listener The register {@link SshFutureListener} - never {@code null}.
* Ignored if not registered or resource already closed
*/
void removeCloseFutureListener(SshFutureListener<CloseFuture> listener);
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/cf674e8e/sshd-core/src/test/java/org/apache/sshd/client/keyverifier/KnownHostsServerKeyVerifierTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/client/keyverifier/KnownHostsServerKeyVerifierTest.java b/sshd-core/src/test/java/org/apache/sshd/client/keyverifier/KnownHostsServerKeyVerifierTest.java
index ec073f8..97ee2de 100644
--- a/sshd-core/src/test/java/org/apache/sshd/client/keyverifier/KnownHostsServerKeyVerifierTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/client/keyverifier/KnownHostsServerKeyVerifierTest.java
@@ -244,11 +244,12 @@ public class KnownHostsServerKeyVerifierTest extends BaseTestSupport {
final AtomicInteger acceptCount = new AtomicInteger(0);
ServerKeyVerifier verifier = new KnownHostsServerKeyVerifier(AcceptAllServerKeyVerifier.INSTANCE, createKnownHostsCopy()) {
@Override
- protected boolean acceptModifiedServerKey(
- ClientSession clientSession, SocketAddress remoteAddress, HostEntryPair match, PublicKey actual) {
+ public boolean acceptModifiedServerKey(
+ ClientSession clientSession, SocketAddress remoteAddress,
+ KnownHostEntry entry, PublicKey expected, PublicKey actual) throws Exception {
acceptCount.incrementAndGet();
assertSame("Mismatched actual key for " + remoteAddress, modifiedKey, actual);
- return super.acceptModifiedServerKey(clientSession, remoteAddress, match, actual);
+ return super.acceptModifiedServerKey(clientSession, remoteAddress, entry, expected, actual);
}
};
@@ -270,8 +271,9 @@ public class KnownHostsServerKeyVerifierTest extends BaseTestSupport {
Path path = createKnownHostsCopy();
ServerKeyVerifier verifier = new KnownHostsServerKeyVerifier(AcceptAllServerKeyVerifier.INSTANCE, path) {
@Override
- protected boolean acceptModifiedServerKey(
- ClientSession clientSession, SocketAddress remoteAddress, HostEntryPair match, PublicKey actual) {
+ public boolean acceptModifiedServerKey(
+ ClientSession clientSession, SocketAddress remoteAddress,
+ KnownHostEntry entry, PublicKey expected, PublicKey actual) throws Exception {
assertSame("Mismatched actual key for " + remoteAddress, modifiedKey, actual);
return true;
}