changeset 309:cb179051f0f2 ganymed

add ecdsa key support everywhere
author Carl Byington <carl@five-ten-sg.com>
date Wed, 30 Jul 2014 14:29:39 -0700
parents 42b15aaa7ac7
children 9e42082d1f93
files src/ch/ethz/ssh2/crypto/dh/DhExchange.java src/ch/ethz/ssh2/crypto/dh/DhGroupExchange.java src/ch/ethz/ssh2/crypto/dh/EcDhExchange.java src/ch/ethz/ssh2/crypto/dh/GenericDhExchange.java
diffstat 4 files changed, 313 insertions(+), 173 deletions(-) [+]
line wrap: on
line diff
--- a/src/ch/ethz/ssh2/crypto/dh/DhExchange.java	Wed Jul 30 14:21:50 2014 -0700
+++ b/src/ch/ethz/ssh2/crypto/dh/DhExchange.java	Wed Jul 30 14:29:39 2014 -0700
@@ -1,193 +1,140 @@
-/*
- * Copyright (c) 2006-2013 Christian Plattner. All rights reserved.
- * Please refer to the LICENSE.txt for licensing details.
+/**
+ *
  */
 package ch.ethz.ssh2.crypto.dh;
 
 import java.io.IOException;
 import java.math.BigInteger;
-import java.security.SecureRandom;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.NoSuchAlgorithmException;
+import java.security.spec.InvalidKeySpecException;
 
-import ch.ethz.ssh2.crypto.digest.HashForSSH2Types;
-import ch.ethz.ssh2.log.Logger;
-import ch.ethz.ssh2.util.StringEncoder;
+import javax.crypto.KeyAgreement;
+import javax.crypto.interfaces.DHPrivateKey;
+import javax.crypto.interfaces.DHPublicKey;
+import javax.crypto.spec.DHParameterSpec;
+import javax.crypto.spec.DHPublicKeySpec;
 
 /**
- * @author Christian Plattner
- * @version $Id: DhExchange.java 152 2014-04-28 11:02:23Z dkocher@sudo.ch $
+ * @author kenny
+ *
  */
