From ac40fcb2412142ac2bca7d2ae27a838dd09f78bc Mon Sep 17 00:00:00 2001 From: Kenny Root Date: Sun, 7 Feb 2016 14:18:39 -0800 Subject: Support multiple known keys per host This will allow hosts we originally saw with a certain hostkey algorithm to continue to use those keys without warning us. --- app/build.gradle | 8 +- .../java/org/connectbot/HostEditorActivity.java | 1 - .../main/java/org/connectbot/data/HostStorage.java | 10 ++ .../main/java/org/connectbot/transport/SSH.java | 18 +- .../java/org/connectbot/util/HostDatabase.java | 181 +++++++++++++++++---- 5 files changed, 180 insertions(+), 38 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 7267c9a..8f7c64b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -23,8 +23,14 @@ buildscript { } } +repositories { + maven { + url 'https://oss.jfrog.org/artifactory/oss-snapshot-local/' + } +} + dependencies { - compile 'org.connectbot:sshlib:2.2.3' + compile 'org.connectbot:sshlib:2.2.4-SNAPSHOT' testCompile 'junit:junit:4.12' testCompile 'org.mockito:mockito-core:1.10.19' diff --git a/app/src/main/java/org/connectbot/HostEditorActivity.java b/app/src/main/java/org/connectbot/HostEditorActivity.java index cbf99e5..902cfef 100644 --- a/app/src/main/java/org/connectbot/HostEditorActivity.java +++ b/app/src/main/java/org/connectbot/HostEditorActivity.java @@ -75,7 +75,6 @@ public class HostEditorActivity extends AppCompatPreferenceActivity implements O if (cursor.moveToFirst()) { for (int i = 0; i < cursor.getColumnCount(); i++) { String key = cursor.getColumnName(i); - if (key.equals(HostDatabase.FIELD_HOST_HOSTKEY)) continue; String value = cursor.getString(i); values.put(key, value); } diff --git a/app/src/main/java/org/connectbot/data/HostStorage.java b/app/src/main/java/org/connectbot/data/HostStorage.java index dc3e5d7..96ffff3 100644 --- a/app/src/main/java/org/connectbot/data/HostStorage.java +++ b/app/src/main/java/org/connectbot/data/HostStorage.java @@ -77,11 +77,21 @@ public interface HostStorage { */ KnownHosts getKnownHosts(); + /** + * Returns the list of host key algorithms known for the host. + */ + List getHostKeyAlgorithmsForHost(String hostname, int port); + /** * Adds a known host to the database for later retrieval using {@link #getKnownHosts()}. */ void saveKnownHost(String hostname, int port, String serverHostKeyAlgorithm, byte[] serverHostKey); + /** + * Removes a known host from the database. + */ + void removeKnownHost(String host, int port, String serverHostKeyAlgorithm, byte[] serverHostKey); + /** * Return all port forwards for the given {@code host}. */ diff --git a/app/src/main/java/org/connectbot/transport/SSH.java b/app/src/main/java/org/connectbot/transport/SSH.java index 158b2c9..cb6703e 100644 --- a/app/src/main/java/org/connectbot/transport/SSH.java +++ b/app/src/main/java/org/connectbot/transport/SSH.java @@ -62,10 +62,10 @@ import com.trilead.ssh2.Connection; import com.trilead.ssh2.ConnectionInfo; import com.trilead.ssh2.ConnectionMonitor; import com.trilead.ssh2.DynamicPortForwarder; +import com.trilead.ssh2.ExtendedServerHostKeyVerifier; import com.trilead.ssh2.InteractiveCallback; import com.trilead.ssh2.KnownHosts; import com.trilead.ssh2.LocalPortForwarder; -import com.trilead.ssh2.ServerHostKeyVerifier; import com.trilead.ssh2.Session; import com.trilead.ssh2.crypto.PEMDecoder; import com.trilead.ssh2.signature.DSASHA1Verify; @@ -136,7 +136,7 @@ public class SSH extends AbsTransport implements ConnectionMonitor, InteractiveC private String useAuthAgent = HostDatabase.AUTHAGENT_NO; private String agentLockPassphrase; - public class HostKeyVerifier implements ServerHostKeyVerifier { + public class HostKeyVerifier extends ExtendedServerHostKeyVerifier { public boolean verifyServerHostKey(String hostname, int port, String serverHostKeyAlgorithm, byte[] serverHostKey) throws IOException { @@ -209,6 +209,20 @@ public class SSH extends AbsTransport implements ConnectionMonitor, InteractiveC } } + @Override + public List getKnownKeyAlgorithmsForHost(String host, int port) { + return manager.hostdb.getHostKeyAlgorithmsForHost(host, port); + } + + @Override + public void removeServerHostKey(String host, int port, String algorithm, byte[] hostKey) { + manager.hostdb.removeKnownHost(host, port, algorithm, hostKey); + } + + @Override + public void addServerHostKey(String host, int port, String algorithm, byte[] hostKey) { + manager.hostdb.saveKnownHost(host, port, algorithm, hostKey); + } } private void authenticate() { diff --git a/app/src/main/java/org/connectbot/util/HostDatabase.java b/app/src/main/java/org/connectbot/util/HostDatabase.java index 632e333..0761c27 100644 --- a/app/src/main/java/org/connectbot/util/HostDatabase.java +++ b/app/src/main/java/org/connectbot/util/HostDatabase.java @@ -18,6 +18,8 @@ package org.connectbot.util; import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; @@ -50,7 +52,7 @@ public class HostDatabase extends RobustSQLiteOpenHelper implements HostStorage, public final static String TAG = "CB.HostDatabase"; public final static String DB_NAME = "hosts"; - public final static int DB_VERSION = 24; + public final static int DB_VERSION = 25; public final static String TABLE_HOSTS = "hosts"; public final static String FIELD_HOST_NICKNAME = "nickname"; @@ -58,8 +60,6 @@ public class HostDatabase extends RobustSQLiteOpenHelper implements HostStorage, public final static String FIELD_HOST_USERNAME = "username"; public final static String FIELD_HOST_HOSTNAME = "hostname"; public final static String FIELD_HOST_PORT = "port"; - public final static String FIELD_HOST_HOSTKEYALGO = "hostkeyalgo"; - public final static String FIELD_HOST_HOSTKEY = "hostkey"; public final static String FIELD_HOST_LASTCONNECT = "lastconnect"; public final static String FIELD_HOST_COLOR = "color"; public final static String FIELD_HOST_USEKEYS = "usekeys"; @@ -74,6 +74,11 @@ public class HostDatabase extends RobustSQLiteOpenHelper implements HostStorage, public final static String FIELD_HOST_STAYCONNECTED = "stayconnected"; public final static String FIELD_HOST_QUICKDISCONNECT = "quickdisconnect"; + public final static String TABLE_KNOWNHOSTS = "knownhosts"; + public final static String FIELD_KNOWNHOSTS_HOSTID = "hostid"; + public final static String FIELD_KNOWNHOSTS_HOSTKEYALGO = "hostkeyalgo"; + public final static String FIELD_KNOWNHOSTS_HOSTKEY = "hostkey"; + public final static String TABLE_PORTFORWARDS = "portforwards"; public final static String FIELD_PORTFORWARD_HOSTID = "hostid"; public final static String FIELD_PORTFORWARD_NICKNAME = "nickname"; @@ -119,11 +124,35 @@ public class HostDatabase extends RobustSQLiteOpenHelper implements HostStorage, public static final int DEFAULT_COLOR_SCHEME = 0; // Table creation strings + public static final String TABLE_HOSTS_COLUMNS = "_id INTEGER PRIMARY KEY, " + + FIELD_HOST_NICKNAME + " TEXT, " + + FIELD_HOST_PROTOCOL + " TEXT DEFAULT 'ssh', " + + FIELD_HOST_USERNAME + " TEXT, " + + FIELD_HOST_HOSTNAME + " TEXT, " + + FIELD_HOST_PORT + " INTEGER, " + + FIELD_HOST_LASTCONNECT + " INTEGER, " + + FIELD_HOST_COLOR + " TEXT, " + + FIELD_HOST_USEKEYS + " TEXT, " + + FIELD_HOST_USEAUTHAGENT + " TEXT, " + + FIELD_HOST_POSTLOGIN + " TEXT, " + + FIELD_HOST_PUBKEYID + " INTEGER DEFAULT " + PUBKEYID_ANY + ", " + + FIELD_HOST_DELKEY + " TEXT DEFAULT '" + DELKEY_DEL + "', " + + FIELD_HOST_FONTSIZE + " INTEGER, " + + FIELD_HOST_WANTSESSION + " TEXT DEFAULT '" + Boolean.toString(true) + "', " + + FIELD_HOST_COMPRESSION + " TEXT DEFAULT '" + Boolean.toString(false) + "', " + + FIELD_HOST_ENCODING + " TEXT DEFAULT '" + ENCODING_DEFAULT + "', " + + FIELD_HOST_STAYCONNECTED + " TEXT DEFAULT '" + Boolean.toString(false) + "', " + + FIELD_HOST_QUICKDISCONNECT + " TEXT DEFAULT '" + Boolean.toString(false) + "'"; + + public static final String CREATE_TABLE_HOSTS = "CREATE TABLE " + TABLE_HOSTS + + " (" + TABLE_HOSTS_COLUMNS + ")"; + public static final String CREATE_TABLE_COLOR_DEFAULTS = "CREATE TABLE " + TABLE_COLOR_DEFAULTS + " (" + FIELD_COLOR_SCHEME + " INTEGER NOT NULL, " + FIELD_COLOR_FG + " INTEGER NOT NULL DEFAULT " + DEFAULT_FG_COLOR + ", " + FIELD_COLOR_BG + " INTEGER NOT NULL DEFAULT " + DEFAULT_BG_COLOR + ")"; + public static final String CREATE_TABLE_COLOR_DEFAULTS_INDEX = "CREATE INDEX " + TABLE_COLOR_DEFAULTS + FIELD_COLOR_SCHEME + "index ON " + TABLE_COLOR_DEFAULTS + " (" + FIELD_COLOR_SCHEME + ");"; @@ -133,6 +162,8 @@ public class HostDatabase extends RobustSQLiteOpenHelper implements HostStorage, static { addTableName(TABLE_HOSTS); + addTableName(TABLE_KNOWNHOSTS); + addIndexName(TABLE_KNOWNHOSTS + FIELD_KNOWNHOSTS_HOSTID + "index"); addTableName(TABLE_PORTFORWARDS); addIndexName(TABLE_PORTFORWARDS + FIELD_PORTFORWARD_HOSTID + "index"); addTableName(TABLE_COLORS); @@ -180,28 +211,16 @@ public class HostDatabase extends RobustSQLiteOpenHelper implements HostStorage, } private void createTables(SQLiteDatabase db) { - db.execSQL("CREATE TABLE " + TABLE_HOSTS + db.execSQL(CREATE_TABLE_HOSTS); + + db.execSQL("CREATE TABLE " + TABLE_KNOWNHOSTS + " (_id INTEGER PRIMARY KEY, " - + FIELD_HOST_NICKNAME + " TEXT, " - + FIELD_HOST_PROTOCOL + " TEXT DEFAULT 'ssh', " - + FIELD_HOST_USERNAME + " TEXT, " - + FIELD_HOST_HOSTNAME + " TEXT, " - + FIELD_HOST_PORT + " INTEGER, " - + FIELD_HOST_HOSTKEYALGO + " TEXT, " - + FIELD_HOST_HOSTKEY + " BLOB, " - + FIELD_HOST_LASTCONNECT + " INTEGER, " - + FIELD_HOST_COLOR + " TEXT, " - + FIELD_HOST_USEKEYS + " TEXT, " - + FIELD_HOST_USEAUTHAGENT + " TEXT, " - + FIELD_HOST_POSTLOGIN + " TEXT, " - + FIELD_HOST_PUBKEYID + " INTEGER DEFAULT " + PUBKEYID_ANY + ", " - + FIELD_HOST_DELKEY + " TEXT DEFAULT '" + DELKEY_DEL + "', " - + FIELD_HOST_FONTSIZE + " INTEGER, " - + FIELD_HOST_WANTSESSION + " TEXT DEFAULT '" + Boolean.toString(true) + "', " - + FIELD_HOST_COMPRESSION + " TEXT DEFAULT '" + Boolean.toString(false) + "', " - + FIELD_HOST_ENCODING + " TEXT DEFAULT '" + ENCODING_DEFAULT + "', " - + FIELD_HOST_STAYCONNECTED + " TEXT DEFAULT '" + Boolean.toString(false) + "', " - + FIELD_HOST_QUICKDISCONNECT + " TEXT DEFAULT '" + Boolean.toString(false) + "')"); + + FIELD_KNOWNHOSTS_HOSTID + " INTEGER, " + + FIELD_KNOWNHOSTS_HOSTKEYALGO + " TEXT, " + + FIELD_KNOWNHOSTS_HOSTKEY + " BLOB)"); + + db.execSQL("CREATE INDEX " + TABLE_KNOWNHOSTS + FIELD_KNOWNHOSTS_HOSTID + "index ON " + + TABLE_KNOWNHOSTS + " (" + FIELD_KNOWNHOSTS_HOSTID + ");"); db.execSQL("CREATE TABLE " + TABLE_PORTFORWARDS + " (_id INTEGER PRIMARY KEY, " @@ -234,6 +253,7 @@ public class HostDatabase extends RobustSQLiteOpenHelper implements HostStorage, mDb.beginTransaction(); mDb.execSQL("DROP TABLE IF EXISTS " + TABLE_HOSTS); + mDb.execSQL("DROP TABLE IF EXISTS " + TABLE_KNOWNHOSTS); mDb.execSQL("DROP TABLE IF EXISTS " + TABLE_PORTFORWARDS); mDb.execSQL("DROP TABLE IF EXISTS " + TABLE_COLORS); mDb.execSQL("DROP TABLE IF EXISTS " + TABLE_COLOR_DEFAULTS); @@ -311,7 +331,7 @@ public class HostDatabase extends RobustSQLiteOpenHelper implements HostStorage, db.execSQL("ALTER TABLE " + TABLE_HOSTS + " ADD COLUMN " + FIELD_HOST_FONTSIZE + " INTEGER"); case 21: - db.execSQL("DROP TABLE " + TABLE_COLOR_DEFAULTS); + db.execSQL("DROP TABLE IF EXISTS " + TABLE_COLOR_DEFAULTS); db.execSQL(CREATE_TABLE_COLOR_DEFAULTS); db.execSQL(CREATE_TABLE_COLOR_DEFAULTS_INDEX); case 22: @@ -320,6 +340,47 @@ public class HostDatabase extends RobustSQLiteOpenHelper implements HostStorage, case 23: db.execSQL("UPDATE " + TABLE_HOSTS + " SET " + FIELD_HOST_FONTSIZE + " = " + FIELD_HOST_FONTSIZE + " / " + displayDensity); + case 24: + // Move all the existing known hostkeys into their own table. + db.execSQL("DROP TABLE IF EXISTS " + TABLE_KNOWNHOSTS); + db.execSQL("CREATE TABLE " + TABLE_KNOWNHOSTS + + " (_id INTEGER PRIMARY KEY, " + + FIELD_KNOWNHOSTS_HOSTID + " INTEGER, " + + FIELD_KNOWNHOSTS_HOSTKEYALGO + " TEXT, " + + FIELD_KNOWNHOSTS_HOSTKEY + " BLOB)"); + db.execSQL("INSERT INTO " + TABLE_KNOWNHOSTS + " (" + + FIELD_KNOWNHOSTS_HOSTID + ", " + + FIELD_KNOWNHOSTS_HOSTKEYALGO + ", " + + FIELD_KNOWNHOSTS_HOSTKEY + ") " + + "SELECT _id, " + + FIELD_KNOWNHOSTS_HOSTKEYALGO + ", " + + FIELD_KNOWNHOSTS_HOSTKEY + + " FROM " + TABLE_HOSTS); + // Work around SQLite not supporting dropping columns + db.execSQL("DROP TABLE IF EXISTS " + TABLE_HOSTS + "_upgrade"); + db.execSQL("CREATE TABLE " + TABLE_HOSTS + "_upgrade (" + TABLE_HOSTS_COLUMNS + ")"); + db.execSQL("INSERT INTO " + TABLE_HOSTS + "_upgrade SELECT _id, " + + FIELD_HOST_NICKNAME + ", " + + FIELD_HOST_PROTOCOL + ", " + + FIELD_HOST_USERNAME + ", " + + FIELD_HOST_HOSTNAME + ", " + + FIELD_HOST_PORT + ", " + + FIELD_HOST_LASTCONNECT + ", " + + FIELD_HOST_COLOR + ", " + + FIELD_HOST_USEKEYS + ", " + + FIELD_HOST_USEAUTHAGENT + ", " + + FIELD_HOST_POSTLOGIN + ", " + + FIELD_HOST_PUBKEYID + ", " + + FIELD_HOST_DELKEY + ", " + + FIELD_HOST_FONTSIZE + ", " + + FIELD_HOST_WANTSESSION + ", " + + FIELD_HOST_COMPRESSION + ", " + + FIELD_HOST_ENCODING + ", " + + FIELD_HOST_STAYCONNECTED + ", " + + FIELD_HOST_QUICKDISCONNECT + + " FROM " + TABLE_HOSTS); + db.execSQL("DROP TABLE " + TABLE_HOSTS); + db.execSQL("ALTER TABLE " + TABLE_HOSTS + "_upgrade RENAME TO " + TABLE_HOSTS); } } @@ -530,15 +591,26 @@ public class HostDatabase extends RobustSQLiteOpenHelper implements HostStorage, */ public void saveKnownHost(String hostname, int port, String hostkeyalgo, byte[] hostkey) { ContentValues values = new ContentValues(); - values.put(FIELD_HOST_HOSTKEYALGO, hostkeyalgo); - values.put(FIELD_HOST_HOSTKEY, hostkey); + values.put(FIELD_KNOWNHOSTS_HOSTKEYALGO, hostkeyalgo); + values.put(FIELD_KNOWNHOSTS_HOSTKEY, hostkey); + + HashMap selection = new HashMap<>(); + selection.put(FIELD_HOST_HOSTNAME, hostname); + selection.put(FIELD_HOST_PORT, String.valueOf(port)); + HostBean hostBean = findHost(selection); + + if (hostBean == null) { + Log.e(TAG, "Tried to save known host for " + hostname + ":" + port + + " it doesn't exist in the database"); + return; + } int numUpdated; mDb.beginTransaction(); try { - numUpdated = mDb.update(TABLE_HOSTS, values, - FIELD_HOST_HOSTNAME + " = ? AND " + FIELD_HOST_PORT + " = ?", - new String[]{hostname, String.valueOf(port)}); + numUpdated = mDb.update(TABLE_KNOWNHOSTS, values, + FIELD_KNOWNHOSTS_HOSTID + " = ?", + new String[] {String.valueOf(hostBean.getId())}); mDb.setTransactionSuccessful(); } finally { mDb.endTransaction(); @@ -547,6 +619,11 @@ public class HostDatabase extends RobustSQLiteOpenHelper implements HostStorage, hostname, numUpdated)); } + @Override + public void removeKnownHost(String host, int port, String serverHostKeyAlgorithm, byte[] serverHostKey) { + throw new UnsupportedOperationException("removeKnownHost is not implemented"); + } + /** * Build list of known hosts for Trilead library. * @return @@ -554,15 +631,18 @@ public class HostDatabase extends RobustSQLiteOpenHelper implements HostStorage, public KnownHosts getKnownHosts() { KnownHosts known = new KnownHosts(); - Cursor c = mDb.query(TABLE_HOSTS, new String[] {FIELD_HOST_HOSTNAME, - FIELD_HOST_PORT, FIELD_HOST_HOSTKEYALGO, FIELD_HOST_HOSTKEY}, + Cursor c = mDb.query(TABLE_HOSTS + " LEFT OUTER JOIN " + TABLE_KNOWNHOSTS + + " ON " + TABLE_HOSTS + "._id = " + + TABLE_KNOWNHOSTS + "." + FIELD_KNOWNHOSTS_HOSTID, + new String[] {FIELD_HOST_HOSTNAME, FIELD_HOST_PORT, FIELD_KNOWNHOSTS_HOSTKEYALGO, + FIELD_KNOWNHOSTS_HOSTKEY}, null, null, null, null, null); if (c != null) { int COL_HOSTNAME = c.getColumnIndexOrThrow(FIELD_HOST_HOSTNAME), COL_PORT = c.getColumnIndexOrThrow(FIELD_HOST_PORT), - COL_HOSTKEYALGO = c.getColumnIndexOrThrow(FIELD_HOST_HOSTKEYALGO), - COL_HOSTKEY = c.getColumnIndexOrThrow(FIELD_HOST_HOSTKEY); + COL_HOSTKEYALGO = c.getColumnIndexOrThrow(FIELD_KNOWNHOSTS_HOSTKEYALGO), + COL_HOSTKEY = c.getColumnIndexOrThrow(FIELD_KNOWNHOSTS_HOSTKEY); while (c.moveToNext()) { String hostname = c.getString(COL_HOSTNAME); @@ -586,6 +666,39 @@ public class HostDatabase extends RobustSQLiteOpenHelper implements HostStorage, return known; } + @Override + public List getHostKeyAlgorithmsForHost(String hostname, int port) { + HashMap selection = new HashMap<>(); + selection.put(FIELD_HOST_HOSTNAME, hostname); + selection.put(FIELD_HOST_PORT, String.valueOf(port)); + HostBean hostBean = findHost(selection); + + if (hostBean == null) { + return null; + } + + ArrayList knownAlgorithms = new ArrayList<>(); + + Cursor c = mDb.query(TABLE_KNOWNHOSTS, new String[] {FIELD_KNOWNHOSTS_HOSTKEYALGO}, + FIELD_KNOWNHOSTS_HOSTID + " = ?", + new String[] {String.valueOf(hostBean.getId())}, null, null, null); + + if (c != null) { + int COL_ALGO = c.getColumnIndexOrThrow(FIELD_KNOWNHOSTS_HOSTKEYALGO); + + while (c.moveToNext()) { + String keyAlgo = c.getString(COL_ALGO); + if (keyAlgo != null) { + knownAlgorithms.add(keyAlgo); + } + } + + c.close(); + } + + return knownAlgorithms; + } + /** * Unset any hosts using a pubkey ID that has been deleted. * @param pubkeyId -- cgit v1.2.3