changeset 298:ab3a99f11a36 ganymed

add ecdsa key support everywhere
author Carl Byington <carl@five-ten-sg.com>
date Tue, 29 Jul 2014 18:01:08 -0700
parents c1f929cb3dd0
children 4c3a4e88c027
files src/ch/ethz/ssh2/AuthAgentCallback.java src/ch/ethz/ssh2/KnownHosts.java src/ch/ethz/ssh2/ServerConnection.java src/ch/ethz/ssh2/channel/AuthAgentForwardThread.java src/ch/ethz/ssh2/transport/ClientKexManager.java src/ch/ethz/ssh2/transport/ServerKexManager.java src/com/five_ten_sg/connectbot/PubkeyListActivity.java
diffstat 7 files changed, 89 insertions(+), 32 deletions(-) [+]
line wrap: on
line diff
--- a/src/ch/ethz/ssh2/AuthAgentCallback.java	Tue Jul 29 16:43:12 2014 -0700
+++ b/src/ch/ethz/ssh2/AuthAgentCallback.java	Tue Jul 29 18:01:08 2014 -0700
@@ -17,9 +17,9 @@
     Map<String, byte[]> retrieveIdentities();
 
     /**
-     * @param key A <code>RSAPrivateKey</code> or <code>DSAPrivateKey</code>
-     *            containing a DSA or RSA private key of
-     *            the user in Trilead object format.
+     * @param pair A <code>RSAPrivateKey</code> or <code>DSAPrivateKey</code> or <code>ECPrivateKey</code>
+     *            containing a DSA or RSA or EC private key of
+     *            the user in standard java key format.
      * @param comment comment associated with this key
      * @param confirmUse whether to prompt before using this key
      * @param lifetime lifetime in seconds for key to be remembered
@@ -41,8 +41,8 @@
     /**
      * @param publicKey byte blob containing the OpenSSH-format encoded public key
      * @return A <code>RSAPrivateKey</code> or <code>DSAPrivateKey</code>
-     *         containing a DSA or RSA private key of
-     *         the user in Trilead object format.
+     *         containing a DSA or RSA or EC private key of
+     *         the user in standard java key format.
      */
     KeyPair getKeyPair(byte[] publicKey);
 
--- a/src/ch/ethz/ssh2/KnownHosts.java	Tue Jul 29 16:43:12 2014 -0700
+++ b/src/ch/ethz/ssh2/KnownHosts.java	Tue Jul 29 18:01:08 2014 -0700
@@ -114,6 +114,13 @@
                 publicKeys.add(new KnownHostsEntry(hostnames, dpk));
             }
         }
+        else if (serverHostKeyAlgorithm.startsWith("ecdsa-")) {
+            ECPublicKey epk = ECDSASHA2Verify.decodeSSHECDSAPublicKey(serverHostKey);
+
+            synchronized(publicKeys) {
+                publicKeys.add(new KnownHostsEntry(hostnames, epk));
+            }
+        }
         else {
             throw new IOException(String.format("Unknown host key type %s", serverHostKeyAlgorithm));
         }
