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 2019/02/20 05:14:38 UTC

[mina-sshd] branch master updated (84196d2 -> 8200e7c)

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

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


    from 84196d2  [SSHD-896] Add support for KEX extension negotiation
     new 3e56932  [SSHD-896] Fixed parsing of compound KEX extension messages
     new 42c47af  Fix typo error in CHANGES file
     new 059ae39  [SSHD-895] Added RSA SHA-256 and SHA-512 signature support
     new f8ba7b9  [SSHD-895] Added server-side support for RSA sha256/512 signatures
     new 562d2fe  [SSHD-895] Implemented a sample DefaultClientKexExtensionHandler that updates the client's signature factories
     new 8200e7c  [SSHD-896] Added handling of SSH_MSG_NEWCOMPRESS via the registered KexExtensionHandler

The 6 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 CHANGES.md                                         |  45 +++-
 README.md                                          |   3 +
 .../sshd/cli/client/SshClientCliSupport.java       |  34 +++
 .../sshd/common/config/keys/IdentityUtils.java     |   3 +-
 .../apache/sshd/common/config/keys/KeyUtils.java   | 131 ++++++++-
 .../config/keys/impl/RSAPublicKeyDecoder.java      |  30 ++-
 .../sshd/common/kex/extension/KexExtensions.java   |   5 +
 .../parser/AbstractKexExtensionParser.java         |   4 -
 .../kex/extension/parser/DelayCompression.java     |   4 +
 .../common/kex/extension/parser/Elevation.java     |   8 +-
 .../common/kex/extension/parser/NoFlowControl.java |   8 +-
 .../parser/ServerSignatureAlgorithms.java          |  13 +-
 .../sshd/common/signature/BuiltinSignatures.java   |  36 ++-
 .../sshd/common/signature/SignatureFactory.java    | 173 +++++++++++-
 .../apache/sshd/common/signature/SignatureRSA.java |  22 +-
 ...SignatureFactory.java => SignatureRSASHA1.java} |  12 +-
 ...gnatureFactory.java => SignatureRSASHA256.java} |  12 +-
 ...gnatureFactory.java => SignatureRSASHA512.java} |  12 +-
 .../keys/LazyClientIdentityIteratorTest.java       |   3 +-
 .../sshd/common/config/keys/KeyUtilsTest.java      |   8 +
 .../RSASignatureVariantsTest.java}                 |  46 ++--
 ...atureRSATest.java => SignatureRSASHA1Test.java} |  15 +-
 .../signature/SignatureVariantTestSupport.java     |  80 ++++++
 .../config/keys => common/signature}/id_dsa        |   0
 .../config/keys => common/signature}/id_ecdsa      |   0
 .../config/keys => common/signature}/id_rsa        |   0
 .../java/org/apache/sshd/client/ClientBuilder.java |  37 +++
 .../java/org/apache/sshd/common/BaseBuilder.java   |  36 ---
 .../DefaultClientKexExtensionHandler.java          | 293 +++++++++++++++++++++
 .../common/kex/extension/KexExtensionHandler.java  |  77 +++++-
 .../org/apache/sshd/common/session/Session.java    |   8 +
 .../common/session/helpers/AbstractSession.java    |  67 ++++-
 .../sshd/common/session/helpers/SessionHelper.java |   1 +
 .../java/org/apache/sshd/server/ServerBuilder.java |  23 ++
 .../sshd/server/session/AbstractServerSession.java |  66 ++---
 .../org/apache/sshd/common/SshBuilderTest.java     |   3 +-
 .../common/config/SshConfigFileReaderTest.java     |   3 +-
 37 files changed, 1142 insertions(+), 179 deletions(-)
 copy sshd-common/src/main/java/org/apache/sshd/common/signature/{SignatureFactory.java => SignatureRSASHA1.java} (84%)
 copy sshd-common/src/main/java/org/apache/sshd/common/signature/{SignatureFactory.java => SignatureRSASHA256.java} (83%)
 copy sshd-common/src/main/java/org/apache/sshd/common/signature/{SignatureFactory.java => SignatureRSASHA512.java} (83%)
 rename sshd-common/src/test/java/{rg => org}/apache/sshd/client/config/keys/LazyClientIdentityIteratorTest.java (97%)
 copy sshd-common/src/test/java/org/apache/sshd/common/{util/io/der/ASN1TypeTest.java => signature/RSASignatureVariantsTest.java} (69%)
 rename sshd-common/src/test/java/org/apache/sshd/common/signature/{SignatureRSATest.java => SignatureRSASHA1Test.java} (80%)
 create mode 100644 sshd-common/src/test/java/org/apache/sshd/common/signature/SignatureVariantTestSupport.java
 copy sshd-common/src/test/resources/org/apache/sshd/{client/config/keys => common/signature}/id_dsa (100%)
 copy sshd-common/src/test/resources/org/apache/sshd/{client/config/keys => common/signature}/id_ecdsa (100%)
 copy sshd-common/src/test/resources/org/apache/sshd/{client/config/keys => common/signature}/id_rsa (100%)
 create mode 100644 sshd-core/src/main/java/org/apache/sshd/common/kex/extension/DefaultClientKexExtensionHandler.java


[mina-sshd] 01/06: [SSHD-896] Fixed parsing of compound KEX extension messages

Posted by lg...@apache.org.
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 3e56932eb496b24962363cb51a9e5ca1798fdc46
Author: Lyor Goldstein <lg...@apache.org>
AuthorDate: Mon Feb 18 10:06:06 2019 +0200

    [SSHD-896] Fixed parsing of compound KEX extension messages
---
 .../kex/extension/parser/AbstractKexExtensionParser.java    |  4 ----
 .../sshd/common/kex/extension/parser/DelayCompression.java  |  4 ++++
 .../apache/sshd/common/kex/extension/parser/Elevation.java  |  8 +++++++-
 .../sshd/common/kex/extension/parser/NoFlowControl.java     |  8 +++++++-
 .../kex/extension/parser/ServerSignatureAlgorithms.java     | 13 ++++++++++++-
 5 files changed, 30 insertions(+), 7 deletions(-)

diff --git a/sshd-common/src/main/java/org/apache/sshd/common/kex/extension/parser/AbstractKexExtensionParser.java b/sshd-common/src/main/java/org/apache/sshd/common/kex/extension/parser/AbstractKexExtensionParser.java
index 1d1a3ba..75265f7 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/kex/extension/parser/AbstractKexExtensionParser.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/kex/extension/parser/AbstractKexExtensionParser.java
@@ -24,7 +24,6 @@ import java.io.IOException;
 import org.apache.sshd.common.kex.extension.KexExtensionParser;
 import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.common.util.buffer.Buffer;
