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 2015/06/10 07:06:26 UTC

mina-sshd git commit: [SSHD-488] Implement (a naive) ssh-keyscan

Repository: mina-sshd
Updated Branches:
  refs/heads/master 6432a7af3 -> 6743fd3e8


[SSHD-488] Implement (a naive) ssh-keyscan

* Fixed and refined some behavior issues


Project: http://git-wip-us.apache.org/repos/asf/mina-sshd/repo
Commit: http://git-wip-us.apache.org/repos/asf/mina-sshd/commit/6743fd3e
Tree: http://git-wip-us.apache.org/repos/asf/mina-sshd/tree/6743fd3e
Diff: http://git-wip-us.apache.org/repos/asf/mina-sshd/diff/6743fd3e

Branch: refs/heads/master
Commit: 6743fd3e8447f70d90734f46dd8d1e930bcb839e
Parents: 6432a7a
Author: Lyor Goldstein <lg...@vmware.com>
Authored: Wed Jun 10 08:06:17 2015 +0300
Committer: Lyor Goldstein <lg...@vmware.com>
Committed: Wed Jun 10 08:06:17 2015 +0300

----------------------------------------------------------------------
 .../java/org/apache/sshd/client/SshKeyScan.java | 269 +++++++++++++++----
 .../sshd/client/session/ClientSessionImpl.java  |  10 +-
 .../sshd/common/session/AbstractSession.java    |   5 +-
 3 files changed, 221 insertions(+), 63 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/6743fd3e/sshd-core/src/main/java/org/apache/sshd/client/SshKeyScan.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/SshKeyScan.java b/sshd-core/src/main/java/org/apache/sshd/client/SshKeyScan.java
index e6b7cfa..4b4a262 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/SshKeyScan.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/SshKeyScan.java
@@ -22,17 +22,18 @@ package org.apache.sshd.client;
 import java.io.BufferedReader;
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
-import java.io.Closeable;
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
+import java.io.InterruptedIOException;
 import java.io.OutputStreamWriter;
 import java.io.PrintStream;
 import java.io.Writer;
 import java.net.ConnectException;
 import java.net.InetSocketAddress;
 import java.net.SocketAddress;
+import java.nio.channels.Channel;
 import java.nio.charset.StandardCharsets;
 import java.security.GeneralSecurityException;
 import java.security.KeyPair;
@@ -46,13 +47,16 @@ import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.TreeMap;
+import java.util.TreeSet;
 import java.util.UUID;
 import java.util.concurrent.Callable;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.logging.Level;
 
 import org.apache.sshd.client.future.ConnectFuture;
 import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.common.NamedFactory;
 import org.apache.sshd.common.SshConstants;
 import org.apache.sshd.common.SshdSocketAddress;
 import org.apache.sshd.common.cipher.ECCurves;
@@ -63,6 +67,8 @@ import org.apache.sshd.common.io.IoSession;
 import org.apache.sshd.common.keyprovider.KeyPairProvider;
 import org.apache.sshd.common.session.Session;
 import org.apache.sshd.common.session.SessionListener;
+import org.apache.sshd.common.signature.BuiltinSignatures;
+import org.apache.sshd.common.signature.Signature;
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.SecurityUtils;
 import org.apache.sshd.common.util.ValidateUtils;
@@ -75,15 +81,18 @@ import org.apache.sshd.common.util.logging.LoggingUtils;
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
 public class SshKeyScan extends AbstractSimplifiedLog
