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 &quot;yes/no&quot;, &quot;true/false&quot; or &quot;on/off&quot;
+     */
+    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;
             }