aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/org/connectbot/PubkeyListActivity.java66
-rw-r--r--src/org/connectbot/bean/AbstractBean.java5
-rw-r--r--src/org/connectbot/bean/HostBean.java6
-rw-r--r--src/org/connectbot/bean/PortForwardBean.java6
-rw-r--r--src/org/connectbot/bean/PubkeyBean.java7
-rw-r--r--src/org/connectbot/util/PubkeyUtils.java115
6 files changed, 192 insertions, 13 deletions
diff --git a/src/org/connectbot/PubkeyListActivity.java b/src/org/connectbot/PubkeyListActivity.java
index 1728f31..4a1b3de 100644
--- a/src/org/connectbot/PubkeyListActivity.java
+++ b/src/org/connectbot/PubkeyListActivity.java
@@ -23,6 +23,7 @@ import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.Collections;
@@ -66,6 +67,7 @@ import android.widget.TextView;
import android.widget.Toast;
import android.widget.AdapterView.OnItemClickListener;
+import com.trilead.ssh2.crypto.Base64;
import com.trilead.ssh2.crypto.PEMDecoder;
import com.trilead.ssh2.crypto.PEMStructure;
@@ -230,27 +232,49 @@ public class PubkeyListActivity extends ListActivity implements EventListener {
.setTitle(R.string.pubkey_list_pick)
.setItems(namesList, new OnClickListener() {
public void onClick(DialogInterface arg0, int arg1) {
+ PubkeyBean pubkey = new PubkeyBean();
+
// find the exact file selected
String name = namesList[arg1];
+ pubkey.setNickname(name);
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
- PubkeyBean pubkey = new PubkeyBean();
- pubkey.setNickname(name);
- pubkey.setType(PubkeyDatabase.KEY_TYPE_IMPORTED);
- pubkey.setPrivateKey(raw);
- pubkey.setEncrypted(encrypted);
+ String data = new String(raw);
+ if (data.startsWith(PubkeyUtils.PKCS8_START)) {
+ int start = data.indexOf(PubkeyUtils.PKCS8_START) + PubkeyUtils.PKCS8_START.length();
+ int end = data.indexOf(PubkeyUtils.PKCS8_END);
+
+ if (end > start) {
+ char[] encoded = data.substring(start, end - 1).toCharArray();
+ Log.d(TAG, "encoded: " + new String(encoded));
+ byte[] decoded = Base64.decode(encoded);
+
+ KeyPair kp = PubkeyUtils.recoverKeyPair(decoded);
+
+ pubkey.setType(kp.getPrivate().getAlgorithm());
+ pubkey.setPrivateKey(kp.getPrivate().getEncoded());
+ pubkey.setPublicKey(kp.getPublic().getEncoded());
+ } else {
+ Log.e(TAG, "Problem parsing PKCS#8 file; corrupt?");
+ Toast.makeText(PubkeyListActivity.this,
+ R.string.pubkey_import_parse_problem,
+ Toast.LENGTH_LONG).show();
+ }
+ } else {
+ PEMStructure struct = PEMDecoder.parsePEM(new String(raw).toCharArray());
+ pubkey.setEncrypted(PEMDecoder.isPEMEncrypted(struct));
+ pubkey.setType(PubkeyDatabase.KEY_TYPE_IMPORTED);
+ pubkey.setPrivateKey(raw);
+ }
+ // write new value into database
pubkeydb.savePubkey(pubkey);
updateHandler.sendEmptyMessage(-1);
-
} catch(Exception e) {
Log.e(TAG, "Problem parsing imported private key", e);
Toast.makeText(PubkeyListActivity.this, R.string.pubkey_import_parse_problem, Toast.LENGTH_LONG).show();
@@ -338,7 +362,7 @@ public class PubkeyListActivity extends ListActivity implements EventListener {
// prompt for password as needed for passworded keys
// cant change password or clipboard imported keys
- boolean imported = PubkeyDatabase.KEY_TYPE_IMPORTED.equals(pubkey.getType());
+ final boolean imported = PubkeyDatabase.KEY_TYPE_IMPORTED.equals(pubkey.getType());
final boolean loaded = bound.isKeyLoaded(pubkey.getNickname());
MenuItem load = menu.add(loaded ? R.string.pubkey_memory_unload : R.string.pubkey_memory_load);
@@ -385,6 +409,28 @@ public class PubkeyListActivity extends ListActivity implements EventListener {
}
});
+ MenuItem copyPrivateToClipboard = menu.add(R.string.pubkey_copy_private);
+ copyPrivateToClipboard.setEnabled(!pubkey.isEncrypted() || imported);
+ copyPrivateToClipboard.setOnMenuItemClickListener(new OnMenuItemClickListener() {
+ public boolean onMenuItemClick(MenuItem item) {
+ try {
+ String data = null;
+
+ if (imported)
+ data = new String(pubkey.getPrivateKey());
+ else {
+ PrivateKey pk = PubkeyUtils.decodePrivate(pubkey.getPrivateKey(), pubkey.getType());
+ data = new String(PubkeyUtils.exportPEM(pk, null));
+ }
+
+ clipboard.setText(data);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return true;
+ }
+ });
+
MenuItem changePassword = menu.add(R.string.pubkey_change_password);
changePassword.setEnabled(!imported);
changePassword.setOnMenuItemClickListener(new OnMenuItemClickListener() {
diff --git a/src/org/connectbot/bean/AbstractBean.java b/src/org/connectbot/bean/AbstractBean.java
index 13dd285..4998727 100644
--- a/src/org/connectbot/bean/AbstractBean.java
+++ b/src/org/connectbot/bean/AbstractBean.java
@@ -29,11 +29,12 @@ import android.content.ContentValues;
*/
abstract class AbstractBean {
public abstract ContentValues getValues();
+ public abstract String getBeanName();
public String toXML() {
XmlBuilder xml = new XmlBuilder();
- xml.append("<host>");
+ xml.append(String.format("<%s>", getBeanName()));
ContentValues values = getValues();
for (Entry<String, Object> entry : values.valueSet()) {
@@ -41,7 +42,7 @@ abstract class AbstractBean {
if (value != null)
xml.append(entry.getKey(), value);
}
- xml.append("</host>");
+ xml.append(String.format("</%s>", getBeanName()));
return xml.toString();
}
diff --git a/src/org/connectbot/bean/HostBean.java b/src/org/connectbot/bean/HostBean.java
index 4ab219b..1b937c9 100644
--- a/src/org/connectbot/bean/HostBean.java
+++ b/src/org/connectbot/bean/HostBean.java
@@ -26,6 +26,8 @@ import android.content.ContentValues;
*
*/
public class HostBean extends AbstractBean {
+ public static final String BEAN_NAME = "host";
+
/* Database fields */
private long id = -1;
private String nickname = null;
@@ -47,6 +49,10 @@ public class HostBean extends AbstractBean {
}
+ public String getBeanName() {
+ return BEAN_NAME;
+ }
+
public HostBean(String nickname, String username, String hostname, int port) {
this.nickname = nickname;
this.username = username;
diff --git a/src/org/connectbot/bean/PortForwardBean.java b/src/org/connectbot/bean/PortForwardBean.java
index ecf9c44..9a2036f 100644
--- a/src/org/connectbot/bean/PortForwardBean.java
+++ b/src/org/connectbot/bean/PortForwardBean.java
@@ -27,6 +27,8 @@ import android.content.ContentValues;
*
*/
public class PortForwardBean extends AbstractBean {
+ public static final String BEAN_NAME = "portforward";
+
/* Database fields */
private long id = -1;
private long hostId = -1;
@@ -72,6 +74,10 @@ public class PortForwardBean extends AbstractBean {
this.setDest(dest);
}
+ public String getBeanName() {
+ return BEAN_NAME;
+ }
+
/**
* @param id the id to set
*/
diff --git a/src/org/connectbot/bean/PubkeyBean.java b/src/org/connectbot/bean/PubkeyBean.java
index b3d3108..bb9f3c0 100644
--- a/src/org/connectbot/bean/PubkeyBean.java
+++ b/src/org/connectbot/bean/PubkeyBean.java
@@ -36,6 +36,8 @@ import android.content.ContentValues;
*
*/
public class PubkeyBean extends AbstractBean {
+ public static final String BEAN_NAME = "pubkey";
+
/* Database fields */
private long id;
private String nickname;
@@ -49,6 +51,10 @@ public class PubkeyBean extends AbstractBean {
private boolean unlocked = false;
private Object unlockedPrivate = null;
+ public String getBeanName() {
+ return BEAN_NAME;
+ }
+
public void setId(long id) {
this.id = id;
}
@@ -128,7 +134,6 @@ public class PubkeyBean extends AbstractBean {
public ContentValues getValues() {
ContentValues values = new ContentValues();
- values.put("_id", id);
values.put(PubkeyDatabase.FIELD_PUBKEY_NICKNAME, nickname);
values.put(PubkeyDatabase.FIELD_PUBKEY_TYPE, type);
values.put(PubkeyDatabase.FIELD_PUBKEY_PRIVATE, privateKey);
diff --git a/src/org/connectbot/util/PubkeyUtils.java b/src/org/connectbot/util/PubkeyUtils.java
index 63e96c3..0ab667c 100644
--- a/src/org/connectbot/util/PubkeyUtils.java
+++ b/src/org/connectbot/util/PubkeyUtils.java
@@ -19,26 +19,40 @@
package org.connectbot.util;
import java.io.IOException;
+import java.math.BigInteger;
+import java.security.AlgorithmParameters;
+import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyFactory;
+import java.security.KeyPair;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
+import java.security.SecureRandom;
import java.security.interfaces.DSAParams;
import java.security.interfaces.DSAPrivateKey;
import java.security.interfaces.DSAPublicKey;
+import java.security.interfaces.RSAPrivateCrtKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
+import java.security.spec.DSAPublicKeySpec;
import java.security.spec.InvalidKeySpecException;
+import java.security.spec.InvalidParameterSpecException;
+import java.security.spec.KeySpec;
import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.RSAPublicKeySpec;
import java.security.spec.X509EncodedKeySpec;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
+import javax.crypto.EncryptedPrivateKeyInfo;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.PBEKeySpec;
+import javax.crypto.spec.PBEParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import com.trilead.ssh2.crypto.Base64;
@@ -46,6 +60,9 @@ import com.trilead.ssh2.signature.DSASHA1Verify;
import com.trilead.ssh2.signature.RSASHA1Verify;
public class PubkeyUtils {
+ public static final String PKCS8_START = "-----BEGIN PRIVATE KEY-----";
+ public static final String PKCS8_END = "-----END PRIVATE KEY-----";
+
public static String formatKey(Key key){
String algo = key.getAlgorithm();
String fmt = key.getFormat();
@@ -124,6 +141,41 @@ public class PubkeyUtils {
return kf.generatePublic(pubKeySpec);
}
+ public static KeyPair recoverKeyPair(byte[] encoded) throws NoSuchAlgorithmException, InvalidKeySpecException {
+ KeySpec privKeySpec = new PKCS8EncodedKeySpec(encoded);
+ KeySpec pubKeySpec;
+
+ PrivateKey priv;
+ PublicKey pub;
+ KeyFactory kf;
+ try {
+ kf = KeyFactory.getInstance(PubkeyDatabase.KEY_TYPE_RSA);
+ priv = kf.generatePrivate(privKeySpec);
+
+ pubKeySpec = new RSAPublicKeySpec(((RSAPrivateCrtKey) priv)
+ .getModulus(), ((RSAPrivateCrtKey) priv)
+ .getPublicExponent());
+
+ pub = kf.generatePublic(pubKeySpec);
+ } catch (ClassCastException e) {
+ kf = KeyFactory.getInstance(PubkeyDatabase.KEY_TYPE_DSA);
+ priv = kf.generatePrivate(privKeySpec);
+
+ DSAParams params = ((DSAPrivateKey) priv).getParams();
+
+ // Calculate public key Y
+ BigInteger y = params.getG().modPow(((DSAPrivateKey) priv).getX(),
+ params.getP());
+
+ pubKeySpec = new DSAPublicKeySpec(y, params.getP(), params.getQ(),
+ params.getG());
+
+ pub = kf.generatePublic(pubKeySpec);
+ }
+
+ return new KeyPair(pub, priv);
+ }
+
/*
* Trilead compatibility methods
*/
@@ -180,4 +232,67 @@ public class PubkeyUtils {
throw new InvalidKeyException("Unknown key type");
}
+
+ public static String exportPEM(PrivateKey key, String secret) throws NoSuchAlgorithmException, InvalidParameterSpecException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, InvalidKeySpecException, IllegalBlockSizeException, IOException {
+ StringBuilder sb = new StringBuilder();
+
+ byte[] data = key.getEncoded();
+
+ sb.append(PKCS8_START);
+ sb.append('\n');
+
+ if (secret != null) {
+ byte[] salt = new byte[8];
+ SecureRandom random = new SecureRandom();
+ random.nextBytes(salt);
+
+ PBEParameterSpec defParams = new PBEParameterSpec(salt, 1);
+ AlgorithmParameters params = AlgorithmParameters.getInstance(key.getAlgorithm());
+
+ params.init(defParams);
+
+ PBEKeySpec pbeSpec = new PBEKeySpec(secret.toCharArray());
+
+ SecretKeyFactory keyFact = SecretKeyFactory.getInstance(key.getAlgorithm());
+ Cipher cipher = Cipher.getInstance(key.getAlgorithm());
+ cipher.init(Cipher.WRAP_MODE, keyFact.generateSecret(pbeSpec), params);
+
+ byte[] wrappedKey = cipher.wrap(key);
+
+ EncryptedPrivateKeyInfo pinfo = new EncryptedPrivateKeyInfo(params, wrappedKey);
+
+ data = pinfo.getEncoded();
+
+ sb.append("Proc-Type: 4,ENCRYPTED\n");
+ sb.append("DEK-Info: DES-EDE3-CBC,");
+ sb.append(encodeHex(salt));
+ sb.append("\n\n");
+ }
+
+ int i = sb.length();
+ sb.append(Base64.encode(data));
+ for (i += 63; i < sb.length(); i += 64) {
+ sb.insert(i, "\n");
+ }
+
+ sb.append('\n');
+ sb.append(PKCS8_END);
+ sb.append('\n');
+
+ return sb.toString();
+ }
+
+ final static private char hexDigit[] = { '0', '1', '2', '3', '4', '5', '6',
+ '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
+ private static String encodeHex(byte[] bytes) {
+ char[] hex = new char[bytes.length * 2];
+
+ int i = 0;
+ for (byte b : bytes) {
+ hex[i++] = hexDigit[(b >> 4) & 0x0f];
+ hex[i++] = hexDigit[b & 0x0f];
+ }
+
+ return new String(hex);
+ }
}