diff options
author | Thialfihar <thialfihar@gmail.com> | 2010-08-17 01:02:39 +0000 |
---|---|---|
committer | Thialfihar <thialfihar@gmail.com> | 2010-08-17 01:02:39 +0000 |
commit | 6e9146c91ab9ec78837fa9ba7e21e2c01b72907f (patch) | |
tree | 16a80fcda66a9675956a6b007839f650dba363e1 /src/org/thialfihar/android | |
parent | b3a63beffcac99d3c80d706f10a0b7144e808fec (diff) | |
download | open-keychain-6e9146c91ab9ec78837fa9ba7e21e2c01b72907f.tar.gz open-keychain-6e9146c91ab9ec78837fa9ba7e21e2c01b72907f.tar.bz2 open-keychain-6e9146c91ab9ec78837fa9ba7e21e2c01b72907f.zip |
added initial support for HKP key servers, allowing searching and key import
Update issue 9
Can search a key server now, touch a result to import the key. Still needs better error handling and some Intents to import keys based on key ID. Also still need key server preferences.
Diffstat (limited to 'src/org/thialfihar/android')
13 files changed, 443 insertions, 19 deletions
diff --git a/src/org/thialfihar/android/apg/Apg.java b/src/org/thialfihar/android/apg/Apg.java index cc8fae4c7..125001024 100644 --- a/src/org/thialfihar/android/apg/Apg.java +++ b/src/org/thialfihar/android/apg/Apg.java @@ -191,6 +191,10 @@ public class Apg { Pattern.compile(".*?(-----BEGIN PGP SIGNED MESSAGE-----.*?-----BEGIN PGP SIGNATURE-----.*?-----END PGP SIGNATURE-----).*", Pattern.DOTALL); + public static Pattern PGP_PUBLIC_KEY = + Pattern.compile(".*?(-----BEGIN PGP PUBLIC KEY BLOCK-----.*?-----END PGP PUBLIC KEY BLOCK-----).*", + Pattern.DOTALL); + private static HashMap<Long, CachedPassPhrase> mPassPhraseCache = new HashMap<Long, CachedPassPhrase>(); private static String mEditPassPhrase = null; @@ -1006,6 +1010,25 @@ public class Apg { return algorithmStr + ", " + keySize + "bit"; } + public static String getFingerPrint(long keyId) { + String fingerPrint = Long.toHexString(keyId & 0xffffffffL).toUpperCase(); + while (fingerPrint.length() < 8) { + fingerPrint = "0" + fingerPrint; + } + return fingerPrint; + } + + public static String keyToHex(long keyId) { + return getFingerPrint(keyId >> 32) + getFingerPrint(keyId); + } + + public static long keyFromHex(String data) { + int len = data.length(); + String s2 = data.substring(len - 8); + String s1 = data.substring(0, len - 8); + return (Long.parseLong(s1, 16) << 32) | Long.parseLong(s2, 16); + } + public static void deleteKey(int keyRingId) { mDatabase.deleteKeyRing(keyRingId); } diff --git a/src/org/thialfihar/android/apg/BaseActivity.java b/src/org/thialfihar/android/apg/BaseActivity.java index 9c40efb4f..9f1c7a2ae 100644 --- a/src/org/thialfihar/android/apg/BaseActivity.java +++ b/src/org/thialfihar/android/apg/BaseActivity.java @@ -157,6 +157,13 @@ public class BaseActivity extends Activity return mProgressDialog; } + case Id.dialog.querying: { + mProgressDialog.setMessage(this.getString(R.string.progress_querying)); + mProgressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER); + mProgressDialog.setCancelable(false); + return mProgressDialog; + } + default: { break; } @@ -362,6 +369,7 @@ public class BaseActivity extends Activity case Id.message.import_done: // intentionally no break case Id.message.export_done: // intentionally no break + case Id.message.query_done: // intentionally no break case Id.message.done: { mProgressDialog = null; doneCallback(msg); diff --git a/src/org/thialfihar/android/apg/DecryptActivity.java b/src/org/thialfihar/android/apg/DecryptActivity.java index a7eb31248..a23e58ca4 100644 --- a/src/org/thialfihar/android/apg/DecryptActivity.java +++ b/src/org/thialfihar/android/apg/DecryptActivity.java @@ -584,7 +584,7 @@ public class DecryptActivity extends BaseActivity { if (data.getBoolean(Apg.EXTRA_SIGNATURE)) { String userId = data.getString(Apg.EXTRA_SIGNATURE_USER_ID); mSignatureKeyId = data.getLong(Apg.EXTRA_SIGNATURE_KEY_ID); - mUserIdRest.setText("id: " + Long.toHexString(mSignatureKeyId & 0xffffffffL)); + mUserIdRest.setText("id: " + Apg.getFingerPrint(mSignatureKeyId)); if (userId == null) { userId = getResources().getString(R.string.unknownUserId); } diff --git a/src/org/thialfihar/android/apg/EditKeyActivity.java b/src/org/thialfihar/android/apg/EditKeyActivity.java index 3fa5a7552..93e048e42 100644 --- a/src/org/thialfihar/android/apg/EditKeyActivity.java +++ b/src/org/thialfihar/android/apg/EditKeyActivity.java @@ -39,7 +39,6 @@ import android.os.Bundle; import android.os.Message; import android.view.LayoutInflater; import android.view.Menu; -import android.view.MenuItem; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; diff --git a/src/org/thialfihar/android/apg/HkpKeyServer.java b/src/org/thialfihar/android/apg/HkpKeyServer.java new file mode 100644 index 000000000..ce65e1da2 --- /dev/null +++ b/src/org/thialfihar/android/apg/HkpKeyServer.java @@ -0,0 +1,119 @@ +package org.thialfihar.android.apg; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLConnection; +import java.net.URLEncoder; +import java.util.GregorianCalendar; +import java.util.List; +import java.util.Vector; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import android.text.Html; + +public class HkpKeyServer extends KeyServer { + private String mHost; + private short mPort = 11371; + + // example: + // pub 2048R/<a href="/pks/lookup?op=get&search=0x887DF4BE9F5C9090">9F5C9090</a> 2009-08-17 <a href="/pks/lookup?op=vindex&search=0x887DF4BE9F5C9090">Jörg Runge <joerg@joergrunge.de></a> + public static Pattern PUB_KEY_LINE = + Pattern.compile("pub +([0-9]+)([a-z]+)/.*?0x([0-9a-z]+).*? +([0-9-]+) +(.+)[\n\r]+((?: +.+[\n\r]+)*)", + Pattern.CASE_INSENSITIVE); + public static Pattern USER_ID_LINE = + Pattern.compile("^ +(.+)$", Pattern.MULTILINE | Pattern.CASE_INSENSITIVE); + + public HkpKeyServer(String host) { + mHost = host; + } + + public HkpKeyServer(String host, short port) { + mHost = host; + mPort = port; + } + + @Override + List<KeyInfo> search(String query) + throws MalformedURLException, IOException { + Vector<KeyInfo> results = new Vector<KeyInfo>(); + + String url = "http://" + mHost + ":" + mPort + "/pks/lookup?op=index&search=" + + URLEncoder.encode(query, "utf8"); + URL realUrl = new URL(url); + URLConnection conn = realUrl.openConnection(); + InputStream is = conn.getInputStream(); + ByteArrayOutputStream raw = new ByteArrayOutputStream(); + + byte buffer[] = new byte[1 << 16]; + int n = 0; + while ((n = is.read(buffer)) != -1) { + raw.write(buffer, 0, n); + } + + String encoding = conn.getContentEncoding(); + if (encoding == null) { + encoding = "utf8"; + } + String data = raw.toString(encoding); + Matcher matcher = PUB_KEY_LINE.matcher(data); + while (matcher.find()) { + KeyInfo info = new KeyInfo(); + info.size = Integer.parseInt(matcher.group(1)); + info.algorithm = matcher.group(2); + info.keyId = Apg.keyFromHex(matcher.group(3)); + info.fingerPrint = Apg.getFingerPrint(info.keyId); + String chunks[] = matcher.group(4).split("-"); + info.date = new GregorianCalendar(Integer.parseInt(chunks[0]), + Integer.parseInt(chunks[1]), + Integer.parseInt(chunks[2])).getTime(); + info.userIds = new Vector<String>(); + if (matcher.group(5).startsWith("*** KEY")) { + info.revoked = matcher.group(5); + } else { + String tmp = matcher.group(5).replaceAll("<.*?>", ""); + tmp = Html.fromHtml(tmp).toString(); + info.userIds.add(tmp); + } + if (matcher.group(6).length() > 0) { + Matcher matcher2 = USER_ID_LINE.matcher(matcher.group(6)); + while (matcher2.find()) { + String tmp = matcher2.group(1).replaceAll("<.*?>", ""); + tmp = Html.fromHtml(tmp).toString(); + info.userIds.add(tmp); + } + } + results.add(info); + } + + return results; + } + + @Override + String get(long keyId) + throws MalformedURLException, IOException { + String url = "http://" + mHost + ":" + mPort + + "/pks/lookup?op=get&search=0x" + Apg.keyToHex(keyId); + URL realUrl = new URL(url); + URLConnection conn = realUrl.openConnection(); + InputStream is = conn.getInputStream(); + ByteArrayOutputStream raw = new ByteArrayOutputStream(); + + byte buffer[] = new byte[1 << 16]; + int n = 0; + while ((n = is.read(buffer)) != -1) { + raw.write(buffer, 0, n); + } + + String data = raw.toString(); + Matcher matcher = Apg.PGP_PUBLIC_KEY.matcher(data); + if (matcher.find()) { + return matcher.group(1); + } + + return null; + } +} diff --git a/src/org/thialfihar/android/apg/Id.java b/src/org/thialfihar/android/apg/Id.java index cc256caee..ce67444cd 100644 --- a/src/org/thialfihar/android/apg/Id.java +++ b/src/org/thialfihar/android/apg/Id.java @@ -35,6 +35,7 @@ public final class Id { public static final int preferences = 0x21070008; public static final int search = 0x21070009; public static final int help = 0x21070010; + public static final int key_server = 0x21070011; } } @@ -48,6 +49,7 @@ public final class Id { public static final int create_key = 0x21070007; public static final int edit_key = 0x21070008; public static final int delete_done = 0x21070009; + public static final int query_done = 0x21070010; } public static final class request { @@ -78,6 +80,7 @@ public final class Id { public static final int delete_file = 0x21070012; public static final int deleting = 0x21070013; public static final int help = 0x21070014; + public static final int querying = 0x21070015; } public static final class task { @@ -156,4 +159,9 @@ public final class Id { public static final int encrypted_data = 1; public static final int keys = 2; } + + public static final class query { + public static final int search = 0x21070001; + public static final int get = 0x21070002; + } } diff --git a/src/org/thialfihar/android/apg/KeyListActivity.java b/src/org/thialfihar/android/apg/KeyListActivity.java index 6f5442502..fbe74a995 100644 --- a/src/org/thialfihar/android/apg/KeyListActivity.java +++ b/src/org/thialfihar/android/apg/KeyListActivity.java @@ -710,10 +710,7 @@ public class KeyListActivity extends BaseActivity { } TextView keyId = (TextView) view.findViewById(R.id.keyId); - String keyIdStr = Long.toHexString(child.keyId & 0xffffffffL); - while (keyIdStr.length() < 8) { - keyIdStr = "0" + keyIdStr; - } + String keyIdStr = Apg.getFingerPrint(child.keyId); keyId.setText(keyIdStr); TextView keyDetails = (TextView) view.findViewById(R.id.keyDetails); String algorithmStr = Apg.getAlgorithmInfo(child.algorithm, child.keySize); diff --git a/src/org/thialfihar/android/apg/KeyServer.java b/src/org/thialfihar/android/apg/KeyServer.java new file mode 100644 index 000000000..25ff26144 --- /dev/null +++ b/src/org/thialfihar/android/apg/KeyServer.java @@ -0,0 +1,23 @@ +package org.thialfihar.android.apg; + +import java.io.IOException; +import java.io.Serializable; +import java.net.MalformedURLException; +import java.util.Date; +import java.util.List; +import java.util.Vector; + +public abstract class KeyServer { + static public class KeyInfo implements Serializable { + private static final long serialVersionUID = -7797972113284992662L; + Vector<String> userIds; + String revoked; + Date date; + String fingerPrint; + long keyId; + int size; + String algorithm; + } + abstract List<KeyInfo> search(String query) throws MalformedURLException, IOException; + abstract String get(long keyId) throws MalformedURLException, IOException; +} diff --git a/src/org/thialfihar/android/apg/KeyServerQueryActivity.java b/src/org/thialfihar/android/apg/KeyServerQueryActivity.java new file mode 100644 index 000000000..e7f393756 --- /dev/null +++ b/src/org/thialfihar/android/apg/KeyServerQueryActivity.java @@ -0,0 +1,246 @@ +package org.thialfihar.android.apg; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.util.List; +import java.util.Vector; + +import org.thialfihar.android.apg.KeyServer.KeyInfo; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.os.Message; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.BaseAdapter; +import android.widget.Button; +import android.widget.EditText; +import android.widget.LinearLayout; +import android.widget.LinearLayout.LayoutParams; +import android.widget.ListView; +import android.widget.TextView; +import android.widget.Toast; + +public class KeyServerQueryActivity extends BaseActivity { + private ListView mList; + private EditText mQuery; + private Button mSearch; + private KeyInfoListAdapter mAdapter; + + private int mQueryType; + private String mQueryString; + private long mQueryId; + private volatile List<KeyInfo> mSearchResult; + private volatile String mKeyData; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.key_server_query_layout); + + mQuery = (EditText) findViewById(R.id.query); + mSearch = (Button) findViewById(R.id.btn_search); + mList = (ListView) findViewById(R.id.list); + mAdapter = new KeyInfoListAdapter(this); + mList.setAdapter(mAdapter); + + mList.setOnItemClickListener(new OnItemClickListener() { + @Override + public void onItemClick(AdapterView<?> adapter, View view, int position, long keyId) { + get(keyId); + } + }); + + mSearch.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + String query = mQuery.getText().toString(); + search(query); + } + }); + + mQuery.setText("cartman"); + } + + private void search(String query) { + showDialog(Id.dialog.querying); + mQueryType = Id.query.search; + mQueryString = query; + startThread(); + } + + private void get(long keyId) { + showDialog(Id.dialog.querying); + mQueryType = Id.query.get; + mQueryId = keyId; + startThread(); + } + + @Override + public void run() { + String error = null; + Bundle data = new Bundle(); + Message msg = new Message(); + + try { + HkpKeyServer server = new HkpKeyServer("198.128.3.63");//"pool.sks-keyservers.net"); + if (mQueryType == Id.query.search) { + mSearchResult = server.search(mQueryString); + } else if (mQueryType == Id.query.get) { + mKeyData = server.get(mQueryId); + } + } catch (MalformedURLException e) { + error = "" + e; + } catch (IOException e) { + error = "" + e; + } + + data.putInt(Apg.EXTRA_STATUS, Id.message.done); + + if (error != null) { + data.putString(Apg.EXTRA_ERROR, error); + } + + msg.setData(data); + sendMessage(msg); + } + + @Override + public void doneCallback(Message msg) { + super.doneCallback(msg); + + removeDialog(Id.dialog.querying); + + Bundle data = msg.getData(); + String error = data.getString(Apg.EXTRA_ERROR); + if (error != null) { + Toast.makeText(this, getString(R.string.errorMessage, error), Toast.LENGTH_SHORT).show(); + return; + } + + if (mQueryType == Id.query.search) { + if (mSearchResult != null) { + mAdapter.setKeys(mSearchResult); + } + } else if (mQueryType == Id.query.get) { + if (mKeyData != null) { + Intent intent = new Intent(this, PublicKeyListActivity.class); + intent.setAction(Apg.Intent.IMPORT); + intent.putExtra(Apg.EXTRA_TEXT, mKeyData); + startActivity(intent); + } + } + } + + public class KeyInfoListAdapter extends BaseAdapter { + protected LayoutInflater mInflater; + protected Activity mActivity; + protected List<KeyInfo> mKeys; + + public KeyInfoListAdapter(Activity activity) { + mActivity = activity; + mInflater = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + mKeys = new Vector<KeyInfo>(); + } + + public void setKeys(List<KeyInfo> keys) { + mKeys = keys; + notifyDataSetChanged(); + } + + @Override + public boolean hasStableIds() { + return true; + } + + @Override + public int getCount() { + return mKeys.size(); + } + + @Override + public Object getItem(int position) { + return mKeys.get(position); + } + + @Override + public long getItemId(int position) { + return mKeys.get(position).keyId; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + KeyInfo keyInfo = mKeys.get(position); + + View view = mInflater.inflate(R.layout.key_server_query_result_item, null); + + TextView mainUserId = (TextView) view.findViewById(R.id.mainUserId); + mainUserId.setText(R.string.unknownUserId); + TextView mainUserIdRest = (TextView) view.findViewById(R.id.mainUserIdRest); + mainUserIdRest.setText(""); + TextView keyId = (TextView) view.findViewById(R.id.keyId); + keyId.setText(R.string.noKey); + TextView algorithm = (TextView) view.findViewById(R.id.algorithm); + algorithm.setText(""); + TextView status = (TextView) view.findViewById(R.id.status); + status.setText(""); + + String userId = keyInfo.userIds.get(0); + if (userId != null) { + String chunks[] = userId.split(" <", 2); + userId = chunks[0]; + if (chunks.length > 1) { + mainUserIdRest.setText("<" + chunks[1]); + } + mainUserId.setText(userId); + } + + keyId.setText(Apg.getFingerPrint(keyInfo.keyId)); + + if (mainUserIdRest.getText().length() == 0) { + mainUserIdRest.setVisibility(View.GONE); + } + + algorithm.setText("" + keyInfo.size + "/" + keyInfo.algorithm); + + if (keyInfo.revoked != null) { + status.setText("revoked"); + } else { + status.setVisibility(View.GONE); + } + + LinearLayout ll = (LinearLayout) view.findViewById(R.id.list); + if (keyInfo.userIds.size() == 1) { + ll.setVisibility(View.GONE); + } else { + boolean first = true; + boolean second = true; + for (String uid : keyInfo.userIds) { + if (first) { + first = false; + continue; + } + if (!second) { + View sep = new View(mActivity); + sep.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, 1)); + sep.setBackgroundResource(android.R.drawable.divider_horizontal_dark); + ll.addView(sep); + } + TextView uidView = (TextView) mInflater.inflate(R.layout.key_server_query_result_user_id, null); + uidView.setText(uid); + ll.addView(uidView); + second = false; + } + } + + return view; + } + } +} diff --git a/src/org/thialfihar/android/apg/MainActivity.java b/src/org/thialfihar/android/apg/MainActivity.java index 310ae062a..c459ad94a 100644 --- a/src/org/thialfihar/android/apg/MainActivity.java +++ b/src/org/thialfihar/android/apg/MainActivity.java @@ -292,12 +292,14 @@ public class MainActivity extends BaseActivity { menu.add(0, Id.menu.option.manage_secret_keys, 1, R.string.menu_manageSecretKeys) .setIcon(android.R.drawable.ic_menu_manage); menu.add(1, Id.menu.option.create, 2, R.string.menu_addAccount) - .setIcon(android.R.drawable.ic_menu_add); + .setIcon(android.R.drawable.ic_menu_add); menu.add(2, Id.menu.option.preferences, 3, R.string.menu_preferences) .setIcon(android.R.drawable.ic_menu_preferences); - menu.add(2, Id.menu.option.about, 4, R.string.menu_about) + menu.add(2, Id.menu.option.key_server, 4, R.string.menu_keyServer) + .setIcon(android.R.drawable.ic_menu_search); + menu.add(3, Id.menu.option.about, 5, R.string.menu_about) .setIcon(android.R.drawable.ic_menu_info_details); - menu.add(22, Id.menu.option.help, 4, R.string.menu_help) + menu.add(3, Id.menu.option.help, 6, R.string.menu_help) .setIcon(android.R.drawable.ic_menu_help); return true; } @@ -325,6 +327,11 @@ public class MainActivity extends BaseActivity { return true; } + case Id.menu.option.key_server: { + startActivity(new Intent(this, KeyServerQueryActivity.class)); + return true; + } + default: { return super.onOptionsItemSelected(item); } diff --git a/src/org/thialfihar/android/apg/SelectPublicKeyListAdapter.java b/src/org/thialfihar/android/apg/SelectPublicKeyListAdapter.java index d7359dbf2..e1277a4b7 100644 --- a/src/org/thialfihar/android/apg/SelectPublicKeyListAdapter.java +++ b/src/org/thialfihar/android/apg/SelectPublicKeyListAdapter.java @@ -187,7 +187,7 @@ public class SelectPublicKeyListAdapter extends BaseAdapter { } long masterKeyId = mCursor.getLong(1); // MASTER_KEY_ID - keyId.setText("" + Long.toHexString(masterKeyId & 0xffffffffL)); + keyId.setText(Apg.getFingerPrint(masterKeyId)); if (mainUserIdRest.getText().length() == 0) { mainUserIdRest.setVisibility(View.GONE); diff --git a/src/org/thialfihar/android/apg/SelectSecretKeyListAdapter.java b/src/org/thialfihar/android/apg/SelectSecretKeyListAdapter.java index 440461aca..979518e68 100644 --- a/src/org/thialfihar/android/apg/SelectSecretKeyListAdapter.java +++ b/src/org/thialfihar/android/apg/SelectSecretKeyListAdapter.java @@ -146,7 +146,7 @@ public class SelectSecretKeyListAdapter extends BaseAdapter { } long masterKeyId = mCursor.getLong(1); // MASTER_KEY_ID - keyId.setText("" + Long.toHexString(masterKeyId & 0xffffffffL)); + keyId.setText(Apg.getFingerPrint(masterKeyId)); if (mainUserIdRest.getText().length() == 0) { mainUserIdRest.setVisibility(View.GONE); diff --git a/src/org/thialfihar/android/apg/ui/widget/KeyEditor.java b/src/org/thialfihar/android/apg/ui/widget/KeyEditor.java index 1bc7515f5..e61451fc0 100644 --- a/src/org/thialfihar/android/apg/ui/widget/KeyEditor.java +++ b/src/org/thialfihar/android/apg/ui/widget/KeyEditor.java @@ -141,14 +141,8 @@ public class KeyEditor extends LinearLayout implements Editor, OnClickListener { } mAlgorithm.setText(Apg.getAlgorithmInfo(key)); - String keyId1Str = Long.toHexString((key.getKeyID() >> 32) & 0xffffffffL); - while (keyId1Str.length() < 8) { - keyId1Str = "0" + keyId1Str; - } - String keyId2Str = Long.toHexString(key.getKeyID() & 0xffffffffL); - while (keyId2Str.length() < 8) { - keyId2Str = "0" + keyId2Str; - } + String keyId1Str = Apg.getFingerPrint(key.getKeyID()); + String keyId2Str = Apg.getFingerPrint(key.getKeyID() >> 32); mKeyId.setText(keyId1Str + " " + keyId2Str); Vector<Choice> choices = new Vector<Choice>(); |