-import org.apache.sshd.common.util.buffer.BufferUtils;
 
 /**
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
@@ -44,10 +43,7 @@ public abstract class AbstractKexExtensionParser<T> implements KexExtensionParse
     @Override
     public void putExtension(T value, Buffer buffer) throws IOException {
         buffer.putString(getName());
-        int lenPos = buffer.wpos();
-        buffer.putInt(0);   // placeholder for the encoded value length
         encode(value, buffer);
-        BufferUtils.updateLengthPlaceholder(buffer, lenPos);
     }
 
     protected abstract void encode(T value, Buffer buffer) throws IOException;
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/kex/extension/parser/DelayCompression.java b/sshd-common/src/main/java/org/apache/sshd/common/kex/extension/parser/DelayCompression.java
index 21763f8..c11002e 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/kex/extension/parser/DelayCompression.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/kex/extension/parser/DelayCompression.java
@@ -22,6 +22,7 @@ package org.apache.sshd.common.kex.extension.parser;
 import java.io.IOException;
 
 import org.apache.sshd.common.util.buffer.Buffer;
+import org.apache.sshd.common.util.buffer.BufferUtils;
 
 /**
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
@@ -46,7 +47,10 @@ public class DelayCompression extends AbstractKexExtensionParser<DelayedCompress
 
     @Override
     protected void encode(DelayedCompressionAlgorithms algos, Buffer buffer) throws IOException {
+        int lenPos = buffer.wpos();
+        buffer.putInt(0);   // total length placeholder
         buffer.putNameList(algos.getClient2Server());
         buffer.putNameList(algos.getServer2Client());
+        BufferUtils.updateLengthPlaceholder(buffer, lenPos);
     }
 }
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/kex/extension/parser/Elevation.java b/sshd-common/src/main/java/org/apache/sshd/common/kex/extension/parser/Elevation.java
index abaa15c..d08eca3 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/kex/extension/parser/Elevation.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/kex/extension/parser/Elevation.java
@@ -20,6 +20,7 @@
 package org.apache.sshd.common.kex.extension.parser;
 
 import java.io.IOException;
+import java.nio.charset.StandardCharsets;
 
 import org.apache.sshd.common.util.buffer.Buffer;
 
@@ -37,8 +38,13 @@ public class Elevation extends AbstractKexExtensionParser<String> {
     }
 
     @Override
+    public String parseExtension(byte[] data, int off, int len) throws IOException {
+        return (len <= 0) ? "" : new String(data, off, len, StandardCharsets.UTF_8);
+    }
+
+    @Override
     public String parseExtension(Buffer buffer) throws IOException {
-        return buffer.getString();
+        return parseExtension(buffer.array(), buffer.rpos(), buffer.available());
     }
 
     @Override
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/kex/extension/parser/NoFlowControl.java b/sshd-common/src/main/java/org/apache/sshd/common/kex/extension/parser/NoFlowControl.java
index f883131..259adae 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/kex/extension/parser/NoFlowControl.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/kex/extension/parser/NoFlowControl.java
@@ -20,6 +20,7 @@
 package org.apache.sshd.common.kex.extension.parser;
 
 import java.io.IOException;
+import java.nio.charset.StandardCharsets;
 
 import org.apache.sshd.common.util.buffer.Buffer;
 
@@ -37,8 +38,13 @@ public class NoFlowControl extends AbstractKexExtensionParser<String> {
     }
 
     @Override
+    public String parseExtension(byte[] data, int off, int len) throws IOException {
+        return (len <= 0) ? "" : new String(data, off, len, StandardCharsets.UTF_8);
+    }
+
+    @Override
     public String parseExtension(Buffer buffer) throws IOException {
-        return buffer.getString();
+        return parseExtension(buffer.array(), buffer.rpos(), buffer.available());
     }
 
     @Override
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/kex/extension/parser/ServerSignatureAlgorithms.java b/sshd-common/src/main/java/org/apache/sshd/common/kex/extension/parser/ServerSignatureAlgorithms.java
index 72b1364..e211cf2 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/kex/extension/parser/ServerSignatureAlgorithms.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/kex/extension/parser/ServerSignatureAlgorithms.java
@@ -20,8 +20,12 @@
 package org.apache.sshd.common.kex.extension.parser;
 
 import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 
+import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.buffer.Buffer;
 
 /**
@@ -38,8 +42,15 @@ public class ServerSignatureAlgorithms extends AbstractKexExtensionParser<List<S
     }
 
     @Override
+    public List<String> parseExtension(byte[] data, int off, int len) throws IOException {
+        String s = (len <= 0) ? "" : new String(data, off, len, StandardCharsets.UTF_8);
+        String[] vals = GenericUtils.isEmpty(s) ? GenericUtils.EMPTY_STRING_ARRAY : GenericUtils.split(s, ',');
+        return GenericUtils.isEmpty(vals) ? Collections.emptyList() : Arrays.asList(vals);
+    }
+
+    @Override
     public List<String> parseExtension(Buffer buffer) throws IOException {
-        return buffer.getNameList();
+        return parseExtension(buffer.array(), buffer.rpos(), buffer.available());
     }
 
     @Override


[mina-sshd] 04/06: [SSHD-895] Added server-side support for RSA sha256/512 signatures

Posted by lg...@apache.org.
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 f8ba7b9baaccb93376a1cf46fe667b4e62687bf7
Author: Lyor Goldstein <lg...@apache.org>
AuthorDate: Sun Feb 17 17:14:16 2019 +0200

    [SSHD-895] Added server-side support for RSA sha256/512 signatures
---
 CHANGES.md                                         | 34 +++++++++++
 README.md                                          |  3 +
 .../apache/sshd/common/config/keys/KeyUtils.java   | 26 +++++++++
 .../sshd/common/signature/SignatureFactory.java    | 68 +++++++++++++++++++++-
 .../java/org/apache/sshd/client/ClientBuilder.java | 37 ++++++++++++
 .../java/org/apache/sshd/common/BaseBuilder.java   | 36 ------------
 .../common/session/helpers/AbstractSession.java    |  1 +
 .../java/org/apache/sshd/server/ServerBuilder.java | 23 ++++++++
 .../sshd/server/session/AbstractServerSession.java | 66 ++++++++-------------
 .../org/apache/sshd/common/SshBuilderTest.java     |  3 +-
 .../common/config/SshConfigFileReaderTest.java     |  3 +-
 11 files changed, 218 insertions(+), 82 deletions(-)

diff --git a/CHANGES.md b/CHANGES.md
index 078e12d..9815aa6 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -28,4 +28,38 @@ to disconnecting and allow intervention via `SessionDisconnectHandler`.
 
 * [SSHD-893](https://issues.apache.org/jira/browse/SSHD-893) - Using Path(s) instead of String(s) as DirectoryScanner results
 
+* [SSHD-895](https://issues.apache.org/jira/browse/SSHD-895) - Add support for RSA + SHA-256/512 signatures. **Note:** according
+to [RFC - 8332 - section 3.3](https://tools.ietf.org/html/rfc8332#section-3.3):
+
+>> Implementation experience has shown that there are servers that apply
+>> authentication penalties to clients attempting public key algorithms
+>> that the SSH server does not support.
+
+>> When authenticating with an RSA key against a server that does not
+>> implement the "server-sig-algs" extension, clients MAY default to an
+>> "ssh-rsa" signature to avoid authentication penalties.  When the new
+>> rsa-sha2-* algorithms have been sufficiently widely adopted to
+>> warrant disabling "ssh-rsa", clients MAY default to one of the new
+>> algorithms.
+
+Therefore we do not include by default the "rsa-sha-*" signature factories in the `SshClient`. They can
+be easily added by using the relevant `BuiltinSignatures`:
+
+```java
+SshClient client = SshClient.setupDefaultClient();
+client.setSignatureFactories(
+    Arrays.asList(
+        /* This is the full list in the recommended preference order,
+         * but the initialization code can choose and/or re-order
+         */
+        BuiltinSignatures.nistp256,
+        BuiltinSignatures.nistp384,
+        BuiltinSignatures.nistp521,
+        BuiltinSignatures.ed25519,
+        BuiltinSignatures.rsaSHA512,
+        BuiltinSignatures.rsaSHA256,     // should check if isSupported since not required by default for Java 8
+        BuiltinSignatures.rsa,
+        BuiltinSignatures.dsa));
+```
+
 * [SSHD-896](https://issues.apache.org/jira/browse/SSHD-896) - Added support for [KEX extension negotiation](https://tools.wordtothewise.com/rfc/rfc8308)
diff --git a/README.md b/README.md
index 5e2d881..fc22ec0 100644
--- a/README.md
+++ b/README.md
@@ -17,6 +17,9 @@ based applications requiring SSH support.
 * [RFC 8308 - Extension Negotiation in the Secure Shell (SSH) Protocol](https://tools.ietf.org/html/rfc8308)
     * **Note:** - the code contains **hooks** for implementing the RFC but beyond allowing convenient
     access to the required protocol details, it does not implement any logic that handles the messages.
+* [RFC 8332 - Use of RSA Keys with SHA-256 and SHA-512 in the Secure Shell (SSH) Protocol](https://tools.ietf.org/html/rfc8332)
+    * **Note:** - the server side supports these signatures by default. The client side requires specific
+    initialization - see [section 3.3](https://tools.ietf.org/html/rfc8332#section-3.3).
 * SFTP version 3-6 + extensions
     * `supported` - [DRAFT 05 - section 4.4](http://tools.ietf.org/wg/secsh/draft-ietf-secsh-filexfer/draft-ietf-secsh-filexfer-05.tx)
     * `supported2` - [DRAFT 13 section 5.4](https://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#page-10)
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java
index 7ced362..61b391a 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java
@@ -800,6 +800,32 @@ public final class KeyUtils {
     }
 
     /**
+     * @param keyType A key type name - ignored if {@code null}/empty
+     * @return A {@link List} of they canonical key name and all its aliases
+     * @see #getCanonicalKeyType(String)
+     */
+    public static List<String> getAllEquivalentKeyTypes(String keyType) {
+        if (GenericUtils.isEmpty(keyType)) {
+            return Collections.emptyList();
+        }
+
+        String canonicalName = getCanonicalKeyType(keyType);
+        List<String> equivalents = new ArrayList<>();
+        equivalents.add(canonicalName);
+        synchronized (KEY_TYPE_ALIASES) {
+            for (Map.Entry<String, String> ae : KEY_TYPE_ALIASES.entrySet()) {
+                String alias = ae.getKey();
+                String name = ae.getValue();
+                if (canonicalName.equalsIgnoreCase(name)) {
+                    equivalents.add(alias);
+                }
+            }
+        }
+
+        return equivalents;
+    }
+
+    /**
      * @param keyType The available key-type - ignored if {@code null}/empty
      * @return The canonical key type - same as input if no alias registered
      * for the provided key type
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureFactory.java b/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureFactory.java
index 0881714..b6273c0 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureFactory.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureFactory.java
@@ -19,13 +19,75 @@
 
 package org.apache.sshd.common.signature;
 
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
 import org.apache.sshd.common.BuiltinFactory;
+import org.apache.sshd.common.NamedFactory;
+import org.apache.sshd.common.NamedResource;
+import org.apache.sshd.common.config.keys.KeyUtils;
+import org.apache.sshd.common.util.GenericUtils;
 
 /**
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
-// CHECKSTYLE:OFF
 public interface SignatureFactory extends BuiltinFactory<Signature> {
-    // nothing extra
+    /**
+     * @param provided The provided signature key types
+     * @param factories The available signature factories
+     * @return A {@link List} of the matching available factories names
+     * that are also listed as provided ones - in the same <U>order</U>
+     * of preference as they appear in the available listing. May be
+     * empty if no provided signature key types, or no available ones
+     * or no match found.
+     * @see #resolveSignatureFactoryNamesProposal(Iterable, Collection)
+     */
+    static List<String> resolveSignatureFactoriesProposal(
+            Iterable<String> provided, Collection<? extends NamedFactory<Signature>> factories) {
+        return resolveSignatureFactoryNamesProposal(provided, NamedResource.getNameList(factories));
+    }
+
+    /**
+     * @param provided The provided signature key types
+     * @param available The available signature factories names
+     * @return A {@link List} of the matching available factories names
+     * that are also listed as provided ones - in the same <U>order</U>
+     * of preference as they appear in the available listing. May be
+     * empty if no provided signature key types, or no available ones
+     * or no match found.
+     */
+    static List<String> resolveSignatureFactoryNamesProposal(
+            Iterable<String> provided, Collection<String> available) {
+        if ((provided == null) || GenericUtils.isEmpty(available)) {
+            return Collections.emptyList();
+        }
+
+        // We want to preserve the original available order as it indicates the preference
+        Set<String> providedKeys = new HashSet<>();
+        for (String providedType : provided) {
+            Collection<String> equivTypes =
+                KeyUtils.getAllEquivalentKeyTypes(providedType);
+            providedKeys.addAll(equivTypes);
+        }
+
+        if (GenericUtils.isEmpty(providedKeys)) {
+            return Collections.emptyList();
+        }
+
+        List<String> supported = new ArrayList<>(available);
+        for (int index = 0; index < supported.size(); index++) {
+            String kt = supported.get(index);
+            if (!providedKeys.contains(kt)) {
+                supported.remove(index);
+                index--;    // compensate for auto-increment
+            }
+        }
+
+        return supported;
+    }
 }
-//CHECKSTYLE:ON
+
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/ClientBuilder.java b/sshd-core/src/main/java/org/apache/sshd/client/ClientBuilder.java
index 4f069d4..e720768 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/ClientBuilder.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/ClientBuilder.java
@@ -19,6 +19,7 @@
 
 package org.apache.sshd.client;
 
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import java.util.function.Function;
@@ -41,12 +42,44 @@ import org.apache.sshd.common.config.keys.FilePasswordProvider;
 import org.apache.sshd.common.kex.DHFactory;
 import org.apache.sshd.common.kex.KeyExchange;
 import org.apache.sshd.common.session.ConnectionService;
+import org.apache.sshd.common.signature.BuiltinSignatures;
 import org.apache.sshd.server.forward.ForwardedTcpipFactory;
 
 /**
  * SshClient builder
  */
 public class ClientBuilder extends BaseBuilder<SshClient, ClientBuilder> {
+    /**
+     * Preferred {@link BuiltinSignatures} according to
+     * <A HREF="https://www.freebsd.org/cgi/man.cgi?query=ssh_config&sektion=5">sshd_config(5)</A>
+     * {@code HostKeyAlgorithms} recommendation
+     */
+    public static final List<BuiltinSignatures> DEFAULT_SIGNATURE_PREFERENCE =
+        /*
+         * According to https://tools.ietf.org/html/rfc8332#section-3.3:
+         *
+         *      Implementation experience has shown that there are servers that apply
+         *      authentication penalties to clients attempting public key algorithms
+         *      that the SSH server does not support.
+         *
+         *      When authenticating with an RSA key against a server that does not
+         *      implement the "server-sig-algs" extension, clients MAY default to an
+         *      "ssh-rsa" signature to avoid authentication penalties.  When the new
+         *      rsa-sha2-* algorithms have been sufficiently widely adopted to
+         *      warrant disabling "ssh-rsa", clients MAY default to one of the new
+         *      algorithms.
+         *
+         * Therefore we do not include by default the "rsa-sha-*" signatures.
+         */
+        Collections.unmodifiableList(
+            Arrays.asList(
+                BuiltinSignatures.nistp256,
+                BuiltinSignatures.nistp384,
+                BuiltinSignatures.nistp521,
+                BuiltinSignatures.ed25519,
+                BuiltinSignatures.rsa,
+                BuiltinSignatures.dsa
+            ));
 
     public static final Function<DHFactory, NamedFactory<KeyExchange>> DH2KEX = factory ->
             factory == null
@@ -102,6 +135,10 @@ public class ClientBuilder extends BaseBuilder<SshClient, ClientBuilder> {
     protected ClientBuilder fillWithDefaultValues() {
         super.fillWithDefaultValues();
 
+        if (signatureFactories == null) {
+            signatureFactories = NamedFactory.setUpBuiltinFactories(false, DEFAULT_SIGNATURE_PREFERENCE);
+        }
+
         if (compressionFactories == null) {
             compressionFactories = NamedFactory.setUpBuiltinFactories(false, DEFAULT_COMPRESSION_FACTORIES);
         }
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/BaseBuilder.java b/sshd-core/src/main/java/org/apache/sshd/common/BaseBuilder.java
index bce2283..c578e5a 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/BaseBuilder.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/BaseBuilder.java
@@ -43,7 +43,6 @@ import org.apache.sshd.common.random.SingletonRandomFactory;
 import org.apache.sshd.common.session.ConnectionService;
 import org.apache.sshd.common.session.UnknownChannelReferenceHandler;
 import org.apache.sshd.common.session.helpers.DefaultUnknownChannelReferenceHandler;
-import org.apache.sshd.common.signature.BuiltinSignatures;
 import org.apache.sshd.common.signature.Signature;
 import org.apache.sshd.common.util.ObjectBuilder;
 import org.apache.sshd.common.util.security.SecurityUtils;
@@ -125,22 +124,6 @@ public class BaseBuilder<T extends AbstractFactoryManager, S extends BaseBuilder
                 BuiltinMacs.hmacmd596
             ));
 
-    /**
-     * Preferred {@link BuiltinSignatures} according to
-     * <A HREF="https://www.freebsd.org/cgi/man.cgi?query=ssh_config&sektion=5">sshd_config(5)</A>
-     * {@code HostKeyAlgorithms} recommendation
-     */
-    public static final List<BuiltinSignatures> DEFAULT_SIGNATURE_PREFERENCE =
-        Collections.unmodifiableList(
-            Arrays.asList(
-                BuiltinSignatures.nistp256,
-                BuiltinSignatures.nistp384,
-                BuiltinSignatures.nistp521,
-                BuiltinSignatures.ed25519,
-                BuiltinSignatures.rsa,
-                BuiltinSignatures.dsa
-            ));
-
     public static final UnknownChannelReferenceHandler DEFAULT_UNKNOWN_CHANNEL_REFERENCE_HANDLER =
             DefaultUnknownChannelReferenceHandler.INSTANCE;
 
@@ -164,10 +147,6 @@ public class BaseBuilder<T extends AbstractFactoryManager, S extends BaseBuilder
     }
 
     protected S fillWithDefaultValues() {
-        if (signatureFactories == null) {
-            signatureFactories = setUpDefaultSignatures(false);
-        }
-
         if (randomFactory == null) {
             randomFactory = new SingletonRandomFactory(SecurityUtils.getRandomFactory());
         }
@@ -332,19 +311,4 @@ public class BaseBuilder<T extends AbstractFactoryManager, S extends BaseBuilder
     public static List<NamedFactory<Mac>> setUpDefaultMacs(boolean ignoreUnsupported) {
         return NamedFactory.setUpBuiltinFactories(ignoreUnsupported, DEFAULT_MAC_PREFERENCE);
     }
-
-    /**
-     * @param ignoreUnsupported If {@code true} all the available built-in
-     *                          {@link Signature} factories are added, otherwise only those that are supported
-     *                          by the current JDK setup
-     * @return A {@link List} of the default {@link NamedFactory}
-     * instances of the {@link Signature}s according to the preference
-     * order defined by {@link #DEFAULT_SIGNATURE_PREFERENCE}.
-     * <B>Note:</B> the list may be filtered to exclude unsupported JCE
-     * signatures according to the <tt>ignoreUnsupported</tt> parameter
-     * @see BuiltinSignatures#isSupported()
-     */
-    public static List<NamedFactory<Signature>> setUpDefaultSignatures(boolean ignoreUnsupported) {
-        return NamedFactory.setUpBuiltinFactories(ignoreUnsupported, DEFAULT_SIGNATURE_PREFERENCE);
-    }
 }
\ No newline at end of file
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 6a26457..1dfc381 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
@@ -1905,6 +1905,7 @@ public abstract class AbstractSession extends SessionHelper {
     @Override
     protected String resolveSessionKexProposal(String hostKeyTypes) throws IOException {
         String proposal = super.resolveSessionKexProposal(hostKeyTypes);
+        // see https://tools.ietf.org/html/rfc8308
         KexExtensionHandler extHandler = getKexExtensionHandler();
         if ((extHandler == null) || (!extHandler.isKexExtensionsAvailable(this))) {
             return proposal;
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/ServerBuilder.java b/sshd-core/src/main/java/org/apache/sshd/server/ServerBuilder.java
index 2698eab..b6c3144 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/ServerBuilder.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/ServerBuilder.java
@@ -33,6 +33,7 @@ import org.apache.sshd.common.compression.CompressionFactory;
 import org.apache.sshd.common.kex.DHFactory;
 import org.apache.sshd.common.kex.KeyExchange;
 import org.apache.sshd.common.session.ConnectionService;
+import org.apache.sshd.common.signature.BuiltinSignatures;
 import org.apache.sshd.server.auth.keyboard.DefaultKeyboardInteractiveAuthenticator;
 import org.apache.sshd.server.auth.keyboard.KeyboardInteractiveAuthenticator;
 import org.apache.sshd.server.auth.pubkey.PublickeyAuthenticator;
@@ -85,6 +86,24 @@ public class ServerBuilder extends BaseBuilder<SshServer, ServerBuilder> {
                 BuiltinCompressions.zlib,
                 BuiltinCompressions.delayedZlib));
 
+    /**
+     * Preferred {@link BuiltinSignatures} according to
+     * <A HREF="http://man7.org/linux/man-pages/man5/sshd_config.5.html">sshd_config(5) - HostKeyAlgorithms</A>
+     * {@code HostKeyAlgorithms} recommendation
+     */
+    public static final List<BuiltinSignatures> DEFAULT_SIGNATURE_PREFERENCE =
+        Collections.unmodifiableList(
+            Arrays.asList(
+                BuiltinSignatures.nistp256,
+                BuiltinSignatures.nistp384,
+                BuiltinSignatures.nistp521,
+                BuiltinSignatures.ed25519,
+                BuiltinSignatures.rsaSHA512,
+                BuiltinSignatures.rsaSHA256,
+                BuiltinSignatures.rsa,
+                BuiltinSignatures.dsa
+            ));
+
     protected PublickeyAuthenticator pubkeyAuthenticator;
     protected KeyboardInteractiveAuthenticator interactiveAuthenticator;
 
@@ -110,6 +129,10 @@ public class ServerBuilder extends BaseBuilder<SshServer, ServerBuilder> {
             compressionFactories = NamedFactory.setUpBuiltinFactories(false, DEFAULT_COMPRESSION_FACTORIES);
         }
 
+        if (signatureFactories == null) {
+            signatureFactories = NamedFactory.setUpBuiltinFactories(false, DEFAULT_SIGNATURE_PREFERENCE);
+        }
+
         if (keyExchangeFactories == null) {
             keyExchangeFactories = setUpDefaultKeyExchanges(false);
         }
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/session/AbstractServerSession.java b/sshd-core/src/main/java/org/apache/sshd/server/session/AbstractServerSession.java
index 79b7bb7..fb9c3aa 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/session/AbstractServerSession.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/session/AbstractServerSession.java
@@ -37,6 +37,7 @@ import org.apache.sshd.common.ServiceFactory;
 import org.apache.sshd.common.SshConstants;
 import org.apache.sshd.common.SshException;
 import org.apache.sshd.common.auth.AbstractUserAuthServiceFactory;
+import org.apache.sshd.common.config.keys.KeyUtils;
 import org.apache.sshd.common.io.IoService;
 import org.apache.sshd.common.io.IoSession;
 import org.apache.sshd.common.io.IoWriteFuture;
@@ -50,6 +51,7 @@ import org.apache.sshd.common.session.ConnectionService;
 import org.apache.sshd.common.session.SessionContext;
 import org.apache.sshd.common.session.SessionDisconnectHandler;
 import org.apache.sshd.common.session.helpers.AbstractSession;
+import org.apache.sshd.common.signature.SignatureFactory;
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.common.util.buffer.Buffer;
@@ -267,6 +269,16 @@ public abstract class AbstractServerSession extends AbstractSession implements S
                     "Authentication success signalled though KEX state=" + curState);
         }
 
+        /*
+         * According to https://tools.ietf.org/html/rfc8308#section-2.4
+         *
+         *      If a server sends SSH_MSG_EXT_INFO, it MAY send it at zero, one, or
+         *      both of the following opportunities:
+         *
+         *      ...
+         *
+         *      + Immediately preceding the server's SSH_MSG_USERAUTH_SUCCESS
+         */
         KexExtensionHandler extHandler = getKexExtensionHandler();
         if ((extHandler != null) && extHandler.isKexExtensionsAvailable(this)) {
             extHandler.sendKexExtensions(this, KexPhase.AUTHOK);
@@ -276,16 +288,6 @@ public abstract class AbstractServerSession extends AbstractSession implements S
         IoWriteFuture future;
         IoSession networkSession = getIoSession();
         synchronized (encodeLock) {
-            /*
-             * According to https://tools.ietf.org/html/rfc8308#section-2.4
-             *
-             *      If a server sends SSH_MSG_EXT_INFO, it MAY send it at zero, one, or
-             *      both of the following opportunities:
-             *
-             *      ...
-             *
-             *      + Immediately preceding the server's SSH_MSG_USERAUTH_SUCCESS
-             */
             Buffer packet = resolveOutputPacket(response);
 
             setUsername(username);
@@ -340,7 +342,6 @@ public abstract class AbstractServerSession extends AbstractSession implements S
         ValidateUtils.checkTrue(proposedManager == getFactoryManager(), "Mismatched signatures proposed factory manager");
 
         KeyPairProvider kpp = getKeyPairProvider();
-        Collection<String> supported = NamedResource.getNameList(getSignatureFactories());
         boolean debugEnabled = log.isDebugEnabled();
         Iterable<String> provided;
         try {
@@ -355,35 +356,17 @@ public abstract class AbstractServerSession extends AbstractSession implements S
             throw new RuntimeSshException(e);
         }
 
-        if ((provided == null) || GenericUtils.isEmpty(supported)) {
-            return resolveEmptySignaturesProposal(supported, provided);
-        }
-
-        StringBuilder resolveKeys = null;
-        for (String keyType : provided) {
-            if (!supported.contains(keyType)) {
-                if (debugEnabled) {
-                    log.debug("resolveAvailableSignaturesProposal({})[{}] {} not in supported list: {}",
-                          this, provided, keyType, supported);
-                }
-                continue;
-            }
-
-            if (resolveKeys == null) {
-                resolveKeys = new StringBuilder(supported.size() * 16 /* ecdsa-sha2-xxxx */);
-            }
-
-            if (resolveKeys.length() > 0) {
-                resolveKeys.append(',');
-            }
-
-            resolveKeys.append(keyType);
+        Collection<String> available = NamedResource.getNameList(getSignatureFactories());
+        if ((provided == null) || GenericUtils.isEmpty(available)) {
+            return resolveEmptySignaturesProposal(available, provided);
         }
 
-        if (GenericUtils.isEmpty(resolveKeys)) {
-            return resolveEmptySignaturesProposal(supported, provided);
+        Collection<String> supported =
+            SignatureFactory.resolveSignatureFactoryNamesProposal(provided, available);
+        if (GenericUtils.isEmpty(supported)) {
+            return resolveEmptySignaturesProposal(available, provided);
         } else {
-            return resolveKeys.toString();
+            return GenericUtils.join(supported, ',');
         }
     }
 
@@ -477,15 +460,16 @@ public abstract class AbstractServerSession extends AbstractSession implements S
 
     @Override
     public KeyPair getHostKey() {
-        String keyType = getNegotiatedKexParameter(KexProposalOption.SERVERKEYS);
+        String proposedKey = getNegotiatedKexParameter(KexProposalOption.SERVERKEYS);
+        String keyType = KeyUtils.getCanonicalKeyType(proposedKey);
         KeyPairProvider provider = Objects.requireNonNull(getKeyPairProvider(), "No host keys provider");
         try {
             return provider.loadKey(this, keyType);
         } catch (IOException | GeneralSecurityException | Error e) {
-            log.warn("getHostKey({}) failed ({}) to load key of type={}: {}",
-                 this, e.getClass().getSimpleName(), keyType, e.getMessage());
+            log.warn("getHostKey({}) failed ({}) to load key of type={}[{}]: {}",
+                 this, e.getClass().getSimpleName(), proposedKey, keyType, e.getMessage());
             if (log.isDebugEnabled()) {
-                log.debug("getHostKey(" + this + ") " + keyType + " key load failure details", e);
+                log.debug("getHostKey(" + this + ") " + proposedKey + "[" + keyType + "] key load failure details", e);
             }
 
             throw new RuntimeSshException(e);
diff --git a/sshd-core/src/test/java/org/apache/sshd/common/SshBuilderTest.java b/sshd-core/src/test/java/org/apache/sshd/common/SshBuilderTest.java
index 74ad5b1..e668670 100644
--- a/sshd-core/src/test/java/org/apache/sshd/common/SshBuilderTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/common/SshBuilderTest.java
@@ -30,6 +30,7 @@ import org.apache.sshd.common.kex.BuiltinDHFactories;
 import org.apache.sshd.common.mac.BuiltinMacs;
 import org.apache.sshd.common.signature.BuiltinSignatures;
 import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.server.ServerBuilder;
 import org.apache.sshd.util.test.BaseTestSupport;
 import org.apache.sshd.util.test.NoIoTestCase;
 import org.junit.FixMethodOrder;
@@ -74,7 +75,7 @@ public class SshBuilderTest extends BaseTestSupport {
      */
     @Test
     public void testAllBuiltinSignaturesListed() {
-        testAllInstancesListed(BuiltinSignatures.VALUES, BaseBuilder.DEFAULT_SIGNATURE_PREFERENCE);
+        testAllInstancesListed(BuiltinSignatures.VALUES, ServerBuilder.DEFAULT_SIGNATURE_PREFERENCE);
     }
 
     /**
diff --git a/sshd-core/src/test/java/org/apache/sshd/common/config/SshConfigFileReaderTest.java b/sshd-core/src/test/java/org/apache/sshd/common/config/SshConfigFileReaderTest.java
index 94ce291..3f7aeda 100644
--- a/sshd-core/src/test/java/org/apache/sshd/common/config/SshConfigFileReaderTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/common/config/SshConfigFileReaderTest.java
@@ -30,6 +30,7 @@ import java.util.List;
 import java.util.Properties;
 import java.util.function.Function;
 
+import org.apache.sshd.client.ClientBuilder;
 import org.apache.sshd.common.BaseBuilder;
 import org.apache.sshd.common.Closeable;
 import org.apache.sshd.common.FactoryManager;
@@ -113,7 +114,7 @@ public class SshConfigFileReaderTest extends BaseTestSupport {
 
     @Test
     public void testParseSignaturesList() {
-        List<? extends NamedResource> expected = BaseBuilder.DEFAULT_SIGNATURE_PREFERENCE;
+        List<? extends NamedResource> expected = ClientBuilder.DEFAULT_SIGNATURE_PREFERENCE;
         Properties props = initNamedResourceProperties(ConfigFileReaderSupport.HOST_KEY_ALGORITHMS_CONFIG_PROP, expected);
         BuiltinSignatures.ParseResult result = SshConfigFileReader.getSignatures(PropertyResolverUtils.toPropertyResolver(props));
         testParsedFactoriesList(expected, result.getParsedFactories(), result.getUnsupportedFactories());


[mina-sshd] 02/06: Fix typo error in CHANGES file

Posted by lg...@apache.org.
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 42c47affe9fa12bed156a1fce7e06c6e1a264b31
Author: Lyor Goldstein <lg...@apache.org>
AuthorDate: Mon Feb 18 07:21:32 2019 +0200

    Fix typo error in CHANGES file
---
 CHANGES.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/CHANGES.md b/CHANGES.md
index 07b4430..078e12d 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -23,7 +23,7 @@ current sesssion - client/server proposals and what has been negotiated.
 * [SSHD-882](https://issues.apache.org/jira/browse/SSHD-882) - Provide hooks to allow users to register a consumer
 for STDERR data sent via the `ChannelSession` - especially for the SFTP subsystem.
 
-* [SSHD=892](https://issues.apache.org/jira/browse/SSHD-882) - Inform user about possible session disconnect prior
+* [SSHD-892](https://issues.apache.org/jira/browse/SSHD-882) - Inform user about possible session disconnect prior
 to disconnecting and allow intervention via `SessionDisconnectHandler`.
 
 * [SSHD-893](https://issues.apache.org/jira/browse/SSHD-893) - Using Path(s) instead of String(s) as DirectoryScanner results


[mina-sshd] 03/06: [SSHD-895] Added RSA SHA-256 and SHA-512 signature support

Posted by lg...@apache.org.
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 059ae39f91b0310f63218139e6d7bfd595d9c06d
Author: Lyor Goldstein <lg...@apache.org>
AuthorDate: Sun Feb 17 15:13:54 2019 +0200

    [SSHD-895] Added RSA SHA-256 and SHA-512 signature support
---
 .../sshd/common/config/keys/IdentityUtils.java     |   3 +-
 .../apache/sshd/common/config/keys/KeyUtils.java   | 105 ++++++++++++++++++++-
 .../config/keys/impl/RSAPublicKeyDecoder.java      |  30 +++---
 .../sshd/common/signature/BuiltinSignatures.java   |  36 ++++++-
 .../apache/sshd/common/signature/SignatureRSA.java |  22 +++--
 .../sshd/common/signature/SignatureRSASHA1.java    |  31 ++++++
 .../sshd/common/signature/SignatureRSASHA256.java  |  31 ++++++
 .../sshd/common/signature/SignatureRSASHA512.java  |  31 ++++++
 .../keys/LazyClientIdentityIteratorTest.java       |   3 +-
 .../sshd/common/config/keys/KeyUtilsTest.java      |   8 ++
 .../common/signature/RSASignatureVariantsTest.java |  67 +++++++++++++
 ...atureRSATest.java => SignatureRSASHA1Test.java} |  15 +--
 .../signature/SignatureVariantTestSupport.java     |  80 ++++++++++++++++
 .../org/apache/sshd/common/signature/id_dsa        |  12 +++
 .../org/apache/sshd/common/signature/id_ecdsa      |   5 +
 .../org/apache/sshd/common/signature/id_rsa        |  27 ++++++
 16 files changed, 472 insertions(+), 34 deletions(-)

diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/IdentityUtils.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/IdentityUtils.java
index 6b15070..178c914 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/IdentityUtils.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/IdentityUtils.java
@@ -77,7 +77,8 @@ public final class IdentityUtils {
             return null;
         } else {
             return GenericUtils.trimToEmpty(prefix)
-                    + type.toLowerCase() + GenericUtils.trimToEmpty(suffix);
+                 + type.toLowerCase()
+                 + GenericUtils.trimToEmpty(suffix);
         }
     }
 
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java
index f788a4c..7ced362 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java
@@ -48,12 +48,14 @@ import java.security.spec.ECParameterSpec;
 import java.security.spec.InvalidKeySpecException;
 import java.security.spec.RSAPublicKeySpec;
 import java.util.AbstractMap.SimpleImmutableEntry;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.EnumSet;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.NavigableSet;
 import java.util.Objects;
@@ -73,6 +75,7 @@ import org.apache.sshd.common.digest.DigestFactory;
 import org.apache.sshd.common.digest.DigestUtils;
 import org.apache.sshd.common.keyprovider.KeyPairProvider;
 import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.MapEntryUtils.NavigableMapBuilder;
 import org.apache.sshd.common.util.OsUtils;
 import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.common.util.buffer.Buffer;
@@ -130,6 +133,10 @@ public final class KeyUtils {
      */
     public static final DigestFactory DEFAULT_FINGERPRINT_DIGEST_FACTORY = BuiltinDigests.sha256;
 
+    /** @see <A HREF="">https://tools.ietf.org/html/rfc8332#section-3</A> */
+    public static final String RSA_SHA256_KEY_TYPE_ALIAS = "rsa-sha2-256";
+    public static final String RSA_SHA512_KEY_TYPE_ALIAS = "rsa-sha2-512";
+
     private static final AtomicReference<DigestFactory> DEFAULT_DIGEST_HOLDER = new AtomicReference<>();
 
     private static final Map<String, PublicKeyEntryDecoder<?, ?>> BY_KEY_TYPE_DECODERS_MAP =
@@ -138,6 +145,12 @@ public final class KeyUtils {
     private static final Map<Class<?>, PublicKeyEntryDecoder<?, ?>> BY_KEY_CLASS_DECODERS_MAP =
             new HashMap<>();
 
+    private static final Map<String, String> KEY_TYPE_ALIASES =
+        NavigableMapBuilder.<String, String>builder(String.CASE_INSENSITIVE_ORDER)
+            .put(RSA_SHA256_KEY_TYPE_ALIAS, KeyPairProvider.SSH_RSA)
+            .put(RSA_SHA512_KEY_TYPE_ALIAS, KeyPairProvider.SSH_RSA)
+            .build();
+
     static {
         registerPublicKeyEntryDecoder(RSAPublicKeyDecoder.INSTANCE);
         registerPublicKeyEntryDecoder(DSSPublicKeyEntryDecoder.INSTANCE);
@@ -614,7 +627,7 @@ public final class KeyUtils {
     /**
      * @param f The {@link Factory} to create the {@link Digest} to use
      * @param s The {@link String} to digest - ignored if {@code null}/empty,
-     *          otherwise its UTF-8 representation is used as input for the fingerprint
+     * otherwise its UTF-8 representation is used as input for the fingerprint
      * @return The fingerprint - {@code null} if {@code null}/empty input.
      * <B>Note:</B> if exception encountered then returns the exception's simple class name
      * @see #getFingerPrint(Digest, String, Charset)
@@ -627,7 +640,7 @@ public final class KeyUtils {
      * @param f       The {@link Factory} to create the {@link Digest} to use
      * @param s       The {@link String} to digest - ignored if {@code null}/empty
      * @param charset The {@link Charset} to use in order to convert the
-     *                string to its byte representation to use as input for the fingerprint
+     * string to its byte representation to use as input for the fingerprint
      * @return The fingerprint - {@code null} if {@code null}/empty input
      * <B>Note:</B> if exception encountered then returns the exception's simple class name
      * @see DigestUtils#getFingerPrint(Digest, String, Charset)
@@ -652,7 +665,7 @@ public final class KeyUtils {
      * @param d       The {@link Digest} to use to calculate the fingerprint
      * @param s       The string to digest - ignored if {@code null}/empty
      * @param charset The {@link Charset} to use in order to convert the
-     *                string to its byte representation to use as input for the fingerprint
+     * string to its byte representation to use as input for the fingerprint
      * @return The fingerprint - {@code null} if {@code null}/empty input.
      * <B>Note:</B> if exception encountered then returns the exception's simple class name
      * @see DigestUtils#getFingerPrint(Digest, String, Charset)
@@ -742,8 +755,8 @@ public final class KeyUtils {
 
     /**
      * @param kp a key pair - ignored if {@code null}. If the private
-     *           key is non-{@code null} then it is used to determine the type,
-     *           otherwise the public one is used.
+     * key is non-{@code null} then it is used to determine the type,
+     * otherwise the public one is used.
      * @return the key type or {@code null} if cannot determine it
      * @see #getKeyType(Key)
      */
@@ -787,6 +800,88 @@ public final class KeyUtils {
     }
 
     /**
+     * @param keyType The available key-type - ignored if {@code null}/empty
+     * @return The canonical key type - same as input if no alias registered
+     * for the provided key type
+     * @see #RSA_SHA256_KEY_TYPE_ALIAS
+     * @see #RSA_SHA512_KEY_TYPE_ALIAS
+     */
+    public static String getCanonicalKeyType(String keyType) {
+        if (GenericUtils.isEmpty(keyType)) {
+            return keyType;
+        }
+
+        String canonicalName;
+        synchronized (KEY_TYPE_ALIASES) {
+            canonicalName = KEY_TYPE_ALIASES.get(keyType);
+        }
+
+        if (GenericUtils.isEmpty(canonicalName)) {
+            return keyType;
+        }
+
+        return canonicalName;
+    }
+
+    /**
+     * @return A case insensitive {@link NavigableSet} of the currently registered
+     * key type &quot;aliases&quot;.
+     * @see #getCanonicalKeyType(String)
+     */
+    public static NavigableSet<String> getRegisteredKeyTypeAliases() {
+        synchronized (KEY_TYPE_ALIASES) {
+            return KEY_TYPE_ALIASES.isEmpty()
+                 ? Collections.emptyNavigableSet()
+                 : GenericUtils.asSortedSet(String.CASE_INSENSITIVE_ORDER, KEY_TYPE_ALIASES.keySet());
+        }
+    }
+
+    /**
+     * Registers a collection of aliases to a canonical key type
+     *
+     * @param keyType The (never {@code null}/empty) canonical name
+     * @param aliases The (never {@code null}/empty) aliases
+     * @return A {@link List} of the replaced aliases - empty
+     * if no previous aliases for the canonical name
+     */
+    public static List<String> registerCanonicalKeyTypes(String keyType, Collection<String> aliases) {
+        ValidateUtils.checkNotNullAndNotEmpty(keyType, "No key type value");
+        ValidateUtils.checkNotNullAndNotEmpty(aliases, "No aliases provided");
+
+        List<String> replaced = Collections.emptyList();
+        synchronized (KEY_TYPE_ALIASES) {
+            for (String a : aliases) {
+                ValidateUtils.checkNotNullAndNotEmpty(a, "Null/empty alias registration for %s", keyType);
+                String prev = KEY_TYPE_ALIASES.put(a, keyType);
+                if (GenericUtils.isEmpty(prev)) {
+                    continue;
+                }
+
+                if (replaced.isEmpty()) {
+                    replaced = new ArrayList<>();
+                }
+                replaced.add(prev);
+            }
+        }
+
+        return replaced;
+    }
+
+    /**
+     * @param alias The alias to unregister (ignored if {@code null}/empty)
+     * @return The associated canonical key type - {@code null} if alias not registered
+     */
+    public static String unregisterCanonicalKeyTypeAlias(String alias) {
+        if (GenericUtils.isEmpty(alias)) {
+            return alias;
+        }
+
+        synchronized (KEY_TYPE_ALIASES) {
+            return KEY_TYPE_ALIASES.remove(alias);
+        }
+    }
+
+    /**
      * Determines the key size in bits
      *
      * @param key The {@link Key} to examine - ignored if {@code null}
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/RSAPublicKeyDecoder.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/RSAPublicKeyDecoder.java
index 52cabce..9c2730d 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/RSAPublicKeyDecoder.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/RSAPublicKeyDecoder.java
@@ -33,6 +33,7 @@ import java.security.interfaces.RSAPublicKey;
 import java.security.spec.InvalidKeySpecException;
 import java.security.spec.RSAPrivateCrtKeySpec;
 import java.security.spec.RSAPublicKeySpec;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.Objects;
 
@@ -49,13 +50,20 @@ public class RSAPublicKeyDecoder extends AbstractPublicKeyEntryDecoder<RSAPublic
     public static final RSAPublicKeyDecoder INSTANCE = new RSAPublicKeyDecoder();
 
     public RSAPublicKeyDecoder() {
-        super(RSAPublicKey.class, RSAPrivateKey.class, Collections.unmodifiableList(Collections.singletonList(KeyPairProvider.SSH_RSA)));
+        super(RSAPublicKey.class, RSAPrivateKey.class,
+            Collections.unmodifiableList(
+                Arrays.asList(KeyPairProvider.SSH_RSA,
+                    // Not really required, but allow it
+                    KeyUtils.RSA_SHA256_KEY_TYPE_ALIAS,
+                    KeyUtils.RSA_SHA512_KEY_TYPE_ALIAS)));
     }
 
     @Override
     public RSAPublicKey decodePublicKey(SessionContext session, String keyType, InputStream keyData)
             throws IOException, GeneralSecurityException {
-        if (!KeyPairProvider.SSH_RSA.equals(keyType)) { // just in case we were invoked directly
+        // Not really required, but allow it
+        String canonicalName = KeyUtils.getCanonicalKeyType(keyType);
+        if (!KeyPairProvider.SSH_RSA.equals(canonicalName)) { // just in case we were invoked directly
             throw new InvalidKeySpecException("Unexpected key type: " + keyType);
         }
 
@@ -96,15 +104,15 @@ public class RSAPublicKeyDecoder extends AbstractPublicKeyEntryDecoder<RSAPublic
 
         RSAPrivateCrtKey rsaPrv = (RSAPrivateCrtKey) key;
         return generatePrivateKey(
-                new RSAPrivateCrtKeySpec(
-                        rsaPrv.getModulus(),
-                        rsaPrv.getPublicExponent(),
-                        rsaPrv.getPrivateExponent(),
-                        rsaPrv.getPrimeP(),
-                        rsaPrv.getPrimeQ(),
-                        rsaPrv.getPrimeExponentP(),
-                        rsaPrv.getPrimeExponentQ(),
-                        rsaPrv.getCrtCoefficient()));
+            new RSAPrivateCrtKeySpec(
+                rsaPrv.getModulus(),
+                rsaPrv.getPublicExponent(),
+                rsaPrv.getPrivateExponent(),
+                rsaPrv.getPrimeP(),
+                rsaPrv.getPrimeQ(),
+                rsaPrv.getPrimeExponentP(),
+                rsaPrv.getPrimeExponentQ(),
+                rsaPrv.getCrtCoefficient()));
     }
 
     @Override
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/signature/BuiltinSignatures.java b/sshd-common/src/main/java/org/apache/sshd/common/signature/BuiltinSignatures.java
index e9cd416..a0856b0 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/signature/BuiltinSignatures.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/signature/BuiltinSignatures.java
@@ -31,11 +31,13 @@ import java.util.NavigableSet;
 import java.util.Objects;
 import java.util.Set;
 import java.util.TreeMap;
+import java.util.concurrent.atomic.AtomicReference;
 
 import org.apache.sshd.common.NamedFactory;
 import org.apache.sshd.common.NamedResource;
 import org.apache.sshd.common.cipher.ECCurves;
 import org.apache.sshd.common.config.NamedFactoriesListParseResult;
+import org.apache.sshd.common.config.keys.KeyUtils;
 import org.apache.sshd.common.keyprovider.KeyPairProvider;
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.ValidateUtils;
@@ -56,7 +58,39 @@ public enum BuiltinSignatures implements SignatureFactory {
     rsa(KeyPairProvider.SSH_RSA) {
         @Override
         public Signature create() {
-            return new SignatureRSA();
+            return new SignatureRSASHA1();
+        }
+    },
+    rsaSHA256(KeyUtils.RSA_SHA256_KEY_TYPE_ALIAS) {
+        @Override
+        public Signature create() {
+            return new SignatureRSASHA256();
+        }
+    },
+    rsaSHA512(KeyUtils.RSA_SHA512_KEY_TYPE_ALIAS) {
+        private final AtomicReference<Boolean> supportHolder = new AtomicReference<>();
+
+        @Override
+        public Signature create() {
+            return new SignatureRSASHA512();
+        }
+
+        @Override
+        public boolean isSupported() {
+            Boolean supported = supportHolder.get();
+            if (supported == null) {
+                try {
+                    java.security.Signature sig =
+                        SecurityUtils.getSignature(SignatureRSASHA512.ALGORITHM);
+                    supported = sig != null;
+                } catch (Exception e) {
+                    supported = Boolean.FALSE;
+                }
+
+                supportHolder.set(supported);
+            }
+
+            return supported;
         }
     },
     nistp256(KeyPairProvider.ECDSA_SHA2_NISTP256) {
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureRSA.java b/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureRSA.java
index ad1d37e..23cc46c 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureRSA.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureRSA.java
@@ -23,6 +23,7 @@ import java.security.PublicKey;
 import java.security.interfaces.RSAKey;
 import java.util.Map;
 
+import org.apache.sshd.common.config.keys.KeyUtils;
 import org.apache.sshd.common.keyprovider.KeyPairProvider;
 import org.apache.sshd.common.util.ValidateUtils;
 
@@ -32,15 +33,9 @@ import org.apache.sshd.common.util.ValidateUtils;
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  * @see <A HREF="https://tools.ietf.org/html/rfc4253#section-6.6">RFC4253 section 6.6</A>
  */
-public class SignatureRSA extends AbstractSignature {
-    public static final String DEFAULT_ALGORITHM = "SHA1withRSA";
-
+public abstract class SignatureRSA extends AbstractSignature {
     private int verifierSignatureSize = -1;
 
-    public SignatureRSA() {
-        super(DEFAULT_ALGORITHM);
-    }
-
     protected SignatureRSA(String algorithm) {
         super(algorithm);
     }
@@ -71,7 +66,18 @@ public class SignatureRSA extends AbstractSignature {
         Map.Entry<String, byte[]> encoding = extractEncodedSignature(data);
         if (encoding != null) {
             String keyType = encoding.getKey();
-            ValidateUtils.checkTrue(KeyPairProvider.SSH_RSA.equals(keyType), "Mismatched key type: %s", keyType);
+            /*
+             * According to https://tools.ietf.org/html/rfc8332#section-3.2:
+             *
+             *      OpenSSH 7.2 (but not 7.2p2) incorrectly encodes the algorithm in the
+             *      signature as "ssh-rsa" when the algorithm in SSH_MSG_USERAUTH_REQUEST
+             *      is "rsa-sha2-256" or "rsa-sha2-512".  In this case, the signature
+             *      does actually use either SHA-256 or SHA-512.  A server MAY, but is
+             *      not required to, accept this variant or another variant that
+             *      corresponds to a good-faith implementation and is considered safe to accept.
+             */
+            String canonicalName = KeyUtils.getCanonicalKeyType(keyType);
+            ValidateUtils.checkTrue(KeyPairProvider.SSH_RSA.equals(canonicalName), "Mismatched key type: %s", keyType);
             data = encoding.getValue();
         }
 
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureRSASHA1.java b/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureRSASHA1.java
new file mode 100644
index 0000000..a20278f
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureRSASHA1.java
@@ -0,0 +1,31 @@
+/*
+ * 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.common.signature;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class SignatureRSASHA1 extends SignatureRSA {
+    public static final String ALGORITHM = "SHA1withRSA";
+
+    public SignatureRSASHA1() {
+        super(ALGORITHM);
+    }
+}
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureRSASHA256.java b/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureRSASHA256.java
new file mode 100644
index 0000000..fae2354
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureRSASHA256.java
@@ -0,0 +1,31 @@
+/*
+ * 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.common.signature;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class SignatureRSASHA256 extends SignatureRSA {
+    public static final String ALGORITHM = "SHA256withRSA";
+
+    public SignatureRSASHA256() {
+        super(ALGORITHM);
+    }
+}
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureRSASHA512.java b/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureRSASHA512.java
new file mode 100644
index 0000000..eff5b0f
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureRSASHA512.java
@@ -0,0 +1,31 @@
+/*
+ * 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.common.signature;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class SignatureRSASHA512 extends SignatureRSA {
+    public static final String ALGORITHM = "SHA512withRSA";
+
+    public SignatureRSASHA512() {
+        super(ALGORITHM);
+    }
+}
diff --git a/sshd-common/src/test/java/rg/apache/sshd/client/config/keys/LazyClientIdentityIteratorTest.java b/sshd-common/src/test/java/org/apache/sshd/client/config/keys/LazyClientIdentityIteratorTest.java
similarity index 97%
rename from sshd-common/src/test/java/rg/apache/sshd/client/config/keys/LazyClientIdentityIteratorTest.java
rename to sshd-common/src/test/java/org/apache/sshd/client/config/keys/LazyClientIdentityIteratorTest.java
index 8e072c1..86a3b96 100644
--- a/sshd-common/src/test/java/rg/apache/sshd/client/config/keys/LazyClientIdentityIteratorTest.java
+++ b/sshd-common/src/test/java/org/apache/sshd/client/config/keys/LazyClientIdentityIteratorTest.java
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-package rg.apache.sshd.client.config.keys;
+package org.apache.sshd.client.config.keys;
 
 import java.security.KeyPair;
 import java.security.PrivateKey;
@@ -27,7 +27,6 @@ import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
 
-import org.apache.sshd.client.config.keys.ClientIdentityProvider;
 import org.apache.sshd.common.session.SessionContext;
 import org.apache.sshd.util.test.JUnitTestSupport;
 import org.junit.FixMethodOrder;
diff --git a/sshd-common/src/test/java/org/apache/sshd/common/config/keys/KeyUtilsTest.java b/sshd-common/src/test/java/org/apache/sshd/common/config/keys/KeyUtilsTest.java
index 00b273f..b62057d 100644
--- a/sshd-common/src/test/java/org/apache/sshd/common/config/keys/KeyUtilsTest.java
+++ b/sshd-common/src/test/java/org/apache/sshd/common/config/keys/KeyUtilsTest.java
@@ -34,6 +34,7 @@ import org.apache.sshd.common.digest.BaseDigest;
 import org.apache.sshd.common.digest.BuiltinDigests;
 import org.apache.sshd.common.digest.Digest;
 import org.apache.sshd.common.digest.DigestFactory;
+import org.apache.sshd.common.keyprovider.KeyPairProvider;
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.OsUtils;
 import org.apache.sshd.common.util.io.IoUtils;
@@ -154,4 +155,11 @@ public class KeyUtilsTest extends JUnitTestSupport {
             assertNull("Unexpected Windows violation for file " + file + " permissions=" + perms, KeyUtils.validateStrictKeyFilePermissions(file));
         }
     }
+
+    @Test   // see SSHD-895
+    public void testRSAKeyTypeAliases() {
+        for (String alias : new String[] {KeyUtils.RSA_SHA256_KEY_TYPE_ALIAS, KeyUtils.RSA_SHA512_KEY_TYPE_ALIAS}) {
+            assertEquals("Mismatched canonical name for " + alias, KeyPairProvider.SSH_RSA, KeyUtils.getCanonicalKeyType(alias));
+        }
+    }
 }
diff --git a/sshd-common/src/test/java/org/apache/sshd/common/signature/RSASignatureVariantsTest.java b/sshd-common/src/test/java/org/apache/sshd/common/signature/RSASignatureVariantsTest.java
new file mode 100644
index 0000000..806be61
--- /dev/null
+++ b/sshd-common/src/test/java/org/apache/sshd/common/signature/RSASignatureVariantsTest.java
@@ -0,0 +1,67 @@
+/*
+ * 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.common.signature;
+
+import java.security.KeyPair;
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.sshd.common.config.keys.KeyUtils;
+import org.apache.sshd.util.test.JUnit4ClassRunnerWithParametersFactory;
+import org.apache.sshd.util.test.NoIoTestCase;
+import org.junit.BeforeClass;
+import org.junit.FixMethodOrder;
+import org.junit.experimental.categories.Category;
+import org.junit.runner.RunWith;
+import org.junit.runners.MethodSorters;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.junit.runners.Parameterized.UseParametersRunnerFactory;
+
+/**
+ * NOTE: some tests are inherited from parent
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Category({ NoIoTestCase.class })
+@RunWith(Parameterized.class)   // see https://github.com/junit-team/junit/wiki/Parameterized-tests
+@UseParametersRunnerFactory(JUnit4ClassRunnerWithParametersFactory.class)
+public class RSASignatureVariantsTest extends SignatureVariantTestSupport {
+    private static KeyPair kp;
+
+    public RSASignatureVariantsTest(SignatureFactory factory) {
+        super(factory, kp);
+    }
+
+    @BeforeClass
+    public static void initializeSigningKeyPair() throws Exception {
+        kp = initializeSigningKeyPair(KeyUtils.RSA_ALGORITHM);
+    }
+
+    @Parameters(name = "{0}")
+    public static List<Object[]> parameters() {
+        return parameterize(
+            Arrays.asList(
+                BuiltinSignatures.rsa,
+                BuiltinSignatures.rsaSHA256,
+                BuiltinSignatures.rsaSHA512));
+    }
+}
diff --git a/sshd-common/src/test/java/org/apache/sshd/common/signature/SignatureRSATest.java b/sshd-common/src/test/java/org/apache/sshd/common/signature/SignatureRSASHA1Test.java
similarity index 80%
rename from sshd-common/src/test/java/org/apache/sshd/common/signature/SignatureRSATest.java
rename to sshd-common/src/test/java/org/apache/sshd/common/signature/SignatureRSASHA1Test.java
index 5671ac3..38d9168 100644
--- a/sshd-common/src/test/java/org/apache/sshd/common/signature/SignatureRSATest.java
+++ b/sshd-common/src/test/java/org/apache/sshd/common/signature/SignatureRSASHA1Test.java
@@ -31,26 +31,29 @@ import org.apache.sshd.common.Factory;
 import org.apache.sshd.common.config.keys.KeyUtils;
 import org.apache.sshd.common.util.security.SecurityUtils;
 import org.apache.sshd.util.test.JUnitTestSupport;
+import org.apache.sshd.util.test.NoIoTestCase;
 import org.junit.BeforeClass;
 import org.junit.FixMethodOrder;
 import org.junit.Test;
+import org.junit.experimental.categories.Category;
 import org.junit.runners.MethodSorters;
 
 /**
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-public class SignatureRSATest extends JUnitTestSupport {
+@Category({ NoIoTestCase.class })
+public class SignatureRSASHA1Test extends JUnitTestSupport {
     private static final Base64.Decoder B64_DECODER = Base64.getDecoder();
     @SuppressWarnings("checkstyle:linelength")
     private static final byte[] TEST_MSG =
-            B64_DECODER.decode("AAAAFPHgK1MeV9zNnok3pwNJhCd8SONqMgAAAAlidWlsZHVzZXIAAAAOc3NoLWNvbm5lY3Rpb24AAAAJcHVibGlja2V5AQAAAAdzc2gtcnNhAAABFQAAAAdzc2gtcnNhAAAAASMAAAEBAMs9HO/NH/Now+6fSnESebaG4wzaYQWA1b/q1TGV1wHNtCg9fGFGVSKs0VxKF4cfVyrSLtgLjnlXQTn+Lm7xiYKGbBbsTQWOqEDaBVBsRbAkxIkpuvr6/EBxwrtDbKmSQYTJZVJSD2bZRYjGsR9gpZXPorOOKFd5EPCMHXsqnhp2hidTGH7cK6RuLk7MNnPISsY0Nbx8/ZvikiPROGcoTZ8bzUv4IaLr3veW6epSeQem8tJqhnrpTHhbLU99zf045M0Gsnk/azjjlBM+qrHZ5FNdC1kowJnLtf2Oy/rUQNpkGJtcBPT8xvreV0wLsn9t [...]
+        B64_DECODER.decode("AAAAFPHgK1MeV9zNnok3pwNJhCd8SONqMgAAAAlidWlsZHVzZXIAAAAOc3NoLWNvbm5lY3Rpb24AAAAJcHVibGlja2V5AQAAAAdzc2gtcnNhAAABFQAAAAdzc2gtcnNhAAAAASMAAAEBAMs9HO/NH/Now+6fSnESebaG4wzaYQWA1b/q1TGV1wHNtCg9fGFGVSKs0VxKF4cfVyrSLtgLjnlXQTn+Lm7xiYKGbBbsTQWOqEDaBVBsRbAkxIkpuvr6/EBxwrtDbKmSQYTJZVJSD2bZRYjGsR9gpZXPorOOKFd5EPCMHXsqnhp2hidTGH7cK6RuLk7MNnPISsY0Nbx8/ZvikiPROGcoTZ8bzUv4IaLr3veW6epSeQem8tJqhnrpTHhbLU99zf045M0Gsnk/azjjlBM+qrHZ5FNdC1kowJnLtf2Oy/rUQNpkGJtcBPT8xvreV0wLsn9t3hSx [...]
     @SuppressWarnings("checkstyle:linelength")
     private static final byte[] TEST_SIGNATURE =
-            B64_DECODER.decode("AAAAB3NzaC1yc2EAAAD/+Ntnf4qfr2J1voDS6I+u3VRjtMn+LdWJsAZfkLDxRkK1rQxP7QAjLdNqpT4CkWHp8dtoTGFlBFt6NieNJCMTA2KSOxJMZKsX7e/lHkh7C+vhQvJ9eLTKWjCxSFUrcM0NvFhmwbRCffwXSHvAKak4wbmofxQMpd+G4jZkNMz5kGpmeICBcNjRLPb7oXzuGr/g4x/3ge5Qaawqrg/gcZr/sKN6SdE8SszgKYO0SB320N4gcUoShVdLYr9uwdJ+kJoobfkUK6Or171JCctP/cu2nM79lDqVnJw/2jOG8OnTc8zRDXAh0RKoR5rOU8cOHm0Ls2MATsFdnyRU5FGUxqZ+");
+        B64_DECODER.decode("AAAAB3NzaC1yc2EAAAD/+Ntnf4qfr2J1voDS6I+u3VRjtMn+LdWJsAZfkLDxRkK1rQxP7QAjLdNqpT4CkWHp8dtoTGFlBFt6NieNJCMTA2KSOxJMZKsX7e/lHkh7C+vhQvJ9eLTKWjCxSFUrcM0NvFhmwbRCffwXSHvAKak4wbmofxQMpd+G4jZkNMz5kGpmeICBcNjRLPb7oXzuGr/g4x/3ge5Qaawqrg/gcZr/sKN6SdE8SszgKYO0SB320N4gcUoShVdLYr9uwdJ+kJoobfkUK6Or171JCctP/cu2nM79lDqVnJw/2jOG8OnTc8zRDXAh0RKoR5rOU8cOHm0Ls2MATsFdnyRU5FGUxqZ+");
     private static PublicKey testKey;
 
-    public SignatureRSATest() {
+    public SignatureRSASHA1Test() {
         super();
     }
 
@@ -68,7 +71,7 @@ public class SignatureRSATest extends JUnitTestSupport {
         testLeadingZeroes(new Factory<SignatureRSA>() {
             @Override
             public SignatureRSA create() {
-                return new SignatureRSA() {
+                return new SignatureRSASHA1() {
                     @Override
                     protected java.security.Signature doInitSignature(String algo, boolean forSigning) throws GeneralSecurityException {
                         assertFalse("Signature not initialized for verification", forSigning);
@@ -87,7 +90,7 @@ public class SignatureRSATest extends JUnitTestSupport {
 
     @Test   // see SSHD-642
     public void testLeadingZeroesJCE() throws Throwable {
-        testLeadingZeroes(() -> new SignatureRSA() {
+        testLeadingZeroes(() -> new SignatureRSASHA1() {
             @Override
             protected java.security.Signature doInitSignature(String algo, boolean forSigning) throws GeneralSecurityException {
                 assertFalse("Signature not initialized for verification", forSigning);
diff --git a/sshd-common/src/test/java/org/apache/sshd/common/signature/SignatureVariantTestSupport.java b/sshd-common/src/test/java/org/apache/sshd/common/signature/SignatureVariantTestSupport.java
new file mode 100644
index 0000000..ede85e8
--- /dev/null
+++ b/sshd-common/src/test/java/org/apache/sshd/common/signature/SignatureVariantTestSupport.java
@@ -0,0 +1,80 @@
+/*
+ * 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.common.signature;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.util.Objects;
+
+import org.apache.sshd.client.config.keys.ClientIdentity;
+import org.apache.sshd.common.NamedResource;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.security.SecurityUtils;
+import org.apache.sshd.util.test.JUnitTestSupport;
+import org.junit.Assume;
+import org.junit.Test;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public abstract class SignatureVariantTestSupport extends JUnitTestSupport {
+    protected final SignatureFactory factory;
+    protected final KeyPair kp;
+
+    protected SignatureVariantTestSupport(SignatureFactory factory, KeyPair kp) {
+        this.factory = Objects.requireNonNull(factory, "No factory provided");
+        this.kp = Objects.requireNonNull(kp, "No key pair provided");
+        Assume.assumeTrue("Unsupported factory: " + factory, factory.isSupported());
+    }
+
+    protected static KeyPair initializeSigningKeyPair(String algorithm) throws IOException, GeneralSecurityException {
+        String resourceKey = ClientIdentity.getIdentityFileName(algorithm);
+        URL urlKeyPair = SignatureVariantTestSupport.class.getResource(resourceKey);
+        assertNotNull("Missing key-pair resource: " + resourceKey, urlKeyPair);
+        try (InputStream stream = urlKeyPair.openStream()) {
+            Iterable<KeyPair> ids =
+                SecurityUtils.loadKeyPairIdentities(null, NamedResource.ofName(resourceKey), stream, null);
+            return GenericUtils.head(ids);
+        }
+    }
+
+    @Test
+    public void testSignature() throws Exception {
+        byte[] data = (getClass().getName() + "#" + getCurrentTestName()).getBytes(StandardCharsets.UTF_8);
+        Signature signer = factory.create();
+        signer.initSigner(kp.getPrivate());
+        signer.update(data);
+
+        byte[] signature = signer.sign();
+        Signature verifier = factory.create();
+        verifier.initVerifier(kp.getPublic());
+        verifier.update(data);
+        verifier.verify(signature);
+    }
+
+    @Override
+    public String toString() {
+        return getClass().getSimpleName() + "[" + factory + "]";
+    }
+}
diff --git a/sshd-common/src/test/resources/org/apache/sshd/common/signature/id_dsa b/sshd-common/src/test/resources/org/apache/sshd/common/signature/id_dsa
new file mode 100644
index 0000000..1d3ef24
--- /dev/null
+++ b/sshd-common/src/test/resources/org/apache/sshd/common/signature/id_dsa
@@ -0,0 +1,12 @@
+-----BEGIN DSA PRIVATE KEY-----
+MIIBvAIBAAKBgQDIPyMbBuQcZxeYDOyCqqkdK37cWQvp+RpWzvieB/oiG/ykfDQX
+oZMRtwqwWTBfejNitbBBmC6G/t5OK+9aFmj7pfJ+a7fZKXfiUquIg9soDsoOindf
+2AwR6MZ3os8uiP2xrC8IQAClnETa15mFShs4a4b2VjddgCQ6tphnY97MywIVAPtr
+YyW11RIXsVTf/9KlbhYaNlt5AoGAX9JzbHykC/0xDKOyKU6xDIOVdEZ0ooAl9Psl
+BEUuNhlv2XgmQScO6C9l2W7gbbut7zIw4FaZ2/dgXa3D4IyexBVug5XMnrssErZo
+NcoF5g0dgEAsb9Hl9gkIK3VHM5kWteeUg1VE700JTtSMisdL8CgIdR+xN8iVH5Ew
+CbLWxmECgYEAtv+cdRfNevYFkp55jVqazc8zRLvfb64jzgc5oSJVc64kFs4yx+ab
+YpGX9WxNxDlG6g2WiY8voDBB0YnUJsn0kVRjBKX9OceROxrfT4K4dVbQZsdt+SLa
+XWL4lGJFrFZL3LZqvySvq6xfhJfakQDDivW4hUOhFPXPHrE5/Ia3T7ACFQCE6flG
+nmVCAbzo9YsbdJWBnxMnBA==
+-----END DSA PRIVATE KEY-----
\ No newline at end of file
diff --git a/sshd-common/src/test/resources/org/apache/sshd/common/signature/id_ecdsa b/sshd-common/src/test/resources/org/apache/sshd/common/signature/id_ecdsa
new file mode 100644
index 0000000..31b1268
--- /dev/null
+++ b/sshd-common/src/test/resources/org/apache/sshd/common/signature/id_ecdsa
@@ -0,0 +1,5 @@
+-----BEGIN EC PRIVATE KEY-----
+MHcCAQEEIPKmiQzAASg656IP4PuuElLdLdO/MIXrGxQG6tGkKZ1HoAoGCCqGSM49
+AwEHoUQDQgAEobHtw9wkL332ep9fi8Gw5g8sEGwslNonPUCDR6YUZ9mjOehliLpF
+DLHLxlIFafrVM+LIpagjpRKZcnpGPWQDnA==
+-----END EC PRIVATE KEY-----
diff --git a/sshd-common/src/test/resources/org/apache/sshd/common/signature/id_rsa b/sshd-common/src/test/resources/org/apache/sshd/common/signature/id_rsa
new file mode 100644
index 0000000..afc6aa8
--- /dev/null
+++ b/sshd-common/src/test/resources/org/apache/sshd/common/signature/id_rsa
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEoQIBAAKCAQEAxr3N5fkt966xJINl0hH7Q6lLDRR1D0yMjcXCE5roE9VFut2c
+tGFuo90TCOxkPOMnwzwConeyScVF4ConZeWsxbG9VtRh61IeZ6R5P5ZTvE9xPdZB
+gIEWvU1bRfrrOfSMihqF98pODspE6NoTtND2eglwSGwxcYFmpdTAmu+8qgxgGxlE
+aaCjqwdiNPZhygrH81Mv2ruolNeZkn4Bj+wFFmZTD/waN1pQaMf+SO1+kEYIYFNl
+5+8JRGuUcr8MhHHJB+gwqMTF2BSBVITJzZUiQR0TMtkK6Vbs7yt1F9hhzDzAFDwh
+V+rsfNQaOHpl3zP07qH+/99A0XG1CVcEdHqVMwIBIwKCAQALW02YHN4OJz1Siypj
+xoNi87slUaBKBF/NlkWauGUIcpZFMTwnkIn6vCz5MhRbQC4oadRDzFNUrC/g7HdH
+prlqYe2P7uEGIfMb3YNFdk3tgOHmRsHqFgFMpVWsOjlTxNTUsQ74N3Isuxnha4wY
+9f90sBULc+WRdRvO9jbkSDaqoYVKAqCFWtocL+ZWwBXWrIrsQW4PElgZ/duc5DX7
+eeJ5DXCSj9dO+1KxsWEOKaoeABEegrRVys1/shcDNPhf/p0QShKIdPcpnDUc8cny
+1bq8GSt6jEQ+tuRoSnYrY+RD+mlkHrx373Xc1a9woV+QKTThmd9TQ8gzHMHNqq0a
+7kR7AoGBAOuPOTRiKaDtQyMTEX7eeHsPNE24EgvONjNpxyQ6gKGthG5SiB0IO7mP
+r7EggbR2EY8eMCY5HjvxzxgH86n2Pqbzsr6UlQq7YTPupCm/7fPgRknu917GA20f
+1cuY8B04Jp4FIGryBmCcScX6usXXhjfAvYCWWfkSytA8gX9+b1TNAoGBANf8shbp
+wRnQfgAzw2S+xs29pdwa6Jb/xuLvHSyklmgidrK4nsVI8G+zeCqwkqkNM02sM+vR
+c8EX7+myrGf+S2V3JS3AMNXEhavrWVH0CuqFHlBjSwHZ0uKuPpWHlCnud+23AdQz
+Bf1H7tYKt5es3J/B37o4YxhAL6U9qq+ewZH/AoGBAOTURjLjA94oT9jt870SoOyS
+bVLQEYfPol3UeE8UQnEsN4Ec+UDGK2PNaNfzsTL2WjNB5aF5UJIA184znD60seQC
+raMxQFORdF5ViYekgMEFwJ+XrnlSpD4e7PGqgtqOUWZOH33VKsRADSa5DTU3xDYo
+8porp9wDoHKD64MqXYWTAoGADFeVJeF4v6WDivukw+2k9dBSntz3WRXuG5iilNY0
+evqnsnDzITdeMkTFCcDycA9iBHA9ezCKRYxW2id3kOn1rgbO7KvliI7jESNkMJGa
+OUlvck7RFgxyc1pp+ep9fr0rbKtfMLJ1Xu4q56jXSn7oCSEFeFr+WSg9PKRwJ0rm
+fV8CgYAkYOiNY8jH5lwwoPWOIPJg62zdzmRQktrU3z7Nzk5udN6JnG3g57QjkozX
+AgHARKQ2MuXW9OfOnYNhbGeavcBQmg5XUx3eL4PRFw8mFZdjpBD/kM/dfCEwEta3
+FpRlVGn0RNqVV5xxClObD/CikkDqKZG4MSj3CrO3JK33gl1Lgg==
+-----END RSA PRIVATE KEY-----


[mina-sshd] 06/06: [SSHD-896] Added handling of SSH_MSG_NEWCOMPRESS via the registered KexExtensionHandler

Posted by lg...@apache.org.
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 8200e7cda22c665c74736e56383d9bc50b8bf708
Author: Lyor Goldstein <lg...@apache.org>
AuthorDate: Tue Feb 19 17:20:31 2019 +0200

    [SSHD-896] Added handling of SSH_MSG_NEWCOMPRESS via the registered KexExtensionHandler
---
 .../common/kex/extension/KexExtensionHandler.java  | 26 +++++++++++++++++++---
 .../common/session/helpers/AbstractSession.java    | 21 ++++++++++++++---
 2 files changed, 41 insertions(+), 6 deletions(-)

diff --git a/sshd-core/src/main/java/org/apache/sshd/common/kex/extension/KexExtensionHandler.java b/sshd-core/src/main/java/org/apache/sshd/common/kex/extension/KexExtensionHandler.java
index 45c6384..a34c0f0 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/kex/extension/KexExtensionHandler.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/kex/extension/KexExtensionHandler.java
@@ -109,24 +109,44 @@ public interface KexExtensionHandler {
 
     /**
      * Parses the {@code SSH_MSG_EXT_INFO} message. <B>Note:</B> this method
-     * is called only if {@link #isKexExtensionsAvailable(Session)} returns
+     * is called regardless of whether {@link #isKexExtensionsAvailable(Session)} returns
      * {@code true} for the session.
      *
      * @param session The {@link Session} through which the message was received
      * @param buffer The message buffer
+     * @return {@code true} if message handled - if {@code false} then {@code SSH_MSG_UNIMPLEMENTED}
+     * will be generated
      * @throws IOException If failed to handle the message
      * @see <A HREF="https://tools.ietf.org/html/rfc8308#section-2.3">RFC-8308 - section 2.3</A>
      * @see #handleKexExtensionRequest(Session, int, int, String, byte[])
      */
-    default void handleKexExtensionsMessage(Session session, Buffer buffer) throws IOException {
+    default boolean handleKexExtensionsMessage(Session session, Buffer buffer) throws IOException {
         int count = buffer.getInt();
         for (int index = 0; index < count; index++) {
             String name = buffer.getString();
             byte[] data = buffer.getBytes();
             if (!handleKexExtensionRequest(session, index, count, name, data)) {
-                return;
+                break;
             }
         }
+
+        return true;
+    }
+
+    /**
+     * Parses the {@code SSH_MSG_NEWCOMPRESS} message. <B>Note:</B> this method
+     * is called regardless of whether {@link #isKexExtensionsAvailable(Session)} returns
+     * {@code true} for the session.
+     *
+     * @param session The {@link Session} through which the message was received
+     * @param buffer The message buffer
+     * @return {@code true} if message handled - if {@code false} then {@code SSH_MSG_UNIMPLEMENTED}
+     * will be generated
+     * @throws IOException If failed to handle the message
+     * @see <A HREF="https://tools.ietf.org/html/rfc8308#section-3.2">RFC-8308 - section 3.2</A>
+     */
+    default boolean handleKexCompressionMessage(Session session, Buffer buffer) throws IOException {
+        return true;
     }
 
     /**
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 11c6d87..c452293 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
@@ -414,6 +414,9 @@ public abstract class AbstractSession extends SessionHelper {
             case KexExtensions.SSH_MSG_EXT_INFO:
                 handleKexExtension(cmd, buffer);
                 break;
+            case KexExtensions.SSH_MSG_NEWCOMPRESS:
+                handleNewCompression(cmd, buffer);
+                break;
             default:
                 if ((cmd >= SshConstants.SSH_MSG_KEX_FIRST) && (cmd <= SshConstants.SSH_MSG_KEX_LAST)) {
                     if (firstKexPacketFollows != null) {
@@ -545,12 +548,24 @@ public abstract class AbstractSession extends SessionHelper {
 
     protected void handleKexExtension(int cmd, Buffer buffer) throws Exception {
         KexExtensionHandler extHandler = getKexExtensionHandler();
-        if ((extHandler == null) || (!extHandler.isKexExtensionsAvailable(this))) {
-            notImplemented(cmd, buffer);
+        int startPos = buffer.rpos();
+        if ((extHandler != null) && extHandler.handleKexExtensionsMessage(this, buffer)) {
+            return;
+        }
+
+        buffer.rpos(startPos);  // restore original read position
+        notImplemented(cmd, buffer);
+    }
+
+    protected void handleNewCompression(int cmd, Buffer buffer) throws Exception {
+        KexExtensionHandler extHandler = getKexExtensionHandler();
+        int startPos = buffer.rpos();
+        if ((extHandler != null) && extHandler.handleKexCompressionMessage(this, buffer)) {
             return;
         }
 
-        extHandler.handleKexExtensionsMessage(this, buffer);
+        buffer.rpos(startPos);  // restore original read position
+        notImplemented(cmd, buffer);
     }
 
     protected void handleServiceRequest(Buffer buffer) throws Exception {


[mina-sshd] 05/06: [SSHD-895] Implemented a sample DefaultClientKexExtensionHandler that updates the client's signature factories

Posted by lg...@apache.org.
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 562d2fe7f8313a14299263dacb29bde626b52a77
Author: Lyor Goldstein <lg...@apache.org>
AuthorDate: Mon Feb 18 10:07:28 2019 +0200

    [SSHD-895] Implemented a sample DefaultClientKexExtensionHandler that updates the client's signature factories
---
 CHANGES.md                                         |   9 +
 .../sshd/cli/client/SshClientCliSupport.java       |  34 +++
 .../sshd/common/kex/extension/KexExtensions.java   |   5 +
 .../sshd/common/signature/SignatureFactory.java    | 105 ++++++++
 .../DefaultClientKexExtensionHandler.java          | 293 +++++++++++++++++++++
 .../common/kex/extension/KexExtensionHandler.java  |  53 +++-
 .../org/apache/sshd/common/session/Session.java    |   8 +
 .../common/session/helpers/AbstractSession.java    |  45 +++-
 .../sshd/common/session/helpers/SessionHelper.java |   1 +
 9 files changed, 544 insertions(+), 9 deletions(-)

diff --git a/CHANGES.md b/CHANGES.md
index 9815aa6..5a2777c 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -18,6 +18,15 @@ current sesssion - client/server proposals and what has been negotiated.
 
 * The `Session` object provides a `KexExtensionHandler` for usage with [KEX extension negotiation](https://tools.wordtothewise.com/rfc/rfc8308)
 
+## Minor code helpers
+
+* The `Session` object provides a `isServerSession` method that can be used to distinguish between
+client/server instances without having to resort to `instanceof`.
+
+* When creating a CLI SSH client one can specify `-o KexExtensionHandler=XXX` option to initialize
+a client-side `KexExtensionHandler` using an FQCN. If `default` is specified as the option value,
+then the internal `DefaultClientKexExtensionHandler` is used.
+
 ## Behavioral changes and enhancements
 
 * [SSHD-882](https://issues.apache.org/jira/browse/SSHD-882) - Provide hooks to allow users to register a consumer
diff --git a/sshd-cli/src/main/java/org/apache/sshd/cli/client/SshClientCliSupport.java b/sshd-cli/src/main/java/org/apache/sshd/cli/client/SshClientCliSupport.java
index ebc8f78..8e68cc6 100644
--- a/sshd-cli/src/main/java/org/apache/sshd/cli/client/SshClientCliSupport.java
+++ b/sshd-cli/src/main/java/org/apache/sshd/cli/client/SshClientCliSupport.java
@@ -70,12 +70,16 @@ import org.apache.sshd.common.config.ConfigFileReaderSupport;
 import org.apache.sshd.common.config.keys.BuiltinIdentities;
 import org.apache.sshd.common.config.keys.KeyUtils;
 import org.apache.sshd.common.config.keys.PublicKeyEntry;
+import org.apache.sshd.common.kex.KexFactoryManager;
+import org.apache.sshd.common.kex.extension.DefaultClientKexExtensionHandler;
+import org.apache.sshd.common.kex.extension.KexExtensionHandler;
 import org.apache.sshd.common.keyprovider.FileKeyPairProvider;
 import org.apache.sshd.common.mac.BuiltinMacs;
 import org.apache.sshd.common.mac.Mac;
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.OsUtils;
 import org.apache.sshd.common.util.io.NoCloseOutputStream;
+import org.apache.sshd.common.util.threads.ThreadUtils;
 
 /**
  * TODO Add javadoc
@@ -335,6 +339,7 @@ public abstract class SshClientCliSupport extends CliSupport {
 
             setupServerKeyVerifier(client, options, stdin, stdout, stderr);
             setupSessionUserInteraction(client, stdin, stdout, stderr);
+            setupSessionExtensions(client, options, stdin, stdout, stderr);
 
             Map<String, Object> props = client.getProperties();
             props.putAll(options);
@@ -422,6 +427,35 @@ public abstract class SshClientCliSupport extends CliSupport {
         return ui;
     }
 
+    public static void setupSessionExtensions(
+            KexFactoryManager manager, Map<String, ?> options, BufferedReader stdin, PrintStream stdout, PrintStream stderr)
+                throws Exception {
+        String kexExtension = Objects.toString(options.remove(KexExtensionHandler.class.getSimpleName()), null);
+        if (GenericUtils.isEmpty(kexExtension)) {
+            return;
+        }
+
+        if ("default".equalsIgnoreCase(kexExtension)) {
+            manager.setKexExtensionHandler(DefaultClientKexExtensionHandler.INSTANCE);
+            stdout.println("Using " + DefaultClientKexExtensionHandler.class.getSimpleName());
+        } else {
+            ClassLoader cl = ThreadUtils.resolveDefaultClassLoader(KexExtensionHandler.class);
+            try {
+                Class<?> clazz = cl.loadClass(kexExtension);
+                KexExtensionHandler handler = KexExtensionHandler.class.cast(clazz.newInstance());
+                manager.setKexExtensionHandler(handler);
+            } catch (Exception e) {
+                stderr.append("ERROR: Failed (").append(e.getClass().getSimpleName()).append(')')
+                    .append(" to instantiate KEX extension handler=").append(kexExtension)
+                    .append(": ").println(e.getMessage());
+                stderr.flush();
+                throw e;
+            }
+
+            stdout.println("Using " + KexExtensionHandler.class.getSimpleName() + "=" + kexExtension);
+        }
+    }
+
     public static ServerKeyVerifier setupServerKeyVerifier(
             ClientAuthenticationManager manager, Map<String, ?> options, BufferedReader stdin, PrintStream stdout, PrintStream stderr) {
         ServerKeyVerifier current = manager.getServerKeyVerifier();
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/kex/extension/KexExtensions.java b/sshd-common/src/main/java/org/apache/sshd/common/kex/extension/KexExtensions.java
index 41fbce0..cdf484a 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/kex/extension/KexExtensions.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/kex/extension/KexExtensions.java
@@ -32,6 +32,7 @@ import java.util.NavigableSet;
 import java.util.Objects;
 import java.util.TreeMap;
 import java.util.function.Function;
+import java.util.function.Predicate;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
@@ -57,6 +58,10 @@ public final class KexExtensions {
     public static final String CLIENT_KEX_EXTENSION = "ext-info-c";
     public static final String SERVER_KEX_EXTENSION = "ext-info-s";
 
+    @SuppressWarnings("checkstyle:Indentation")
+    public static final Predicate<String> IS_KEX_EXTENSION_SIGNAL =
+        n -> CLIENT_KEX_EXTENSION.equalsIgnoreCase(n) || SERVER_KEX_EXTENSION.equalsIgnoreCase(n);
+
     /**
      * A case <U>insensitive</U> map of all the default known {@link KexExtensionParser}
      * where key=the extension name
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureFactory.java b/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureFactory.java
index b6273c0..25296d0 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureFactory.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureFactory.java
@@ -20,16 +20,20 @@
 package org.apache.sshd.common.signature;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
+import java.util.TreeMap;
 
 import org.apache.sshd.common.BuiltinFactory;
 import org.apache.sshd.common.NamedFactory;
 import org.apache.sshd.common.NamedResource;
 import org.apache.sshd.common.config.keys.KeyUtils;
+import org.apache.sshd.common.keyprovider.KeyPairProvider;
 import org.apache.sshd.common.util.GenericUtils;
 
 /**
@@ -37,6 +41,26 @@ import org.apache.sshd.common.util.GenericUtils;
  */
 public interface SignatureFactory extends BuiltinFactory<Signature> {
     /**
+     * ECC signature types in ascending order of preference (i.e., most preferred 1st)
+     */
+    List<String> ECC_SIGNATURE_TYPE_PREFERENCES =
+        Collections.unmodifiableList(
+            Arrays.asList(
+                    KeyPairProvider.ECDSA_SHA2_NISTP521,
+                    KeyPairProvider.ECDSA_SHA2_NISTP384,
+                    KeyPairProvider.ECDSA_SHA2_NISTP256));
+
+    /**
+     * RSA signature types in ascending order of preference (i.e., most preferred 1st)
+     */
+    List<String> RSA_SIGNATURE_TYPE_PREFERENCES =
+        Collections.unmodifiableList(
+            Arrays.asList(
+                KeyUtils.RSA_SHA512_KEY_TYPE_ALIAS,
+                KeyUtils.RSA_SHA256_KEY_TYPE_ALIAS,
+                KeyPairProvider.SSH_RSA));
+
+    /**
      * @param provided The provided signature key types
      * @param factories The available signature factories
      * @return A {@link List} of the matching available factories names
@@ -89,5 +113,86 @@ public interface SignatureFactory extends BuiltinFactory<Signature> {
 
         return supported;
     }
+
+    // returns -1 or > size() if append to end
+    static int resolvePreferredSignaturePosition(
+            List<? extends NamedFactory<Signature>> factories, NamedFactory<Signature> factory) {
+        if (GenericUtils.isEmpty(factories)) {
+            return -1;  // just add it to the end
+        }
+
+        String name = factory.getName();
+        if (KeyPairProvider.SSH_RSA.equalsIgnoreCase(name)) {
+            return -1;
+        }
+
+        int pos = RSA_SIGNATURE_TYPE_PREFERENCES.indexOf(name);
+        if (pos >= 0) {
+            Map<String, Integer> posMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+            for (int index = 0, count = factories.size(); index < count; index++) {
+                NamedFactory<Signature> f = factories.get(index);
+                String keyType = f.getName();
+                String canonicalName = KeyUtils.getCanonicalKeyType(keyType);
+                if (!KeyPairProvider.SSH_RSA.equalsIgnoreCase(canonicalName)) {
+                    continue;   // debug breakpoint
+                }
+
+                posMap.put(keyType, index);
+            }
+
+            return resolvePreferredSignaturePosition(RSA_SIGNATURE_TYPE_PREFERENCES, pos, posMap);
+        }
+
+        pos = ECC_SIGNATURE_TYPE_PREFERENCES.indexOf(name);
+        if (pos >= 0) {
+            Map<String, Integer> posMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+            for (int index = 0, count = factories.size(); index < count; index++) {
+                NamedFactory<Signature> f = factories.get(index);
+                String keyType = f.getName();
+                if (!ECC_SIGNATURE_TYPE_PREFERENCES.contains(keyType)) {
+                    continue;   // debug breakpoint
+                }
+
+                posMap.put(keyType, index);
+            }
+
+            return resolvePreferredSignaturePosition(ECC_SIGNATURE_TYPE_PREFERENCES, pos, posMap);
+        }
+
+        return -1;  // no special preference - stick it as last
+    }
+
+    static int resolvePreferredSignaturePosition(
+            List<String> preferredOrder, int prefValue, Map<String, Integer> posMap) {
+        if (GenericUtils.isEmpty(preferredOrder) || (prefValue < 0) || GenericUtils.isEmpty(posMap)) {
+            return -1;
+        }
+
+        int posValue = -1;
+        for (Map.Entry<String, Integer> pe : posMap.entrySet()) {
+            String name = pe.getKey();
+            int order = preferredOrder.indexOf(name);
+            if (order < 0) {
+                continue;   // should not happen, but tolerate
+            }
+
+            Integer curIndex = pe.getValue();
+            int resIndex;
+            if (order < prefValue) {
+                resIndex = curIndex.intValue() + 1;
+            } else if (order > prefValue) {
+                resIndex = curIndex.intValue(); // by using same index we insert in front of it in effect
+            } else {
+                continue;   // should not happen, but tolerate
+            }
+
+            // Preferred factories should be as close as possible to the beginning of the list
+            if ((posValue < 0) || (resIndex < posValue)) {
+                posValue = resIndex;
+            }
+        }
+
+        return posValue;
+    }
 }
 
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/kex/extension/DefaultClientKexExtensionHandler.java b/sshd-core/src/main/java/org/apache/sshd/common/kex/extension/DefaultClientKexExtensionHandler.java
new file mode 100644
index 0000000..8654a84
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/common/kex/extension/DefaultClientKexExtensionHandler.java
@@ -0,0 +1,293 @@
+/*
+ * 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.common.kex.extension;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumMap;
+import java.util.List;
+import java.util.Map;
+import java.util.NavigableSet;
+import java.util.Objects;
+import java.util.stream.Stream;
+
+import org.apache.sshd.common.AttributeRepository.AttributeKey;
+import org.apache.sshd.common.NamedFactory;
+import org.apache.sshd.common.NamedResource;
+import org.apache.sshd.common.OptionalFeature;
+import org.apache.sshd.common.config.keys.KeyUtils;
+import org.apache.sshd.common.kex.KexProposalOption;
+import org.apache.sshd.common.kex.extension.parser.ServerSignatureAlgorithms;
+import org.apache.sshd.common.session.Session;
+import org.apache.sshd.common.signature.BuiltinSignatures;
+import org.apache.sshd.common.signature.Signature;
+import org.apache.sshd.common.signature.SignatureFactory;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.logging.AbstractLoggingBean;
+
+/**
+ * Detects if the server sends a <A HREF="https://tools.ietf.org/html/rfc8308#section-3.1">&quot;server-sig-algs&quot;</A>
+ * and updates the client session by adding the <A HREF="https://tools.ietf.org/html/rfc8332">&quot;rsa-sha2-256/512&quot;</A>
+ * signature factories (if not already added).
+ *
+ * <B>Note:</B> experimental - used for development purposes and as an example
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class DefaultClientKexExtensionHandler extends AbstractLoggingBean implements KexExtensionHandler {
+    /**
+     * Session {@link AttributeKey} used to store the client's proposal
+     */
+    public static final AttributeKey<Map<KexProposalOption, String>> PROPOSAL_KEY = new AttributeKey<>();
+
+    public static final NavigableSet<String> DEFAULT_EXTRA_SIGNATURES =
+        Collections.unmodifiableNavigableSet(
+            GenericUtils.asSortedSet(String.CASE_INSENSITIVE_ORDER,
+                KeyUtils.RSA_SHA256_KEY_TYPE_ALIAS,
+                KeyUtils.RSA_SHA512_KEY_TYPE_ALIAS));
+
+    public static final DefaultClientKexExtensionHandler INSTANCE = new DefaultClientKexExtensionHandler();
+
+    public DefaultClientKexExtensionHandler() {
+        super();
+    }
+
+    @Override
+    public boolean isKexExtensionsAvailable(Session session) throws IOException {
+        return (session != null) && (!session.isServerSession());
+    }
+
+    @Override
+    public void handleKexInitProposal(
+            Session session, boolean initiator, Map<KexProposalOption, String> proposal)
+                throws IOException {
+        if (session.isServerSession()) {
+            return; // just in case
+        }
+
+        boolean debugEnabled = log.isDebugEnabled();
+        Collection<String> extraAlgos = getExtraSignatureAlgorithms(session);
+        if (GenericUtils.isEmpty(extraAlgos)) {
+            if (debugEnabled) {
+                log.debug("handleKexInitProposal({}) no extra signatures to add to {}", session, proposal);
+            }
+            return;
+        }
+
+        Collection<? extends NamedResource> sigList = session.getSignatureFactories();
+        long existCount = sigList.stream().filter(f -> extraAlgos.contains(f.getName())).count();
+        if (existCount == extraAlgos.size()) {
+            if (debugEnabled) {
+                log.debug("handleKexInitProposal({}) required extra signatures ({}) already supported for {}",
+                    session, extraAlgos, proposal);
+            }
+            return;
+        }
+
+        if (initiator) {
+            session.setAttribute(PROPOSAL_KEY, new EnumMap<>(proposal));
+            if (debugEnabled) {
+                log.debug("handleKexInitProposal({}) initial proposal={}", session, proposal);
+            }
+            return;
+        }
+
+        // Check if client already sent its proposal - if not, we can still influence it
+        Map<KexProposalOption, String> sentProposal = session.getAttribute(PROPOSAL_KEY);
+        if (GenericUtils.isNotEmpty(sentProposal)) {
+            if (debugEnabled) {
+                log.debug("handleKexInitProposal({}) already sent proposal={} (server={})",
+                    session, sentProposal, proposal);
+            }
+            return;
+        }
+
+        String algos = proposal.get(KexProposalOption.ALGORITHMS);
+        String extDeclared = Stream.of(GenericUtils.split(algos, ','))
+            .filter(s -> KexExtensions.SERVER_KEX_EXTENSION.equalsIgnoreCase(s))
+            .findFirst()
+            .orElse(null);
+        if (GenericUtils.isEmpty(extDeclared)) {
+            if (debugEnabled) {
+                log.debug("handleKexInitProposal({}) server proposal={} does not include extension indicator",
+                    session, proposal);
+            }
+            return;
+        }
+
+        updateAvailableSignatureFactories(session, extraAlgos);
+    }
+
+    protected Collection<String> getExtraSignatureAlgorithms(Session session) throws IOException {
+        return DEFAULT_EXTRA_SIGNATURES;
+    }
+
+    @Override
+    public boolean handleKexExtensionRequest(
+            Session session, int index, int count, String name, byte[] data)
+                throws IOException {
+        if (!ServerSignatureAlgorithms.NAME.equalsIgnoreCase(name)) {
+            return true;    // process next extension (if available)
+        }
+
+        Collection<String> sigAlgos = ServerSignatureAlgorithms.INSTANCE.parseExtension(data);
+        updateAvailableSignatureFactories(session, sigAlgos);
+        return false;   // don't care about any more extensions (for now)
+    }
+
+    public List<NamedFactory<Signature>> updateAvailableSignatureFactories(
+            Session session, Collection<String> extraAlgos)
+                throws IOException {
+        List<NamedFactory<Signature>> available = session.getSignatureFactories();
+        List<NamedFactory<Signature>> updated =
+            resolveUpdatedSignatureFactories(session, available, extraAlgos);
+        if (!GenericUtils.isSameReference(available, updated)) {
+            if (log.isDebugEnabled()) {
+                log.debug("updateAvailableSignatureFactories({}) available={}, updated={}",
+                    session, available, updated);
+            }
+            session.setSignatureFactories(updated);
+        }
+
+        return updated;
+    }
+
+    /**
+     * Checks if the extra signature algorithms are already included in the available ones,
+     * and adds the extra ones (if supported).
+     *
+     * @param session The {@link Session} for which the resolution occurs
+     * @param available The available signature factories
+     * @param extraAlgos The extra requested signatures - ignored if {@code null}/empty
+     * @return The resolved signature factories - same as input if nothing added
+     * @throws IOException If failed to resolve the factories
+     */
+    public List<NamedFactory<Signature>> resolveUpdatedSignatureFactories(
+            Session session, List<NamedFactory<Signature>> available, Collection<String> extraAlgos)
+                throws IOException {
+        boolean debugEnabled = log.isDebugEnabled();
+        List<NamedFactory<Signature>> toAdd =
+            resolveRequestedSignatureFactories(session, extraAlgos);
+        if (GenericUtils.isEmpty(toAdd)) {
+            if (debugEnabled) {
+                log.debug("resolveUpdatedSignatureFactories({}) Nothing to add to {} out of {}",
+                    session, NamedResource.getNames(available), extraAlgos);
+            }
+            return available;
+        }
+
+        for (int index = 0; index < toAdd.size(); index++) {
+            NamedFactory<Signature> f = toAdd.get(index);
+            String name = f.getName();
+            NamedFactory<Signature> a = available.stream()
+                .filter(s -> Objects.equals(name, s.getName()))
+                .findFirst()
+                .orElse(null);
+            if (a == null) {
+                continue;
+            }
+
+            if (debugEnabled) {
+                log.debug("resolveUpdatedSignatureFactories({}) skip {} - already available", session, name);
+            }
+
+            toAdd.remove(index);
+            index--;    // compensate for loop auto-increment
+        }
+
+        return updateAvailableSignatureFactories(session, available, toAdd);
+    }
+
+    public List<NamedFactory<Signature>> updateAvailableSignatureFactories(
+            Session session, List<NamedFactory<Signature>> available, Collection<? extends NamedFactory<Signature>> toAdd)
+                throws IOException {
+        boolean debugEnabled = log.isDebugEnabled();
+        if (GenericUtils.isEmpty(toAdd)) {
+            if (debugEnabled) {
+                log.debug("updateAvailableSignatureFactories({}) nothing to add to {}",
+                    session, NamedResource.getNames(available));
+            }
+            return available;
+        }
+
+        List<NamedFactory<Signature>> updated =
+            new ArrayList<>(available.size() + toAdd.size());
+        updated.addAll(available);
+
+        for (NamedFactory<Signature> f : toAdd) {
+            int index = resolvePreferredSignaturePosition(session, updated, f);
+            if (debugEnabled) {
+                log.debug("updateAvailableSignatureFactories({}) add {} at position={}", session, f, index);
+            }
+            if ((index < 0) || (index >= updated.size())) {
+                updated.add(f);
+            } else {
+                updated.add(index, f);
+            }
+        }
+
+        return updated;
+    }
+
+    public int resolvePreferredSignaturePosition(
+            Session session, List<? extends NamedFactory<Signature>> factories, NamedFactory<Signature> factory)
+                throws IOException {
+        return SignatureFactory.resolvePreferredSignaturePosition(factories, factory);
+    }
+
+    public List<NamedFactory<Signature>> resolveRequestedSignatureFactories(
+            Session session, Collection<String> extraAlgos)
+                throws IOException {
+        if (GenericUtils.isEmpty(extraAlgos)) {
+            return Collections.emptyList();
+        }
+
+        List<NamedFactory<Signature>> toAdd = Collections.emptyList();
+        boolean debugEnabled = log.isDebugEnabled();
+        for (String algo : extraAlgos) {
+            NamedFactory<Signature> factory = resolveRequestedSignatureFactory(session, algo);
+            if (factory == null) {
+                if (debugEnabled) {
+                    log.debug("resolveRequestedSignatureFactories({}) skip {} - no factory found", session, algo);
+                }
+                continue;
+            }
+
+            if ((factory instanceof OptionalFeature) && (!((OptionalFeature) factory).isSupported())) {
+                if (debugEnabled) {
+                    log.debug("resolveRequestedSignatureFactories({}) skip {} - not supported", session, algo);
+                }
+                continue;
+            }
+
+            if (toAdd.isEmpty()) {
+                toAdd = new ArrayList<>(extraAlgos.size());
+            }
+            toAdd.add(factory);
+        }
+
+        return toAdd;
+    }
+
+    public NamedFactory<Signature> resolveRequestedSignatureFactory(Session session, String name) throws IOException {
+        return BuiltinSignatures.fromFactoryName(name);
+    }
+}
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/kex/extension/KexExtensionHandler.java b/sshd-core/src/main/java/org/apache/sshd/common/kex/extension/KexExtensionHandler.java
index 0464bc3..45c6384 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/kex/extension/KexExtensionHandler.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/kex/extension/KexExtensionHandler.java
@@ -22,8 +22,10 @@ package org.apache.sshd.common.kex.extension;
 import java.io.IOException;
 import java.util.Collections;
 import java.util.EnumSet;
+import java.util.Map;
 import java.util.Set;
 
+import org.apache.sshd.common.kex.KexProposalOption;
 import org.apache.sshd.common.session.Session;
 import org.apache.sshd.common.util.buffer.Buffer;
 
@@ -43,7 +45,7 @@ public interface KexExtensionHandler {
 
     /**
      * @param session The {@link Session} about to execute KEX
-     * @return {@code true} whether to declare KEX extensions availability for the session
+     * @return {@code true} whether to KEX extensions are supported/allowed for the session
      * @throws IOException If failed to process the request
      */
     default boolean isKexExtensionsAvailable(Session session) throws IOException {
@@ -51,8 +53,51 @@ public interface KexExtensionHandler {
     }
 
     /**
-     * Invoked in order to allow the handler to send an {@code SSH_MSG_EXT_INFO} message.
+     * Invoked when a peer is ready to send the KEX options proposal or has received
+     * such a proposal. <B>Note:</B> this method is called during the negotiation phase
+     * even if {@link #isKexExtensionsAvailable(Session)} returns {@code false} for the session.
      *
+     * @param session The {@link Session} initiating or receiving the proposal
+     * @param initiator {@code true} if the proposal is about to be sent, {@code false}
+     * if this is a proposal received from the peer.
+     * @param proposal The proposal contents -  <B>Caveat emptor:</B> the proposal is
+     * <U>modifiable</U> i.e., the handler can modify before being sent or before
+     * being processed (if incoming)
+     * @throws IOException If failed to handle the request
+     */
+    default void handleKexInitProposal(
+            Session session, boolean initiator, Map<KexProposalOption, String> proposal)
+                throws IOException {
+        // ignored
+    }
+
+    /**
+     * Invoked during the KEX negotiation phase to inform about option
+     * being negotiated. <B>Note:</B> this method is called during the
+     * negotiation phase even if {@link #isKexExtensionsAvailable(Session)}
+     * returns {@code false} for the session.
+     *
+     * @param session The {@link Session} executing the negotiation
+     * @param option The negotiated {@link KexProposalOption}
+     * @param nValue The negotiated option value (may be {@code null}/empty).
+     * @param c2sOptions The client proposals
+     * @param cValue The client-side value for the option (may be {@code null}/empty).
+     * @param s2cOptions The server proposals
+     * @param sValue The server-side value for the option (may be {@code null}/empty).
+     * @throws IOException If failed to handle the invocation
+     */
+    default void handleKexExtensionNegotiation(
+            Session session, KexProposalOption option, String nValue,
+            Map<KexProposalOption, String> c2sOptions, String cValue,
+            Map<KexProposalOption, String> s2cOptions, String sValue)
+                throws IOException {
+        // do nothing
+    }
+
+    /**
+     * Invoked in order to allow the handler to send an {@code SSH_MSG_EXT_INFO} message.
+     * <B>Note:</B> this method is called only if {@link #isKexExtensionsAvailable(Session)}
+     * returns {@code true} for the session.
      * @param session The {@link Session}
      * @param phase The phase at which the handler is invoked
      * @throws IOException If failed to handle the invocation
@@ -63,7 +108,9 @@ public interface KexExtensionHandler {
     }
 
     /**
-     * Parses the {@code SSH_MSG_EXT_INFO} message
+     * Parses the {@code SSH_MSG_EXT_INFO} message. <B>Note:</B> this method
+     * is called only if {@link #isKexExtensionsAvailable(Session)} returns
+     * {@code true} for the session.
      *
      * @param session The {@link Session} through which the message was received
      * @param buffer The message buffer
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 7943e6a..ee55a26 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
@@ -67,6 +67,14 @@ public interface Session
                 Closeable {
 
     /**
+     * Quick indication if this is a server or client session (instead of
+     * having to ask {@code instanceof}).
+     *
+     * @return {@code true} if this is a server session
+     */
+    boolean isServerSession();
+
+    /**
      * Timeout status.
      */
     enum TimeoutStatus {
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 1dfc381..11c6d87 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
@@ -1257,8 +1257,9 @@ public abstract class AbstractSession extends SessionHelper {
      * @param buffer   the {@link Buffer} containing the key exchange init packet
      * @param proposal the remote proposal to fill
      * @return the packet data
+     * @throws IOException If failed to handle the message
      */
-    protected byte[] receiveKexInit(Buffer buffer, Map<KexProposalOption, String> proposal) {
+    protected byte[] receiveKexInit(Buffer buffer, Map<KexProposalOption, String> proposal) throws IOException {
         // Recreate the packet payload which will be needed at a later time
         byte[] d = buffer.array();
         byte[] data = new byte[buffer.available() + 1 /* the opcode */];
@@ -1290,13 +1291,26 @@ public abstract class AbstractSession extends SessionHelper {
             size += readLen;
         }
 
+        KexExtensionHandler extHandler = getKexExtensionHandler();
+        if (extHandler != null) {
+            if (traceEnabled) {
+                log.trace("receiveKexInit({}) options before handler: {}", this, proposal);
+            }
+
+            extHandler.handleKexInitProposal(this, false, proposal);
+
+            if (traceEnabled) {
+                log.trace("receiveKexInit({}) options after handler: {}", this, proposal);
+            }
+        }
+
         firstKexPacketFollows = buffer.getBoolean();
         if (traceEnabled) {
             log.trace("receiveKexInit({}) first kex packet follows: {}", this, firstKexPacketFollows);
         }
 
         long reserved = buffer.getUInt();
-        if (reserved != 0) {
+        if (reserved != 0L) {
             if (traceEnabled) {
                 log.trace("receiveKexInit({}) non-zero reserved value: {}", this, reserved);
             }
@@ -1489,6 +1503,7 @@ public abstract class AbstractSession extends SessionHelper {
             boolean debugEnabled = log.isDebugEnabled();
             boolean traceEnabled = log.isTraceEnabled();
             SessionDisconnectHandler discHandler = getSessionDisconnectHandler();
+            KexExtensionHandler extHandler = getKexExtensionHandler();
             for (KexProposalOption paramType : KexProposalOption.VALUES) {
                 String clientParamValue = c2sOptions.get(paramType);
                 String serverParamValue = s2cOptions.get(paramType);
@@ -1517,6 +1532,11 @@ public abstract class AbstractSession extends SessionHelper {
 
                 // check if reached an agreement
                 String value = guess.get(paramType);
+                if (extHandler != null) {
+                    extHandler.handleKexExtensionNegotiation(
+                        this, paramType, value, c2sOptions, clientParamValue, s2cOptions, serverParamValue);
+                }
+
                 if (value != null) {
                     if (traceEnabled) {
                         log.trace("negotiate({})[{}] guess={} (client={} / server={})",
@@ -1553,8 +1573,7 @@ public abstract class AbstractSession extends SessionHelper {
              *      key exchange method, the parties MUST disconnect.
              */
             String kexOption = guess.get(KexProposalOption.ALGORITHMS);
-            if (KexExtensions.CLIENT_KEX_EXTENSION.equalsIgnoreCase(kexOption)
-                    || KexExtensions.SERVER_KEX_EXTENSION.equalsIgnoreCase(kexOption)) {
+            if (KexExtensions.IS_KEX_EXTENSION_SIGNAL.test(kexOption)) {
                 if ((discHandler != null)
                         && discHandler.handleKexDisconnectReason(
                                 this, c2sOptions, s2cOptions, negotiatedGuess, KexProposalOption.ALGORITHMS)) {
@@ -1923,17 +1942,31 @@ public abstract class AbstractSession extends SessionHelper {
         String resolvedAlgorithms = resolveAvailableSignaturesProposal();
         if (GenericUtils.isEmpty(resolvedAlgorithms)) {
             throw new SshException(SshConstants.SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE,
-                    "sendKexInit() no resolved signatures available");
+                "sendKexInit() no resolved signatures available");
         }
 
         Map<KexProposalOption, String> proposal = createProposal(resolvedAlgorithms);
+        KexExtensionHandler extHandler = getKexExtensionHandler();
+        boolean traceEnabled = log.isTraceEnabled();
+        if (extHandler != null) {
+            if (traceEnabled) {
+                log.trace("sendKexInit({}) options before handler: {}", this, proposal);
+            }
+
+            extHandler.handleKexInitProposal(this, true, proposal);
+
+            if (traceEnabled) {
+                log.trace("sendKexInit({}) options after handler: {}", this, proposal);
+            }
+        }
+
         byte[] seed;
         synchronized (kexState) {
             seed = sendKexInit(proposal);
             setKexSeed(seed);
         }
 
-        if (log.isTraceEnabled()) {
+        if (traceEnabled) {
             log.trace("sendKexInit({}) proposal={} seed: {}", this, proposal, BufferUtils.toHex(':', seed));
         }
         return seed;
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/session/helpers/SessionHelper.java b/sshd-core/src/main/java/org/apache/sshd/common/session/helpers/SessionHelper.java
index 1bc8aea..86f479b 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/session/helpers/SessionHelper.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/session/helpers/SessionHelper.java
@@ -131,6 +131,7 @@ public abstract class SessionHelper extends AbstractKexFactoryManager implements
         return ioSession;
     }
 
+    @Override
     public boolean isServerSession() {
         return serverSession;
     }