You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mina.apache.org by gn...@apache.org on 2014/01/28 00:39:42 UTC

[2/7] [SSHD-277] Add RFC 4419 (DH Group Exchange) support

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/25948183/sshd-core/src/main/java/org/apache/sshd/SshClient.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/SshClient.java b/sshd-core/src/main/java/org/apache/sshd/SshClient.java
index 8762021..410ae51 100644
--- a/sshd-core/src/main/java/org/apache/sshd/SshClient.java
+++ b/sshd-core/src/main/java/org/apache/sshd/SshClient.java
@@ -45,6 +45,8 @@ import org.apache.sshd.client.future.ConnectFuture;
 import org.apache.sshd.client.future.DefaultConnectFuture;
 import org.apache.sshd.client.kex.DHG1;
 import org.apache.sshd.client.kex.DHG14;
+import org.apache.sshd.client.kex.DHGEX;
+import org.apache.sshd.client.kex.DHGEX256;
 import org.apache.sshd.client.kex.ECDHP256;
 import org.apache.sshd.client.kex.ECDHP384;
 import org.apache.sshd.client.kex.ECDHP521;
@@ -283,6 +285,8 @@ public class SshClient extends AbstractFactoryManager implements ClientFactoryMa
         // DHG14 uses 2048 bits key which are not supported by the default JCE provider
         if (SecurityUtils.isBouncyCastleRegistered()) {
             client.setKeyExchangeFactories(Arrays.<NamedFactory<KeyExchange>>asList(
+                    new DHGEX256.Factory(),
+                    new DHGEX.Factory(),
                     new ECDHP256.Factory(),
                     new ECDHP384.Factory(),
                     new ECDHP521.Factory(),

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/25948183/sshd-core/src/main/java/org/apache/sshd/SshServer.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/SshServer.java b/sshd-core/src/main/java/org/apache/sshd/SshServer.java
index 7ea0870..eaf27ea 100644
--- a/sshd-core/src/main/java/org/apache/sshd/SshServer.java
+++ b/sshd-core/src/main/java/org/apache/sshd/SshServer.java
@@ -98,6 +98,8 @@ import org.apache.sshd.server.channel.ChannelSession;
 import org.apache.sshd.server.command.ScpCommandFactory;
 import org.apache.sshd.server.kex.DHG1;
 import org.apache.sshd.server.kex.DHG14;
+import org.apache.sshd.server.kex.DHGEX;
+import org.apache.sshd.server.kex.DHGEX256;
 import org.apache.sshd.server.kex.ECDHP256;
 import org.apache.sshd.server.kex.ECDHP384;
 import org.apache.sshd.server.kex.ECDHP521;
@@ -425,6 +427,8 @@ public class SshServer extends AbstractFactoryManager implements ServerFactoryMa
         // EC keys are not supported until OpenJDK 8
         if (SecurityUtils.isBouncyCastleRegistered()) {
             sshd.setKeyExchangeFactories(Arrays.<NamedFactory<KeyExchange>>asList(
+                    new DHGEX256.Factory(),
+                    new DHGEX.Factory(),
                     new ECDHP256.Factory(),
                     new ECDHP384.Factory(),
                     new ECDHP521.Factory(),
@@ -440,6 +444,8 @@ public class SshServer extends AbstractFactoryManager implements ServerFactoryMa
         // EC keys are not supported until OpenJDK 7
         } else if (SecurityUtils.hasEcc()) {
             sshd.setKeyExchangeFactories(Arrays.<NamedFactory<KeyExchange>>asList(
+                    new DHGEX256.Factory(),
+                    new DHGEX.Factory(),
                     new ECDHP256.Factory(),
                     new ECDHP384.Factory(),
                     new ECDHP521.Factory(),
@@ -453,6 +459,8 @@ public class SshServer extends AbstractFactoryManager implements ServerFactoryMa
             sshd.setRandomFactory(new SingletonRandomFactory(new JceRandom.Factory()));
         } else {
             sshd.setKeyExchangeFactories(Arrays.<NamedFactory<KeyExchange>>asList(
+                    new DHGEX256.Factory(),
+                    new DHGEX.Factory(),
                     new DHG1.Factory()));
             sshd.setSignatureFactories(Arrays.<NamedFactory<Signature>>asList(
                     new SignatureDSA.Factory(),

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/25948183/sshd-core/src/main/java/org/apache/sshd/client/kex/DHGEX.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/kex/DHGEX.java b/sshd-core/src/main/java/org/apache/sshd/client/kex/DHGEX.java
new file mode 100644
index 0000000..4e82453
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/client/kex/DHGEX.java
@@ -0,0 +1,204 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sshd.client.kex;
+
+import java.math.BigInteger;
+import java.security.PublicKey;
+import java.security.interfaces.DSAPublicKey;
+import java.security.interfaces.ECPublicKey;
+import java.security.interfaces.RSAPublicKey;
+
+import org.apache.sshd.client.session.ClientSessionImpl;
+import org.apache.sshd.common.Digest;
+import org.apache.sshd.common.KeyExchange;
+import org.apache.sshd.common.KeyPairProvider;
+import org.apache.sshd.common.NamedFactory;
+import org.apache.sshd.common.Signature;
+import org.apache.sshd.common.SshConstants;
+import org.apache.sshd.common.SshException;
+import org.apache.sshd.common.cipher.ECCurves;
+import org.apache.sshd.common.kex.AbstractDH;
+import org.apache.sshd.common.kex.DH;
+import org.apache.sshd.common.session.AbstractSession;
+import org.apache.sshd.common.util.Buffer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Client side Diffie Hellman Group Exchange
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class DHGEX implements KeyExchange {
+
+    /**
+     * Named factory for DHGEX key exchange
+     */
+    public static class Factory implements NamedFactory<KeyExchange> {
+
+        public String getName() {
+            return "diffie-hellman-group-exchange-sha1";
+        }
+
+        public KeyExchange create() {
+            return new DHGEX();
+        }
+
+    }
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    private ClientSessionImpl session;
+    private byte[] V_S;
+    private byte[] V_C;
+    private byte[] I_S;
+    private byte[] I_C;
+    private Digest hash;
+    private AbstractDH dh;
+    private byte[] p;
+    private byte[] g;
+    private byte[] e;
+    private byte[] f;
+    private byte[] K;
+    private byte[] H;
+    private PublicKey serverKey;
+    private SshConstants.Message expected;
+
+    int min = 1024;
+    int prf = 4096;
+    int max = 8192;
+
+    public void init(AbstractSession s, byte[] V_S, byte[] V_C, byte[] I_S, byte[] I_C) throws Exception {
+        if (!(s instanceof ClientSessionImpl)) {
+            throw new IllegalStateException("Using a client side KeyExchange on a server");
+        }
+        session = (ClientSessionImpl) s;
+        this.V_S = V_S;
+        this.V_C = V_C;
+        this.I_S = I_S;
+        this.I_C = I_C;
+
+        log.info("Send SSH_MSG_KEX_DH_GEX_REQUEST");
+        Buffer buffer = session.createBuffer(SshConstants.Message.SSH_MSG_KEX_DH_GEX_REQUEST, 0);
+        buffer.putInt(min);
+        buffer.putInt(prf);
+        buffer.putInt(max);
+        session.writePacket(buffer);
+
+        expected = SshConstants.Message.SSH_MSG_KEXDH_REPLY_KEX_DH_GEX_GROUP;
+    }
+
+    public boolean next(Buffer buffer) throws Exception {
+        SshConstants.Message cmd = buffer.getCommand();
+        log.info("Received " + cmd);
+        if (cmd != expected) {
+            throw new SshException(SshConstants.SSH2_DISCONNECT_KEY_EXCHANGE_FAILED,
+                    "Protocol error: expected packet " + expected + ", got " + cmd);
+        }
+
+        if (cmd == SshConstants.Message.SSH_MSG_KEXDH_REPLY_KEX_DH_GEX_GROUP) {
+            p = buffer.getMPIntAsBytes();
+            g = buffer.getMPIntAsBytes();
+
+            dh = getDH(new BigInteger(p), new BigInteger(g));
+            hash =  dh.getHash();
+            hash.init();
+            e = dh.getE();
+
+            log.info("Send SSH_MSG_KEX_DH_GEX_INIT");
+            buffer = session.createBuffer(SshConstants.Message.SSH_MSG_KEX_DH_GEX_INIT, 0);
+            buffer.putMPInt(e);
+            session.writePacket(buffer);
+            expected = SshConstants.Message.SSH_MSG_KEX_DH_GEX_REPLY;
+            return false;
+        }
+
+        if (cmd == SshConstants.Message.SSH_MSG_KEX_DH_GEX_REPLY) {
+            byte[] K_S = buffer.getBytes();
+            f = buffer.getMPIntAsBytes();
+            byte[] sig = buffer.getBytes();
+            dh.setF(f);
+            K = dh.getK();
+
+            buffer = new Buffer(K_S);
+            serverKey = buffer.getRawPublicKey();
+            final String keyAlg;
+            if (serverKey instanceof RSAPublicKey) {
+                keyAlg = KeyPairProvider.SSH_RSA;
+            } else if (serverKey instanceof DSAPublicKey) {
+                keyAlg = KeyPairProvider.SSH_DSS;
+            } else if (serverKey instanceof ECPublicKey) {
+                keyAlg = ECCurves.ECDSA_SHA2_PREFIX + ECCurves.getCurveName(((ECPublicKey) serverKey).getParams());
+            } else {
+                throw new SshException("Unsupported server key type");
+            }
+
+            buffer = new Buffer();
+            buffer.putString(V_C);
+            buffer.putString(V_S);
+            buffer.putString(I_C);
+            buffer.putString(I_S);
+            buffer.putString(K_S);
+            buffer.putInt(min);
+            buffer.putInt(prf);
+            buffer.putInt(max);
+            buffer.putMPInt(p);
+            buffer.putMPInt(g);
+            buffer.putMPInt(e);
+            buffer.putMPInt(f);
+            buffer.putMPInt(K);
+            hash.update(buffer.array(), 0, buffer.available());
+            H = hash.digest();
+
+            Signature verif = NamedFactory.Utils.create(session.getFactoryManager().getSignatureFactories(), keyAlg);
+            verif.init(serverKey, null);
+            verif.update(H, 0, H.length);
+            if (!verif.verify(sig)) {
+                throw new SshException(SshConstants.SSH2_DISCONNECT_KEY_EXCHANGE_FAILED,
+                        "KeyExchange signature verification failed");
+            }
+            return true;
+        }
+
+        throw new IllegalStateException();
+    }
+
+    protected DH getDH(BigInteger p, BigInteger g) throws Exception {
+        DH dh = new DH();
+        dh.setP(p);
+        dh.setG(g);
+        return dh;
+    }
+
+    public Digest getHash() {
+        return hash;
+    }
+
+    public byte[] getH() {
+        return H;
+    }
+
+    public byte[] getK() {
+        return K;
+    }
+
+    public PublicKey getServerKey() {
+        return serverKey;
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/25948183/sshd-core/src/main/java/org/apache/sshd/client/kex/DHGEX256.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/kex/DHGEX256.java b/sshd-core/src/main/java/org/apache/sshd/client/kex/DHGEX256.java
new file mode 100644
index 0000000..a9582af
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/client/kex/DHGEX256.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sshd.client.kex;
+
+import java.math.BigInteger;
+
+import org.apache.sshd.common.KeyExchange;
+import org.apache.sshd.common.NamedFactory;
+import org.apache.sshd.common.digest.SHA256;
+import org.apache.sshd.common.kex.DH;
+
+/**
+ * Client side Diffie Hellman Group Exchange
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class DHGEX256 extends DHGEX {
+
+    /**
+     * Named factory for DHGEX key exchange
+     */
+    public static class Factory implements NamedFactory<KeyExchange> {
+
+        public String getName() {
+            return "diffie-hellman-group-exchange-sha256";
+        }
+
+        public KeyExchange create() {
+            return new DHGEX256();
+        }
+
+    }
+
+    @Override
+    protected DH getDH(BigInteger p, BigInteger g) throws Exception {
+        DH dh = new DH(new SHA256.Factory());
+        dh.setP(p);
+        dh.setG(g);
+        return dh;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/25948183/sshd-core/src/main/java/org/apache/sshd/common/Random.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/Random.java b/sshd-core/src/main/java/org/apache/sshd/common/Random.java
index 75f63c4..0e11ad6 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/Random.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/Random.java
@@ -34,4 +34,10 @@ public interface Random {
      */
     void fill(byte[] bytes, int start, int len);
 
+    /**
+     * Returns a pseudo-random uniformly distributed {@code int}
+     * in the half-open range [0, n).
+     */
+    int random(int n);
+
 }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/25948183/sshd-core/src/main/java/org/apache/sshd/common/kex/DH.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/kex/DH.java b/sshd-core/src/main/java/org/apache/sshd/common/kex/DH.java
index cb8e1ab..d576fd4 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/kex/DH.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/kex/DH.java
@@ -29,6 +29,7 @@ import javax.crypto.spec.DHParameterSpec;
 import javax.crypto.spec.DHPublicKeySpec;
 
 import org.apache.sshd.common.Digest;
+import org.apache.sshd.common.Factory;
 import org.apache.sshd.common.digest.SHA1;
 import org.apache.sshd.common.util.SecurityUtils;
 
@@ -46,10 +47,16 @@ public class DH extends AbstractDH {
     private BigInteger f;  // your public key
     private KeyPairGenerator myKpairGen;
     private KeyAgreement myKeyAgree;
+    private Factory<Digest> factory;
 
     public DH() throws Exception {
+        this(new SHA1.Factory());
+    }
+
+    public DH(Factory<Digest> factory) throws Exception {
         myKpairGen = SecurityUtils.getKeyPairGenerator("DH");
         myKeyAgree = SecurityUtils.getKeyAgreement("DH");
+        this.factory = factory;
     }
 
     public byte[] getE() throws Exception {
@@ -84,20 +91,28 @@ public class DH extends AbstractDH {
         setF(new BigInteger(f));
     }
 
-    void setP(BigInteger p) {
+    public BigInteger getP() {
+        return p;
+    }
+
+    public void setP(BigInteger p) {
         this.p = p;
     }
 
-    void setG(BigInteger g) {
+    public BigInteger getG() {
+        return g;
+    }
+
+    public void setG(BigInteger g) {
         this.g = g;
     }
 
-    void setF(BigInteger f) {
+    public void setF(BigInteger f) {
         this.f = f;
     }
 
     @Override
     public Digest getHash() throws Exception {
-        return new SHA1();
+        return factory.create();
     }
 }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/25948183/sshd-core/src/main/java/org/apache/sshd/common/random/BouncyCastleRandom.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/random/BouncyCastleRandom.java b/sshd-core/src/main/java/org/apache/sshd/common/random/BouncyCastleRandom.java
index 5b530b8..5c35f87 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/random/BouncyCastleRandom.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/random/BouncyCastleRandom.java
@@ -61,4 +61,35 @@ public class BouncyCastleRandom implements Random {
     public void fill(byte[] bytes, int start, int len) {
         this.random.nextBytes(bytes, start, len);
     }
+
+    /**
+     * Returns a pseudo-random uniformly distributed {@code int}
+     * in the half-open range [0, n).
+     */
+    public int random(int n) {
+        if (n > 0) {
+            if ((n & -n) == n) {
+                return (int)((n * (long) next(31)) >> 31);
+            }
+            int bits, val;
+            do {
+                bits = next(31);
+                val = bits % n;
+            } while (bits - val + (n-1) < 0);
+            return val;
+        }
+        throw new IllegalArgumentException();
+    }
+
+    final protected int next(int numBits) {
+        int bytes = (numBits+7)/8;
+        byte next[] = new byte[bytes];
+        int ret = 0;
+        random.nextBytes(next);
+        for (int i = 0; i < bytes; i++) {
+            ret = (next[i] & 0xFF) | (ret << 8);
+        }
+        return ret >>> (bytes*8 - numBits);
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/25948183/sshd-core/src/main/java/org/apache/sshd/common/random/JceRandom.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/random/JceRandom.java b/sshd-core/src/main/java/org/apache/sshd/common/random/JceRandom.java
index ec7bbe8..24a61da 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/random/JceRandom.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/random/JceRandom.java
@@ -66,4 +66,8 @@ public class JceRandom implements Random {
         }
     }
 
+    public synchronized int random(int n) {
+        return random.nextInt(n);
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/25948183/sshd-core/src/main/java/org/apache/sshd/common/random/SingletonRandomFactory.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/random/SingletonRandomFactory.java b/sshd-core/src/main/java/org/apache/sshd/common/random/SingletonRandomFactory.java
index 1e18564..384d475 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/random/SingletonRandomFactory.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/random/SingletonRandomFactory.java
@@ -41,6 +41,10 @@ public class SingletonRandomFactory implements Random, NamedFactory<Random> {
         random.fill(bytes, start, len);
     }
 
+    public int random(int max) {
+        return random.random(max);
+    }
+
     public String getName() {
         return factory.getName();
     }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/25948183/sshd-core/src/main/java/org/apache/sshd/server/kex/DHGEX.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/kex/DHGEX.java b/sshd-core/src/main/java/org/apache/sshd/server/kex/DHGEX.java
new file mode 100644
index 0000000..a75fe6a
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/server/kex/DHGEX.java
@@ -0,0 +1,245 @@
+/*
+ * 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.server.kex;
+
+import java.math.BigInteger;
+import java.net.URL;
+import java.security.KeyPair;
+import java.security.PublicKey;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.sshd.common.Digest;
+import org.apache.sshd.common.KeyExchange;
+import org.apache.sshd.common.NamedFactory;
+import org.apache.sshd.common.Random;
+import org.apache.sshd.common.Signature;
+import org.apache.sshd.common.SshConstants;
+import org.apache.sshd.common.SshException;
+import org.apache.sshd.common.digest.SHA1;
+import org.apache.sshd.common.kex.DH;
+import org.apache.sshd.common.session.AbstractSession;
+import org.apache.sshd.common.util.Buffer;
+import org.apache.sshd.common.util.BufferUtils;
+import org.apache.sshd.common.util.SecurityUtils;
+import org.apache.sshd.server.session.ServerSession;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Server side Diffie Hellman Group Exchange
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class DHGEX implements KeyExchange {
+
+    public static class Factory implements NamedFactory<KeyExchange> {
+
+        public String getName() {
+            return "diffie-hellman-group-exchange-sha1";
+        }
+
+        public KeyExchange create() {
+            return new DHGEX();
+        }
+
+    }
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    private ServerSession session;
+    private byte[] V_S;
+    private byte[] V_C;
+    private byte[] I_S;
+    private byte[] I_C;
+    private Digest hash;
+    private DH dh;
+    private byte[] e;
+    private byte[] f;
+    private byte[] K;
+    private byte[] H;
+
+    int min;
+    int prf;
+    int max;
+    private SshConstants.Message expected;
+
+    public void init(AbstractSession s, byte[] V_S, byte[] V_C, byte[] I_S, byte[] I_C) throws Exception {
+        if (!(s instanceof ServerSession)) {
+            throw new IllegalStateException("Using a server side KeyExchange on a client");
+        }
+        session = (ServerSession) s;
+        this.V_S = V_S;
+        this.V_C = V_C;
+        this.I_S = I_S;
+        this.I_C = I_C;
+
+        expected = SshConstants.Message.SSH_MSG_KEX_DH_GEX_REQUEST;
+    }
+
+    public boolean next(Buffer buffer) throws Exception {
+        SshConstants.Message cmd = buffer.getCommand();
+        log.info("Received " + cmd);
+        if (cmd != expected) {
+            throw new SshException(SshConstants.SSH2_DISCONNECT_KEY_EXCHANGE_FAILED,
+                    "Protocol error: expected packet " + expected + ", got " + cmd);
+        }
+
+        if (cmd == SshConstants.Message.SSH_MSG_KEX_DH_GEX_REQUEST) {
+            min = buffer.getInt();
+            prf = buffer.getInt();
+            max = buffer.getInt();
+            if (max < min || prf < min || max < prf) {
+                throw new SshException(SshConstants.SSH2_DISCONNECT_KEY_EXCHANGE_FAILED,
+                        "Protocol error: bad parameters " + min + " !< " + prf + " !< " + max);
+            }
+            dh = chooseDH(min, prf, max);
+            f = dh.getE();
+            hash = dh.getHash();
+            hash.init();
+
+            log.info("Send SSH_MSG_KEXDH_REPLY_KEX_DH_GEX_GROUP");
+            buffer = session.createBuffer(SshConstants.Message.SSH_MSG_KEXDH_REPLY_KEX_DH_GEX_GROUP, 0);
+            buffer.putMPInt(dh.getP());
+            buffer.putMPInt(dh.getG());
+            session.writePacket(buffer);
+
+            expected = SshConstants.Message.SSH_MSG_KEX_DH_GEX_INIT;
+            return false;
+        }
+
+        if (cmd == SshConstants.Message.SSH_MSG_KEX_DH_GEX_INIT) {
+            e = buffer.getMPIntAsBytes();
+            dh.setF(e);
+            K = dh.getK();
+
+
+            byte[] K_S;
+            KeyPair kp = session.getHostKey();
+            String algo = session.getNegociated(SshConstants.PROPOSAL_SERVER_HOST_KEY_ALGS);
+            Signature sig = NamedFactory.Utils.create(session.getFactoryManager().getSignatureFactories(), algo);
+            sig.init(kp.getPublic(), kp.getPrivate());
+
+            buffer = new Buffer();
+            buffer.putRawPublicKey(kp.getPublic());
+            K_S = buffer.getCompactData();
+
+            buffer.clear();
+            buffer.putString(V_C);
+            buffer.putString(V_S);
+            buffer.putString(I_C);
+            buffer.putString(I_S);
+            buffer.putString(K_S);
+            buffer.putInt(min);
+            buffer.putInt(prf);
+            buffer.putInt(max);
+            buffer.putMPInt(dh.getP());
+            buffer.putMPInt(dh.getG());
+            buffer.putMPInt(e);
+            buffer.putMPInt(f);
+            buffer.putMPInt(K);
+            hash.update(buffer.array(), 0, buffer.available());
+            H = hash.digest();
+
+            byte[] sigH;
+            buffer.clear();
+            sig.update(H, 0, H.length);
+            buffer.putString(algo);
+            buffer.putString(sig.sign());
+            sigH = buffer.getCompactData();
+
+            if (log.isDebugEnabled()) {
+                log.debug("K_S:  {}", BufferUtils.printHex(K_S));
+                log.debug("f:    {}", BufferUtils.printHex(f));
+                log.debug("sigH: {}", BufferUtils.printHex(sigH));
+            }
+
+            // Send response
+            log.debug("Send SSH_MSG_KEX_DH_GEX_REPLY");
+            buffer.clear();
+            buffer.rpos(5);
+            buffer.wpos(5);
+            buffer.putCommand(SshConstants.Message.SSH_MSG_KEX_DH_GEX_REPLY);
+            buffer.putString(K_S);
+            buffer.putString(f);
+            buffer.putString(sigH);
+            session.writePacket(buffer);
+            return true;
+        }
+
+        return false;
+    }
+
+    private DH chooseDH(int min, int prf, int max) throws Exception {
+        URL moduli = getClass().getResource("/org/apache/sshd/moduli");
+        List<Moduli.DhGroup> groups = Moduli.parseModuli(moduli);
+
+        min = Math.max(min, 1024);
+        prf = Math.max(prf, 1024);
+        // Keys of size > 1024 are not support by default with JCE, so only enable
+        // those if BouncyCastle is registered
+        prf = Math.min(prf, SecurityUtils.isBouncyCastleRegistered() ? 8192 : 1024);
+        max = Math.min(max, 8192);
+        int bestSize = 0;
+        List<Moduli.DhGroup> selected = new ArrayList<Moduli.DhGroup>();
+        for (Moduli.DhGroup group : groups) {
+            if (group.size < min || group.size > max) {
+                continue;
+            }
+            if ((group.size > prf && group.size < bestSize) || (group.size > bestSize && bestSize < prf)) {
+                bestSize = group.size;
+                selected.clear();
+            }
+            if (group.size == bestSize) {
+                selected.add(group);
+            }
+        }
+        if (selected.isEmpty()) {
+            throw new IllegalArgumentException("No suitable primes");
+        }
+        Random random = session.getFactoryManager().getRandomFactory().create();
+        int which = random.random(selected.size());
+        Moduli.DhGroup group = selected.get(which);
+        return getDH(group.p, group.g);
+    }
+
+    protected DH getDH(BigInteger p, BigInteger g) throws Exception {
+        DH dh = new DH(new SHA1.Factory());
+        dh.setP(p);
+        dh.setG(g);
+        return dh;
+    }
+
+    public Digest getHash() {
+        return hash;
+    }
+
+    public byte[] getH() {
+        return H;
+    }
+
+    public byte[] getK() {
+        return K;
+    }
+
+    public PublicKey getServerKey() {
+        return session.getHostKey().getPublic();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/25948183/sshd-core/src/main/java/org/apache/sshd/server/kex/DHGEX256.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/kex/DHGEX256.java b/sshd-core/src/main/java/org/apache/sshd/server/kex/DHGEX256.java
new file mode 100644
index 0000000..a6dd41e
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/server/kex/DHGEX256.java
@@ -0,0 +1,55 @@
+/*
+ * 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.server.kex;
+
+import java.math.BigInteger;
+
+import org.apache.sshd.common.KeyExchange;
+import org.apache.sshd.common.NamedFactory;
+import org.apache.sshd.common.digest.SHA256;
+import org.apache.sshd.common.kex.DH;
+
+/**
+ * Server side Diffie Hellman Group Exchange
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class DHGEX256 extends DHGEX {
+
+    public static class Factory implements NamedFactory<KeyExchange> {
+
+        public String getName() {
+            return "diffie-hellman-group-exchange-sha256";
+        }
+
+        public KeyExchange create() {
+            return new DHGEX256();
+        }
+
+    }
+
+    @Override
+    protected DH getDH(BigInteger p, BigInteger g) throws Exception {
+        DH dh = new DH(new SHA256.Factory());
+        dh.setP(p);
+        dh.setG(g);
+        return dh;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/25948183/sshd-core/src/main/java/org/apache/sshd/server/kex/Moduli.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/kex/Moduli.java b/sshd-core/src/main/java/org/apache/sshd/server/kex/Moduli.java
new file mode 100644
index 0000000..f51f2ec
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/server/kex/Moduli.java
@@ -0,0 +1,91 @@
+/*
+ * 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.server.kex;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.math.BigInteger;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Helper class to load DH group primes from a file.
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class Moduli {
+
+    public static final int MODULI_TYPE_SAFE = 2;
+    public static final int MODULI_TESTS_COMPOSITE = 0x01;
+
+    public static class DhGroup {
+        int size;
+        BigInteger g;
+        BigInteger p;
+    }
+
+    public static List<DhGroup> parseModuli(URL url) throws IOException {
+        List<DhGroup> groups = new ArrayList<DhGroup>();
+        BufferedReader r = new BufferedReader(new InputStreamReader(url.openStream()));
+        try {
+            String line;
+            while ((line = r.readLine()) != null) {
+                line = line.trim();
+                if (line.startsWith("#")) {
+                    continue;
+                }
+                String[] parts = line.split("\\s+");
+                // Ensure valid line
+                if (parts.length != 7) {
+                    continue;
+                }
+                // Discard moduli types which are not safe
+                int type = Integer.parseInt(parts[1]);
+                if (type != MODULI_TYPE_SAFE) {
+                    continue;
+                }
+                // Discard untested modulis
+                int tests = Integer.parseInt(parts[2]);
+                if ((tests & MODULI_TESTS_COMPOSITE) != 0 || (tests & ~MODULI_TESTS_COMPOSITE) == 0) {
+                    continue;
+                }
+                // Discard untried
+                int tries = Integer.parseInt(parts[3]);
+                if (tries == 0) {
+                    continue;
+                }
+                DhGroup group = new DhGroup();
+                group.size = Integer.parseInt(parts[4]) + 1;
+                group.g = new BigInteger(parts[5], 16);
+                group.p = new BigInteger(parts[6], 16);
+                groups.add(group);
+            }
+            return groups;
+        } finally {
+            r.close();
+        }
+    }
+
+    // Private constructor
+    private Moduli() {
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/25948183/sshd-core/src/test/java/org/apache/sshd/KexTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/KexTest.java b/sshd-core/src/test/java/org/apache/sshd/KexTest.java
new file mode 100644
index 0000000..dd6680c
--- /dev/null
+++ b/sshd-core/src/test/java/org/apache/sshd/KexTest.java
@@ -0,0 +1,153 @@
+/*
+ * 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;
+
+import java.io.ByteArrayOutputStream;
+import java.io.OutputStream;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+import java.util.Collections;
+
+import org.apache.sshd.client.kex.DHG1;
+import org.apache.sshd.client.kex.DHG14;
+import org.apache.sshd.client.kex.DHGEX;
+import org.apache.sshd.client.kex.DHGEX256;
+import org.apache.sshd.client.kex.ECDHP256;
+import org.apache.sshd.client.kex.ECDHP384;
+import org.apache.sshd.client.kex.ECDHP521;
+import org.apache.sshd.common.KeyExchange;
+import org.apache.sshd.common.NamedFactory;
+import org.apache.sshd.common.util.SecurityUtils;
+import org.apache.sshd.util.BogusPasswordAuthenticator;
+import org.apache.sshd.util.EchoShellFactory;
+import org.apache.sshd.util.TeeOutputStream;
+import org.apache.sshd.util.Utils;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Test key exchange algorithms.
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class KexTest {
+
+    private SshServer sshd;
+    private int port;
+
+    @Before
+    public void setUp() throws Exception {
+        port = Utils.getFreePort();
+
+        sshd = SshServer.setUpDefaultServer();
+        sshd.setPort(port);
+        sshd.setKeyPairProvider(Utils.createTestHostKeyProvider());
+        sshd.setShellFactory(new EchoShellFactory());
+        sshd.setPasswordAuthenticator(new BogusPasswordAuthenticator());
+        sshd.start();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        sshd.stop();
+    }
+
+    @Test
+    public void testDHGEX256() throws Exception {
+        testClient(new DHGEX256.Factory());
+    }
+
+    @Test
+    public void testDHGEX() throws Exception {
+        testClient(new DHGEX.Factory());
+    }
+
+    @Test
+    public void testDHG14() throws Exception {
+        if (SecurityUtils.isBouncyCastleRegistered()) {
+            testClient(new DHG14.Factory());
+        }
+    }
+
+    @Test
+    public void testDHG1() throws Exception {
+        testClient(new DHG1.Factory());
+    }
+
+    @Test
+    public void testECDHP521() throws Exception {
+        testClient(new ECDHP521.Factory());
+    }
+
+    @Test
+    public void testECDHP384() throws Exception {
+        testClient(new ECDHP384.Factory());
+    }
+
+    @Test
+    public void testECDHP256() throws Exception {
+        testClient(new ECDHP256.Factory());
+    }
+
+    private void testClient(NamedFactory<KeyExchange> kex) throws Exception {
+        SshClient client = SshClient.setUpDefaultClient();
+        ByteArrayOutputStream sent = new ByteArrayOutputStream();
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        try {
+            client.setKeyExchangeFactories(Collections.singletonList(kex));
+            client.start();
+            ClientSession session = client.connect("localhost", port).await().getSession();
+            assertTrue(session.authPassword("smx", "smx").await().isSuccess());
+            ClientChannel channel = session.createChannel(ClientChannel.CHANNEL_SHELL);
+
+            PipedOutputStream pipedIn = new PipedOutputStream();
+            channel.setIn(new PipedInputStream(pipedIn));
+            OutputStream teeOut = new TeeOutputStream(sent, pipedIn);
+            ByteArrayOutputStream err = new ByteArrayOutputStream();
+            channel.setOut(out);
+            channel.setErr(err);
+            assertTrue(channel.open().await().isOpened());
+
+            teeOut.write("this is my command\n".getBytes());
+            teeOut.flush();
+
+            StringBuilder sb = new StringBuilder();
+            for (int i = 0; i < 10; i++) {
+                sb.append("0123456789");
+            }
+            sb.append("\n");
+            teeOut.write(sb.toString().getBytes());
+
+            teeOut.write("exit\n".getBytes());
+            teeOut.flush();
+
+            channel.waitFor(ClientChannel.CLOSED, 0);
+
+            channel.close(false);
+        } finally {
+            client.stop();
+        }
+
+        assertArrayEquals(sent.toByteArray(), out.toByteArray());
+    }
+}