Skip to content

Commit

Permalink
Added support for curve25519-sha256@libssh.org (Fixes #171)
Browse files Browse the repository at this point in the history
  • Loading branch information
hierynomus committed Nov 3, 2015
1 parent eece80c commit 29a6cf6
Show file tree
Hide file tree
Showing 8 changed files with 188 additions and 65 deletions.
2 changes: 2 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
22 changes: 1 addition & 21 deletions src/main/java/net/schmizz/sshj/transport/kex/AbstractDH.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

}
54 changes: 54 additions & 0 deletions src/main/java/net/schmizz/sshj/transport/kex/Curve25519DH.java
Original file line number Diff line number Diff line change
@@ -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());
}
}
38 changes: 38 additions & 0 deletions src/main/java/net/schmizz/sshj/transport/kex/Curve25519SHA256.java
Original file line number Diff line number Diff line change
@@ -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());
}
}
46 changes: 3 additions & 43 deletions src/main/java/net/schmizz/sshj/transport/kex/ECDH.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
}
26 changes: 26 additions & 0 deletions src/main/java/net/schmizz/sshj/transport/kex/KeyExchangeBase.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
}

}
55 changes: 55 additions & 0 deletions src/main/java/net/schmizz/sshj/transport/kex/SecgUtils.java
Original file line number Diff line number Diff line change
@@ -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;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<org.apache.sshd.common.kex.KeyExchange> serverFactory,
Factory.Named<net.schmizz.sshj.transport.kex.KeyExchange> clientFactory) throws IOException {
Expand Down

0 comments on commit 29a6cf6

Please sign in to comment.