aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKenny Root <kenny@the-b.org>2016-01-24 11:12:27 -0800
committerKenny Root <kenny@the-b.org>2016-01-24 11:12:27 -0800
commit4e80ba5fbec4e68749b395c489148fa5d704b87b (patch)
tree0917dd41541f03cb604933d4504a6f87302d0a15
parenta905a351a1480f121e75e065549ad7b1b5b4c862 (diff)
parent0bcb82c407b334b6c3dd0e243cabcfa2913a2f5a (diff)
downloadsshlib-4e80ba5fbec4e68749b395c489148fa5d704b87b.tar.gz
sshlib-4e80ba5fbec4e68749b395c489148fa5d704b87b.tar.bz2
sshlib-4e80ba5fbec4e68749b395c489148fa5d704b87b.zip
Merge pull request #8 from kruton/curve25519-kex
Add curve25519-sha256@libssh.org exchange method
-rw-r--r--sshlib/build.gradle1
-rw-r--r--sshlib/src/main/java/com/trilead/ssh2/crypto/dh/Curve25519Exchange.java71
-rw-r--r--sshlib/src/main/java/com/trilead/ssh2/crypto/dh/GenericDhExchange.java3
-rw-r--r--sshlib/src/main/java/com/trilead/ssh2/transport/KexManager.java8
-rw-r--r--sshlib/src/test/java/com/trilead/ssh2/crypto/dh/Curve25519ExchangeTest.java80
5 files changed, 161 insertions, 2 deletions
diff --git a/sshlib/build.gradle b/sshlib/build.gradle
index d43c56e..fb266ff 100644
--- a/sshlib/build.gradle
+++ b/sshlib/build.gradle
@@ -15,6 +15,7 @@ dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.jcraft:jzlib:1.1.3'
compile 'org.connectbot:simplesocks:1.0.1'
+ compile 'org.zeromq:curve25519-java:0.1.0'
testCompile 'junit:junit:4.12'
}
diff --git a/sshlib/src/main/java/com/trilead/ssh2/crypto/dh/Curve25519Exchange.java b/sshlib/src/main/java/com/trilead/ssh2/crypto/dh/Curve25519Exchange.java
new file mode 100644
index 0000000..d28393b
--- /dev/null
+++ b/sshlib/src/main/java/com/trilead/ssh2/crypto/dh/Curve25519Exchange.java
@@ -0,0 +1,71 @@
+package com.trilead.ssh2.crypto.dh;
+
+import djb.Curve25519;
+
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.SecureRandom;
+
+/**
+ * Created by Kenny Root on 1/23/16.
+ */
+public class Curve25519Exchange extends GenericDhExchange {
+ public static final String NAME = "curve25519-sha256@libssh.org";
+
+ private final byte[] clientPublic = new byte[Curve25519.KEY_SIZE];
+ private final byte[] clientPrivate = new byte[Curve25519.KEY_SIZE];
+ private final byte[] serverPublic = new byte[Curve25519.KEY_SIZE];
+
+ public Curve25519Exchange() {
+ super();
+ }
+
+ /*
+ * Used to test known vectors.
+ */
+ public Curve25519Exchange(byte[] secret) {
+ if (secret.length != Curve25519.KEY_SIZE) {
+ throw new AssertionError("secret must be key size");
+ }
+ System.arraycopy(secret, 0, clientPrivate, 0, secret.length);
+ Curve25519.keygen(clientPublic, null, clientPrivate);
+ }
+
+ @Override
+ public void init(String name) throws IOException {
+ if (!NAME.equals(name)) {
+ throw new IOException("Invalid name " + name);
+ }
+
+ SecureRandom sr = new SecureRandom();
+ sr.nextBytes(clientPrivate);
+ Curve25519.keygen(clientPublic, null, clientPrivate);
+ }
+
+ @Override
+ public byte[] getE() {
+ return clientPublic.clone();
+ }
+
+ @Override
+ protected byte[] getServerE() {
+ return serverPublic.clone();
+ }
+
+ @Override
+ public void setF(byte[] f) throws IOException {
+ if (f.length != serverPublic.length) {
+ throw new IOException("Server sent invalid key length " + f.length + " (expected " +
+ serverPublic.length + ")");
+ }
+ System.arraycopy(f, 0, serverPublic, 0, f.length);
+ byte[] sharedSecretBytes = new byte[Curve25519.KEY_SIZE];
+ Curve25519.curve(sharedSecretBytes, clientPrivate, serverPublic);
+ sharedSecret = new BigInteger(1, sharedSecretBytes);
+ }
+
+ @Override
+ public String getHashAlgo() {
+ return "SHA-256";
+ }
+}
diff --git a/sshlib/src/main/java/com/trilead/ssh2/crypto/dh/GenericDhExchange.java b/sshlib/src/main/java/com/trilead/ssh2/crypto/dh/GenericDhExchange.java
index 039ff75..1ad2554 100644
--- a/sshlib/src/main/java/com/trilead/ssh2/crypto/dh/GenericDhExchange.java
+++ b/sshlib/src/main/java/com/trilead/ssh2/crypto/dh/GenericDhExchange.java
@@ -28,6 +28,9 @@ public abstract class GenericDhExchange
}
public static GenericDhExchange getInstance(String algo) {
+ if (algo.startsWith("curve25519-sha256@libssh.org")) {
+ return new Curve25519Exchange();
+ }
if (algo.startsWith("ecdh-sha2-")) {
return new EcDhExchange();
} else {
diff --git a/sshlib/src/main/java/com/trilead/ssh2/transport/KexManager.java b/sshlib/src/main/java/com/trilead/ssh2/transport/KexManager.java
index ee0784a..741268b 100644
--- a/sshlib/src/main/java/com/trilead/ssh2/transport/KexManager.java
+++ b/sshlib/src/main/java/com/trilead/ssh2/transport/KexManager.java
@@ -21,6 +21,7 @@ import com.trilead.ssh2.crypto.CryptoWishList;
import com.trilead.ssh2.crypto.KeyMaterial;
import com.trilead.ssh2.crypto.cipher.BlockCipher;
import com.trilead.ssh2.crypto.cipher.BlockCipherFactory;
+import com.trilead.ssh2.crypto.dh.Curve25519Exchange;
import com.trilead.ssh2.crypto.dh.DhGroupExchange;
import com.trilead.ssh2.crypto.dh.GenericDhExchange;
import com.trilead.ssh2.crypto.digest.MAC;
@@ -75,6 +76,7 @@ public class KexManager
private static final Set<String> KEX_ALGS = new LinkedHashSet<String>();
static {
+ KEX_ALGS.add(Curve25519Exchange.NAME);
if (supportsEc) {
KEX_ALGS.add("ecdh-sha2-nistp256");
KEX_ALGS.add("ecdh-sha2-nistp384");
@@ -495,7 +497,8 @@ public class KexManager
|| kxs.np.kex_algo.equals("diffie-hellman-group14-sha1")
|| kxs.np.kex_algo.equals("ecdh-sha2-nistp256")
|| kxs.np.kex_algo.equals("ecdh-sha2-nistp384")
- || kxs.np.kex_algo.equals("ecdh-sha2-nistp521")) {
+ || kxs.np.kex_algo.equals("ecdh-sha2-nistp521")
+ || kxs.np.kex_algo.equals(Curve25519Exchange.NAME)) {
kxs.dhx = GenericDhExchange.getInstance(kxs.np.kex_algo);
kxs.dhx.init(kxs.np.kex_algo);
@@ -633,7 +636,8 @@ public class KexManager
|| kxs.np.kex_algo.equals("diffie-hellman-group14-sha1")
|| kxs.np.kex_algo.equals("ecdh-sha2-nistp256")
|| kxs.np.kex_algo.equals("ecdh-sha2-nistp384")
- || kxs.np.kex_algo.equals("ecdh-sha2-nistp521"))
+ || kxs.np.kex_algo.equals("ecdh-sha2-nistp521")
+ || kxs.np.kex_algo.equals(Curve25519Exchange.NAME))
{
if (kxs.state == 1)
{
diff --git a/sshlib/src/test/java/com/trilead/ssh2/crypto/dh/Curve25519ExchangeTest.java b/sshlib/src/test/java/com/trilead/ssh2/crypto/dh/Curve25519ExchangeTest.java
new file mode 100644
index 0000000..2c9d795
--- /dev/null
+++ b/sshlib/src/test/java/com/trilead/ssh2/crypto/dh/Curve25519ExchangeTest.java
@@ -0,0 +1,80 @@
+package com.trilead.ssh2.crypto.dh;
+
+import djb.Curve25519;
+import org.junit.Test;
+
+import javax.xml.bind.DatatypeConverter;
+import java.math.BigInteger;
+import java.security.SecureRandom;
+
+import static org.junit.Assert.*;
+
+/**
+ * Created by Kenny Root on 1/23/16.
+ */
+public class Curve25519ExchangeTest {
+ private static final byte[] ALICE_PRIVATE = toByteArray("77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a");
+ private static final byte[] ALICE_PUBLIC = toByteArray("8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a");
+
+ private static final byte[] BOB_PRIVATE = toByteArray("5dab087e624a8a4b79e17f8b83800ee66f3bb1292618b6fd1c2f8b27ff88e0eb");
+ private static final byte[] BOB_PUBLIC = toByteArray("de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f");
+
+ private static final byte[] KNOWN_SHARED_SECRET = toByteArray("4a5d9d5ba4ce2de1728e3bf480350f25e07e21c947d19e3376f09b3c1e161742");
+ private static final BigInteger KNOWN_SHARED_SECRET_BI = new BigInteger(1, KNOWN_SHARED_SECRET);
+
+ private static byte[] toByteArray(String s) {
+ return DatatypeConverter.parseHexBinary(s);
+ }
+
+ @Test
+ public void selfAgreement() throws Exception {
+ SecureRandom sr = new SecureRandom();
+
+ byte[] alicePrivKey = new byte[Curve25519.KEY_SIZE];
+ sr.nextBytes(alicePrivKey);
+ byte[] alicePubKey = new byte[Curve25519.KEY_SIZE];
+ Curve25519.keygen(alicePubKey, null, alicePrivKey);
+
+ byte[] bobPrivKey = new byte[Curve25519.KEY_SIZE];
+ sr.nextBytes(bobPrivKey);
+ byte[] bobPubKey = new byte[Curve25519.KEY_SIZE];
+ Curve25519.keygen(bobPubKey, null, bobPrivKey);
+
+ Curve25519Exchange alice = new Curve25519Exchange(alicePrivKey);
+ alice.setF(bobPubKey);
+
+ Curve25519Exchange bob = new Curve25519Exchange(bobPrivKey);
+ bob.setF(alicePubKey);
+
+ assertNotNull(alice.sharedSecret);
+ assertEquals(alice.sharedSecret, bob.sharedSecret);
+ }
+
+ @Test
+ public void deriveAlicePublicKey() throws Exception {
+ byte[] pubKey = new byte[Curve25519.KEY_SIZE];
+ Curve25519.keygen(pubKey, null, ALICE_PRIVATE);
+ assertArrayEquals(ALICE_PUBLIC, pubKey);
+ }
+
+ @Test
+ public void deriveBobPublicKey() throws Exception {
+ byte[] pubKey = new byte[Curve25519.KEY_SIZE];
+ Curve25519.keygen(pubKey, null, BOB_PRIVATE);
+ assertArrayEquals(BOB_PUBLIC, pubKey);
+ }
+
+ @Test
+ public void knownValues_Alice() throws Exception {
+ Curve25519Exchange ex = new Curve25519Exchange(ALICE_PRIVATE);
+ ex.setF(BOB_PUBLIC);
+ assertEquals(KNOWN_SHARED_SECRET_BI, ex.sharedSecret);
+ }
+
+ @Test
+ public void knownValues_Bob() throws Exception {
+ Curve25519Exchange ex = new Curve25519Exchange(BOB_PRIVATE);
+ ex.setF(ALICE_PUBLIC);
+ assertEquals(KNOWN_SHARED_SECRET_BI, ex.sharedSecret);
+ }
+} \ No newline at end of file