# HG changeset patch # User Carl Byington # Date 1406682068 25200 # Node ID ab3a99f11a360e34223e71c0e94af91ba22ed1c3 # Parent c1f929cb3dd04ccf3ab42eb185b45d2685cd2cdb add ecdsa key support everywhere diff -r c1f929cb3dd0 -r ab3a99f11a36 src/ch/ethz/ssh2/AuthAgentCallback.java --- 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 retrieveIdentities(); /** - * @param key A RSAPrivateKey or DSAPrivateKey - * containing a DSA or RSA private key of - * the user in Trilead object format. + * @param pair A RSAPrivateKey or DSAPrivateKey or ECPrivateKey + * 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 RSAPrivateKey or DSAPrivateKey - * 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); diff -r c1f929cb3dd0 -r ab3a99f11a36 src/ch/ethz/ssh2/KnownHosts.java --- 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 @@ *

* 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 */ diff -r c1f929cb3dd0 -r ab3a99f11a36 src/ch/ethz/ssh2/ServerConnection.java --- 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 NULL * @param rsa_key The RSA hostkey, may be NULL + * @param ec_key The EC hostkey, may be NULL */ - 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. *

- * 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. *

* 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. *

* 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. *

* 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. + *

+ * 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 null) 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 algos = new ArrayList(); + 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); } /** diff -r c1f929cb3dd0 -r ab3a99f11a36 src/ch/ethz/ssh2/channel/AuthAgentForwardThread.java --- 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; diff -r c1f929cb3dd0 -r ab3a99f11a36 src/ch/ethz/ssh2/transport/ClientKexManager.java --- 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); diff -r c1f929cb3dd0 -r ab3a99f11a36 src/ch/ethz/ssh2/transport/ServerKexManager.java --- 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); } diff -r c1f929cb3dd0 -r ab3a99f11a36 src/com/five_ten_sg/connectbot/PubkeyListActivity.java --- 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) {