This is an automated email from the ASF dual-hosted git repository.

lgoldstein pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/mina-sshd.git

commit ad2b9b6574c5e3d3d42ef975bdf68af8e9c3b8fc
Author: Lyor Goldstein <lgoldst...@apache.org>
AuthorDate: Wed Jul 22 11:02:41 2020 +0300

    [SSHD-1040] Make server key available after KEX completed
---
 CHANGES.md                                         |  2 ++
 .../sshd/client/ClientAuthenticationManager.java   |  2 +-
 .../client/kex/AbstractDHClientKeyExchange.java    | 18 +++--------
 .../java/org/apache/sshd/client/kex/DHGClient.java |  6 ++--
 .../org/apache/sshd/client/kex/DHGEXClient.java    | 13 +++++---
 .../sshd/client/session/AbstractClientSession.java | 17 +++++++++-
 .../apache/sshd/client/session/ClientSession.java  |  8 +++++
 .../org/apache/sshd/common/kex/KeyExchange.java    |  8 -----
 .../org/apache/sshd/common/session/Session.java    |  5 ++-
 .../common/session/helpers/AbstractSession.java    |  2 +-
 .../server/kex/AbstractDHServerKeyExchange.java    | 12 --------
 .../sshd/common/auth/AuthenticationTest.java       | 36 ++++++++++++++++++++++
 12 files changed, 86 insertions(+), 43 deletions(-)

diff --git a/CHANGES.md b/CHANGES.md
index 1f843bc..4e8967d 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -18,6 +18,8 @@
 
 ## Minor code helpers
 