-                        implements Closeable, Callable<Void>, ServerKeyVerifier, SessionListener {
+                        implements Channel, Callable<Void>, ServerKeyVerifier, SessionListener {
+    public static final String RSA_KEY_TYPE = "rsa", DSS_KEY_TYPE = "dsa", EC_KEY_TYPE = "ecdsa";
     /**
      * Default key types if not overridden from the command line
      */
     public static final List<String> DEFAULT_KEY_TYPES =
-            Collections.unmodifiableList(Arrays.asList("rsa", "ecdsa"));
+            Collections.unmodifiableList(Arrays.asList(RSA_KEY_TYPE, EC_KEY_TYPE));
     public static final long DEFAULT_TIMEOUT = TimeUnit.SECONDS.toMillis(5L);
     public static final Level DEFAULT_LEVEL = Level.INFO;
 
+    private final AtomicBoolean open = new AtomicBoolean(true);
+    private SshClient client;
     private int port;
     private long timeout;
     private List<String> keyTypes;
@@ -157,34 +166,77 @@ public class SshKeyScan extends AbstractSimplifiedLog
 
     @Override
     public Void call() throws Exception {
+        ValidateUtils.checkTrue(isOpen(), "Scanner is closed", GenericUtils.EMPTY_OBJECT_ARRAY);
+
+        Collection<String> typeNames = getKeyTypes();
+        Map<String,List<KeyPair>> pairsMap = createKeyPairs(typeNames);
+        /*
+         * We will need to switch signature factories for each specific
+         * key type in order to force the server to send ONLY that specific
+         * key, so pre-create the factories map according to the selected
+         * key types
+         */
+        Map<String,List<NamedFactory<Signature>>> sigFactories = new TreeMap<String,List<NamedFactory<Signature>>>(String.CASE_INSENSITIVE_ORDER);
+        for (String kt : new TreeSet<String>(pairsMap.keySet())) {
+            List<NamedFactory<Signature>> factories = resolveSignatureFactories(kt);
+            if (GenericUtils.isEmpty(factories)) {
+                if (isEnabled(Level.FINEST)) {
+                    log(Level.FINEST, "Skip empty signature factories for " + kt);
+                }
+                pairsMap.remove(kt);
+            } else {
+                sigFactories.put(kt, factories);
+            }
+        }
+
+        ValidateUtils.checkTrue(!GenericUtils.isEmpty(pairsMap), "No client key pairs", GenericUtils.EMPTY_OBJECT_ARRAY);
+        ValidateUtils.checkTrue(!GenericUtils.isEmpty(sigFactories), "No signature factories", GenericUtils.EMPTY_OBJECT_ARRAY);
+
         Exception err = null;
-        try(SshClient   client = SshClient.setUpDefaultClient()) {
-            Collection<String> typeNames = getKeyTypes();
-            Map<String,KeyPair> pairsMap = createKeyPairs(typeNames);
+        try {
+            ValidateUtils.checkTrue(client == null, "Client still active", GenericUtils.EMPTY_OBJECT_ARRAY);
+            client = SshClient.setUpDefaultClient();
             client.setServerKeyVerifier(this);
 
-            try(BufferedReader rdr = new BufferedReader(new InputStreamReader(getInputStream(), StandardCharsets.UTF_8))) {
+            BufferedReader rdr = new BufferedReader(new InputStreamReader(getInputStream(), StandardCharsets.UTF_8));
+            try {
                 client.start();
-
                 for (String line = rdr.readLine(); line != null; line = rdr.readLine()) {
-                    line = GenericUtils.trimToEmpty(line);
-                    if (GenericUtils.isEmpty(line)) {
+                    String[] hosts = GenericUtils.split(GenericUtils.trimToEmpty(line), ',');
+                    if (GenericUtils.isEmpty(hosts)) {
                         continue;
                     }
                     
-                    try {
-                        resolveServerKeys(client, line, pairsMap);
-                    } catch(Exception e) {
-                        if (isEnabled(Level.FINE)) {
-                            log(Level.FINE, "Failed to retrieve keys from " + line, e);
+                    for (String h : hosts) {
+                        if (!isOpen()) {
+                            throw new InterruptedIOException("Closed while preparing to contact host=" + h);
+                        }
+
+                        try {
+                            resolveServerKeys(client, h, pairsMap, sigFactories);
+                        } catch(Exception e) {
+                            // check if interrupted while scanning host keys
+                            if (e instanceof InterruptedIOException) {
+                                throw e;
+                            }
+
+                            if (isEnabled(Level.FINE)) {
+                                log(Level.FINE, "Failed to retrieve keys from " + h, e);
+                            }
+                            err = GenericUtils.accumulateException(err, e);
+                        } finally {
+                            currentHostFingerprints.clear();
                         }
-                        err = GenericUtils.accumulateException(err, e);
-                    } finally {
-                        currentHostFingerprints.clear();
                     }
                 }
             } finally {
-                client.stop();
+                rdr.close();
+            }
+        } finally {
+            try {
+                close();
+            } catch(IOException e) {
+                err = GenericUtils.accumulateException(err, e);
             }
         }
         
@@ -195,10 +247,22 @@ public class SshKeyScan extends AbstractSimplifiedLog
         return null;
     }
 
-    protected void resolveServerKeys(SshClient client, String host, Map<String,KeyPair> pairsMap) {
-        for (Map.Entry<String,KeyPair> pe : pairsMap.entrySet()) {
+    protected void resolveServerKeys(SshClient client, String host, Map<String,List<KeyPair>> pairsMap, Map<String,List<NamedFactory<Signature>>> sigFactories) throws IOException {
+        for (Map.Entry<String,List<KeyPair>> pe : pairsMap.entrySet()) {
             String kt = pe.getKey();
+            if (!isOpen()) {
+                throw new InterruptedIOException("Closed while attempting to retrieve key type=" + kt + " from " + host);
+            }
+
+            List<NamedFactory<Signature>> current = client.getSignatureFactories();
             try {
+                /*
+                 * Replace whatever factories there are right now with the
+                 * specific one for the key in order to extract only the
+                 * specific host key type
+                 */
+                List<NamedFactory<Signature>>   forced = sigFactories.get(kt);
+                client.setSignatureFactories(forced);
                 resolveServerKeys(client, host, kt, pe.getValue());
             } catch(Exception e) {
                 if (isEnabled(Level.FINE)) {
@@ -208,20 +272,24 @@ public class SshKeyScan extends AbstractSimplifiedLog
                 if (e instanceof ConnectException) {
                     return; // makes no sense to try again with another key type...
                 }
+            } finally {
+                client.setSignatureFactories(current);  // don't have to, but be nice...
             }
         }
     }
     
-    protected void resolveServerKeys(SshClient client, String host, String kt, KeyPair kp) throws Exception {
+    protected void resolveServerKeys(SshClient client, String host, String kt, List<KeyPair> ids) throws Exception {
         int connectPort = getPort();
         if (isEnabled(Level.FINE)) {
-            log(Level.FINE, "Connecting to " + host + ":" + connectPort);
+            log(Level.FINE, "Connecting to " + host + ":" + connectPort + " to retrieve key type=" + kt);
         }
 
         ConnectFuture future = client.connect(UUID.randomUUID().toString(), host, connectPort);
         long waitTime = getTimeout();
         if (!future.await(waitTime)) {
-            throw new ConnectException("Failed to connect to " + host + ":" + connectPort + " within " + waitTime + " msec.");
+            throw new ConnectException("Failed to connect to " + host + ":" + connectPort
+                                     + " within " + waitTime + " msec."
+                                     + " to retrieve key type=" + kt);
         }
 
         try(ClientSession session = future.getSession()) {
@@ -229,24 +297,31 @@ public class SshKeyScan extends AbstractSimplifiedLog
             SocketAddress remoteAddress = ioSession.getRemoteAddress();
             String remoteLocation = toString(remoteAddress);
             if (isEnabled(Level.FINE)) {
-                log(Level.FINE, "Connected to " + remoteLocation);
+                log(Level.FINE, "Connected to " + remoteLocation + " to retrieve key type=" + kt);
             }
 
             try {
                 session.addListener(this);
                 if (isEnabled(Level.FINER)) {
-                    log(Level.FINER, "Authenticating with " + kt + " to " + remoteLocation);
+                    log(Level.FINER, "Authenticating with key type=" + kt + " to " + remoteLocation);
+                }
+
+                for (KeyPair kp : ids) {
+                    session.addPublicKeyIdentity(kp);
                 }
 
-                // shouldn't really succeed, but do it since key exchange occurs only on auth attempt
-                session.addPublicKeyIdentity(kp);
                 try {
+                    // shouldn't really succeed, but do it since key exchange occurs only on auth attempt
                     session.auth().verify(waitTime);
                     log(Level.WARNING, "Unexpected authentication success using key type=" + kt + " with " + remoteLocation);
                 } catch(Exception e) {
                     if (isEnabled(Level.FINER)) {
                         log(Level.FINER, "Failed to authenticate using key type=" + kt + " with " + remoteLocation);
                     }
+                } finally {
+                    for (KeyPair kp : ids) {
+                        session.removePublicKeyIdentity(kp);
+                    }
                 }
             } finally {
                 session.removeListener(this);
@@ -290,8 +365,8 @@ public class SshKeyScan extends AbstractSimplifiedLog
     @Override
     public boolean verifyServerKey(ClientSession sshClientSession, SocketAddress remoteAddress, PublicKey serverKey) {
         String remoteLocation = toString(remoteAddress);
+        String extra = KeyUtils.getFingerPrint(serverKey);
         try {
-            String extra = KeyUtils.getFingerPrint(serverKey);
             String keyType = KeyUtils.getKeyType(serverKey);
             String current = GenericUtils.isEmpty(keyType) ? null : currentHostFingerprints.get(keyType);
             if (Objects.equals(current, extra)) {
@@ -303,21 +378,25 @@ public class SshKeyScan extends AbstractSimplifiedLog
                     log(Level.FINE, "verifyServerKey(" + remoteLocation + ")[" + keyType + "] found new key: " + extra);
                 }
 
-                StringBuilder   sb = new StringBuilder(256).append(remoteLocation).append(' ');
-                PublicKeyEntry.appendPublicKeyEntry(sb, serverKey);
-                log(Level.INFO, sb);
-                
+                writeServerKey(remoteLocation, keyType, serverKey);
+
                 if (!GenericUtils.isEmpty(keyType)) {
                     currentHostFingerprints.put(keyType, extra);
                 }
             }
         } catch(Exception e) {
-            log(Level.SEVERE, "Failed to output the public key from " + remoteLocation, e);
+            log(Level.SEVERE, "Failed to output the public key " + extra + " from " + remoteLocation, e);
         }
 
         return true;
     }
 
+    protected void writeServerKey(String remoteLocation, String keyType, PublicKey serverKey) throws Exception {
+        StringBuilder sb = new StringBuilder(256).append(remoteLocation).append(' ');
+        PublicKeyEntry.appendPublicKeyEntry(sb, serverKey);
+        log(Level.INFO, sb);
+    }
+
     private static final String toString(SocketAddress addr) {
         if (addr == null) {
             return null;
@@ -330,47 +409,103 @@ public class SshKeyScan extends AbstractSimplifiedLog
         }
     }
 
-    protected Map<String,KeyPair> createKeyPairs(Collection<String> typeNames) throws GeneralSecurityException {
+    protected List<NamedFactory<Signature>> resolveSignatureFactories(String keyType) throws GeneralSecurityException {
+        if (isEnabled(Level.FINE)) {
+            log(Level.FINE, "Resolve signature factories for " + keyType);
+        }
+
+        if (RSA_KEY_TYPE.equalsIgnoreCase(keyType)) {
+            return Collections.singletonList((NamedFactory<Signature>) BuiltinSignatures.rsa);
+        } else if (DSS_KEY_TYPE.equalsIgnoreCase(keyType)) {
+            return Collections.singletonList((NamedFactory<Signature>) BuiltinSignatures.dsa);
+        } else if (EC_KEY_TYPE.equalsIgnoreCase(keyType)) {
+            List<NamedFactory<Signature>> factories = new ArrayList<NamedFactory<Signature>>(ECCurves.NAMES.size());
+            for (String n : ECCurves.NAMES) {
+                if (isEnabled(Level.FINER)) {
+                    log(Level.FINER, "Resolve signature factory for curve=" + n);
+                }
+
+                NamedFactory<Signature> f =
+                    ValidateUtils.checkNotNull(BuiltinSignatures.fromString(n), "Unknown curve signature: %s", n);
+                factories.add(f);
+            }
+
+            return factories;
+        } else {
+            throw new InvalidKeySpecException("Unknown key type: " + keyType);
+        }
+    }
+
+    protected Map<String,List<KeyPair>> createKeyPairs(Collection<String> typeNames) throws GeneralSecurityException {
         if (GenericUtils.isEmpty(typeNames)) {
             return Collections.emptyMap();
         }
 
-        Map<String,KeyPair> pairsMap = new TreeMap<String, KeyPair>(String.CASE_INSENSITIVE_ORDER);
+        Map<String,List<KeyPair>> pairsMap = new TreeMap<String,List<KeyPair>>(String.CASE_INSENSITIVE_ORDER);
         for (String kt : typeNames) {
-            if (isEnabled(Level.FINE)) {
-                log(Level.FINE, "Generate key pair for " + kt);
+            if (pairsMap.containsKey(kt)) {
+                log(Level.WARNING, "Key type " + kt + " re-specified");
+                continue;
             }
 
-            if ("rsa".equalsIgnoreCase(kt)) {
-                pairsMap.put(KeyPairProvider.SSH_RSA, KeyUtils.generateKeyPair(KeyPairProvider.SSH_RSA, 1024));
-            } else if ("dsa".equalsIgnoreCase(kt)) {
-                pairsMap.put(KeyPairProvider.SSH_DSS, KeyUtils.generateKeyPair(KeyPairProvider.SSH_DSS, 512));
-            } else if ("ecdsa".equalsIgnoreCase(kt)) {
-                if (!SecurityUtils.hasEcc()) {
-                    throw new InvalidKeySpecException("ECC not supported");
-                }
+            List<KeyPair> kps = createKeyPairs(kt);
+            if (GenericUtils.isEmpty(kps)) {
+                log(Level.WARNING, "No key-pairs generated for key type " + kt);
+                continue;
+            }
+            
+            pairsMap.put(kt, kps);
+        }
+        
+        return pairsMap;
+    }
 
-                for (String curveName : ECCurves.NAMES) {
-                    Integer keySize = ECCurves.getCurveSize(curveName);
-                    if (keySize == null) {
-                        throw new InvalidKeySpecException("Unknown curve: " + curveName);
-                    }
+    protected List<KeyPair> createKeyPairs(String keyType) throws GeneralSecurityException {
+        if (isEnabled(Level.FINE)) {
+            log(Level.FINE, "Generate key pairs for " + keyType);
+        }
 
-                    if (isEnabled(Level.FINER)) {
-                        log(Level.FINER, "Generate key pair for curve=" + curveName);
-                    }
+        if (RSA_KEY_TYPE.equalsIgnoreCase(keyType)) {
+            return Collections.singletonList(KeyUtils.generateKeyPair(KeyPairProvider.SSH_RSA, 1024));
+        } else if (DSS_KEY_TYPE.equalsIgnoreCase(keyType)) {
+            return Collections.singletonList(KeyUtils.generateKeyPair(KeyPairProvider.SSH_DSS, 512));
+        } else if (EC_KEY_TYPE.equalsIgnoreCase(keyType)) {
+            if (!SecurityUtils.hasEcc()) {
+                throw new InvalidKeySpecException("ECC not supported");
+            }
 
-                    String keyName = ECCurves.ECDSA_SHA2_PREFIX + curveName;
-                    pairsMap.put(keyName, KeyUtils.generateKeyPair(keyName, keySize.intValue()));
+            List<KeyPair> kps = new ArrayList<KeyPair>(ECCurves.NAMES.size());
+            for (String curveName : ECCurves.NAMES) {
+                Integer keySize = ECCurves.getCurveSize(curveName);
+                if (keySize == null) {
+                    throw new InvalidKeySpecException("Unknown curve: " + curveName);
                 }
+
+                if (isEnabled(Level.FINER)) {
+                    log(Level.FINER, "Generate key pair for curve=" + curveName);
+                }
+
+                String keyName = ECCurves.ECDSA_SHA2_PREFIX + curveName;
+                kps.add(KeyUtils.generateKeyPair(keyName, keySize.intValue()));
             }
+            
+            return kps;
+        } else {
+            throw new InvalidKeySpecException("Unknown key type: " + keyType);
         }
-        
-        return pairsMap;
+    }
+
+    @Override
+    public boolean isOpen() {
+        return open.get();
     }
 
     @Override
     public void close() throws IOException {
+        if (!open.getAndSet(false)) {
+            return; // already closed
+        }
+
         IOException err = null;
         if (input != null) {
             try {
@@ -381,6 +516,23 @@ public class SshKeyScan extends AbstractSimplifiedLog
                 input = null;
             }
         }
+
+        if (client != null) {
+            try {
+                client.close();
+            } catch(IOException e) {
+                err = GenericUtils.accumulateException(err, e);
+            } finally {
+                try {
+                    client.stop();
+                } finally {
+                    client = null;
+                }
+            }
+        }
+        if (err != null) {
+            throw err;
+        }
     }
 
     //////////////////////////////////////////////////////////////////////////
@@ -409,6 +561,7 @@ public class SshKeyScan extends AbstractSimplifiedLog
                 String typeList = args[index];
                 String[] types = GenericUtils.split(typeList, ',');
                 ValidateUtils.checkTrue(GenericUtils.length(types) > 0, "No types specified for %s", optName);
+                scanner.setKeyTypes(Arrays.asList(types));
             } else if ("-p".equals(optName)) {
                 index++;
                 ValidateUtils.checkTrue(index < numArgs, "Missing %s option argument", optName);

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/6743fd3e/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSessionImpl.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSessionImpl.java b/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSessionImpl.java
index eaf1901..ce7fc7e 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSessionImpl.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSessionImpl.java
@@ -44,6 +44,7 @@ import org.apache.sshd.client.sftp.DefaultSftpClient;
 import org.apache.sshd.client.sftp.SftpClient;
 import org.apache.sshd.client.sftp.SftpFileSystem;
 import org.apache.sshd.client.sftp.SftpFileSystemProvider;
+import org.apache.sshd.common.FactoryManager;
 import org.apache.sshd.common.NamedResource;
 import org.apache.sshd.common.Service;
 import org.apache.sshd.common.ServiceFactory;
@@ -476,13 +477,15 @@ public class ClientSessionImpl extends AbstractSession implements ClientSession
     }
 
     protected void sendClientIdentification() {
-        clientVersion = "SSH-2.0-" + getFactoryManager().getVersion();
+        FactoryManager manager = getFactoryManager();
+        clientVersion = "SSH-2.0-" + manager.getVersion();
         sendIdentification(clientVersion);
     }
 
     @Override
     protected void sendKexInit() throws IOException {
-        String algs = NamedResource.Utils.getNames(getFactoryManager().getSignatureFactories());
+        FactoryManager manager = getFactoryManager();
+        String algs = NamedResource.Utils.getNames(manager.getSignatureFactories());
         clientProposal = createProposal(algs);
         I_C = sendKexInit(clientProposal);
     }
@@ -495,7 +498,8 @@ public class ClientSessionImpl extends AbstractSession implements ClientSession
 
     @Override
     protected void checkKeys() throws SshException {
-        ServerKeyVerifier serverKeyVerifier = getFactoryManager().getServerKeyVerifier();
+        ClientFactoryManager manager = getFactoryManager();
+        ServerKeyVerifier serverKeyVerifier = manager.getServerKeyVerifier();
         SocketAddress remoteAddress = ioSession.getRemoteAddress();
 
         if (!serverKeyVerifier.verifyServerKey(this, remoteAddress, kex.getServerKey())) {

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/6743fd3e/sshd-core/src/main/java/org/apache/sshd/common/session/AbstractSession.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/session/AbstractSession.java b/sshd-core/src/main/java/org/apache/sshd/common/session/AbstractSession.java
index ce0287d..efd7222 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/session/AbstractSession.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/session/AbstractSession.java
@@ -62,6 +62,7 @@ import org.apache.sshd.common.mac.Mac;
 import org.apache.sshd.common.random.Random;
 import org.apache.sshd.common.util.CloseableUtils;
 import org.apache.sshd.common.util.EventListenerUtils;
+import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.Readable;
 import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.common.util.buffer.Buffer;
@@ -1162,8 +1163,8 @@ public abstract class AbstractSession extends CloseableUtils.AbstractInnerClosea
         	String paramName = SshConstants.PROPOSAL_KEX_NAMES.get(i);
         	String clientParamValue = clientProposal[i];
         	String serverParamValue = serverProposal[i];
-            String[] c = clientParamValue.split(",");
-            String[] s = serverParamValue.split(",");
+            String[] c = GenericUtils.split(clientParamValue, ',');
+            String[] s = GenericUtils.split(serverParamValue, ',');
             for (String ci : c) {
                 for (String si : s) {
                     if (ci.equals(si)) {