diff --git a/build.gradle b/build.gradle index 2d19b3121..b2e92f5cb 100644 --- a/build.gradle +++ b/build.gradle @@ -51,6 +51,8 @@ dependencies { compile "org.bouncycastle:bcpkix-jdk15on:$bouncycastleVersion" compile "com.jcraft:jzlib:1.1.3" + compile "net.vrallev.ecc:ecc-25519-java:1.0.1" + testCompile "junit:junit:4.11" testCompile "org.mockito:mockito-core:1.9.5" testCompile "org.apache.sshd:sshd-core:1.0.0" diff --git a/src/main/java/net/schmizz/sshj/transport/kex/AbstractDH.java b/src/main/java/net/schmizz/sshj/transport/kex/AbstractDH.java index 3370de671..6156aed0c 100644 --- a/src/main/java/net/schmizz/sshj/transport/kex/AbstractDH.java +++ b/src/main/java/net/schmizz/sshj/transport/kex/AbstractDH.java @@ -7,35 +7,15 @@ import java.util.Arrays; public abstract class AbstractDH extends KeyExchangeBase { - protected final Digest digest; protected final DHBase dh; - protected byte[] H; - protected PublicKey hostKey; - public AbstractDH(DHBase dh, Digest digest) { + super(digest); this.dh = dh; - this.digest = digest; - } - - @Override - public byte[] getH() { - return Arrays.copyOf(H, H.length); } @Override public BigInteger getK() { return dh.getK(); } - - @Override - public Digest getHash() { - return digest; - } - - @Override - public PublicKey getHostKey() { - return hostKey; - } - } diff --git a/src/main/java/net/schmizz/sshj/transport/kex/Curve25519DH.java b/src/main/java/net/schmizz/sshj/transport/kex/Curve25519DH.java new file mode 100644 index 000000000..7e87a7055 --- /dev/null +++ b/src/main/java/net/schmizz/sshj/transport/kex/Curve25519DH.java @@ -0,0 +1,54 @@ +package net.schmizz.sshj.transport.kex; + +import net.schmizz.sshj.common.SecurityUtils; +import org.bouncycastle.asn1.x9.X9ECParameters; +import org.bouncycastle.crypto.ec.CustomNamedCurves; +import org.bouncycastle.jce.spec.ECParameterSpec; +import org.bouncycastle.math.ec.ECFieldElement; +import org.bouncycastle.math.ec.custom.djb.Curve25519; + +import java.math.BigInteger; +import java.security.*; +import java.security.interfaces.ECPublicKey; +import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.ECPoint; +import java.security.spec.ECPublicKeySpec; +import java.util.Arrays; +import java.util.BitSet; + +public class Curve25519DH extends DHBase { + + + private byte[] secretKey; + + public Curve25519DH() { + super("ECDSA", "ECDH"); + } + + @Override + void computeK(byte[] f) throws GeneralSecurityException { + byte[] k = new byte[32]; + djb.Curve25519.curve(k, secretKey, f); + setK(new BigInteger(1, k)); + } + + @Override + public void init(AlgorithmParameterSpec params) throws GeneralSecurityException { + SecureRandom secureRandom = new SecureRandom(); + byte[] secretBytes = new byte[32]; + secureRandom.nextBytes(secretBytes); + byte[] publicBytes = new byte[32]; + djb.Curve25519.keygen(publicBytes, null, secretBytes); + this.secretKey = Arrays.copyOf(secretBytes, secretBytes.length); + setE(publicBytes); + } + + /** + * TODO want to figure out why BouncyCastle does not work. + * @return + */ + public static AlgorithmParameterSpec getCurve25519Params() { + X9ECParameters ecP = CustomNamedCurves.getByName("curve25519"); + return new ECParameterSpec(ecP.getCurve(), ecP.getG(), ecP.getN(), ecP.getH(), ecP.getSeed()); + } +} diff --git a/src/main/java/net/schmizz/sshj/transport/kex/Curve25519SHA256.java b/src/main/java/net/schmizz/sshj/transport/kex/Curve25519SHA256.java new file mode 100644 index 000000000..b6053e035 --- /dev/null +++ b/src/main/java/net/schmizz/sshj/transport/kex/Curve25519SHA256.java @@ -0,0 +1,38 @@ +package net.schmizz.sshj.transport.kex; + +import net.schmizz.sshj.common.*; +import net.schmizz.sshj.signature.Signature; +import net.schmizz.sshj.transport.TransportException; +import net.schmizz.sshj.transport.digest.SHA256; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.security.GeneralSecurityException; + +public class Curve25519SHA256 extends AbstractDHG { + private static final Logger logger = LoggerFactory.getLogger(Curve25519SHA256.class); + + /** Named factory for Curve25519SHA256 key exchange */ + public static class Factory + implements net.schmizz.sshj.common.Factory.Named { + + @Override + public KeyExchange create() { + return new Curve25519SHA256(); + } + + @Override + public String getName() { + return "curve25519-sha256@libssh.org"; + } + } + + public Curve25519SHA256() { + super(new Curve25519DH(), new SHA256()); + } + + @Override + protected void initDH(DHBase dh) throws GeneralSecurityException { + dh.init(Curve25519DH.getCurve25519Params()); + } +} diff --git a/src/main/java/net/schmizz/sshj/transport/kex/ECDH.java b/src/main/java/net/schmizz/sshj/transport/kex/ECDH.java index 0c71af3c1..d9c972bae 100644 --- a/src/main/java/net/schmizz/sshj/transport/kex/ECDH.java +++ b/src/main/java/net/schmizz/sshj/transport/kex/ECDH.java @@ -12,6 +12,9 @@ import java.security.spec.*; import java.util.Arrays; +import static net.schmizz.sshj.transport.kex.SecgUtils.getDecoded; +import static net.schmizz.sshj.transport.kex.SecgUtils.getEncoded; + public class ECDH extends DHBase { private ECParameterSpec ecParameterSpec; @@ -40,47 +43,4 @@ public void computeK(byte[] f) throws GeneralSecurityException { setK(new BigInteger(1, agreement.generateSecret())); } - /** - * SECG 2.3.4 Octet String to ECPoint - */ - private static ECPoint getDecoded(byte[] M, EllipticCurve curve) { - int elementSize = getElementSize(curve); - if (M.length != 2 * elementSize + 1 || M[0] != 0x04) { - throw new SSHRuntimeException("Invalid 'f' for Elliptic Curve " + curve.toString()); - } - byte[] xBytes = new byte[elementSize]; - byte[] yBytes = new byte[elementSize]; - System.arraycopy(M, 1, xBytes, 0, elementSize); - System.arraycopy(M, 1 + elementSize, yBytes, 0, elementSize); - return new ECPoint(new BigInteger(1, xBytes), new BigInteger(1, yBytes)); - } - - /** - * SECG 2.3.3 ECPoint to Octet String - */ - private static byte[] getEncoded(ECPoint point, EllipticCurve curve) { - int elementSize = getElementSize(curve); - byte[] M = new byte[2 * elementSize + 1]; - M[0] = 0x04; - - byte[] xBytes = stripLeadingZeroes(point.getAffineX().toByteArray()); - byte[] yBytes = stripLeadingZeroes(point.getAffineY().toByteArray()); - System.arraycopy(xBytes, 0, M, 1 + elementSize - xBytes.length, xBytes.length); - System.arraycopy(yBytes, 0, M, 1 + 2 * elementSize - yBytes.length, yBytes.length); - return M; - } - - private static byte[] stripLeadingZeroes(byte[] bytes) { - int start = 0; - while (bytes[start] == 0x0) { - start++; - } - - return Arrays.copyOfRange(bytes, start, bytes.length); - } - - private static int getElementSize(EllipticCurve curve) { - int fieldSize = curve.getField().getFieldSize(); - return (fieldSize + 7) / 8; - } } diff --git a/src/main/java/net/schmizz/sshj/transport/kex/KeyExchangeBase.java b/src/main/java/net/schmizz/sshj/transport/kex/KeyExchangeBase.java index a012b37aa..d1a7269d8 100644 --- a/src/main/java/net/schmizz/sshj/transport/kex/KeyExchangeBase.java +++ b/src/main/java/net/schmizz/sshj/transport/kex/KeyExchangeBase.java @@ -3,18 +3,28 @@ import net.schmizz.sshj.common.Buffer; import net.schmizz.sshj.transport.Transport; import net.schmizz.sshj.transport.TransportException; +import net.schmizz.sshj.transport.digest.Digest; import java.security.GeneralSecurityException; +import java.security.PublicKey; import java.util.Arrays; public abstract class KeyExchangeBase implements KeyExchange { protected Transport trans; + protected final Digest digest; + protected byte[] H; + protected PublicKey hostKey; + private String V_S; private String V_C; private byte[] I_S; private byte[] I_C; + public KeyExchangeBase(Digest digest) { + this.digest = digest; + } + @Override public void init(Transport trans, String V_S, String V_C, byte[] I_S, byte[] I_C) throws GeneralSecurityException, TransportException { this.trans = trans; @@ -31,4 +41,20 @@ protected Buffer.PlainBuffer initializedBuffer() { .putString(I_C) .putString(I_S); } + + @Override + public byte[] getH() { + return Arrays.copyOf(H, H.length); + } + + @Override + public Digest getHash() { + return digest; + } + + @Override + public PublicKey getHostKey() { + return hostKey; + } + } diff --git a/src/main/java/net/schmizz/sshj/transport/kex/SecgUtils.java b/src/main/java/net/schmizz/sshj/transport/kex/SecgUtils.java new file mode 100644 index 000000000..776edcf8b --- /dev/null +++ b/src/main/java/net/schmizz/sshj/transport/kex/SecgUtils.java @@ -0,0 +1,55 @@ +package net.schmizz.sshj.transport.kex; + +import net.schmizz.sshj.common.SSHRuntimeException; + +import java.math.BigInteger; +import java.security.spec.ECPoint; +import java.security.spec.EllipticCurve; +import java.util.Arrays; + +class SecgUtils { + /** + * SECG 2.3.4 Octet String to ECPoint + */ + static ECPoint getDecoded(byte[] M, EllipticCurve curve) { + int elementSize = getElementSize(curve); + if (M.length != 2 * elementSize + 1 || M[0] != 0x04) { + throw new SSHRuntimeException("Invalid 'f' for Elliptic Curve " + curve.toString()); + } + byte[] xBytes = new byte[elementSize]; + byte[] yBytes = new byte[elementSize]; + System.arraycopy(M, 1, xBytes, 0, elementSize); + System.arraycopy(M, 1 + elementSize, yBytes, 0, elementSize); + return new ECPoint(new BigInteger(1, xBytes), new BigInteger(1, yBytes)); + } + + /** + * SECG 2.3.3 ECPoint to Octet String + */ + static byte[] getEncoded(ECPoint point, EllipticCurve curve) { + int elementSize = getElementSize(curve); + byte[] M = new byte[2 * elementSize + 1]; + M[0] = 0x04; + + byte[] xBytes = stripLeadingZeroes(point.getAffineX().toByteArray()); + byte[] yBytes = stripLeadingZeroes(point.getAffineY().toByteArray()); + System.arraycopy(xBytes, 0, M, 1 + elementSize - xBytes.length, xBytes.length); + System.arraycopy(yBytes, 0, M, 1 + 2 * elementSize - yBytes.length, yBytes.length); + return M; + } + + private static byte[] stripLeadingZeroes(byte[] bytes) { + int start = 0; + while (bytes[start] == 0x0) { + start++; + } + + return Arrays.copyOfRange(bytes, start, bytes.length); + } + + private static int getElementSize(EllipticCurve curve) { + int fieldSize = curve.getField().getFieldSize(); + return (fieldSize + 7) / 8; + } + +} diff --git a/src/test/java/com/hierynomus/sshj/transport/kex/KeyExchangeTest.java b/src/test/java/com/hierynomus/sshj/transport/kex/KeyExchangeTest.java index 225ef06f5..cc19b6ef3 100644 --- a/src/test/java/com/hierynomus/sshj/transport/kex/KeyExchangeTest.java +++ b/src/test/java/com/hierynomus/sshj/transport/kex/KeyExchangeTest.java @@ -5,14 +5,17 @@ import net.schmizz.sshj.DefaultConfig; import net.schmizz.sshj.SSHClient; import net.schmizz.sshj.common.Factory; +import net.schmizz.sshj.transport.kex.Curve25519SHA256; import net.schmizz.sshj.transport.kex.DHGexSHA1; import net.schmizz.sshj.transport.kex.DHGexSHA256; import net.schmizz.sshj.transport.kex.ECDHNistP; +import net.schmizz.sshj.transport.verification.PromiscuousVerifier; import org.apache.sshd.common.NamedFactory; import org.apache.sshd.common.kex.BuiltinDHFactories; import org.apache.sshd.server.kex.DHGEXServer; import org.apache.sshd.server.kex.DHGServer; import org.junit.After; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.experimental.categories.Category; @@ -56,11 +59,16 @@ public void shouldKexWithEllipticCurveDiffieHellmanNistP384() throws IOException } @Test - @Category({KnownFailingTests.class}) public void shouldKexWithEllipticCurveDiffieHellmanNistP521() throws IOException { attemptKex(100, DHGServer.newFactory(BuiltinDHFactories.ecdhp521), new ECDHNistP.Factory521()); } + @Test + @Ignore("Apache SSHD does (not yet) have Curve25519 support") + public void shouldKexWithCurve25519() throws IOException { + attemptKex(100, null, new Curve25519SHA256.Factory()); + } + private void attemptKex(int times, NamedFactory serverFactory, Factory.Named clientFactory) throws IOException {