This commit is contained in:
Jeroen van Erp
2015-11-03 16:18:53 +01:00
parent eece80cf48
commit 29a6cf6f79
8 changed files with 188 additions and 65 deletions

View File

@@ -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"

View File

@@ -7,35 +7,15 @@ import java.security.PublicKey;
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;
}
}

View File

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

View File

@@ -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<KeyExchange> {
@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());
}
}

View File

@@ -12,6 +12,9 @@ import java.security.interfaces.ECPublicKey;
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 class ECDH extends DHBase {
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;
}
}

View File

@@ -3,18 +3,28 @@ package net.schmizz.sshj.transport.kex;
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 @@ public abstract class KeyExchangeBase implements KeyExchange {
.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;
}
}

View File

@@ -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;
}
}

View File

@@ -5,14 +5,17 @@ import com.hierynomus.sshj.test.SshFixture;
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 class KeyExchangeTest {
}
@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<org.apache.sshd.common.kex.KeyExchange> serverFactory,
Factory.Named<net.schmizz.sshj.transport.kex.KeyExchange> clientFactory) throws IOException {