diff options
author | Jeffrey Sharkey <jsharkey@jsharkey.org> | 2008-11-02 23:12:26 +0000 |
---|---|---|
committer | Jeffrey Sharkey <jsharkey@jsharkey.org> | 2008-11-02 23:12:26 +0000 |
commit | 7a4f3578afc79f409ed8da118d9809557cf5ec46 (patch) | |
tree | 4f5b5b9e936842e24c07ce3c77c5f71ca9926248 /src | |
parent | ccc3a32792175a561fc2c75ccc4fd9f441295830 (diff) | |
download | connectbot-7a4f3578afc79f409ed8da118d9809557cf5ec46.tar.gz connectbot-7a4f3578afc79f409ed8da118d9809557cf5ec46.tar.bz2 connectbot-7a4f3578afc79f409ed8da118d9809557cf5ec46.zip |
* added "in-memory" function to backend service so that unlocked keys can be stored there. this is also disable-able from settings
* "use any key" for a host will only look through unlocked in-memory keys
* implemented "load on start" functionality in backend service
* implemented "import key" which lets you select any openssh-formatted key (including passworded-ones) from simple /sdcard browser
* cleaned up context menu in pubkeylist, now includes toggle checkbox for "load at start" but only available when password-less and non-imported
* clicking a key in pubkeylist will toggle its backend status (decrypt and put in memory, or remove from memory)
* created preference for screen orientation forcing versus auto, but still need to test
* created preference for camera button behavior, but still need to test
Diffstat (limited to 'src')
-rw-r--r-- | src/com/trilead/ssh2/auth/AuthenticationManager.java | 4 | ||||
-rw-r--r-- | src/com/trilead/ssh2/crypto/PEMDecoder.java | 6 | ||||
-rw-r--r-- | src/com/trilead/ssh2/crypto/PEMStructure.java | 4 | ||||
-rw-r--r-- | src/org/connectbot/ConsoleActivity.java | 12 | ||||
-rw-r--r-- | src/org/connectbot/GeneratePubkeyActivity.java | 9 | ||||
-rw-r--r-- | src/org/connectbot/HostEditorActivity.java | 70 | ||||
-rw-r--r-- | src/org/connectbot/HostListActivity.java | 4 | ||||
-rw-r--r-- | src/org/connectbot/PubkeyListActivity.java | 345 | ||||
-rw-r--r-- | src/org/connectbot/service/TerminalBridge.java | 159 | ||||
-rw-r--r-- | src/org/connectbot/service/TerminalManager.java | 94 | ||||
-rw-r--r-- | src/org/connectbot/util/HostDatabase.java | 2 | ||||
-rw-r--r-- | src/org/connectbot/util/KeyDatabase.java | 62 | ||||
-rw-r--r-- | src/org/connectbot/util/PubkeyDatabase.java | 67 |
13 files changed, 630 insertions, 208 deletions
diff --git a/src/com/trilead/ssh2/auth/AuthenticationManager.java b/src/com/trilead/ssh2/auth/AuthenticationManager.java index 99d62ca..43c226a 100644 --- a/src/com/trilead/ssh2/auth/AuthenticationManager.java +++ b/src/com/trilead/ssh2/auth/AuthenticationManager.java @@ -233,7 +233,9 @@ public class AuthenticationManager implements MessageHandler PacketUserauthRequestPublicKey ua = new PacketUserauthRequestPublicKey("ssh-connection", user,
"ssh-rsa", pk_enc, rsa_sig_enc);
+
tm.sendMessage(ua.getPayload());
+
}
else
{
@@ -241,7 +243,6 @@ public class AuthenticationManager implements MessageHandler }
byte[] ar = getNextMessage();
-
if (ar[0] == Packets.SSH_MSG_USERAUTH_SUCCESS)
{
authenticated = true;
@@ -264,6 +265,7 @@ public class AuthenticationManager implements MessageHandler }
catch (IOException e)
{
+e.printStackTrace();
tm.close(e, false);
throw (IOException) new IOException("Publickey authentication failed.").initCause(e);
}
diff --git a/src/com/trilead/ssh2/crypto/PEMDecoder.java b/src/com/trilead/ssh2/crypto/PEMDecoder.java index ac1b842..7d0a015 100644 --- a/src/com/trilead/ssh2/crypto/PEMDecoder.java +++ b/src/com/trilead/ssh2/crypto/PEMDecoder.java @@ -23,8 +23,8 @@ import com.trilead.ssh2.signature.RSAPrivateKey; */
public class PEMDecoder
{
- private static final int PEM_RSA_PRIVATE_KEY = 1;
- private static final int PEM_DSA_PRIVATE_KEY = 2;
+ public static final int PEM_RSA_PRIVATE_KEY = 1;
+ public static final int PEM_DSA_PRIVATE_KEY = 2;
private static final int hexToInt(char c)
{
@@ -120,7 +120,7 @@ public class PEMDecoder return tmp;
}
- private static final PEMStructure parsePEM(char[] pem) throws IOException
+ public static final PEMStructure parsePEM(char[] pem) throws IOException
{
PEMStructure ps = new PEMStructure();
diff --git a/src/com/trilead/ssh2/crypto/PEMStructure.java b/src/com/trilead/ssh2/crypto/PEMStructure.java index 3bb4b5a..6b657e8 100644 --- a/src/com/trilead/ssh2/crypto/PEMStructure.java +++ b/src/com/trilead/ssh2/crypto/PEMStructure.java @@ -10,8 +10,8 @@ package com.trilead.ssh2.crypto; public class PEMStructure
{
- int pemType;
+ public int pemType;
String dekInfo[];
String procType[];
- byte[] data;
+ public byte[] data;
}
\ No newline at end of file diff --git a/src/org/connectbot/ConsoleActivity.java b/src/org/connectbot/ConsoleActivity.java index 76793a6..27afffa 100644 --- a/src/org/connectbot/ConsoleActivity.java +++ b/src/org/connectbot/ConsoleActivity.java @@ -30,6 +30,7 @@ import android.content.DialogInterface; import android.content.Intent; import android.content.ServiceConnection; import android.content.SharedPreferences; +import android.content.pm.ActivityInfo; import android.net.Uri; import android.os.Bundle; import android.os.Handler; @@ -314,6 +315,15 @@ public class ConsoleActivity extends Activity { this.clipboard = (ClipboardManager)this.getSystemService(CLIPBOARD_SERVICE); this.prefs = PreferenceManager.getDefaultSharedPreferences(this); + // request a forced orientation if requested by user + String rotate = this.prefs.getString(getString(R.string.pref_rotation), getString(R.string.list_rotation_land)); + if(getString(R.string.list_rotation_land).equals(rotate)) { + this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); + } else if (getString(R.string.list_rotation_port).equals(rotate)) { + this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); + } + + PowerManager manager = (PowerManager)getSystemService(Context.POWER_SERVICE); wakelock = manager.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, TAG); @@ -594,7 +604,7 @@ public class ConsoleActivity extends Activity { public boolean onMenuItemClick(MenuItem item) { // close the currently visible session TerminalView terminal = (TerminalView)view; - bound.disconnect(terminal.bridge); + terminal.bridge.dispatchDisconnect(); // movement should now be happening over in onDisconnect() handler //flip.removeView(flip.getCurrentView()); //shiftLeft(); diff --git a/src/org/connectbot/GeneratePubkeyActivity.java b/src/org/connectbot/GeneratePubkeyActivity.java index 8115601..5e0e31d 100644 --- a/src/org/connectbot/GeneratePubkeyActivity.java +++ b/src/org/connectbot/GeneratePubkeyActivity.java @@ -55,9 +55,6 @@ import android.widget.SeekBar.OnSeekBarChangeListener; public class GeneratePubkeyActivity extends Activity implements OnEntropyGatheredListener { public final static String TAG = GeneratePubkeyActivity.class.toString(); - final static String KEY_TYPE_RSA = "RSA", - KEY_TYPE_DSA = "DSA"; - final static int DEFAULT_BITS = 1024; protected LayoutInflater inflater = null; @@ -73,7 +70,7 @@ public class GeneratePubkeyActivity extends Activity implements OnEntropyGathere private EditText password1, password2; - private String keyType = KEY_TYPE_RSA; + private String keyType = PubkeyDatabase.KEY_TYPE_RSA; private int minBits = 768; private int bits = DEFAULT_BITS; @@ -117,7 +114,7 @@ public class GeneratePubkeyActivity extends Activity implements OnEntropyGathere bitsText.setText(String.valueOf(DEFAULT_BITS)); bitsText.setEnabled(true); - keyType = KEY_TYPE_RSA; + keyType = PubkeyDatabase.KEY_TYPE_RSA; } else if (checkedId == R.id.dsa) { // DSA keys can only be 1024 bits @@ -127,7 +124,7 @@ public class GeneratePubkeyActivity extends Activity implements OnEntropyGathere bitsText.setText(String.valueOf(DEFAULT_BITS)); bitsText.setEnabled(false); - keyType = KEY_TYPE_DSA; + keyType = PubkeyDatabase.KEY_TYPE_DSA; } } }); diff --git a/src/org/connectbot/HostEditorActivity.java b/src/org/connectbot/HostEditorActivity.java index 0ae921e..daa81a0 100644 --- a/src/org/connectbot/HostEditorActivity.java +++ b/src/org/connectbot/HostEditorActivity.java @@ -49,7 +49,7 @@ public class HostEditorActivity extends PreferenceActivity implements OnSharedPr protected final int id; protected Map<String, String> values = new HashMap<String, String>(); - protected Map<String, String> pubkeys = new HashMap<String, String>(); +// protected Map<String, String> pubkeys = new HashMap<String, String>(); public CursorPreferenceHack(String table, int id) { this.table = table; @@ -78,21 +78,21 @@ public class HostEditorActivity extends PreferenceActivity implements OnSharedPr cursor.close(); db.close(); - db = pubkeydb.getReadableDatabase(); - cursor = db.query(PubkeyDatabase.TABLE_PUBKEYS, - new String[] { "_id", PubkeyDatabase.FIELD_PUBKEY_NICKNAME }, - null, null, null, null, null); - - if (cursor.moveToFirst()) { - do { - String pubkeyid = String.valueOf(cursor.getLong(0)); - String value = cursor.getString(1); - pubkeys.put(pubkeyid, value); - } while (cursor.moveToNext()); - } - - cursor.close(); - db.close(); +// db = pubkeydb.getReadableDatabase(); +// cursor = db.query(PubkeyDatabase.TABLE_PUBKEYS, +// new String[] { "_id", PubkeyDatabase.FIELD_PUBKEY_NICKNAME }, +// null, null, null, null, null); +// +// if (cursor.moveToFirst()) { +// do { +// String pubkeyid = String.valueOf(cursor.getLong(0)); +// String value = cursor.getString(1); +// pubkeys.put(pubkeyid, value); +// } while (cursor.moveToNext()); +// } +// +// cursor.close(); +// db.close(); } public boolean contains(String key) { @@ -110,7 +110,7 @@ public class HostEditorActivity extends PreferenceActivity implements OnSharedPr } public boolean commit() { - Log.d(this.getClass().toString(), "commit() changes back to database"); + //Log.d(this.getClass().toString(), "commit() changes back to database"); SQLiteDatabase db = hostdb.getWritableDatabase(); db.update(table, update, "_id = ?", new String[] { Integer.toString(id) }); db.close(); @@ -143,13 +143,13 @@ public class HostEditorActivity extends PreferenceActivity implements OnSharedPr } public android.content.SharedPreferences.Editor putString(String key, String value) { - Log.d(this.getClass().toString(), String.format("Editor.putString(key=%s, value=%s)", key, value)); + //Log.d(this.getClass().toString(), String.format("Editor.putString(key=%s, value=%s)", key, value)); update.put(key, value); return this; } public android.content.SharedPreferences.Editor remove(String key) { - Log.d(this.getClass().toString(), String.format("Editor.remove(key=%s)", key)); + //Log.d(this.getClass().toString(), String.format("Editor.remove(key=%s)", key)); update.remove(key); return this; } @@ -158,7 +158,7 @@ public class HostEditorActivity extends PreferenceActivity implements OnSharedPr public Editor edit() { - Log.d(this.getClass().toString(), "edit()"); + //Log.d(this.getClass().toString(), "edit()"); return new Editor(); } @@ -183,7 +183,7 @@ public class HostEditorActivity extends PreferenceActivity implements OnSharedPr } public String getString(String key, String defValue) { - Log.d(this.getClass().toString(), String.format("getString(key=%s, defValue=%s)", key, defValue)); + //Log.d(this.getClass().toString(), String.format("getString(key=%s, defValue=%s)", key, defValue)); if(!values.containsKey(key)) return defValue; return values.get(key); @@ -204,7 +204,7 @@ public class HostEditorActivity extends PreferenceActivity implements OnSharedPr @Override public SharedPreferences getSharedPreferences(String name, int mode) { - Log.d(this.getClass().toString(), String.format("getSharedPreferences(name=%s)", name)); + //Log.d(this.getClass().toString(), String.format("getSharedPreferences(name=%s)", name)); return this.pref; } @@ -230,15 +230,17 @@ public class HostEditorActivity extends PreferenceActivity implements OnSharedPr this.addPreferencesFromResource(R.xml.host_prefs); - // Grab all the pubkeys from the database cache we have. + // add all existing pubkeys to our listpreference for user to choose from + // TODO: may be an issue here when this activity is recycled after adding a new pubkey + // TODO: should consider moving into onStart, but we dont have a good way of resetting the listpref after filling once ListPreference pubkeyPref = (ListPreference)this.findPreference(HostDatabase.FIELD_HOST_PUBKEYID); - + List<CharSequence> pubkeyNicks = new LinkedList<CharSequence>(Arrays.asList(pubkeyPref.getEntries())); - pubkeyNicks.addAll(this.pref.pubkeys.values()); + pubkeyNicks.addAll(pubkeydb.allValues(PubkeyDatabase.FIELD_PUBKEY_NICKNAME)); pubkeyPref.setEntries((CharSequence[]) pubkeyNicks.toArray(new CharSequence[pubkeyNicks.size()])); List<CharSequence> pubkeyIds = new LinkedList<CharSequence>(Arrays.asList(pubkeyPref.getEntryValues())); - pubkeyIds.addAll(this.pref.pubkeys.keySet()); + pubkeyIds.addAll(pubkeydb.allValues("_id")); pubkeyPref.setEntryValues((CharSequence[]) pubkeyIds.toArray(new CharSequence[pubkeyIds.size()])); this.updateSummaries(); @@ -249,6 +251,9 @@ public class HostEditorActivity extends PreferenceActivity implements OnSharedPr if(this.hostdb == null) this.hostdb = new HostDatabase(this); + if(this.pubkeydb == null) + this.pubkeydb = new PubkeyDatabase(this); + } public void onStop() { @@ -257,6 +262,11 @@ public class HostEditorActivity extends PreferenceActivity implements OnSharedPr this.hostdb.close(); this.hostdb = null; } + + if(this.pubkeydb != null) { + this.pubkeydb.close(); + this.pubkeydb = null; + } } public void updateSummaries() { @@ -272,14 +282,18 @@ public class HostEditorActivity extends PreferenceActivity implements OnSharedPr try { int pubkeyId = Integer.parseInt(value); if (pubkeyId >= 0) - pref.setSummary(this.pref.pubkeys.get(this.pref.getString(key, ""))); + pref.setSummary(pubkeydb.getNickname(pubkeyId)); + else if(pubkeyId == HostDatabase.PUBKEYID_ANY) + pref.setSummary(R.string.list_pubkeyids_any); + else if(pubkeyId == HostDatabase.PUBKEYID_NEVER) + pref.setSummary(R.string.list_pubkeyids_none); continue; } catch (NumberFormatException nfe) { // Fall through. } } - pref.setSummary(this.pref.getString(key, "")); + pref.setSummary(value); } } diff --git a/src/org/connectbot/HostListActivity.java b/src/org/connectbot/HostListActivity.java index cda2131..429d4e0 100644 --- a/src/org/connectbot/HostListActivity.java +++ b/src/org/connectbot/HostListActivity.java @@ -388,7 +388,7 @@ public class HostListActivity extends ListActivity { connect.setEnabled((bridge != null)); connect.setOnMenuItemClickListener(new OnMenuItemClickListener() { public boolean onMenuItemClick(MenuItem item) { - bound.disconnect(bridge); + bridge.dispatchDisconnect(); updateHandler.sendEmptyMessage(-1); return true; } @@ -414,7 +414,7 @@ public class HostListActivity extends ListActivity { public void onClick(DialogInterface dialog, int which) { // make sure we disconnect if(bridge != null) - bound.disconnect(bridge); + bridge.dispatchDisconnect(); hostdb.deleteHost(id); updateHandler.sendEmptyMessage(-1); diff --git a/src/org/connectbot/PubkeyListActivity.java b/src/org/connectbot/PubkeyListActivity.java index 095a1d8..b7fd9ae 100644 --- a/src/org/connectbot/PubkeyListActivity.java +++ b/src/org/connectbot/PubkeyListActivity.java @@ -18,20 +18,40 @@ package org.connectbot; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; import java.security.PublicKey; +import java.security.spec.InvalidKeySpecException; import java.util.EventListener; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.Semaphore; +import org.connectbot.service.TerminalManager; import org.connectbot.util.PubkeyDatabase; import org.connectbot.util.PubkeyUtils; +import com.trilead.ssh2.crypto.PEMDecoder; +import com.trilead.ssh2.crypto.PEMStructure; + import android.app.AlertDialog; import android.app.ListActivity; +import android.content.ComponentName; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; +import android.content.ServiceConnection; +import android.content.DialogInterface.OnClickListener; import android.database.Cursor; import android.os.Bundle; +import android.os.Environment; import android.os.Handler; +import android.os.IBinder; import android.os.Message; import android.text.ClipboardManager; import android.util.Log; @@ -48,6 +68,8 @@ import android.widget.ListView; import android.widget.SimpleCursorAdapter; import android.widget.TableRow; import android.widget.TextView; +import android.widget.Toast; +import android.widget.AdapterView.OnItemClickListener; import android.widget.SimpleCursorAdapter.ViewBinder; public class PubkeyListActivity extends ListActivity implements EventListener { @@ -61,24 +83,43 @@ public class PubkeyListActivity extends ListActivity implements EventListener { protected ClipboardManager clipboard; protected LayoutInflater inflater = null; + + protected TerminalManager bound = null; + + private ServiceConnection connection = new ServiceConnection() { + public void onServiceConnected(ComponentName className, IBinder service) { + bound = ((TerminalManager.TerminalBinder) service).getService(); + + // update our listview binder to find the service + PubkeyListActivity.this.updateCursor(); + } + + public void onServiceDisconnected(ComponentName className) { + bound = null; + PubkeyListActivity.this.updateCursor(); + } + }; @Override public void onStart() { super.onStart(); + this.bindService(new Intent(this, TerminalManager.class), connection, Context.BIND_AUTO_CREATE); + if(this.pubkeydb == null) this.pubkeydb = new PubkeyDatabase(this); - - ListView list = this.getListView(); - this.registerForContextMenu(list); } @Override public void onStop() { super.onStop(); - if(this.pubkeydb != null) + this.unbindService(connection); + + if(this.pubkeydb != null) { this.pubkeydb.close(); + this.pubkeydb = null; + } } @Override @@ -91,6 +132,8 @@ public class PubkeyListActivity extends ListActivity implements EventListener { this.updateCursor(); + this.registerForContextMenu(this.getListView()); + this.COL_ID = pubkeys.getColumnIndexOrThrow("_id"); this.COL_NICKNAME = pubkeys.getColumnIndexOrThrow(PubkeyDatabase.FIELD_PUBKEY_NICKNAME); this.COL_TYPE = pubkeys.getColumnIndexOrThrow(PubkeyDatabase.FIELD_PUBKEY_TYPE); @@ -99,61 +142,234 @@ public class PubkeyListActivity extends ListActivity implements EventListener { this.COL_ENCRYPTED = pubkeys.getColumnIndexOrThrow(PubkeyDatabase.FIELD_PUBKEY_ENCRYPTED); this.COL_STARTUP = pubkeys.getColumnIndexOrThrow(PubkeyDatabase.FIELD_PUBKEY_STARTUP); + this.getListView().setOnItemClickListener(new OnItemClickListener() { + public void onItemClick(AdapterView<?> adapter, View view, int position, long id) { + Cursor cursor = (Cursor) getListView().getItemAtPosition(position); + String nickname = cursor.getString(COL_NICKNAME); + boolean loaded = bound.isKeyLoaded(nickname); + + // handle toggling key in-memory on/off + if(loaded) { + bound.removeKey(nickname); + updateHandler.sendEmptyMessage(-1); + } else { + handleAddKey(cursor); + } + + } + }); + this.clipboard = (ClipboardManager)this.getSystemService(CLIPBOARD_SERVICE); this.inflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE); } + /** + * Read given file into memory as <code>byte[]</code>. + */ + public static byte[] readRaw(File file) throws Exception { + InputStream is = new FileInputStream(file); + ByteArrayOutputStream os = new ByteArrayOutputStream(); + + int bytesRead; + byte[] buffer = new byte[1024]; + while ((bytesRead = is.read(buffer)) != -1) { + os.write(buffer, 0, bytesRead); + } + + os.flush(); + os.close(); + is.close(); + + return os.toByteArray(); + + } @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); MenuItem generatekey = menu.add(R.string.pubkey_generate); - generatekey.setIcon(android.R.drawable.ic_lock_lock); + generatekey.setIcon(android.R.drawable.ic_menu_manage); generatekey.setIntent(new Intent(PubkeyListActivity.this, GeneratePubkeyActivity.class)); - // TODO: allow importing of keys - //MenuItem importkey = menu.add("Import"); - //importkey.setIcon(android.R.drawable.ic_lock_lock); - //importkey.setIntent(new Intent(PubkeyListActivity.this, ImportPubkeyActivity.class)); + MenuItem importkey = menu.add("Import"); + importkey.setIcon(android.R.drawable.ic_menu_upload); + importkey.setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + + // TODO: replace this with well-known intent over to file browser + // TODO: if browser not installed (?) then fallback to this simple method? + + // build list of all files in sdcard root + final File sdcard = Environment.getExternalStorageDirectory(); + Log.d(TAG, sdcard.toString()); + List<String> names = new LinkedList<String>(); + for(File file : sdcard.listFiles()) { + if(file.isDirectory()) continue; + names.add(file.getName()); + } + final String[] namesList = names.toArray(new String[] {}); + Log.d(TAG, names.toString()); + + // prompt user to select any file from the sdcard root + new AlertDialog.Builder(PubkeyListActivity.this) + .setTitle("Pick from /sdcard") + .setItems(namesList, new OnClickListener() { + public void onClick(DialogInterface arg0, int arg1) { + // find the exact file selected + String name = namesList[arg1]; + File actual = new File(sdcard, name); + + // parse the actual key once to check if its encrypted + // then save original file contents into our database + try { + byte[] raw = readRaw(actual); + PEMStructure struct = PEMDecoder.parsePEM(new String(raw).toCharArray()); + boolean encrypted = PEMDecoder.isPEMEncrypted(struct); + + // write new value into database + pubkeydb.createPubkey(null, name, PubkeyDatabase.KEY_TYPE_IMPORTED, raw, new byte[] {}, encrypted, false); + updateHandler.sendEmptyMessage(-1); + + } catch(Exception e) { + Log.e(TAG, "Problem parsing imported private key", e); + Toast.makeText(PubkeyListActivity.this, "Problem parsing imported private key", Toast.LENGTH_LONG).show(); + } + } + }) + .setNegativeButton("Cancel", null).create().show(); + + return true; + } + }); return true; } + protected void handleAddKey(final Cursor c) { + int encrypted = c.getInt(COL_ENCRYPTED); + + if(encrypted != 0) { + final View view = inflater.inflate(R.layout.dia_password, null); + final EditText passwordField = (EditText)view.findViewById(android.R.id.text1); + + new AlertDialog.Builder(PubkeyListActivity.this) + .setView(view) + .setPositiveButton("Unlock key", new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + handleAddKey(c, passwordField.getText().toString()); + } + }) + .setNegativeButton("Cancel", null).create().show(); + } else { + handleAddKey(c, null); + + } + + + } + + protected void handleAddKey(Cursor c, String password) { + String keyNickname = c.getString(COL_NICKNAME); + Object trileadKey = null; + String type = c.getString(COL_TYPE); + if(PubkeyDatabase.KEY_TYPE_IMPORTED.equals(type)) { + // load specific key using pem format + byte[] raw = c.getBlob(COL_PRIVATE); + try { + trileadKey = PEMDecoder.decode(new String(raw).toCharArray(), password); + } catch(Exception e) { + String message = String.format("Bad password for key '%s'. Authentication failed.", keyNickname); + Log.e(TAG, message, e); + Toast.makeText(PubkeyListActivity.this, message, Toast.LENGTH_LONG); + } + + } else { + // load using internal generated format + PrivateKey privKey = null; + PublicKey pubKey = null; + try { + privKey = PubkeyUtils.decodePrivate(c.getBlob(COL_PRIVATE), c.getString(COL_TYPE), password); + pubKey = PubkeyUtils.decodePublic(c.getBlob(COL_PUBLIC), c.getString(COL_TYPE)); + } catch (Exception e) { + String message = String.format("Bad password for key '%s'. Authentication failed.", keyNickname); + Log.e(TAG, message, e); + Toast.makeText(PubkeyListActivity.this, message, Toast.LENGTH_LONG); + } + + // convert key to trilead format + trileadKey = PubkeyUtils.convertToTrilead(privKey, pubKey); + Log.d(TAG, "Unlocked key " + PubkeyUtils.formatKey(pubKey)); + } + + if(trileadKey == null) return; + + Log.d(TAG, String.format("Unlocked key '%s'", keyNickname)); + + // save this key in-memory if option enabled + if(bound.isSavingKeys()) { + bound.addKey(keyNickname, trileadKey); + } + + updateHandler.sendEmptyMessage(-1); + + + } + + protected MenuItem onstartToggle = null; + @Override public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { // Create menu to handle deleting and editing pubkey AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo; - Cursor cursor = (Cursor) this.getListView().getItemAtPosition(info.position); + final Cursor cursor = (Cursor) this.getListView().getItemAtPosition(info.position); final String nickname = cursor.getString(COL_NICKNAME); menu.setHeaderTitle(nickname); + // TODO: option load/unload key from in-memory list + // prompt for password as needed for passworded keys + final int id = cursor.getInt(COL_ID); final byte[] pubkeyEncoded = cursor.getBlob(COL_PUBLIC); final String keyType = cursor.getString(COL_TYPE); final int encrypted = cursor.getInt(COL_ENCRYPTED); + + // cant change password or clipboard imported keys + boolean imported = PubkeyDatabase.KEY_TYPE_IMPORTED.equals(cursor.getString(COL_TYPE)); + final boolean loaded = bound.isKeyLoaded(nickname); + final boolean onstart = (cursor.getInt(COL_STARTUP) == 1); - MenuItem delete = menu.add(R.string.pubkey_delete); - delete.setOnMenuItemClickListener(new OnMenuItemClickListener() { + MenuItem load = menu.add(loaded ? "Unload from memory" : "Load into memory"); + load.setOnMenuItemClickListener(new OnMenuItemClickListener() { public boolean onMenuItemClick(MenuItem item) { - // prompt user to make sure they really want this - new AlertDialog.Builder(PubkeyListActivity.this) - .setMessage(getString(R.string.delete_message, nickname)) - .setPositiveButton(R.string.delete_pos, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - pubkeydb.deletePubkey(id); - updateHandler.sendEmptyMessage(-1); - } - }) - .setNegativeButton(R.string.delete_neg, null).create().show(); - + if(loaded) { + bound.removeKey(nickname); + updateHandler.sendEmptyMessage(-1); + } else { + handleAddKey(cursor); + //bound.addKey(nickname, trileadKey); + } return true; } }); + onstartToggle = menu.add("Load key on start"); + onstartToggle.setEnabled((encrypted == 0)); + onstartToggle.setCheckable(true); + onstartToggle.setChecked(onstart); + onstartToggle.setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + // toggle onstart status + pubkeydb.setOnStart(id, !onstart); + updateHandler.sendEmptyMessage(-1); + return true; + } + }); + MenuItem copyToClipboard = menu.add(R.string.pubkey_copy_clipboard); + copyToClipboard.setEnabled(!imported); copyToClipboard.setOnMenuItemClickListener(new OnMenuItemClickListener() { public boolean onMenuItemClick(MenuItem item) { try { @@ -169,6 +385,7 @@ public class PubkeyListActivity extends ListActivity implements EventListener { }); MenuItem changePassword = menu.add(R.string.pubkey_change_password); + changePassword.setEnabled(!imported); changePassword.setOnMenuItemClickListener(new OnMenuItemClickListener() { public boolean onMenuItemClick(MenuItem item) { final View changePasswordView = inflater.inflate(R.layout.dia_changepassword, null, false); @@ -199,8 +416,7 @@ public class PubkeyListActivity extends ListActivity implements EventListener { else updateHandler.sendEmptyMessage(-1); } catch (Exception e) { - Log.e(TAG, "Could not change private key password"); - e.printStackTrace(); + Log.e(TAG, "Could not change private key password", e); new AlertDialog.Builder(PubkeyListActivity.this) .setMessage(R.string.alert_key_corrupted_msg) .setPositiveButton(android.R.string.ok, null) @@ -213,8 +429,28 @@ public class PubkeyListActivity extends ListActivity implements EventListener { return true; } }); + + MenuItem delete = menu.add(R.string.pubkey_delete); + delete.setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + // prompt user to make sure they really want this + new AlertDialog.Builder(PubkeyListActivity.this) + .setMessage(getString(R.string.delete_message, nickname)) + .setPositiveButton(R.string.delete_pos, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + pubkeydb.deletePubkey(id); + updateHandler.sendEmptyMessage(-1); + } + }) + .setNegativeButton(R.string.delete_neg, null).create().show(); + + return true; + } + }); + } + public Handler updateHandler = new Handler() { @Override public void handleMessage(Message msg) { @@ -224,7 +460,8 @@ public class PubkeyListActivity extends ListActivity implements EventListener { protected void updateCursor() { if (this.pubkeys != null) - pubkeys.requery(); + pubkeys.close(); + //pubkeys.requery(); if (this.pubkeydb == null) return; @@ -236,7 +473,7 @@ public class PubkeyListActivity extends ListActivity implements EventListener { adapter.setViewBinder(new PubkeyBinder()); this.setListAdapter(adapter); - this.startManagingCursor(pubkeys); + //this.startManagingCursor(pubkeys); } class PubkeyBinder implements ViewBinder { @@ -244,25 +481,55 @@ public class PubkeyListActivity extends ListActivity implements EventListener { switch (view.getId()) { case android.R.id.text2: int encrypted = cursor.getInt(cursor.getColumnIndexOrThrow(PubkeyDatabase.FIELD_PUBKEY_ENCRYPTED)); + boolean imported = PubkeyDatabase.KEY_TYPE_IMPORTED.equals(cursor.getString(COL_TYPE)); + TextView caption = (TextView)view; - PublicKey pub; - try { - pub = PubkeyUtils.decodePublic(cursor.getBlob(cursor.getColumnIndexOrThrow(PubkeyDatabase.FIELD_PUBKEY_PUBLIC)), - cursor.getString(columnIndex)); - ((TextView)view).setText(PubkeyUtils.describeKey(pub, encrypted)); - } catch (Exception e) { - e.printStackTrace(); + if(imported) { + // for imported keys, have trilead parse them to get stats + try { + byte[] raw = cursor.getBlob(cursor.getColumnIndexOrThrow(PubkeyDatabase.FIELD_PUBKEY_PRIVATE)); + PEMStructure struct = PEMDecoder.parsePEM(new String(raw).toCharArray()); + String type = (struct.pemType == PEMDecoder.PEM_RSA_PRIVATE_KEY) ? "RSA" : "DSA"; + caption.setText(String.format("%s unknown-bit", type)); + } catch (IOException e) { + Log.e(TAG, "Error decoding IMPORTED public key at " + cursor.toString(), e); + } + - ((TextView)view).setText(R.string.pubkey_unknown_format); - Log.e(TAG, "Error decoding public key at " + cursor.toString()); + } else { + + try { + PublicKey pub = PubkeyUtils.decodePublic(cursor.getBlob(cursor.getColumnIndexOrThrow(PubkeyDatabase.FIELD_PUBKEY_PUBLIC)), + cursor.getString(columnIndex)); + caption.setText(PubkeyUtils.describeKey(pub, encrypted)); + } catch (Exception e) { + Log.e(TAG, "Error decoding public key at " + cursor.toString(), e); + caption.setText(R.string.pubkey_unknown_format); + } } + return true; case android.R.id.icon1: - if (cursor.getInt(columnIndex) != 0) - ((ImageView)view).setImageState(new int[] { android.R.attr.state_checked }, true); + + ImageView icon = (ImageView)view; + if(bound == null) { + icon.setVisibility(View.GONE); + return true; + + } else { + icon.setVisibility(View.VISIBLE); + + } + + // read key in-memory status from backend terminalmanager + String nickname = cursor.getString(COL_NICKNAME); + boolean loaded = bound.isKeyLoaded(nickname); + + if(loaded) + icon.setImageState(new int[] { android.R.attr.state_checked }, true); else - ((ImageView)view).setImageState(new int[] { }, true); + icon.setImageState(new int[] { }, true); return true; } diff --git a/src/org/connectbot/service/TerminalBridge.java b/src/org/connectbot/service/TerminalBridge.java index 6d7a51c..48ad9d9 100644 --- a/src/org/connectbot/service/TerminalBridge.java +++ b/src/org/connectbot/service/TerminalBridge.java @@ -26,6 +26,7 @@ import java.security.PrivateKey; import java.security.PublicKey; import java.security.spec.InvalidKeySpecException; +import org.connectbot.R; import org.connectbot.TerminalView; import org.connectbot.util.HostDatabase; import org.connectbot.util.PubkeyDatabase; @@ -51,6 +52,7 @@ import com.trilead.ssh2.InteractiveCallback; import com.trilead.ssh2.KnownHosts; import com.trilead.ssh2.ServerHostKeyVerifier; import com.trilead.ssh2.Session; +import com.trilead.ssh2.crypto.PEMDecoder; import de.mud.terminal.VDUBuffer; import de.mud.terminal.VDUDisplay; @@ -124,9 +126,9 @@ public class TerminalBridge implements VDUDisplay, OnKeyListener, InteractiveCal private boolean altPressed = false; private boolean shiftPressed = false; - protected PubkeyDatabase pubkeydb = null; - protected Cursor pubkeys = null; - final int COL_NICKNAME, COL_TYPE, COL_PRIVATE, COL_PUBLIC, COL_ENCRYPTED; + //protected PubkeyDatabase pubkeydb = null; + //protected Cursor pubkeys = null; + //final int COL_NICKNAME, COL_TYPE, COL_PRIVATE, COL_PUBLIC, COL_ENCRYPTED; private boolean pubkeysExhausted = false; private boolean forcedSize = false; @@ -240,14 +242,14 @@ public class TerminalBridge implements VDUDisplay, OnKeyListener, InteractiveCal this.connection = new Connection(hostname, port); this.connection.addConnectionMonitor(this); - this.pubkeydb = new PubkeyDatabase(manager); - this.pubkeys = this.pubkeydb.allPubkeys(); - - this.COL_NICKNAME = pubkeys.getColumnIndexOrThrow(PubkeyDatabase.FIELD_PUBKEY_NICKNAME); - this.COL_TYPE = pubkeys.getColumnIndexOrThrow(PubkeyDatabase.FIELD_PUBKEY_TYPE); - this.COL_PRIVATE = pubkeys.getColumnIndexOrThrow(PubkeyDatabase.FIELD_PUBKEY_PRIVATE); - this.COL_PUBLIC = pubkeys.getColumnIndexOrThrow(PubkeyDatabase.FIELD_PUBKEY_PUBLIC); - this.COL_ENCRYPTED = pubkeys.getColumnIndexOrThrow(PubkeyDatabase.FIELD_PUBKEY_ENCRYPTED); +// this.pubkeydb = new PubkeyDatabase(manager); +// this.pubkeys = this.pubkeydb.allPubkeys(); +// +// this.COL_NICKNAME = pubkeys.getColumnIndexOrThrow(PubkeyDatabase.FIELD_PUBKEY_NICKNAME); +// this.COL_TYPE = pubkeys.getColumnIndexOrThrow(PubkeyDatabase.FIELD_PUBKEY_TYPE); +// this.COL_PRIVATE = pubkeys.getColumnIndexOrThrow(PubkeyDatabase.FIELD_PUBKEY_PRIVATE); +// this.COL_PUBLIC = pubkeys.getColumnIndexOrThrow(PubkeyDatabase.FIELD_PUBKEY_PUBLIC); +// this.COL_ENCRYPTED = pubkeys.getColumnIndexOrThrow(PubkeyDatabase.FIELD_PUBKEY_ENCRYPTED); } public final static int AUTH_TRIES = 20; @@ -285,35 +287,72 @@ public class TerminalBridge implements VDUDisplay, OnKeyListener, InteractiveCal * @throws IOException */ public boolean tryPublicKey(Cursor c) throws NoSuchAlgorithmException, InvalidKeySpecException, IOException { + int COL_NICKNAME = c.getColumnIndexOrThrow(PubkeyDatabase.FIELD_PUBKEY_NICKNAME), + COL_TYPE = c.getColumnIndexOrThrow(PubkeyDatabase.FIELD_PUBKEY_TYPE), + COL_PRIVATE = c.getColumnIndexOrThrow(PubkeyDatabase.FIELD_PUBKEY_PRIVATE), + COL_PUBLIC = c.getColumnIndexOrThrow(PubkeyDatabase.FIELD_PUBKEY_PUBLIC), + COL_ENCRYPTED = c.getColumnIndexOrThrow(PubkeyDatabase.FIELD_PUBKEY_ENCRYPTED); + String keyNickname = c.getString(COL_NICKNAME); int encrypted = c.getInt(COL_ENCRYPTED); - String password = null; - if (encrypted != 0) - password = promptHelper.requestStringPrompt(String.format("Password for key '%s'", keyNickname)); - - PrivateKey privKey; - try { - privKey = PubkeyUtils.decodePrivate(c.getBlob(COL_PRIVATE), - c.getString(COL_TYPE), password); - } catch (Exception e) { - e.printStackTrace(); - outputLine("Bad password for key '" + keyNickname + "'. Authentication failed."); - return false; + Object trileadKey = null; + if(manager.isKeyLoaded(keyNickname)) { + // load this key from memory if its already there + Log.d(TAG, String.format("Found unlocked key '%s' already in-memory", keyNickname)); + trileadKey = manager.getKey(keyNickname); + + } else { + // otherwise load key from database and prompt for password as needed + String password = null; + if (encrypted != 0) + password = promptHelper.requestStringPrompt(String.format("Password for key '%s'", keyNickname)); + + String type = c.getString(COL_TYPE); + if(PubkeyDatabase.KEY_TYPE_IMPORTED.equals(type)) { + // load specific key using pem format + byte[] raw = c.getBlob(COL_PRIVATE); + trileadKey = PEMDecoder.decode(new String(raw).toCharArray(), password); + + } else { + // load using internal generated format + PrivateKey privKey; + try { + privKey = PubkeyUtils.decodePrivate(c.getBlob(COL_PRIVATE), + c.getString(COL_TYPE), password); + } catch (Exception e) { + String message = String.format("Bad password for key '%s'. Authentication failed.", keyNickname); + Log.e(TAG, message, e); + outputLine(message); + return false; + } + + PublicKey pubKey = PubkeyUtils.decodePublic(c.getBlob(COL_PUBLIC), + c.getString(COL_TYPE)); + + // convert key to trilead format + trileadKey = PubkeyUtils.convertToTrilead(privKey, pubKey); + Log.d(TAG, "Unlocked key " + PubkeyUtils.formatKey(pubKey)); + } + + Log.d(TAG, String.format("Unlocked key '%s'", keyNickname)); + + // save this key in-memory if option enabled + if(manager.isSavingKeys()) { + manager.addKey(keyNickname, trileadKey); + } } + + return this.tryPublicKey(this.username, nickname, trileadKey); - PublicKey pubKey = PubkeyUtils.decodePublic(c.getBlob(COL_PUBLIC), - c.getString(COL_TYPE)); - - Log.d("TerminalBridge", "Trying key " + PubkeyUtils.formatKey(pubKey)); - - if (connection.authenticateWithPublicKey(username, - PubkeyUtils.convertToTrilead(privKey, pubKey))) - return true; - else - outputLine("Authentication method 'publickey' with key " + keyNickname + " failed"); - - return false; + } + + protected boolean tryPublicKey(String username, String keyNickname, Object trileadKey) throws IOException { + outputLine(String.format("Attempting 'publickey' with key '%s' [%s]...", keyNickname, trileadKey.toString())); + boolean success = connection.authenticateWithPublicKey(username, trileadKey); + if(!success) + outputLine(String.format("Authentication method 'publickey' with key '%s' failed", keyNickname)); + return success; } public void handleAuthentication() { @@ -334,25 +373,33 @@ public class TerminalBridge implements VDUDisplay, OnKeyListener, InteractiveCal if (!pubkeysExhausted && pubkeyId != HostDatabase.PUBKEYID_NEVER && connection.isAuthMethodAvailable(username, AUTH_PUBLICKEY)) { - Cursor cursor; + + // if explicit pubkey defined for this host, then prompt for password as needed + // otherwise just try all in-memory keys held in terminalmanager + outputLine("Attempting 'publickey' authentication"); if (pubkeyId == HostDatabase.PUBKEYID_ANY) { - cursor = pubkeydb.allPubkeys(); - - while (cursor.moveToNext()) { - if (cursor.getInt(COL_ENCRYPTED) == 0) { - if (tryPublicKey(cursor)) - finishConnection(); + // try each of the in-memory keys + outputLine("Trying any loaded SSH keys"); + for(String nickname : manager.loadedPubkeys.keySet()) { + Object trileadKey = manager.loadedPubkeys.get(nickname); + if(this.tryPublicKey(this.username, nickname, trileadKey)) { + finishConnection(); + break; } } + } else { - cursor = pubkeydb.getPubkey(pubkeyId); + outputLine("Host settings requested a specific SSH key"); + // use a specific key for this host, as requested + Cursor cursor = manager.pubkeydb.getPubkey(pubkeyId); if (cursor.moveToFirst()) if (tryPublicKey(cursor)) finishConnection(); + cursor.close(); + } - cursor.close(); pubkeysExhausted = true; } else if (connection.isAuthMethodAvailable(username, AUTH_PASSWORD)) { outputLine("Attempting 'password' authentication"); @@ -491,7 +538,7 @@ public class TerminalBridge implements VDUDisplay, OnKeyListener, InteractiveCal /** * Force disconnection of this terminal bridge. */ - public void disconnect() { + public void dispatchDisconnect() { // disconnection request hangs if we havent really connected to a host yet // temporary fix is to just spawn disconnection into a thread new Thread(new Runnable() { @@ -619,8 +666,21 @@ public class TerminalBridge implements VDUDisplay, OnKeyListener, InteractiveCal // look for special chars switch(keyCode) { case KeyEvent.KEYCODE_CAMERA: - this.stdin.write(0x01); - this.stdin.write(' '); + + // check to see which shortcut the camera button triggers + String camera = manager.prefs.getString(manager.res.getString(R.string.pref_camera), manager.res.getString(R.string.list_camera_ctrlaspace)); + if(manager.res.getString(R.string.list_camera_ctrlaspace).equals(camera)) { + this.stdin.write(0x01); + this.stdin.write(' '); + + } else if(manager.res.getString(R.string.list_camera_ctrla).equals(camera)) { + this.stdin.write(0x01); + + } else if(manager.res.getString(R.string.list_camera_esc).equals(camera)) { + ((vt320)buffer).keyTyped(vt320.KEY_ESCAPE, ' ', 0); + + } + //((vt320)buffer).keyTyped('a', 'a', vt320.KEY_CONTROL); //((vt320)buffer).keyTyped(' ', ' ', 0); break; @@ -759,9 +819,6 @@ public class TerminalBridge implements VDUDisplay, OnKeyListener, InteractiveCal this.bitmap.recycle(); this.bitmap = null; this.canvas.setBitmap(null); - - if (this.pubkeydb != null) - this.pubkeydb.close(); } public void setVDUBuffer(VDUBuffer buffer) { @@ -862,7 +919,7 @@ public class TerminalBridge implements VDUDisplay, OnKeyListener, InteractiveCal public void connectionLost(Throwable reason) { // weve lost our ssh connection, so pass along to manager and gui Log.e(TAG, "Somehow our underlying SSH socket died", reason); - this.disconnect(); + this.dispatchDisconnect(); } /** diff --git a/src/org/connectbot/service/TerminalManager.java b/src/org/connectbot/service/TerminalManager.java index 8559f5c..054e24f 100644 --- a/src/org/connectbot/service/TerminalManager.java +++ b/src/org/connectbot/service/TerminalManager.java @@ -18,15 +18,24 @@ package org.connectbot.service; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.spec.InvalidKeySpecException; +import java.util.HashMap; import java.util.LinkedList; import java.util.List; import org.connectbot.R; import org.connectbot.util.HostDatabase; +import org.connectbot.util.PubkeyDatabase; +import org.connectbot.util.PubkeyUtils; import android.app.Service; import android.content.Intent; import android.content.SharedPreferences; +import android.content.res.Resources; +import android.database.Cursor; import android.net.Uri; import android.os.Binder; import android.os.Handler; @@ -51,9 +60,15 @@ public class TerminalManager extends Service implements BridgeDisconnectedListen public List<String> disconnected = new LinkedList<String>(); + protected HashMap<String, Object> loadedPubkeys = new HashMap<String, Object>(); + + protected Resources res; + protected HostDatabase hostdb; + protected PubkeyDatabase pubkeydb; + protected SharedPreferences prefs; - protected String pref_emulation, pref_scrollback, pref_keymode; + protected String pref_emulation, pref_scrollback, pref_keymode, pref_memkeys; @Override public void onCreate() { @@ -62,9 +77,36 @@ public class TerminalManager extends Service implements BridgeDisconnectedListen this.pref_emulation = this.getResources().getString(R.string.pref_emulation); this.pref_scrollback = this.getResources().getString(R.string.pref_scrollback); this.pref_keymode = this.getResources().getString(R.string.pref_keymode); + this.pref_memkeys = this.getResources().getString(R.string.pref_memkeys); + + this.res = this.getResources(); this.hostdb = new HostDatabase(this); + this.pubkeydb = new PubkeyDatabase(this); + + // load all marked pubkeys into memory + Cursor c = pubkeydb.getAllStartPubkeys(); + int COL_NICKNAME = c.getColumnIndexOrThrow(PubkeyDatabase.FIELD_PUBKEY_NICKNAME), + COL_TYPE = c.getColumnIndexOrThrow(PubkeyDatabase.FIELD_PUBKEY_TYPE), + COL_PRIVATE = c.getColumnIndexOrThrow(PubkeyDatabase.FIELD_PUBKEY_PRIVATE), + COL_PUBLIC = c.getColumnIndexOrThrow(PubkeyDatabase.FIELD_PUBKEY_PUBLIC); + + while(c.moveToNext()) { + String keyNickname = c.getString(COL_NICKNAME); + try { + PrivateKey privKey = PubkeyUtils.decodePrivate(c.getBlob(COL_PRIVATE), c.getString(COL_TYPE)); + PublicKey pubKey = PubkeyUtils.decodePublic(c.getBlob(COL_PUBLIC), c.getString(COL_TYPE)); + Object trileadKey = PubkeyUtils.convertToTrilead(privKey, pubKey); + + this.loadedPubkeys.put(keyNickname, trileadKey); + Log.d(TAG, String.format("Added key '%s' to in-memory cache", keyNickname)); + } catch (Exception e) { + Log.d(TAG, String.format("Problem adding key '%s' to in-memory cache", keyNickname), e); + } + } + c.close(); + } @Override @@ -73,10 +115,17 @@ public class TerminalManager extends Service implements BridgeDisconnectedListen // disconnect and dispose of any existing bridges for(TerminalBridge bridge : bridges) - bridge.disconnect(); + bridge.dispatchDisconnect(); - if(this.hostdb != null) + if(this.hostdb != null) { this.hostdb.close(); + this.hostdb = null; + } + + if(this.pubkeydb != null) { + this.pubkeydb.close(); + this.pubkeydb = null; + } } @@ -112,12 +161,16 @@ public class TerminalManager extends Service implements BridgeDisconnectedListen return scrollback; } + public boolean isSavingKeys() { + return prefs.getBoolean(this.pref_memkeys, true); + } + public String getPostLogin(String nickname) { return hostdb.getPostLogin(nickname); } public String getKeyMode() { - return prefs.getString(this.pref_keymode, "Use right-side keys"); + return prefs.getString(this.pref_keymode, getString(R.string.list_keymode_right)); // "Use right-side keys" } /** @@ -155,14 +208,14 @@ public class TerminalManager extends Service implements BridgeDisconnectedListen public Handler disconnectHandler = null; - /** - * Force disconnection of this {@link TerminalBridge} and remove it from our - * internal list of active connections. - */ - public void disconnect(TerminalBridge bridge) { - // we will be notified about this through call back up to onDisconnected() - bridge.disconnect(); - } +// /** +// * Force disconnection of this {@link TerminalBridge} and remove it from our +// * internal list of active connections. +// */ +// public void disconnect(TerminalBridge bridge) { +// // we will be notified about this through call back up to onDisconnected() +// bridge.disconnect(); +// } /** * Called by child bridge when somehow it's been disconnected. @@ -177,6 +230,23 @@ public class TerminalManager extends Service implements BridgeDisconnectedListen Message.obtain(this.disconnectHandler, -1, bridge).sendToTarget(); } + + public boolean isKeyLoaded(String nickname) { + return this.loadedPubkeys.containsKey(nickname); + } + + public void addKey(String nickname, Object trileadKey) { + this.loadedPubkeys.remove(nickname); + this.loadedPubkeys.put(nickname, trileadKey); + } + + public void removeKey(String nickname) { + this.loadedPubkeys.remove(nickname); + } + + public Object getKey(String nickname) { + return this.loadedPubkeys.get(nickname); + } public class TerminalBinder extends Binder { diff --git a/src/org/connectbot/util/HostDatabase.java b/src/org/connectbot/util/HostDatabase.java index 3556875..fe4c8d4 100644 --- a/src/org/connectbot/util/HostDatabase.java +++ b/src/org/connectbot/util/HostDatabase.java @@ -79,7 +79,7 @@ public class HostDatabase extends SQLiteOpenHelper { + FIELD_HOST_COLOR + " TEXT, " + FIELD_HOST_USEKEYS + " TEXT, " + FIELD_HOST_POSTLOGIN + " TEXT, " - + FIELD_HOST_PUBKEYID + " INTEGER DEFAULT " + PUBKEYID_ANY); + + FIELD_HOST_PUBKEYID + " INTEGER DEFAULT " + PUBKEYID_ANY + ")"); // insert a few sample hosts, none of which probably connect //this.createHost(db, "connectbot@bravo", "connectbot", "192.168.254.230", 22, COLOR_GRAY); diff --git a/src/org/connectbot/util/KeyDatabase.java b/src/org/connectbot/util/KeyDatabase.java deleted file mode 100644 index aaca3c1..0000000 --- a/src/org/connectbot/util/KeyDatabase.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - ConnectBot: simple, powerful, open-source SSH client for Android - Copyright (C) 2007-2008 Kenny Root, Jeffrey Sharkey - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see <http://www.gnu.org/licenses/>. -*/ - -package org.connectbot.util; - -import android.content.Context; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteOpenHelper; - -/** - * Contains information about personal private keys used for key-based - * authentication. Find more information here: - * - * http://www.wlug.org.nz/PublicKeyAuthentication - * - * @author jsharkey - */ -public class KeyDatabase extends SQLiteOpenHelper { - - public final static String DB_NAME = "keys"; - public final static int DB_VERSION = 1; - - public final static String TABLE_PRIVKEYS = "keys"; - public final static String FIELD_KEY_NAME = "name"; - public final static String FIELD_KEY_PRIVATE = "private"; - - public KeyDatabase(Context context) { - super(context, DB_NAME, null, DB_VERSION); - } - - @Override - public void onCreate(SQLiteDatabase db) { - db.execSQL("CREATE TABLE " + TABLE_PRIVKEYS - + " (_id INTEGER PRIMARY KEY, " - + FIELD_KEY_NAME + " TEXT, " - + FIELD_KEY_PRIVATE + " TEXT)"); - - } - - @Override - public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { - db.execSQL("DROP TABLE IF EXISTS " + TABLE_PRIVKEYS); - onCreate(db); - } - - -} diff --git a/src/org/connectbot/util/PubkeyDatabase.java b/src/org/connectbot/util/PubkeyDatabase.java index 1c3278b..d3fa3d2 100644 --- a/src/org/connectbot/util/PubkeyDatabase.java +++ b/src/org/connectbot/util/PubkeyDatabase.java @@ -21,17 +21,23 @@ package org.connectbot.util; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; +import java.security.PublicKey; import java.security.spec.InvalidKeySpecException; +import java.util.LinkedList; +import java.util.List; import javax.crypto.BadPaddingException; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; +import org.connectbot.service.PromptHelper; + import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; +import android.util.Log; /** * Public Key Encryption database. Contains private and public key pairs @@ -54,6 +60,10 @@ public class PubkeyDatabase extends SQLiteOpenHelper { public final static String FIELD_PUBKEY_ENCRYPTED = "encrypted"; public final static String FIELD_PUBKEY_STARTUP = "startup"; + public final static String KEY_TYPE_RSA = "RSA", + KEY_TYPE_DSA = "DSA", + KEY_TYPE_IMPORTED = "IMPORTED"; + private Context context; public PubkeyDatabase(Context context) { @@ -133,6 +143,63 @@ public class PubkeyDatabase extends SQLiteOpenHelper { null, null, null); } + public Cursor getAllStartPubkeys() { + SQLiteDatabase db = this.getReadableDatabase(); + return db.query(TABLE_PUBKEYS, new String[] { "_id", + FIELD_PUBKEY_NICKNAME, FIELD_PUBKEY_TYPE, FIELD_PUBKEY_PRIVATE, + FIELD_PUBKEY_PUBLIC, FIELD_PUBKEY_ENCRYPTED, FIELD_PUBKEY_STARTUP }, + FIELD_PUBKEY_STARTUP + " = 1", null, null, null, null); + } + + + /** + * Pull all values for a given column as a list of Strings, probably for use + * in a ListPreference. Sorted by <code>_id</code> ascending. + */ + public List<CharSequence> allValues(String column) { + List<CharSequence> list = new LinkedList<CharSequence>(); + + SQLiteDatabase db = this.getReadableDatabase(); + Cursor c = db.query(TABLE_PUBKEYS, new String[] { "_id", column }, + null, null, null, null, "_id ASC"); + + int COL = c.getColumnIndexOrThrow(column); + while(c.moveToNext()) { + list.add(c.getString(COL)); + } + c.close(); + + return list; + } + + public String getNickname(long id) { + String nickname = null; + + SQLiteDatabase db = this.getReadableDatabase(); + Cursor c = db.query(TABLE_PUBKEYS, new String[] { "_id", + FIELD_PUBKEY_NICKNAME }, "_id = ?", + new String[] { Long.toString(id) }, null, null, null); + + if (c != null && c.moveToFirst()) + nickname = c.getString(c.getColumnIndexOrThrow(FIELD_PUBKEY_NICKNAME)); + + c.close(); + return nickname; + + } + + public void setOnStart(long id, boolean onStart) { + + SQLiteDatabase db = this.getWritableDatabase(); + + ContentValues values = new ContentValues(); + values.put(FIELD_PUBKEY_STARTUP, onStart ? 1 : 0); + + db.update(TABLE_PUBKEYS, values, "_id = ?", new String[] { Long.toString(id) }); + + } + + public boolean changePassword(long id, String oldPassword, String newPassword) throws NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, InvalidKeyException, BadPaddingException { SQLiteDatabase db = this.getWritableDatabase(); |