aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/com/trilead/ssh2/Connection.java52
-rw-r--r--src/de/mud/terminal/VDUBuffer.java4
-rw-r--r--src/de/mud/terminal/VDUInput.java2
-rw-r--r--src/org/connectbot/GeneratePubkeyActivity.java284
-rw-r--r--src/org/connectbot/HostListActivity.java6
-rw-r--r--src/org/connectbot/PubkeyListActivity.java226
-rw-r--r--src/org/connectbot/service/TerminalBridge.java57
-rw-r--r--src/org/connectbot/util/EntropyDialog.java50
-rw-r--r--src/org/connectbot/util/EntropyView.java144
-rw-r--r--src/org/connectbot/util/OnEntropyGatheredListener.java23
-rw-r--r--src/org/connectbot/util/PubkeyDatabase.java110
-rw-r--r--src/org/connectbot/util/PubkeyUtils.java194
12 files changed, 1138 insertions, 14 deletions
diff --git a/src/com/trilead/ssh2/Connection.java b/src/com/trilead/ssh2/Connection.java
index 38b96c5..8fbf6fa 100644
--- a/src/com/trilead/ssh2/Connection.java
+++ b/src/com/trilead/ssh2/Connection.java
@@ -444,7 +444,59 @@ public class Connection
return authenticated;
}
+
+ /**
+ * After a successful connect, one has to authenticate oneself. The
+ * authentication method "publickey" works by signing a challenge sent by
+ * the server. The signature is either DSA or RSA based - it just depends on
+ * the type of private key you specify, either a DSA or RSA private key in
+ * PEM format. And yes, this is may seem to be a little confusing, the
+ * method is called "publickey" in the SSH-2 protocol specification, however
+ * since we need to generate a signature, you actually have to supply a
+ * private key =).
+ * <p>
+ * If the authentication phase is complete, <code>true</code> will be
+ * returned. If the server does not accept the request (or if further
+ * authentication steps are needed), <code>false</code> is returned and
+ * one can retry either by using this or any other authentication method
+ * (use the <code>getRemainingAuthMethods</code> method to get a list of
+ * the remaining possible methods).
+ *
+ * @param user
+ * A <code>String</code> holding the username.
+ * @param key
+ * A <code>RSAPrivateKey</code> or <code>DSAPrivateKey</code>
+ * containing a DSA or RSA private key of
+ * the user in Trilead object format.
+ *
+ * @return whether the connection is now authenticated.
+ * @throws IOException
+ */
+ public synchronized boolean authenticateWithPublicKey(String user, Object key)
+ throws IOException
+ {
+ if (tm == null)
+ throw new IllegalStateException("Connection is not established!");
+
+ if (authenticated)
+ throw new IllegalStateException("Connection is already authenticated!");
+
+ if (am == null)
+ am = new AuthenticationManager(tm);
+ if (cm == null)
+ cm = new ChannelManager(tm);
+
+ if (user == null)
+ throw new IllegalArgumentException("user argument is null");
+
+ if (key == null)
+ throw new IllegalArgumentException("Key argument is null");
+
+ authenticated = am.authenticatePublicKey(user, key, getOrCreateSecureRND());
+
+ return authenticated;
+ }
/**
* A convenience wrapper function which reads in a private key (PEM format,
* either DSA or RSA) and then calls
diff --git a/src/de/mud/terminal/VDUBuffer.java b/src/de/mud/terminal/VDUBuffer.java
index 60e7abf..a0d6b3b 100644
--- a/src/de/mud/terminal/VDUBuffer.java
+++ b/src/de/mud/terminal/VDUBuffer.java
@@ -1,7 +1,7 @@
/*
* This file is part of "JTA - Telnet/SSH for the JAVA(tm) platform".
*
- * (c) Matthias L. Jugel, Marcus Meiner 1996-2005. All Rights Reserved.
+ * (c) Matthias L. Jugel, Marcus Mei§ner 1996-2005. All Rights Reserved.
*
* Please visit http://javatelnet.org/ for updates and contact.
*
@@ -33,7 +33,7 @@ import android.util.Log;
* all methods to manipulate the buffer that stores characters and their
* attributes as well as the regions displayed.
*
- * @author Matthias L. Jugel, Marcus Meiner
+ * @author Matthias L. Jugel, Marcus Mei§ner
* @version $Id: VDUBuffer.java 503 2005-10-24 07:34:13Z marcus $
*/
public class VDUBuffer {
diff --git a/src/de/mud/terminal/VDUInput.java b/src/de/mud/terminal/VDUInput.java
index 0d89f7a..ca7e68b 100644
--- a/src/de/mud/terminal/VDUInput.java
+++ b/src/de/mud/terminal/VDUInput.java
@@ -1,7 +1,7 @@
/*
* This file is part of "JTA - Telnet/SSH for the JAVA(tm) platform".
*
- * (c) Matthias L. Jugel, Marcus Meiner 1996-2005. All Rights Reserved.
+ * (c) Matthias L. Jugel, Marcus Mei§ner 1996-2005. All Rights Reserved.
*
* Please visit http://javatelnet.org/ for updates and contact.
*
diff --git a/src/org/connectbot/GeneratePubkeyActivity.java b/src/org/connectbot/GeneratePubkeyActivity.java
new file mode 100644
index 0000000..d33ffb3
--- /dev/null
+++ b/src/org/connectbot/GeneratePubkeyActivity.java
@@ -0,0 +1,284 @@
+/*
+ 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;
+
+import java.security.Key;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+
+import javax.crypto.Cipher;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.connectbot.util.EntropyDialog;
+import org.connectbot.util.EntropyView;
+import org.connectbot.util.PubkeyDatabase;
+import org.connectbot.util.OnEntropyGatheredListener;
+import org.connectbot.util.PubkeyUtils;
+
+import android.app.Activity;
+import android.app.Dialog;
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnFocusChangeListener;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.EditText;
+import android.widget.RadioGroup;
+import android.widget.SeekBar;
+import android.widget.RadioGroup.OnCheckedChangeListener;
+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;
+
+ private EditText nickname;
+ private RadioGroup keyTypeGroup;
+ private SeekBar bitsSlider;
+ private EditText bitsText;
+ private CheckBox unlockAtStartup;
+ private Button save;
+ private Dialog entropyDialog;
+ private ProgressDialog progress;
+
+ private EditText password1, password2;
+
+ private String keyType = KEY_TYPE_RSA;
+ private int minBits = 768;
+ private int bits = DEFAULT_BITS;
+
+ private byte[] entropy;
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ setContentView(R.layout.act_generatepubkey);
+
+ nickname = (EditText) findViewById(R.id.nickname);
+
+ keyTypeGroup = (RadioGroup) findViewById(R.id.key_type);
+
+ bitsText = (EditText) findViewById(R.id.bits);
+ bitsSlider = (SeekBar) findViewById(R.id.bits_slider);
+
+ password1 = (EditText) findViewById(R.id.password1);
+ password2 = (EditText) findViewById(R.id.password2);
+
+ unlockAtStartup = (CheckBox) findViewById(R.id.unlock_at_startup);
+
+ save = (Button) findViewById(R.id.save);
+
+ inflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+
+ nickname.addTextChangedListener(textChecker);
+ password1.addTextChangedListener(textChecker);
+ password2.addTextChangedListener(textChecker);
+
+ keyTypeGroup.setOnCheckedChangeListener(new OnCheckedChangeListener() {
+
+ public void onCheckedChanged(RadioGroup group, int checkedId) {
+ if (checkedId == R.id.rsa) {
+ minBits = 768;
+
+ bitsSlider.setEnabled(true);
+ bitsSlider.setProgress(DEFAULT_BITS - minBits);
+
+ bitsText.setText(String.valueOf(DEFAULT_BITS));
+ bitsText.setEnabled(true);
+
+ keyType = KEY_TYPE_RSA;
+ } else if (checkedId == R.id.dsa) {
+ // DSA keys can only be 1024 bits
+
+ bitsSlider.setEnabled(false);
+ bitsSlider.setProgress(DEFAULT_BITS - minBits);
+
+ bitsText.setText(String.valueOf(DEFAULT_BITS));
+ bitsText.setEnabled(false);
+
+ keyType = KEY_TYPE_DSA;
+ }
+ }
+ });
+
+ bitsSlider.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
+
+ public void onProgressChanged(SeekBar seekBar, int progress,
+ boolean fromTouch) {
+ // Stay evenly divisible by 8 because it looks nicer to have
+ // 2048 than 2043 bits.
+
+ int leftover = progress % 8;
+
+ if (leftover > 0)
+ progress += 8 - leftover;
+
+ bits = minBits + progress;
+ bitsText.setText(String.valueOf(bits));
+ }
+
+ public void onStartTrackingTouch(SeekBar seekBar) {
+ // We don't care about the start.
+ }
+
+ public void onStopTrackingTouch(SeekBar seekBar) {
+ // We don't care about the stop.
+ }
+ });
+
+ bitsText.setOnFocusChangeListener(new OnFocusChangeListener() {
+ public void onFocusChange(View v, boolean hasFocus) {
+ if (!hasFocus) {
+ try {
+ bits = Integer.parseInt(bitsText.getText().toString());
+ } catch (NumberFormatException nfe) {
+ bits = DEFAULT_BITS;
+ bitsText.setText(String.valueOf(bits));
+ }
+ }
+ }
+ });
+
+ save.setOnClickListener(new OnClickListener() {
+ public void onClick(View view) {
+ GeneratePubkeyActivity.this.save.setEnabled(false);
+
+ GeneratePubkeyActivity.this.startEntropyGather();
+ }
+ });
+
+ }
+
+ private void checkEntries() {
+ boolean allowSave = true;
+
+ if (!password1.getText().toString().equals(password2.getText().toString()))
+ allowSave = false;
+
+ if (nickname.getText().length() == 0)
+ allowSave = false;
+
+ save.setEnabled(allowSave);
+ }
+
+ private void startEntropyGather() {
+ final View entropyView = inflater.inflate(R.layout.dia_gatherentropy, null, false);
+ ((EntropyView)entropyView.findViewById(R.id.entropy)).addOnEntropyGatheredListener(GeneratePubkeyActivity.this);
+ entropyDialog = new EntropyDialog(GeneratePubkeyActivity.this, entropyView);
+ entropyDialog.show();
+ }
+
+ public void onEntropyGathered(byte[] entropy) {
+ this.entropy = entropy;
+
+ Log.d(TAG, "entropy gathered; attemping to generate key...");
+ startKeyGen();
+ }
+
+ private void startKeyGen() {
+ progress = new ProgressDialog(GeneratePubkeyActivity.this);
+ progress.setMessage(GeneratePubkeyActivity.this.getResources().getText(R.string.pubkey_generating));
+ progress.setIndeterminate(true);
+ progress.setCancelable(false);
+ progress.show();
+
+ new Thread(mKeyGen).start();
+ }
+
+ private Handler handler = new Handler() {
+ public void handleMessage(Message msg) {
+ progress.dismiss();
+ GeneratePubkeyActivity.this.finish();
+ }
+ };
+
+ final private Runnable mKeyGen = new Runnable() {
+ public void run() {
+ try {
+ boolean encrypted = false;
+
+ SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
+
+ random.setSeed(entropy);
+
+ KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(keyType);
+
+ keyPairGen.initialize(bits, random);
+
+ KeyPair pair = keyPairGen.generateKeyPair();
+ PrivateKey priv = pair.getPrivate();
+ PublicKey pub = pair.getPublic();
+
+ String secret = password1.getText().toString();
+ if (secret.length() > 0)
+ encrypted = true;
+
+ Log.d(TAG, "private: " + PubkeyUtils.formatKey(priv));
+ Log.d(TAG, "public: " + PubkeyUtils.formatKey(pub));
+
+ PubkeyDatabase hostdb = new PubkeyDatabase(GeneratePubkeyActivity.this);
+ hostdb.createPubkey(null,
+ nickname.getText().toString(),
+ keyType,
+ PubkeyUtils.getEncodedPrivate(priv, secret),
+ PubkeyUtils.getEncodedPublic(pub),
+ encrypted,
+ unlockAtStartup.isChecked());
+ } catch (Exception e) {
+ Log.e(TAG, "Could not generate key pair");
+
+ e.printStackTrace();
+ }
+
+ handler.sendEmptyMessage(0);
+ }
+
+ };
+
+ final private TextWatcher textChecker = new TextWatcher() {
+ public void afterTextChanged(Editable s) {}
+
+ public void beforeTextChanged(CharSequence s, int start, int count,
+ int after) {}
+
+ public void onTextChanged(CharSequence s, int start, int before,
+ int count) {
+ checkEntries();
+ }
+ };
+}
diff --git a/src/org/connectbot/HostListActivity.java b/src/org/connectbot/HostListActivity.java
index 0e6c915..fc1ef6b 100644
--- a/src/org/connectbot/HostListActivity.java
+++ b/src/org/connectbot/HostListActivity.java
@@ -346,9 +346,9 @@ public class HostListActivity extends ListActivity {
}
});
- //MenuItem keys = menu.add("Manage keys");
- //keys.setIcon(android.R.drawable.ic_lock_lock);
- //keys.setEnabled(false);
+ MenuItem keys = menu.add("Manage keys");
+ keys.setIcon(android.R.drawable.ic_lock_lock);
+ keys.setIntent(new Intent(HostListActivity.this, PubkeyListActivity.class));
MenuItem settings = menu.add(R.string.list_menu_settings);
settings.setIcon(android.R.drawable.ic_menu_preferences);
diff --git a/src/org/connectbot/PubkeyListActivity.java b/src/org/connectbot/PubkeyListActivity.java
new file mode 100644
index 0000000..a8b2bed
--- /dev/null
+++ b/src/org/connectbot/PubkeyListActivity.java
@@ -0,0 +1,226 @@
+/*
+ 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;
+
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.EncodedKeySpec;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.EventListener;
+
+import org.connectbot.util.EntropyView;
+import org.connectbot.util.PubkeyDatabase;
+import org.connectbot.util.PubkeyUtils;
+
+import android.app.AlertDialog;
+import android.app.ListActivity;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.database.Cursor;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.text.ClipboardManager;
+import android.util.Log;
+import android.view.ContextMenu;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.MenuItem.OnMenuItemClickListener;
+import android.widget.AdapterView;
+import android.widget.CursorAdapter;
+import android.widget.LinearLayout;
+import android.widget.ListView;
+import android.widget.TextView;
+
+public class PubkeyListActivity extends ListActivity implements EventListener {
+ public final static String TAG = PubkeyListActivity.class.toString();
+
+ protected PubkeyDatabase pubkeydb;
+ protected Cursor pubkeys;
+
+ protected int COL_ID, COL_NICKNAME, COL_TYPE, COL_PRIVATE, COL_PUBLIC, COL_ENCRYPTED, COL_STARTUP;
+
+ protected ClipboardManager clipboard;
+
+ @Override
+ public void onStart() {
+ super.onStart();
+
+ if(this.pubkeydb == null)
+ this.pubkeydb = new PubkeyDatabase(this);
+
+ this.updateCursor();
+
+ ListView list = this.getListView();
+ this.registerForContextMenu(list);
+ }
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ setContentView(R.layout.act_pubkeylist);
+
+ // connect with hosts database and populate list
+ this.pubkeydb = new PubkeyDatabase(this);
+
+ this.updateCursor();
+
+ this.COL_ID = pubkeys.getColumnIndexOrThrow("_id");
+ 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.COL_STARTUP = pubkeys.getColumnIndexOrThrow(PubkeyDatabase.FIELD_PUBKEY_STARTUP);
+
+ this.clipboard = (ClipboardManager)this.getSystemService(CLIPBOARD_SERVICE);
+ }
+
+ @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.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));
+
+ return true;
+ }
+
+ @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 String nickname = cursor.getString(COL_NICKNAME);
+ menu.setHeaderTitle(nickname);
+ final int id = cursor.getInt(COL_ID);
+ final byte[] pubkeyEncoded = cursor.getBlob(COL_PUBLIC);
+ final String keyType = cursor.getString(COL_TYPE);
+
+ 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;
+ }
+ });
+
+ MenuItem copyToClipboard = menu.add(R.string.pubkey_copy_clipboard);
+ copyToClipboard.setOnMenuItemClickListener(new OnMenuItemClickListener() {
+ public boolean onMenuItemClick(MenuItem item) {
+ try {
+ Log.d(TAG, "Trying to decode public key format: " + keyType);
+
+ PublicKey pk = PubkeyUtils.decodePublic(pubkeyEncoded, keyType);
+ String openSSHPubkey = new String(PubkeyUtils.convertToOpenSSHFormat(pk));
+
+ Log.d(TAG, "OpenSSH format: " + openSSHPubkey);
+
+ clipboard.setText(openSSHPubkey);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return true;
+ }
+ });
+ }
+
+ public Handler updateHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ PubkeyListActivity.this.updateCursor();
+ }
+ };
+
+ protected void updateCursor() {
+ this.pubkeys = this.pubkeydb.allPubkeys();
+
+ this.setListAdapter(new PubkeyCursorAdapter(this, this.pubkeys));
+ }
+
+ class PubkeyCursorAdapter extends CursorAdapter {
+ private final LayoutInflater mInflater;
+ private final int mNickname;
+ private final int mPubkey;
+ private final int mKeyType;
+ private final int mEncrypted;
+
+ public PubkeyCursorAdapter(Context context, Cursor c) {
+ super(context, c);
+
+ mInflater = LayoutInflater.from(context);
+ mNickname = c.getColumnIndexOrThrow(PubkeyDatabase.FIELD_PUBKEY_NICKNAME);
+ mPubkey = c.getColumnIndexOrThrow(PubkeyDatabase.FIELD_PUBKEY_PUBLIC);
+ mEncrypted = c.getColumnIndexOrThrow(PubkeyDatabase.FIELD_PUBKEY_ENCRYPTED);
+ mKeyType = c.getColumnIndexOrThrow(PubkeyDatabase.FIELD_PUBKEY_TYPE);
+ }
+
+ @Override
+ public void bindView(View view, Context context, Cursor cursor) {
+ TextView text1 = (TextView) view.findViewById(android.R.id.text1);
+ TextView text2 = (TextView) view.findViewById(android.R.id.text2);
+
+ text1.setText(cursor.getString(mNickname));
+
+ String keyType = cursor.getString(mKeyType);
+ int encrypted = cursor.getInt(mEncrypted);
+ PublicKey pk;
+ try {
+ pk = PubkeyUtils.decodePublic(cursor.getBlob(mPubkey), keyType);
+ text2.setText(PubkeyUtils.describeKey(pk, encrypted));
+ } catch (Exception e) {
+ e.printStackTrace();
+
+ Log.e(TAG, "Error decoding public key at " + cursor.toString());
+ }
+ }
+
+ @Override
+ public View newView(Context context, Cursor cursor, ViewGroup parent) {
+ final LinearLayout view = (LinearLayout) mInflater.inflate(
+ R.layout.item_pubkey, parent, false);
+ return view;
+ }
+ }
+
+}
diff --git a/src/org/connectbot/service/TerminalBridge.java b/src/org/connectbot/service/TerminalBridge.java
index c8df48f..9f8bd9d 100644
--- a/src/org/connectbot/service/TerminalBridge.java
+++ b/src/org/connectbot/service/TerminalBridge.java
@@ -21,12 +21,15 @@ package org.connectbot.service;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
-import java.util.concurrent.Semaphore;
+import java.security.PrivateKey;
+import java.security.PublicKey;
-import org.connectbot.ConsoleActivity;
import org.connectbot.TerminalView;
-import org.connectbot.util.HostDatabase;
+import org.connectbot.util.PubkeyDatabase;
+import org.connectbot.util.PubkeyUtils;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
@@ -34,8 +37,6 @@ import android.graphics.Paint;
import android.graphics.Typeface;
import android.graphics.Bitmap.Config;
import android.graphics.Paint.FontMetricsInt;
-import android.os.Handler;
-import android.os.Message;
import android.util.Log;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
@@ -121,6 +122,10 @@ public class TerminalBridge implements VDUDisplay, OnKeyListener, InteractiveCal
private String currentMethod = null;
+ protected PubkeyDatabase pubkeydb = null;
+ protected Cursor pubkeys = null;
+ final int COL_NICKNAME, COL_TYPE, COL_PRIVATE, COL_PUBLIC, COL_ENCRYPTED;
+ private boolean pubkeysExhausted = false;
public class HostKeyVerifier implements ServerHostKeyVerifier {
@@ -226,6 +231,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);
}
public final static int AUTH_TRIES = 20;
@@ -268,9 +281,34 @@ public class TerminalBridge implements VDUDisplay, OnKeyListener, InteractiveCal
try {
- // TODO: insert publickey auth check here
-
- if(connection.isAuthMethodAvailable(username, AUTH_PASSWORD)) {
+ if (!pubkeysExhausted &&
+ connection.isAuthMethodAvailable(username, AUTH_PUBLICKEY)) {
+ Cursor cursor = pubkeydb.allPubkeys();
+ String keyNickname;
+ PrivateKey privKey;
+ PublicKey pubKey;
+
+ while (cursor.moveToNext()) {
+ if (cursor.getInt(COL_ENCRYPTED) == 0) {
+ keyNickname = cursor.getString(COL_NICKNAME);
+ privKey = PubkeyUtils.decodePrivate(cursor.getBlob(COL_PRIVATE),
+ cursor.getString(COL_TYPE));
+ pubKey = PubkeyUtils.decodePublic(cursor.getBlob(COL_PUBLIC),
+ cursor.getString(COL_TYPE));
+
+ Log.d("TerminalBridge", "Trying key " + PubkeyUtils.formatKey(pubKey));
+
+ if (connection.authenticateWithPublicKey(username,
+ PubkeyUtils.convertToTrilead(privKey, pubKey))) {
+ finishConnection();
+ } else {
+ outputLine("Authentication method 'publickey' with key " + keyNickname + " failed");
+ }
+ }
+ }
+
+ pubkeysExhausted = true;
+ } else if (connection.isAuthMethodAvailable(username, AUTH_PASSWORD)) {
outputLine("Attempting 'password' authentication");
String password = promptHelper.requestStringPrompt("Password");
if(connection.authenticateWithPassword(username, password)) {
@@ -638,6 +676,9 @@ 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) {
diff --git a/src/org/connectbot/util/EntropyDialog.java b/src/org/connectbot/util/EntropyDialog.java
new file mode 100644
index 0000000..c6dc4e1
--- /dev/null
+++ b/src/org/connectbot/util/EntropyDialog.java
@@ -0,0 +1,50 @@
+/*
+ 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 org.connectbot.R;
+
+import android.app.Dialog;
+import android.content.Context;
+import android.view.View;
+
+public class EntropyDialog extends Dialog implements OnEntropyGatheredListener {
+
+ public EntropyDialog(Context context) {
+ super(context);
+
+ this.setContentView(R.layout.dia_gatherentropy);
+ this.setTitle(R.string.pubkey_gather_entropy);
+
+ ((EntropyView) findViewById(R.id.entropy)).addOnEntropyGatheredListener(this);
+ }
+
+ public EntropyDialog(Context context, View view) {
+ super(context);
+
+ this.setContentView(view);
+ this.setTitle(R.string.pubkey_gather_entropy);
+
+ ((EntropyView) findViewById(R.id.entropy)).addOnEntropyGatheredListener(this);
+ }
+
+ public void onEntropyGathered(byte[] entropy) {
+ this.dismiss();
+ }
+
+}
diff --git a/src/org/connectbot/util/EntropyView.java b/src/org/connectbot/util/EntropyView.java
new file mode 100644
index 0000000..977b536
--- /dev/null
+++ b/src/org/connectbot/util/EntropyView.java
@@ -0,0 +1,144 @@
+/*
+ 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 java.util.Vector;
+
+import org.connectbot.R;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Typeface;
+import android.graphics.Paint.FontMetrics;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+
+public class EntropyView extends View {
+ private Paint mPaint;
+ private FontMetrics mFontMetrics;
+ private boolean mFlipFlop;
+ private long mLastTime;
+ private Vector<OnEntropyGatheredListener> listeners;
+
+ private byte[] mEntropy;
+ private int mEntropyIdx;
+
+ private int splitText = 0;
+
+ private float lastX = 0.0f, lastY = 0.0f;
+
+ public EntropyView(Context context) {
+ super(context);
+
+ setUpEntropy();
+ }
+
+ public EntropyView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ setUpEntropy();
+ }
+
+ private void setUpEntropy() {
+ mPaint = new Paint();
+ mPaint.setAntiAlias(true);
+ mPaint.setTypeface(Typeface.DEFAULT);
+ mPaint.setTextAlign(Paint.Align.CENTER);
+ mPaint.setTextSize(16);
+ mPaint.setColor(Color.WHITE);
+ mFontMetrics = mPaint.getFontMetrics();
+
+ mEntropy = new byte[20];
+ mEntropyIdx = 0;
+
+ listeners = new Vector<OnEntropyGatheredListener>();
+ }
+
+ public void addOnEntropyGatheredListener(OnEntropyGatheredListener listener) {
+ listeners.add(listener);
+ }
+
+ public void removeOnEntropyGatheredListener(OnEntropyGatheredListener listener) {
+ listeners.remove(listener);
+ }
+
+ public void onDraw(Canvas c) {
+ String prompt = getResources().getString(R.string.pubkey_touch_prompt)
+ + " " + (int)(100.0 * (mEntropyIdx / 20.0)) + "% done";
+ if (splitText > 0 ||
+ mPaint.measureText(prompt) > (getWidth() * 0.8)) {
+ if (splitText == 0)
+ splitText = prompt.indexOf(" ", prompt.length() / 2);
+
+ c.drawText(prompt.substring(0, splitText),
+ getWidth() / 2,
+ getHeight() / 2 + (mPaint.ascent() + mPaint.descent()),
+ mPaint);
+ c.drawText(prompt.substring(splitText),
+ getWidth() / 2,
+ getHeight() / 2 - (mPaint.ascent() + mPaint.descent()),
+ mPaint);
+ } else {
+ c.drawText(prompt,
+ getWidth() / 2,
+ getHeight() / 2 - (mFontMetrics.ascent + mFontMetrics.descent) / 2,
+ mPaint);
+ }
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ if (mEntropyIdx >= 20
+ || lastX == event.getX()
+ || lastY == event.getY())
+ return true;
+
+ // Only get entropy every 200 milliseconds to ensure the user has moved around.
+ long now = System.currentTimeMillis();
+ if ((now - mLastTime) < 200)
+ return true;
+ else
+ mLastTime = now;
+
+ // Get the lowest 4 bits of each X, Y input and concat to the entropy-gathering
+ // string.
+ if (mFlipFlop)
+ mEntropy[mEntropyIdx++] += (byte)((((int)event.getX() & 0x0F) << 4) | ((int)event.getY() & 0x0F));
+ else
+ mEntropy[mEntropyIdx++] += (byte)((((int)event.getY() & 0x0F) << 4) | ((int)event.getX() & 0x0F));
+
+ mFlipFlop = !mFlipFlop;
+ lastX = event.getX();
+ lastY = event.getY();
+
+ // SHA1PRNG only keeps 20 bytes (160 bits) of entropy.
+ if (mEntropyIdx >= 20) {
+ for (OnEntropyGatheredListener listener: listeners) {
+ listener.onEntropyGathered(mEntropy);
+ }
+ }
+
+ invalidate();
+
+ return true;
+ }
+}
diff --git a/src/org/connectbot/util/OnEntropyGatheredListener.java b/src/org/connectbot/util/OnEntropyGatheredListener.java
new file mode 100644
index 0000000..9dd2e44
--- /dev/null
+++ b/src/org/connectbot/util/OnEntropyGatheredListener.java
@@ -0,0 +1,23 @@
+/*
+ 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;
+
+public interface OnEntropyGatheredListener {
+ void onEntropyGathered(byte[] entropy);
+}
diff --git a/src/org/connectbot/util/PubkeyDatabase.java b/src/org/connectbot/util/PubkeyDatabase.java
new file mode 100644
index 0000000..8cfebe9
--- /dev/null
+++ b/src/org/connectbot/util/PubkeyDatabase.java
@@ -0,0 +1,110 @@
+/*
+ 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.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+
+/**
+ * Public Key Encryption database. Contains private and public key pairs
+ * for public key authentication.
+ *
+ * @author kroot
+ */
+public class PubkeyDatabase extends SQLiteOpenHelper {
+
+ public final static String TAG = PubkeyDatabase.class.toString();
+
+ public final static String DB_NAME = "pubkeys";
+ public final static int DB_VERSION = 1;
+
+ public final static String TABLE_PUBKEYS = "pubkeys";
+ public final static String FIELD_PUBKEY_NICKNAME = "nickname";
+ public final static String FIELD_PUBKEY_TYPE = "type";
+ public final static String FIELD_PUBKEY_PRIVATE = "private";
+ public final static String FIELD_PUBKEY_PUBLIC = "public";
+ public final static String FIELD_PUBKEY_ENCRYPTED = "encrypted";
+ public final static String FIELD_PUBKEY_STARTUP = "startup";
+
+ public PubkeyDatabase(Context context) {
+ super(context, DB_NAME, null, DB_VERSION);
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ db.execSQL("CREATE TABLE " + TABLE_PUBKEYS
+ + " (_id INTEGER PRIMARY KEY, "
+ + FIELD_PUBKEY_NICKNAME + " TEXT, "
+ + FIELD_PUBKEY_TYPE + " TEXT, "
+ + FIELD_PUBKEY_PRIVATE + " BLOB, "
+ + FIELD_PUBKEY_PUBLIC + " BLOB, "
+ + FIELD_PUBKEY_ENCRYPTED + " INTEGER, "
+ + FIELD_PUBKEY_STARTUP + " INTEGER)");
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+
+ }
+
+ /**
+ * Create a new pubkey using the given parameters, and return its new
+ * <code>_id</code> value.
+ */
+ public long createPubkey(SQLiteDatabase db, String nickname, String type, byte[] privatekey,
+ byte[] publickey, boolean encrypted, boolean startup) {
+ // create and insert new host
+
+ if (db == null)
+ db = this.getWritableDatabase();
+
+ ContentValues values = new ContentValues();
+ values.put(FIELD_PUBKEY_NICKNAME, nickname);
+ values.put(FIELD_PUBKEY_TYPE, type);
+ values.put(FIELD_PUBKEY_PRIVATE, privatekey);
+ values.put(FIELD_PUBKEY_PUBLIC, publickey);
+ values.put(FIELD_PUBKEY_ENCRYPTED, encrypted ? 1 : 0);
+ values.put(FIELD_PUBKEY_STARTUP, startup ? 1 : 0);
+
+ return db.insert(TABLE_PUBKEYS, null, values);
+ }
+
+ /**
+ * Delete a specific host by its <code>_id</code> value.
+ */
+ public void deletePubkey(long id) {
+ SQLiteDatabase db = this.getWritableDatabase();
+ db.delete(TABLE_PUBKEYS, "_id = ?", new String[] { Long.toString(id) });
+ }
+
+ /**
+ * Return a cursor that contains information about all known hosts.
+ */
+ public Cursor allPubkeys() {
+ 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 },
+ null, null, null, null, null);
+ }
+
+}
diff --git a/src/org/connectbot/util/PubkeyUtils.java b/src/org/connectbot/util/PubkeyUtils.java
new file mode 100644
index 0000000..b73c58e
--- /dev/null
+++ b/src/org/connectbot/util/PubkeyUtils.java
@@ -0,0 +1,194 @@
+/*
+ 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 java.io.IOException;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.KeyFactory;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.interfaces.DSAParams;
+import java.security.interfaces.DSAPrivateKey;
+import java.security.interfaces.DSAPublicKey;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.spec.SecretKeySpec;
+
+import android.util.Log;
+
+import com.trilead.ssh2.crypto.Base64;
+import com.trilead.ssh2.signature.DSASHA1Verify;
+import com.trilead.ssh2.signature.RSASHA1Verify;
+
+public class PubkeyUtils {
+ public static String formatKey(Key key){
+ String algo = key.getAlgorithm();
+ String fmt = key.getFormat();
+ byte[] encoded = key.getEncoded();
+ return "Key[algorithm=" + algo + ", format=" + fmt +
+ ", bytes=" + encoded.length + "]";
+ }
+
+ public static String describeKey(Key key, int encrypted) {
+ String desc = null;
+ if (key instanceof RSAPublicKey) {
+ desc = "RSA " + String.valueOf(((RSAPublicKey)key).getModulus().bitLength()) + "-bit";
+ } else if (key instanceof DSAPublicKey) {
+ desc = "DSA 1024-bit";
+ } else {
+ desc = "Unknown Key Type";
+ }
+
+ if (encrypted != 0)
+ desc += " (encrypted)";
+
+ return desc;
+ }
+
+ public static byte[] sha1(byte[] data) throws NoSuchAlgorithmException {
+ MessageDigest hash = MessageDigest.getInstance("SHA-256");
+ byte[] hashed = hash.digest(data);
+ Log.d("KeyUtils", "hash is " + hashed.length + " bytes");
+ return hash.digest(data);
+ }
+
+ public static byte[] cipher(int mode, byte[] data, byte[] secret) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
+ SecretKeySpec secretKeySpec = new SecretKeySpec(sha1(secret), "AES");
+ Cipher c = Cipher.getInstance("AES");
+ c.init(mode, secretKeySpec);
+ return c.doFinal(data);
+ }
+
+ public static byte[] encrypt(byte[] cleartext, byte[] secret) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
+ return cipher(Cipher.ENCRYPT_MODE, cleartext, secret);
+ }
+
+ public static byte[] encrypt(byte[] cleartext, String secret) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException {
+ return cipher(Cipher.ENCRYPT_MODE, cleartext, secret.getBytes());
+ }
+
+ public static byte[] decrypt(byte[] ciphertext, byte[] secret) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
+ return cipher(Cipher.DECRYPT_MODE, ciphertext, secret);
+ }
+
+ public static byte[] decrypt(byte[] ciphertext, String secret) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
+ return cipher(Cipher.DECRYPT_MODE, ciphertext, secret.getBytes());
+ }
+
+ public static byte[] getEncodedPublic(PublicKey pk) {
+ X509EncodedKeySpec x509 = new X509EncodedKeySpec(pk.getEncoded());
+ return x509.getEncoded();
+ }
+
+ public static byte[] getEncodedPrivate(PrivateKey pk) {
+ PKCS8EncodedKeySpec pkcs8 = new PKCS8EncodedKeySpec(pk.getEncoded());
+ return pkcs8.getEncoded();
+ }
+
+ public static byte[] getEncodedPrivate(PrivateKey pk, String secret) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException {
+ if (secret.length() > 0)
+ return encrypt(getEncodedPrivate(pk), secret);
+ else
+ return getEncodedPrivate(pk);
+ }
+
+ public static PrivateKey decodePrivate(byte[] encoded, String keyType) throws NoSuchAlgorithmException, InvalidKeySpecException {
+ PKCS8EncodedKeySpec privKeySpec = new PKCS8EncodedKeySpec(encoded);
+ KeyFactory kf = KeyFactory.getInstance(keyType);
+ return kf.generatePrivate(privKeySpec);
+ }
+
+ public static PrivateKey decodePrivate(byte[] encoded, String keyType, String secret) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, InvalidKeySpecException {
+ if (secret.length() > 0)
+ return decodePrivate(decrypt(encoded, secret), keyType);
+ else
+ return decodePrivate(encoded, keyType);
+ }
+
+ public static PublicKey decodePublic(byte[] encoded, String keyType) throws NoSuchAlgorithmException, InvalidKeySpecException {
+ X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(encoded);
+ KeyFactory kf = KeyFactory.getInstance(keyType);
+ return kf.generatePublic(pubKeySpec);
+ }
+
+ /*
+ * Trilead compatibility methods
+ */
+
+ public static Object convertToTrilead(PublicKey pk) {
+ if (pk instanceof RSAPublicKey) {
+ return new com.trilead.ssh2.signature.RSAPublicKey(
+ ((RSAPublicKey) pk).getPublicExponent(),
+ ((RSAPublicKey) pk).getModulus());
+ } else if (pk instanceof DSAPublicKey) {
+ DSAParams dp = ((DSAPublicKey) pk).getParams();
+ return new com.trilead.ssh2.signature.DSAPublicKey(
+ dp.getP(), dp.getQ(), dp.getG(), ((DSAPublicKey) pk).getY());
+ }
+
+ throw new IllegalArgumentException("PrivateKey is not RSA or DSA format");
+ }
+
+ public static Object convertToTrilead(PrivateKey priv, PublicKey pub) {
+ if (priv instanceof RSAPrivateKey) {
+ return new com.trilead.ssh2.signature.RSAPrivateKey(
+ ((RSAPrivateKey) priv).getPrivateExponent(),
+ ((RSAPublicKey) pub).getPublicExponent(),
+ ((RSAPrivateKey) priv).getModulus());
+ } else if (priv instanceof DSAPrivateKey) {
+ DSAParams dp = ((DSAPrivateKey) priv).getParams();
+ return new com.trilead.ssh2.signature.DSAPrivateKey(
+ dp.getP(), dp.getQ(), dp.getG(), ((DSAPublicKey) pub).getY(),
+ ((DSAPrivateKey) priv).getX());
+ }
+
+ throw new IllegalArgumentException("Key is not RSA or DSA format");
+ }
+
+ /*
+ * OpenSSH compatibility methods
+ */
+
+ public static String convertToOpenSSHFormat(PublicKey pk) throws IOException, InvalidKeyException {
+ if (pk instanceof RSAPublicKey) {
+ String data = "ssh-rsa ";
+ data += String.valueOf(Base64.encode(RSASHA1Verify.encodeSSHRSAPublicKey(
+ (com.trilead.ssh2.signature.RSAPublicKey)convertToTrilead(pk))));
+ return data + " connectbot@android";
+ } else if (pk instanceof DSAPublicKey) {
+ String data = "ssh-dss ";
+ data += String.valueOf(Base64.encode(DSASHA1Verify.encodeSSHDSAPublicKey(
+ (com.trilead.ssh2.signature.DSAPublicKey)convertToTrilead(pk))));
+ return data + " connectbot@android";
+ }
+
+ throw new InvalidKeyException("Unknown key type");
+ }
+}