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());
+ }
+}