@@ -397,7 +404,9 @@
             String[] arr = line.split(" ");
 
             if(arr.length >= 3) {
-                if((arr[1].compareTo("ssh-rsa") == 0) || (arr[1].compareTo("ssh-dss") == 0)) {
+                if((arr[1].compareTo("ssh-rsa") == 0) ||
+                   (arr[1].compareTo("ssh-dss") == 0) ||
+                   (arr[1].startsWith("ecdsa-sha2-") == 0)) {
                     String[] hostnames = arr[0].split(",");
 
                     byte[] msg = Base64.decode(arr[2].toCharArray());
@@ -506,6 +515,11 @@
             else if(key instanceof DSAPublicKey) {
                 thisAlgo = "ssh-dss";
             }
+            else if (key instanceof ECPublicKey) {
+                ECPublicKey ecPub = (ECPublicKey) pk;
+                String keyType = ECDSASHA2Verify.getCurveName(ecPub.getParams().getCurve().getField().getFieldSize());
+                thisAlgo = ECDSASHA2Verify.ECDSA_SHA2_PREFIX + keyType;
+            }
             else {
                 continue;
             }
@@ -541,10 +555,10 @@
 				   */
 
         if(preferredAlgo.equals("ssh-rsa")) {
-            return new String[]{"ssh-rsa", "ssh-dss"};
+            return new String[]{"ssh-rsa", "ssh-dss", "ecdsa-sha2-nistp256"};
         }
 
-        return new String[]{"ssh-dss", "ssh-rsa"};
+        return new String[]{"ssh-dss", "ssh-rsa", "ecdsa-sha2-nistp256"};
     }
 
     /**
@@ -667,7 +681,7 @@
      * Generates a "raw" fingerprint of a hostkey.
      *
      * @param type    either "md5" or "sha1"
-     * @param keyType either "ssh-rsa" or "ssh-dss"
+     * @param keyType either "ssh-rsa" or "ssh-dss" or "ecdsa-sha2..."
      * @param hostkey the hostkey
      * @return the raw fingerprint
      */
@@ -688,6 +702,8 @@
         }
         else if("ssh-dss".equals(keyType)) {
         }
+        else if (keyType.startsWith("ecdsa-sha2-") {
+        }
         else {
             throw new IllegalArgumentException("Unknown key type " + keyType);
         }
@@ -780,7 +796,7 @@
      * <p/>
      * Example fingerprint: d0:cb:76:19:99:5a:03:fc:73:10:70:93:f2:44:63:47.
      *
-     * @param keytype   either "ssh-rsa" or "ssh-dss"
+     * @param keytype   either "ssh-rsa" or "ssh-dss" or "ecdsa-sha2..."
      * @param publickey key blob
      * @return Hex fingerprint
      */
--- a/src/ch/ethz/ssh2/ServerConnection.java	Tue Jul 29 16:43:12 2014 -0700
+++ b/src/ch/ethz/ssh2/ServerConnection.java	Tue Jul 29 18:01:08 2014 -0700
@@ -71,14 +71,16 @@
 	 * @param s The socket
 	 * @param dsa_key The DSA hostkey, may be <code>NULL</code>
 	 * @param rsa_key The RSA hostkey, may be <code>NULL</code>
+	 * @param ec_key  The EC  hostkey, may be <code>NULL</code>
 	 */
-	public ServerConnection(Socket s, KeyPair dsa_key, KeyPair rsa_key)
+	public ServerConnection(Socket s, KeyPair dsa_key, KeyPair rsa_key, KeyPair ec_key)
 	{
 		state.s = s;
 		state.softwareversion = softwareversion;
 		state.next_dsa_key = dsa_key;
 		state.next_rsa_key = rsa_key;
-		fixCryptoWishList(state.next_cryptoWishList, state.next_dsa_key, state.next_rsa_key);
+		state.next_ec_key  = ec_key;
+		fixCryptoWishList(state.next_cryptoWishList, state.next_dsa_key, state.next_rsa_key, state.next_ec_key);
 	}
 
 	/**
@@ -100,7 +102,7 @@
 	/**
 	 * Establish the connection and block until the first handshake has completed.
 	 * <p>
-	 * Note 1: either a DSA or a RSA (or both) hostkey must be set before calling this method.
+	 * Note 1: at least one DSA, RSA or EC hostkey must be set before calling this method.
 	 * <p>
 	 * Note 2: You must set the callbacks for authentication ({@link #setAuthenticationCallback(ServerAuthenticationCallback)})
 	 * and connection events ({@link #setServerConnectionCallback(ServerConnectionCallback)}).
@@ -126,8 +128,8 @@
 			if (state.tm != null)
 				throw new IllegalStateException("The initial handshake has already been started.");
 
-			if ((state.next_dsa_key == null) && (state.next_rsa_key == null))
-				throw new IllegalStateException("Neither a RSA nor a DSA host key has been specified!");
+			if ((state.next_dsa_key == null) && (state.next_rsa_key == null) && (state.next_ec_key == null))
+				throw new IllegalStateException("Neither an RSA nor a DSA nor an EC host key has been specified!");
 
 			state.tm = new ServerTransportManager(state.s);
 		}
@@ -171,7 +173,7 @@
 				throw new IllegalStateException(
 						"Cannot force another key exchange, you need to start the key exchange first.");
 
-			state.tm.forceKeyExchange(state.next_cryptoWishList, null, state.next_dsa_key, state.next_rsa_key);
+			state.tm.forceKeyExchange(state.next_cryptoWishList, null, state.next_dsa_key, state.next_rsa_key, state.next_ec_key);
 		}
 	}
 
@@ -199,7 +201,7 @@
 	}
 
 	/**
-	 * Change the current DSA hostkey. Either a DSA or RSA private key must be set for a successful handshake with
+	 * Change the current DSA hostkey. Either a DSA or RSA or EC private key must be set for a successful handshake with
 	 * the client.
 	 * <p>
 	 * Note: You can change an existing DSA hostkey after the initial kex exchange (the new value will
@@ -216,12 +218,12 @@
 				throw new IllegalStateException("Cannot remove DSA hostkey after first key exchange.");
 
 			state.next_dsa_key = dsa_hostkey;
-			fixCryptoWishList(state.next_cryptoWishList, state.next_dsa_key, state.next_rsa_key);
+			fixCryptoWishList(state.next_cryptoWishList, state.next_dsa_key, state.next_rsa_key, state.next_ec_key);
 		}
 	}
 
 	/**
-	 * Change the current RSA hostkey. Either a DSA or RSA private key must be set for a successful handshake with
+	 * Change the current RSA hostkey. Either a DSA or RSA or EC private key must be set for a successful handshake with
 	 * the client.
 	 * <p>
 	 * Note: You can change an existing RSA hostkey after the initial kex exchange (the new value will
@@ -238,7 +240,29 @@
 				throw new IllegalStateException("Cannot remove RSA hostkey after first key exchange.");
 
 			state.next_rsa_key = rsa_hostkey;
-			fixCryptoWishList(state.next_cryptoWishList, state.next_dsa_key, state.next_rsa_key);
+			fixCryptoWishList(state.next_cryptoWishList, state.next_dsa_key, state.next_rsa_key, state.next_ec_key);
+		}
+	}
+
+	/**
+	 * Change the current EC hostkey. Either a DSA or RSA or EC private key must be set for a successful handshake with
+	 * the client.
+	 * <p>
+	 * Note: You can change an existing EC hostkey after the initial kex exchange (the new value will
+	 * be used during the next server initiated key exchange), but you cannot remove (i.e., set to <code>null</code>) the
+	 * current EC key, otherwise the next key exchange may fail in case the client supports only EC hostkeys.
+	 *
+	 * @param rsa_hostkey
+	 */
+	public synchronized void setEcHostKey(KeyPair ec_hostkey)
+	{
+		synchronized (state)
+		{
+			if ((ec_hostkey == null) && (state.next_ec_key != null) && (state.tm != null))
+				throw new IllegalStateException("Cannot remove EC hostkey after first key exchange.");
+
+			state.next_ec_key = ec_hostkey;
+			fixCryptoWishList(state.next_cryptoWishList, state.next_dsa_key, state.next_rsa_key, state.next_ec_key);
 		}
 	}
 
@@ -258,6 +282,8 @@
 		if (key instanceof DSAPrivateKey) setDsaHostKey(pair);
 
 		if (key instanceof RSAPrivateKey) setRsaHostKey(pair);
+
+        if (key instanceof ECPrivateKey) setEcHostKey(pair);
 	}
 
 	/**
@@ -292,16 +318,14 @@
 		setPEMHostKey(cw.toCharArray(), password);
 	}
 
-	private void fixCryptoWishList(CryptoWishList next_cryptoWishList, KeyPair next_dsa_key, KeyPair next_rsa_key)
+	private void fixCryptoWishList(CryptoWishList next_cryptoWishList, KeyPair next_dsa_key, KeyPair next_rsa_key, KeyPair next_ec_key)
 	{
-		if ((next_dsa_key != null) && (next_rsa_key != null))
-			next_cryptoWishList.serverHostKeyAlgorithms = new String[] { "ssh-rsa", "ssh-dss" };
-		else if (next_dsa_key != null)
-			next_cryptoWishList.serverHostKeyAlgorithms = new String[] { "ssh-dss" };
-		else if (next_rsa_key != null)
-			next_cryptoWishList.serverHostKeyAlgorithms = new String[] { "ssh-rsa" };
-		else
-			next_cryptoWishList.serverHostKeyAlgorithms = new String[0];
+        List<String> algos = new ArrayList<string>();
+		if (next_dsa_key != null) algos.add("ssh-dss");
+		if (next_rsa_key != null) algos.add("ssh-rsa");
+		if (next_ec_key != null)  algos.add("ssh-ec");
+ 	    next_cryptoWishList.serverHostKeyAlgorithms = new String[algos.size()];
+        algos.toArray(next_cryptoWishList.serverHostKeyAlgorithms);
 	}
 
 	/**
--- a/src/ch/ethz/ssh2/channel/AuthAgentForwardThread.java	Tue Jul 29 16:43:12 2014 -0700
+++ b/src/ch/ethz/ssh2/channel/AuthAgentForwardThread.java	Tue Jul 29 18:01:08 2014 -0700
@@ -462,6 +462,11 @@
                                    (DSAPrivateKey) privKey, new SecureRandom());
                 response = DSASHA1Verify.encodeSSHDSASignature(signature);
             }
+            else if (privKey instanceof ECPrivateKey) {
+                byte[] signature = ECDSASHA2Verify.generateSignature(challenge,
+                                   (ECPrivateKey) privKey);
+                response = ECDSASHA2Verify.encodeSSHECDSASignature(signature);
+            }
             else {
                 os.write(SSH_AGENT_FAILURE);
                 return;
--- a/src/ch/ethz/ssh2/transport/ClientKexManager.java	Tue Jul 29 16:43:12 2014 -0700
+++ b/src/ch/ethz/ssh2/transport/ClientKexManager.java	Tue Jul 29 18:01:08 2014 -0700
@@ -61,6 +61,7 @@
             log.debug("Verifying ecdsa signature");
             return ECDSASHA2Verify.verifySignature(kxs.H, rs, epk);
         }
+
         if (kxs.np.server_host_key_algo.equals("ssh-rsa")) {
             byte[] rs = RSASHA1Verify.decodeSSHRSASignature(sig);
             RSAPublicKey rpk = RSASHA1Verify.decodeSSHRSAPublicKey(hostkey);
--- a/src/ch/ethz/ssh2/transport/ServerKexManager.java	Tue Jul 29 16:43:12 2014 -0700
+++ b/src/ch/ethz/ssh2/transport/ServerKexManager.java	Tue Jul 29 18:01:08 2014 -0700
@@ -79,6 +79,7 @@
                 kxs = new KexState();
                 kxs.local_dsa_key = nextKEXdsakey;
                 kxs.local_rsa_key = nextKEXrsakey;
+                kxs.local_ec_key  = nextKEXeckey;
                 kxs.dhgexParameters = nextKEXdhgexParameters;
                 kip = new PacketKexInit(nextKEXcryptoWishList, rnd);
                 kxs.localKEX = kip;
@@ -174,6 +175,10 @@
 
                 byte[] hostKey = null;
 
+                if (kxs.np.server_host_key_algo.startsWith("ecdsa-sha2-")) {
+                    hostKey = ECDSASHA2Verify.encodeSSHECDSAPublicKey((ECDSAPublicKey)kxs.local_ec_key.getPublic());
+                }
+
                 if(kxs.np.server_host_key_algo.equals("ssh-rsa")) {
                     hostKey = RSASHA1Verify.encodeSSHRSAPublicKey((RSAPublicKey)kxs.local_rsa_key.getPublic());
                 }
@@ -194,12 +199,17 @@
 
                 byte[] signature = null;
 
-                if(kxs.np.server_host_key_algo.equals("ssh-rsa")) {
+                if (kxs.np.server_host_key_algo.startsWith("ecdsa-sha2-")) {
+                    byte[] es = ECDSASHA2Verify.generateSignature(kxs.H, (ECDSAPrivateKey)kxs.local_ec_key.getPrivate());
+                    signature = ECDSASHA2Verify.encodeSSHECDSASignature(es);
+                }
+
+                if (kxs.np.server_host_key_algo.equals("ssh-rsa")) {
                     byte[] rs = RSASHA1Verify.generateSignature(kxs.H, (RSAPrivateKey)kxs.local_rsa_key.getPrivate());
                     signature = RSASHA1Verify.encodeSSHRSASignature(rs);
                 }
 
-                if(kxs.np.server_host_key_algo.equals("ssh-dss")) {
+                if (kxs.np.server_host_key_algo.equals("ssh-dss")) {
                     byte[] ds = DSASHA1Verify.generateSignature(kxs.H, (DSAPrivateKey)kxs.local_dsa_key.getPrivate(), rnd);
                     signature = DSASHA1Verify.encodeSSHDSASignature(ds);
                 }
--- a/src/com/five_ten_sg/connectbot/PubkeyListActivity.java	Tue Jul 29 16:43:12 2014 -0700
+++ b/src/com/five_ten_sg/connectbot/PubkeyListActivity.java	Tue Jul 29 18:01:08 2014 -0700
@@ -638,7 +638,8 @@
             if (imported) {
                 try {
                     PEMStructure struct = PEMDecoder.parsePEM(new String(pubkey.getPrivateKey()).toCharArray());
-                    String type = (struct.pemType == PEMDecoder.PEM_RSA_PRIVATE_KEY) ? "RSA" : "DSA";
+                    String type = (struct.pemType == PEMDecoder.PEM_RSA_PRIVATE_KEY) ? "RSA" :
+                                  (struct.pemType == PEMDecoder.PEM_DSA_PRIVATE_KEY) ? "DSA" : "EC"
                     holder.caption.setText(String.format("%s unknown-bit", type));
                 }
                 catch (IOException e) {