+* [SSHD-1040](https://issues.apache.org/jira/browse/SSHD-1040) Make server key 
available after KEX completed.
+
 ## Behavioral changes and enhancements
 
 * [SSHD-1028](https://issues.apache.org/jira/browse/SSHD-1028) Fix 
SSH_MSG_DISCONNECT: Too many concurrent connections.
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/client/ClientAuthenticationManager.java
 
b/sshd-core/src/main/java/org/apache/sshd/client/ClientAuthenticationManager.java
index 1965633..b42eae5 100644
--- 
a/sshd-core/src/main/java/org/apache/sshd/client/ClientAuthenticationManager.java
+++ 
b/sshd-core/src/main/java/org/apache/sshd/client/ClientAuthenticationManager.java
@@ -38,7 +38,7 @@ import org.apache.sshd.common.util.ValidateUtils;
 
 /**
  * Holds information required for the client to perform authentication with 
the server
- * 
+ *
  * @author <a href="mailto:d...@mina.apache.org";>Apache MINA SSHD Project</a>
  */
 public interface ClientAuthenticationManager
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/client/kex/AbstractDHClientKeyExchange.java
 
b/sshd-core/src/main/java/org/apache/sshd/client/kex/AbstractDHClientKeyExchange.java
index a3a2a54..92f10e8 100644
--- 
a/sshd-core/src/main/java/org/apache/sshd/client/kex/AbstractDHClientKeyExchange.java
+++ 
b/sshd-core/src/main/java/org/apache/sshd/client/kex/AbstractDHClientKeyExchange.java
@@ -19,9 +19,7 @@
 
 package org.apache.sshd.client.kex;
 
-import java.security.PublicKey;
-
-import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.client.session.AbstractClientSession;
 import org.apache.sshd.client.session.ClientSessionHolder;
 import org.apache.sshd.common.kex.dh.AbstractDHKeyExchange;
 import org.apache.sshd.common.session.Session;
@@ -33,20 +31,14 @@ import org.apache.sshd.common.util.ValidateUtils;
 public abstract class AbstractDHClientKeyExchange
         extends AbstractDHKeyExchange
         implements ClientSessionHolder {
-    protected PublicKey serverKey;
 
     protected AbstractDHClientKeyExchange(Session session) {
-        super(ValidateUtils.checkInstanceOf(session, ClientSession.class,
-                "Using a client side KeyExchange on a server: %s", session));
-    }
-
-    @Override
-    public final ClientSession getClientSession() {
-        return (ClientSession) getSession();
+        super(ValidateUtils.checkInstanceOf(session, 
AbstractClientSession.class,
+                "Non-AbstractClientSession: %s", session));
     }
 
     @Override
-    public PublicKey getServerKey() {
-        return serverKey;
+    public final AbstractClientSession getClientSession() {
+        return (AbstractClientSession) getSession();
     }
 }
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/kex/DHGClient.java 
b/sshd-core/src/main/java/org/apache/sshd/client/kex/DHGClient.java
index 23c7b7e..559206b 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/kex/DHGClient.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/kex/DHGClient.java
@@ -25,6 +25,7 @@ import java.util.Collection;
 import java.util.Objects;
 import java.util.concurrent.TimeUnit;
 
+import org.apache.sshd.client.session.AbstractClientSession;
 import org.apache.sshd.common.NamedFactory;
 import org.apache.sshd.common.SshConstants;
 import org.apache.sshd.common.SshException;
@@ -113,7 +114,7 @@ public class DHGClient extends AbstractDHClientKeyExchange {
     @Override
     @SuppressWarnings("checkstyle:VariableDeclarationUsageDistance")
     public boolean next(int cmd, Buffer buffer) throws Exception {
-        Session session = getSession();
+        AbstractClientSession session = getClientSession();
         if (log.isDebugEnabled()) {
             log.debug("next({})[{}] process command={}",
                     this, session, KeyExchange.getSimpleKexOpcodeName(cmd));
@@ -132,7 +133,7 @@ public class DHGClient extends AbstractDHClientKeyExchange {
         k = dh.getK();
 
         buffer = new ByteArrayBuffer(k_s);
-        serverKey = buffer.getRawPublicKey();
+        PublicKey serverKey = buffer.getRawPublicKey();
         PublicKey serverPublicHostKey = serverKey;
 
         if (serverKey instanceof OpenSshCertificate) {
@@ -182,6 +183,7 @@ public class DHGClient extends AbstractDHClientKeyExchange {
                     "KeyExchange signature verification failed for key type=" 
+ keyAlg);
         }
 
+        session.setServerKey(serverKey);
         return true;
     }
 
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/client/kex/DHGEXClient.java 
b/sshd-core/src/main/java/org/apache/sshd/client/kex/DHGEXClient.java
index 166fd99..d02d7e6 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/kex/DHGEXClient.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/kex/DHGEXClient.java
@@ -20,8 +20,10 @@
 package org.apache.sshd.client.kex;
 
 import java.math.BigInteger;
+import java.security.PublicKey;
 import java.util.Objects;
 
+import org.apache.sshd.client.session.AbstractClientSession;
 import org.apache.sshd.common.NamedFactory;
 import org.apache.sshd.common.SshConstants;
 import org.apache.sshd.common.SshException;
@@ -58,8 +60,10 @@ public class DHGEXClient extends AbstractDHClientKeyExchange 
{
         this.factory = Objects.requireNonNull(factory, "No factory");
 
         // SSHD-941 give the user a chance to intervene in the choice
-        min = 
CoreModuleProperties.PROP_DHGEX_CLIENT_MIN_KEY.get(session).orElse(SecurityUtils.MIN_DHGEX_KEY_SIZE);
-        max = 
CoreModuleProperties.PROP_DHGEX_CLIENT_MAX_KEY.get(session).orElse(SecurityUtils.getMaxDHGroupExchangeKeySize());
+        min = CoreModuleProperties.PROP_DHGEX_CLIENT_MIN_KEY.get(session)
+                .orElse(SecurityUtils.MIN_DHGEX_KEY_SIZE);
+        max = CoreModuleProperties.PROP_DHGEX_CLIENT_MAX_KEY.get(session)
+                .orElse(SecurityUtils.getMaxDHGroupExchangeKeySize());
         prf = CoreModuleProperties.PROP_DHGEX_CLIENT_PRF_KEY.get(session)
                 .orElse(Math.min(SecurityUtils.PREFERRED_DHGEX_KEY_SIZE, max));
     }
@@ -117,7 +121,7 @@ public class DHGEXClient extends 
AbstractDHClientKeyExchange {
     @Override
     @SuppressWarnings("checkstyle:VariableDeclarationUsageDistance")
     public boolean next(int cmd, Buffer buffer) throws Exception {
-        Session session = getSession();
+        AbstractClientSession session = getClientSession();
         boolean debugEnabled = log.isDebugEnabled();
         if (debugEnabled) {
             log.debug("next({})[{}] process command={} (expected={})",
@@ -166,7 +170,7 @@ public class DHGEXClient extends 
AbstractDHClientKeyExchange {
             k = dh.getK();
 
             buffer = new ByteArrayBuffer(k_s);
-            serverKey = buffer.getRawPublicKey();
+            PublicKey serverKey = buffer.getRawPublicKey();
 
             String keyAlg = KeyUtils.getKeyType(serverKey);
             if (GenericUtils.isEmpty(keyAlg)) {
@@ -202,6 +206,7 @@ public class DHGEXClient extends 
AbstractDHClientKeyExchange {
                         SshConstants.SSH2_DISCONNECT_KEY_EXCHANGE_FAILED,
                         "KeyExchange signature verification failed for key 
type=" + keyAlg);
             }
+            session.setServerKey(serverKey);
             return true;
         }
 
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/client/session/AbstractClientSession.java
 
b/sshd-core/src/main/java/org/apache/sshd/client/session/AbstractClientSession.java
index 4a0312e..5421ee6 100644
--- 
a/sshd-core/src/main/java/org/apache/sshd/client/session/AbstractClientSession.java
+++ 
b/sshd-core/src/main/java/org/apache/sshd/client/session/AbstractClientSession.java
@@ -86,6 +86,7 @@ public abstract class AbstractClientSession extends 
AbstractSession implements C
     private final AuthenticationIdentitiesProvider identitiesProvider;
     private final AttributeRepository connectionContext;
 
+    private PublicKey serverKey;
     private ServerKeyVerifier serverKeyVerifier;
     private UserInteraction userInteraction;
     private PasswordIdentityProvider passwordIdentityProvider;
@@ -124,6 +125,20 @@ public abstract class AbstractClientSession extends 
AbstractSession implements C
     }
 
     @Override
+    public PublicKey getServerKey() {
+        return serverKey;
+    }
+
+    public void setServerKey(PublicKey serverKey) {
+        if (log.isDebugEnabled()) {
+            log.debug("setServerKey({}) keyType={}, digest={}",
+                    this, KeyUtils.getKeyType(serverKey), 
KeyUtils.getFingerPrint(serverKey));
+        }
+
+        this.serverKey = serverKey;
+    }
+
+    @Override
     public ServerKeyVerifier getServerKeyVerifier() {
         ClientFactoryManager manager = getFactoryManager();
         return resolveEffectiveProvider(ServerKeyVerifier.class, 
serverKeyVerifier, manager.getServerKeyVerifier());
@@ -544,7 +559,7 @@ public abstract class AbstractClientSession extends 
AbstractSession implements C
         ServerKeyVerifier serverKeyVerifier = 
Objects.requireNonNull(getServerKeyVerifier(), "No server key verifier");
         IoSession networkSession = getIoSession();
         SocketAddress remoteAddress = networkSession.getRemoteAddress();
-        PublicKey serverKey = kex.getServerKey();
+        PublicKey serverKey = Objects.requireNonNull(getServerKey(), "No 
server key to verify");
 
         boolean verified = false;
         if (serverKey instanceof OpenSshCertificate) {
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSession.java 
b/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSession.java
index b7d21fa..86a8ccb 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSession.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSession.java
@@ -28,6 +28,7 @@ import java.nio.charset.StandardCharsets;
 import java.rmi.RemoteException;
 import java.rmi.ServerException;
 import java.security.KeyPair;
+import java.security.PublicKey;
 import java.time.Duration;
 import java.util.Collection;
 import java.util.Collections;
@@ -122,6 +123,13 @@ public interface ClientSession
     AuthFuture auth() throws IOException;
 
     /**
+     * Retrieves the server's key
+     *
+     * @return The server's {@link PublicKey} - {@code null} if KEX not 
started or not completed
+     */
+    PublicKey getServerKey();
+
+    /**
      * Create a channel of the given type. Same as calling 
<code>createChannel(type, null)</code>.
      *
      * @param  type        The channel type
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/common/kex/KeyExchange.java 
b/sshd-core/src/main/java/org/apache/sshd/common/kex/KeyExchange.java
index 754b01f..a0ad22e 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/kex/KeyExchange.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/kex/KeyExchange.java
@@ -18,7 +18,6 @@
  */
 package org.apache.sshd.common.kex;
 
-import java.security.PublicKey;
 import java.util.Collections;
 import java.util.NavigableMap;
 
@@ -85,13 +84,6 @@ public interface KeyExchange extends NamedResource, 
SessionHolder<Session> {
      */
     byte[] getK();
 
-    /**
-     * Retrieves the server's key
-     *
-     * @return The server's {@link PublicKey}
-     */
-    PublicKey getServerKey();
-
     static String getGroupKexOpcodeName(int cmd) {
         String name = GROUP_KEX_OPCODES_MAP.get(cmd);
         if (GenericUtils.isEmpty(name)) {
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/common/session/Session.java 
b/sshd-core/src/main/java/org/apache/sshd/common/session/Session.java
index 46a3fc4..047dac7 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/session/Session.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/session/Session.java
@@ -88,7 +88,7 @@ public interface Session
 
     /**
      * Prepare a new &quot;clean&quot; buffer while reserving the needed space 
(5 bytes) for the packet header.
-     * 
+     *
      * @param  cmd    The SSH command to initialize the buffer with
      * @param  buffer The {@link Buffer} instance to initialize
      * @return        The initialized buffer
@@ -300,6 +300,9 @@ public interface Session
 
     void setAuthenticated() throws IOException;
 
+    /**
+     * @return The current {@link KeyExchange} in progress - {@code null} if 
KEX not started or successfully completed
+     */
     KeyExchange getKex();
 
     /**
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/common/session/helpers/AbstractSession.java
 
b/sshd-core/src/main/java/org/apache/sshd/common/session/helpers/AbstractSession.java
index 9583773..8e2655e 100644
--- 
a/sshd-core/src/main/java/org/apache/sshd/common/session/helpers/AbstractSession.java
+++ 
b/sshd-core/src/main/java/org/apache/sshd/common/session/helpers/AbstractSession.java
@@ -184,7 +184,7 @@ public abstract class AbstractSession extends SessionHelper 
{
     protected final AtomicLong outBytesCount = new AtomicLong(0L);
     protected final AtomicLong inBlocksCount = new AtomicLong(0L);
     protected final AtomicLong outBlocksCount = new AtomicLong(0L);
-    protected final AtomicReference<Instant> lastKeyTimeValue = new 
AtomicReference(Instant.now());
+    protected final AtomicReference<Instant> lastKeyTimeValue = new 
AtomicReference<>(Instant.now());
     // we initialize them here in case super constructor calls some methods 
that use these values
     protected long maxRekyPackets;
     protected long maxRekeyBytes;
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/server/kex/AbstractDHServerKeyExchange.java
 
b/sshd-core/src/main/java/org/apache/sshd/server/kex/AbstractDHServerKeyExchange.java
index 0cc375b..a27b3f1 100644
--- 
a/sshd-core/src/main/java/org/apache/sshd/server/kex/AbstractDHServerKeyExchange.java
+++ 
b/sshd-core/src/main/java/org/apache/sshd/server/kex/AbstractDHServerKeyExchange.java
@@ -19,10 +19,6 @@
 
 package org.apache.sshd.server.kex;
 
-import java.security.KeyPair;
-import java.security.PublicKey;
-import java.util.Objects;
-
 import org.apache.sshd.common.kex.dh.AbstractDHKeyExchange;
 import org.apache.sshd.common.session.Session;
 import org.apache.sshd.common.util.ValidateUtils;
@@ -44,12 +40,4 @@ public abstract class AbstractDHServerKeyExchange
     public final ServerSession getServerSession() {
         return (ServerSession) getSession();
     }
-
-    @Override
-    public PublicKey getServerKey() {
-        ServerSession session = getServerSession();
-        KeyPair kpHost = Objects.requireNonNull(
-                session.getHostKey(), "No server key pair available");
-        return kpHost.getPublic();
-    }
 }
diff --git 
a/sshd-core/src/test/java/org/apache/sshd/common/auth/AuthenticationTest.java 
b/sshd-core/src/test/java/org/apache/sshd/common/auth/AuthenticationTest.java
index e57f17d..04149dd 100644
--- 
a/sshd-core/src/test/java/org/apache/sshd/common/auth/AuthenticationTest.java
+++ 
b/sshd-core/src/test/java/org/apache/sshd/common/auth/AuthenticationTest.java
@@ -51,6 +51,7 @@ import 
org.apache.sshd.common.config.keys.FilePasswordProvider;
 import org.apache.sshd.common.config.keys.KeyUtils;
 import org.apache.sshd.common.io.IoSession;
 import org.apache.sshd.common.io.IoWriteFuture;
+import org.apache.sshd.common.kex.KeyExchange;
 import org.apache.sshd.common.keyprovider.KeyIdentityProvider;
 import org.apache.sshd.common.keyprovider.KeyPairProvider;
 import org.apache.sshd.common.session.Session;
@@ -944,6 +945,41 @@ public class AuthenticationTest extends BaseTestSupport {
         }
     }
 
+    @Test   // see SSHD-1040
+    public void testServerKeyAvailableAfterAuth() throws Exception {
+        KeyPairProvider keyPairProvider = sshd.getKeyPairProvider();
+        Iterable<KeyPair> availableKeys = keyPairProvider.loadKeys(null);
+        PublicKey actualKey = null;
+
+        try (SshClient client = setupTestClient()) {
+            client.start();
+
+            try (ClientSession session = client.connect(getCurrentTestName(), 
TEST_LOCALHOST, port)
+                    .verify(CONNECT_TIMEOUT).getSession()) {
+                session.addPasswordIdentity(getCurrentTestName());
+                session.auth().verify(AUTH_TIMEOUT);
+
+                KeyExchange kex = session.getKex();
+                assertNull("KEX no nullified after completion", kex);
+
+                actualKey = session.getServerKey();
+            } finally {
+                client.stop();
+            }
+        }
+
+        assertNotNull("No server key extracted", actualKey);
+
+        for (KeyPair kp : availableKeys) {
+            PublicKey expectedKey = kp.getPublic();
+            if (KeyUtils.compareKeys(expectedKey, actualKey)) {
+                return;
+            }
+        }
+
+        fail("No matching server key found for " + actualKey);
+    }
+
     private static void assertAuthenticationResult(String message, AuthFuture 
future, boolean expected) throws IOException {
         assertTrue(message + ": failed to get result on time", 
future.await(AUTH_TIMEOUT));
         assertEquals(message + ": mismatched authentication result", expected, 
future.isSuccess());

Reply via email to