-public class DhExchange {
-    private static final Logger log = Logger.getLogger(DhExchange.class);
+public class DhExchange extends GenericDhExchange {
 
     /* Given by the standard */
 
-    static final BigInteger p1, p14;
-    static final BigInteger g;
+    private static final BigInteger P1 = new BigInteger(
+        "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1"
+        + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD"
+        + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245"
+        + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED"
+        + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381"
+        + "FFFFFFFFFFFFFFFF", 16);
 
-    BigInteger p;
+    private static final BigInteger P14 = new BigInteger(
+        "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1"
+        + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD"
+        + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245"
+        + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED"
+        + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D"
+        + "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F"
+        + "83655D23DCA3AD961C62F356208552BB9ED529077096966D"
+        + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B"
+        + "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9"
+        + "DE2BCBF6955817183995497CEA956AE515D2261898FA0510"
+        + "15728E5A8AACAA68FFFFFFFFFFFFFFFF", 16);
+
+    private static final BigInteger G = BigInteger.valueOf(2);
 
     /* Client public and private */
 
-    BigInteger e;
-    BigInteger x;
-
-    /* Server public and private */
+    private DHPrivateKey clientPrivate;
+    private DHPublicKey clientPublic;
 
-    BigInteger f;
-    BigInteger y;
+    /* Server public */
 
-    /* Shared secret */
-
-    BigInteger k;
+    private DHPublicKey serverPublic;
 
-    static {
-        final String p1_string = "17976931348623159077083915679378745319786029604875"
-                                 + "60117064444236841971802161585193689478337958649255415021805654859805036464"
-                                 + "40548199239100050792877003355816639229553136239076508735759914822574862575"
-                                 + "00742530207744771258955095793777842444242661733472762929938766870920560605"
-                                 + "0270810842907692932019128194467627007";
-        final String p14_string = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129"
-                                  + "024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0"
-                                  + "A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB"
-                                  + "6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A"
-                                  + "163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208"
-                                  + "552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36C"
-                                  + "E3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF69558171"
-                                  + "83995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF";
-        p1 = new BigInteger(p1_string);
-        p14 = new BigInteger(p14_string, 16);
-        g = new BigInteger("2");
-    }
+    @Override
+    public void init(String name) throws IOException {
+        final DHParameterSpec spec;
 
-    public DhExchange() {
-    }
-
-    public void clientInit(int group, SecureRandom rnd) {
-        k = null;
-
-        if (group == 1) {
-            p = p1;
+        if ("diffie-hellman-group1-sha1".equals(name)) {
+            spec = new DHParameterSpec(P1, G);
         }
-        else if (group == 14) {
-            p = p14;
+        else if ("diffie-hellman-group14-sha1".equals(name)) {
+            spec = new DHParameterSpec(P14, G);
         }
         else {
-            throw new IllegalArgumentException("Unknown DH group " + group);
-        }
-
-        while (true) {
-            x = new BigInteger(p.bitLength() - 1, rnd);
-
-            if (x.compareTo(BigInteger.ONE) > 0) {
-                break;
-            }
+            throw new IllegalArgumentException("Unknown DH group " + name);
         }
 
-        e = g.modPow(x, p);
-    }
-
-    public void serverInit(int group, SecureRandom rnd) {
-        k = null;
-
-        if (group == 1) {
-            p = p1;
+        try {
+            KeyPairGenerator kpg = KeyPairGenerator.getInstance("DH");
+            kpg.initialize(spec);
+            KeyPair pair = kpg.generateKeyPair();
+            clientPrivate = (DHPrivateKey) pair.getPrivate();
+            clientPublic = (DHPublicKey) pair.getPublic();
         }
-        else if (group == 14) {
-            p = p14;
+        catch (NoSuchAlgorithmException e) {
+            throw(IOException) new IOException("No DH keypair generator").initCause(e);
         }
-        else {
-            throw new IllegalArgumentException("Unknown DH group " + group);
+        catch (InvalidAlgorithmParameterException e) {
+            throw(IOException) new IOException("Invalid DH parameters").initCause(e);
         }
-
-        y = new BigInteger(p.bitLength() - 1, rnd);
-        f = g.modPow(y, p);
     }
 
-    /**
-     * @return Returns the e.
-     * @throws IllegalStateException
-     */
-    public BigInteger getE() {
-        if (e == null) {
-            throw new IllegalStateException("DhDsaExchange not initialized!");
-        }
+    @Override
+    public byte[] getE() {
+        if (clientPublic == null)
+            throw new IllegalStateException("DhExchange not initialized!");
 
-        return e;
+        return clientPublic.getY().toByteArray();
+    }
+
+    @Override
+    protected byte[] getServerE() {
+        if (serverPublic == null)
+            throw new IllegalStateException("DhExchange not initialized!");
+
+        return serverPublic.getY().toByteArray();
     }
 
-    /**
-     * @return Returns the f.
-     * @throws IllegalStateException
-     */
-    public BigInteger getF() {
-        if (f == null) {
-            throw new IllegalStateException("DhDsaExchange not initialized!");
-        }
+    @Override
+    public void setF(byte[] f) throws IOException {
+        if (clientPublic == null)
+            throw new IllegalStateException("DhExchange not initialized!");
 
-        return f;
-    }
+        final KeyAgreement ka;
 
-    /**
-     * @return Returns the shared secret k.
-     * @throws IllegalStateException
-     */
-    public BigInteger getK() {
-        if (k == null) {
-            throw new IllegalStateException("Shared secret not yet known, need f first!");
+        try {
+            KeyFactory kf = KeyFactory.getInstance("DH");
+            DHParameterSpec params = clientPublic.getParams();
+            this.serverPublic = (DHPublicKey) kf.generatePublic(new DHPublicKeySpec(
+                                    new BigInteger(f), params.getP(), params.getG()));
+            ka = KeyAgreement.getInstance("DH");
+            ka.init(clientPrivate);
+            ka.doPhase(serverPublic, true);
         }
-
-        return k;
-    }
-
-    /**
-     * @param f
-     */
-    public void setF(BigInteger f) {
-        if (e == null) {
-            throw new IllegalStateException("DhDsaExchange not initialized!");
+        catch (NoSuchAlgorithmException e) {
+            throw(IOException) new IOException("No DH key agreement method").initCause(e);
+        }
+        catch (InvalidKeyException e) {
+            throw(IOException) new IOException("Invalid DH key").initCause(e);
+        }
+        catch (InvalidKeySpecException e) {
+            throw(IOException) new IOException("Invalid DH key").initCause(e);
         }
 
-        if (BigInteger.ZERO.compareTo(f) >= 0 || p.compareTo(f) <= 0) {
-            throw new IllegalArgumentException("Invalid f specified!");
-        }
-
-        this.f = f;
-        this.k = f.modPow(x, p);
+        sharedSecret = new BigInteger(ka.generateSecret());
     }
 
-    /**
-     * @param e
-     */
-    public void setE(BigInteger e) {
-        if (f == null) {
-            throw new IllegalStateException("DhDsaExchange not initialized!");
-        }
-
-        if (BigInteger.ZERO.compareTo(e) >= 0 || p.compareTo(e) <= 0) {
-            throw new IllegalArgumentException("Invalid e specified!");
-        }
-
-        this.e = e;
-        this.k = e.modPow(y, p);
-    }
-
-    public byte[] calculateH(byte[] clientversion, byte[] serverversion, byte[] clientKexPayload,
-                             byte[] serverKexPayload, byte[] hostKey) throws IOException {
-        HashForSSH2Types hash = new HashForSSH2Types("SHA1");
-
-        if (log.isInfoEnabled()) {
-            log.info("Client: '" + StringEncoder.GetString(clientversion) + "'");
-            log.info("Server: '" + StringEncoder.GetString(serverversion) + "'");
-        }
-
-        hash.updateByteString(clientversion);
-        hash.updateByteString(serverversion);
-        hash.updateByteString(clientKexPayload);
-        hash.updateByteString(serverKexPayload);
-        hash.updateByteString(hostKey);
-        hash.updateBigInt(e);
-        hash.updateBigInt(f);
-        hash.updateBigInt(k);
-        return hash.getDigest();
+    @Override
+    public String getHashAlgo() {
+        return "SHA1";
     }
 }
--- a/src/ch/ethz/ssh2/crypto/dh/DhGroupExchange.java	Wed Jul 30 14:21:50 2014 -0700
+++ b/src/ch/ethz/ssh2/crypto/dh/DhGroupExchange.java	Wed Jul 30 14:29:39 2014 -0700
@@ -1,21 +1,18 @@
-/*
- * Copyright (c) 2006-2011 Christian Plattner. All rights reserved.
- * Please refer to the LICENSE.txt for licensing details.
- */
+
 package ch.ethz.ssh2.crypto.dh;
 
-import java.io.IOException;
 import java.math.BigInteger;
 import java.security.SecureRandom;
 
 import ch.ethz.ssh2.DHGexParameters;
 import ch.ethz.ssh2.crypto.digest.HashForSSH2Types;
 
+
 /**
  * DhGroupExchange.
  *
- * @author Christian Plattner
- * @version 2.50, 03/15/10
+ * @author Christian Plattner, plattner@trilead.com
+ * @version $Id: DhGroupExchange.java,v 1.1 2007/10/15 12:49:57 cplattne Exp $
  */
 public class DhGroupExchange {
     /* Given by the standard */
@@ -51,9 +48,8 @@
      * @return Returns the e.
      */
     public BigInteger getE() {
-        if (e == null) {
+        if (e == null)
             throw new IllegalStateException("Not initialized!");
-        }
 
         return e;
     }
@@ -62,9 +58,8 @@
      * @return Returns the shared secret k.
      */
     public BigInteger getK() {
-        if (k == null) {
+        if (k == null)
             throw new IllegalStateException("Shared secret not yet known, need f first!");
-        }
 
         return k;
     }
@@ -73,38 +68,34 @@
      * Sets f and calculates the shared secret.
      */
     public void setF(BigInteger f) {
-        if (e == null) {
+        if (e == null)
             throw new IllegalStateException("Not initialized!");
-        }
 
         BigInteger zero = BigInteger.valueOf(0);
 
-        if (zero.compareTo(f) >= 0 || p.compareTo(f) <= 0) {
+        if (zero.compareTo(f) >= 0 || p.compareTo(f) <= 0)
             throw new IllegalArgumentException("Invalid f specified!");
-        }
 
         this.f = f;
         this.k = f.modPow(x, p);
     }
 
-    public byte[] calculateH(byte[] clientversion, byte[] serverversion, byte[] clientKexPayload,
-                             byte[] serverKexPayload, byte[] hostKey, DHGexParameters para) throws IOException {
-        HashForSSH2Types hash = new HashForSSH2Types("SHA1");
+    public byte[] calculateH(String hashAlgo, byte[] clientversion, byte[] serverversion,
+                             byte[] clientKexPayload, byte[] serverKexPayload, byte[] hostKey, DHGexParameters para) {
+        HashForSSH2Types hash = new HashForSSH2Types(hashAlgo);
         hash.updateByteString(clientversion);
         hash.updateByteString(serverversion);
         hash.updateByteString(clientKexPayload);
         hash.updateByteString(serverKexPayload);
         hash.updateByteString(hostKey);
 
-        if (para.getMin_group_len() > 0) {
+        if (para.getMin_group_len() > 0)
             hash.updateUINT32(para.getMin_group_len());
-        }
 
         hash.updateUINT32(para.getPref_group_len());
 
-        if (para.getMax_group_len() > 0) {
+        if (para.getMax_group_len() > 0)
             hash.updateUINT32(para.getMax_group_len());
-        }
 
         hash.updateBigInt(p);
         hash.updateBigInt(g);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ch/ethz/ssh2/crypto/dh/EcDhExchange.java	Wed Jul 30 14:29:39 2014 -0700
@@ -0,0 +1,114 @@
+/**
+ *
+ */
+package ch.ethz.ssh2.crypto.dh;
+
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.NoSuchAlgorithmException;
+import java.security.interfaces.ECPrivateKey;
+import java.security.interfaces.ECPublicKey;
+import java.security.spec.ECParameterSpec;
+import java.security.spec.ECPoint;
+import java.security.spec.ECPublicKeySpec;
+import java.security.spec.InvalidKeySpecException;
+
+import javax.crypto.KeyAgreement;
+
+import ch.ethz.ssh2.signature.ECDSASHA2Verify;
+
+/**
+ * @author kenny
+ *
+ */
+public class EcDhExchange extends GenericDhExchange {
+    private ECPrivateKey clientPrivate;
+    private ECPublicKey clientPublic;
+    private ECPublicKey serverPublic;
+
+    @Override
+    public void init(String name) throws IOException {
+        final ECParameterSpec spec;
+
+        if ("ecdh-sha2-nistp256".equals(name)) {
+            spec = ECDSASHA2Verify.EllipticCurves.nistp256;
+        }
+        else if ("ecdh-sha2-nistp384".equals(name)) {
+            spec = ECDSASHA2Verify.EllipticCurves.nistp384;
+        }
+        else if ("ecdh-sha2-nistp521".equals(name)) {
+            spec = ECDSASHA2Verify.EllipticCurves.nistp521;
+        }
+        else {
+            throw new IllegalArgumentException("Unknown EC curve " + name);
+        }
+
+        KeyPairGenerator kpg;
+
+        try {
+            kpg = KeyPairGenerator.getInstance("EC");
+            kpg.initialize(spec);
+            KeyPair pair = kpg.generateKeyPair();
+            clientPrivate = (ECPrivateKey) pair.getPrivate();
+            clientPublic = (ECPublicKey) pair.getPublic();
+        }
+        catch (NoSuchAlgorithmException e) {
+            throw(IOException) new IOException("No DH keypair generator").initCause(e);
+        }
+        catch (InvalidAlgorithmParameterException e) {
+            throw(IOException) new IOException("Invalid DH parameters").initCause(e);
+        }
+    }
+
+    @Override
+    public byte[] getE() {
+        return ECDSASHA2Verify.encodeECPoint(clientPublic.getW(), clientPublic.getParams()
+                                             .getCurve());
+    }
+
+    @Override
+    protected byte[] getServerE() {
+        return ECDSASHA2Verify.encodeECPoint(serverPublic.getW(), serverPublic.getParams()
+                                             .getCurve());
+    }
+
+    @Override
+    public void setF(byte[] f) throws IOException {
+        if (clientPublic == null)
+            throw new IllegalStateException("DhDsaExchange not initialized!");
+
+        final KeyAgreement ka;
+
+        try {
+            KeyFactory kf = KeyFactory.getInstance("EC");
+            ECParameterSpec params = clientPublic.getParams();
+            ECPoint serverPoint = ECDSASHA2Verify.decodeECPoint(f, params.getCurve());
+            this.serverPublic = (ECPublicKey) kf.generatePublic(new ECPublicKeySpec(serverPoint,
+                                params));
+            ka = KeyAgreement.getInstance("ECDH");
+            ka.init(clientPrivate);
+            ka.doPhase(serverPublic, true);
+        }
+        catch (NoSuchAlgorithmException e) {
+            throw(IOException) new IOException("No ECDH key agreement method").initCause(e);
+        }
+        catch (InvalidKeyException e) {
+            throw(IOException) new IOException("Invalid ECDH key").initCause(e);
+        }
+        catch (InvalidKeySpecException e) {
+            throw(IOException) new IOException("Invalid ECDH key").initCause(e);
+        }
+
+        sharedSecret = new BigInteger(ka.generateSecret());
+    }
+
+    @Override
+    public String getHashAlgo() {
+        return ECDSASHA2Verify.getDigestAlgorithmForParams(clientPublic.getParams());
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ch/ethz/ssh2/crypto/dh/GenericDhExchange.java	Wed Jul 30 14:29:39 2014 -0700
@@ -0,0 +1,88 @@
+
+package ch.ethz.ssh2.crypto.dh;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.math.BigInteger;
+
+import ch.ethz.ssh2.crypto.digest.HashForSSH2Types;
+import ch.ethz.ssh2.log.Logger;
+
+
+/**
+ * DhExchange.
+ *
+ * @author Christian Plattner, plattner@trilead.com
+ * @version $Id: DhExchange.java,v 1.2 2008/04/01 12:38:09 cplattne Exp $
+ */
+public abstract class GenericDhExchange {
+    private static final Logger log = Logger.getLogger(GenericDhExchange.class);
+
+    /* Shared secret */
+
+    BigInteger sharedSecret;
+
+    protected GenericDhExchange() {
+    }
+
+    public static GenericDhExchange getInstance(String algo) {
+        if (algo.startsWith("ecdh-sha2-")) {
+            return new EcDhExchange();
+        }
+        else {
+            return new DhExchange();
+        }
+    }
+
+    public abstract void init(String name) throws IOException;
+
+    /**
+     * @return Returns the e (public value)
+     * @throws IllegalStateException
+     */
+    public abstract byte[] getE();
+
+    /**
+     * @return Returns the server's e (public value)
+     * @throws IllegalStateException
+     */
+    protected abstract byte[] getServerE();
+
+    /**
+     * @return Returns the shared secret k.
+     * @throws IllegalStateException
+     */
+    public BigInteger getK() {
+        if (sharedSecret == null)
+            throw new IllegalStateException("Shared secret not yet known, need f first!");
+
+        return sharedSecret;
+    }
+
+    /**
+     * @param f
+     */
+    public abstract void setF(byte[] f) throws IOException;
+
+    public byte[] calculateH(byte[] clientversion, byte[] serverversion, byte[] clientKexPayload,
+                             byte[] serverKexPayload, byte[] hostKey) throws UnsupportedEncodingException {
+        HashForSSH2Types hash = new HashForSSH2Types(getHashAlgo());
+
+        if (log.isEnabled()) {
+            log.log(90, "Client: '" + new String(clientversion, "ISO-8859-1") + "'");
+            log.log(90, "Server: '" + new String(serverversion, "ISO-8859-1") + "'");
+        }
+
+        hash.updateByteString(clientversion);
+        hash.updateByteString(serverversion);
+        hash.updateByteString(clientKexPayload);
+        hash.updateByteString(serverKexPayload);
+        hash.updateByteString(hostKey);
+        hash.updateByteString(getE());
+        hash.updateByteString(getServerE());
+        hash.updateBigInt(sharedSecret);
+        return hash.getDigest();
+    }
+
+    public abstract String getHashAlgo();
+}