diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/org/connectbot/PubkeyListActivity.java | 66 | ||||
| -rw-r--r-- | src/org/connectbot/bean/AbstractBean.java | 5 | ||||
| -rw-r--r-- | src/org/connectbot/bean/HostBean.java | 6 | ||||
| -rw-r--r-- | src/org/connectbot/bean/PortForwardBean.java | 6 | ||||
| -rw-r--r-- | src/org/connectbot/bean/PubkeyBean.java | 7 | ||||
| -rw-r--r-- | src/org/connectbot/util/PubkeyUtils.java | 115 | 
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); +    }  } | 
