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 | |
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')
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>(); |