aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTim Bray <timbray@gmail.com>2014-04-18 12:44:42 -0700
committerTim Bray <timbray@gmail.com>2014-04-29 15:04:05 -0700
commite663dadc32633dc12f846539196276265ccc3534 (patch)
tree4f3569105bba19ce7dbc93e53dabb147b2e83142
parente0a0bf04ee6fa4794a82b44dae905bc814d85491 (diff)
downloadopen-keychain-e663dadc32633dc12f846539196276265ccc3534.tar.gz
open-keychain-e663dadc32633dc12f846539196276265ccc3534.tar.bz2
open-keychain-e663dadc32633dc12f846539196276265ccc3534.zip
can search openkeychain, retrieve & install & use keys from there
-rw-r--r--.gitignore3
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java53
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java41
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysClipboardFragment.java2
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysFileFragment.java2
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysKeybaseFragment.java108
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java52
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysQrCodeFragment.java4
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysServerFragment.java2
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListEntry.java2
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListKeybaseLoader.java109
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/JWalk.java56
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/KeybaseKeyServer.java173
-rw-r--r--OpenKeychain/src/main/res/layout/import_keys_keybase_fragment.xml51
-rw-r--r--OpenKeychain/src/main/res/values/arrays.xml1
-rw-r--r--OpenKeychain/src/main/res/values/strings.xml3
16 files changed, 649 insertions, 13 deletions
diff --git a/.gitignore b/.gitignore
index 11b413fd9..1dfe84d5a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -30,4 +30,5 @@ pom.xml.*
*.iml
#OS Specific
-[Tt]humbs.db \ No newline at end of file
+[Tt]humbs.db
+.DS_Store
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java
index 5615b59c4..fcd31b2fe 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java
@@ -56,6 +56,8 @@ import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListEntry;
import org.sufficientlysecure.keychain.util.HkpKeyServer;
import org.sufficientlysecure.keychain.util.InputData;
+import org.sufficientlysecure.keychain.util.KeyServer;
+import org.sufficientlysecure.keychain.util.KeybaseKeyServer;
import org.sufficientlysecure.keychain.util.KeychainServiceListener;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.ProgressScaler;
@@ -103,6 +105,7 @@ public class KeychainIntentService extends IntentService
public static final String ACTION_UPLOAD_KEYRING = Constants.INTENT_PREFIX + "UPLOAD_KEYRING";
public static final String ACTION_DOWNLOAD_AND_IMPORT_KEYS = Constants.INTENT_PREFIX + "QUERY_KEYRING";
+ public static final String ACTION_IMPORT_KEYBASE_KEYS = Constants.INTENT_PREFIX + "DOWNLOAD_KEYBASE";
public static final String ACTION_CERTIFY_KEYRING = Constants.INTENT_PREFIX + "SIGN_KEYRING";
@@ -739,6 +742,56 @@ public class KeychainIntentService extends IntentService
} catch (Exception e) {
sendErrorToHandler(e);
}
+ } else if (ACTION_IMPORT_KEYBASE_KEYS.equals(action)) {
+ ArrayList<ImportKeysListEntry> entries = data.getParcelableArrayList(DOWNLOAD_KEY_LIST);
+
+ try {
+ KeybaseKeyServer server = new KeybaseKeyServer();
+ for (ImportKeysListEntry entry : entries) {
+ // the keybase handle is in userId(1)
+ String username = entry.getUserIds().get(1);
+ byte[] downloadedKeyBytes = server.get(username).getBytes();
+
+ // create PGPKeyRing object based on downloaded armored key
+ PGPKeyRing downloadedKey = null;
+ BufferedInputStream bufferedInput =
+ new BufferedInputStream(new ByteArrayInputStream(downloadedKeyBytes));
+ if (bufferedInput.available() > 0) {
+ InputStream in = PGPUtil.getDecoderStream(bufferedInput);
+ PGPObjectFactory objectFactory = new PGPObjectFactory(in);
+
+ // get first object in block
+ Object obj;
+ if ((obj = objectFactory.nextObject()) != null) {
+ Log.d(Constants.TAG, "Found class: " + obj.getClass());
+
+ if (obj instanceof PGPKeyRing) {
+ downloadedKey = (PGPKeyRing) obj;
+ } else {
+ throw new PgpGeneralException("Object not recognized as PGPKeyRing!");
+ }
+ }
+ }
+
+ // save key bytes in entry object for doing the
+ // actual import afterwards
+ entry.setBytes(downloadedKey.getEncoded());
+ }
+
+ Intent importIntent = new Intent(this, KeychainIntentService.class);
+ importIntent.setAction(ACTION_IMPORT_KEYRING);
+ Bundle importData = new Bundle();
+ importData.putParcelableArrayList(IMPORT_KEY_LIST, entries);
+ importIntent.putExtra(EXTRA_DATA, importData);
+ importIntent.putExtra(EXTRA_MESSENGER, mMessenger);
+
+ // now import it with this service
+ onHandleIntent(importIntent);
+
+ // result is handled in ACTION_IMPORT_KEYRING
+ } catch (Exception e) {
+ sendErrorToHandler(e);
+ }
} else if (ACTION_DOWNLOAD_AND_IMPORT_KEYS.equals(action)) {
try {
ArrayList<ImportKeysListEntry> entries = data.getParcelableArrayList(DOWNLOAD_KEY_LIST);
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java
index 0fccd668f..650e51069 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java
@@ -62,6 +62,8 @@ public class ImportKeysActivity extends ActionBarActivity implements ActionBar.O
+ "IMPORT_KEY_FROM_KEYSERVER";
public static final String ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN = Constants.INTENT_PREFIX
+ "IMPORT_KEY_FROM_KEY_SERVER_AND_RETURN";
+ public static final String ACTION_IMPORT_KEY_FROM_KEYBASE = Constants.INTENT_PREFIX
+ + "IMPORT_KEY_FROM_KEYBASE";
// Actions for internal use only:
public static final String ACTION_IMPORT_KEY_FROM_FILE = Constants.INTENT_PREFIX
@@ -92,13 +94,15 @@ public class ImportKeysActivity extends ActionBarActivity implements ActionBar.O
ImportKeysFileFragment.class,
ImportKeysQrCodeFragment.class,
ImportKeysClipboardFragment.class,
- ImportKeysNFCFragment.class
+ ImportKeysNFCFragment.class,
+ ImportKeysKeybaseFragment.class
};
private static final int NAV_SERVER = 0;
private static final int NAV_FILE = 1;
private static final int NAV_QR_CODE = 2;
private static final int NAV_CLIPBOARD = 3;
private static final int NAV_NFC = 4;
+ private static final int NAV_KEYBASE = 5;
private int mCurrentNavPosition = -1;
@@ -238,6 +242,12 @@ public class ImportKeysActivity extends ActionBarActivity implements ActionBar.O
// no immediate actions!
startListFragment(savedInstanceState, null, null, null);
+ } else if (ACTION_IMPORT_KEY_FROM_KEYBASE.equals(action)) {
+ // NOTE: this only displays the appropriate fragment, no actions are taken
+ loadNavFragment(NAV_KEYBASE, null);
+
+ // no immediate actions!
+ startListFragment(savedInstanceState, null, null, null);
} else {
startListFragment(savedInstanceState, null, null, null);
}
@@ -340,8 +350,8 @@ public class ImportKeysActivity extends ActionBarActivity implements ActionBar.O
startListFragment(savedInstanceState, null, null, query);
}
- public void loadCallback(byte[] importData, Uri dataUri, String serverQuery, String keyServer) {
- mListFragment.loadNew(importData, dataUri, serverQuery, keyServer);
+ public void loadCallback(byte[] importData, Uri dataUri, String serverQuery, String keyServer, String keybaseQuery) {
+ mListFragment.loadNew(importData, dataUri, serverQuery, keyServer, keybaseQuery);
}
/**
@@ -449,6 +459,31 @@ public class ImportKeysActivity extends ActionBarActivity implements ActionBar.O
// start service with intent
startService(intent);
+ } else if (mListFragment.getKeybaseQuery() != null) {
+ // Send all information needed to service to query keys in other thread
+ Intent intent = new Intent(this, KeychainIntentService.class);
+
+ intent.setAction(KeychainIntentService.ACTION_IMPORT_KEYBASE_KEYS);
+
+ // fill values for this action
+ Bundle data = new Bundle();
+
+ // get selected key entries
+ ArrayList<ImportKeysListEntry> selectedEntries = mListFragment.getSelectedData();
+ data.putParcelableArrayList(KeychainIntentService.DOWNLOAD_KEY_LIST, selectedEntries);
+
+ intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
+
+ // Create a new Messenger for the communication back
+ Messenger messenger = new Messenger(saveHandler);
+ intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
+
+ // show progress dialog
+ saveHandler.showProgressDialog(this);
+
+ // start service with intent
+ startService(intent);
+
} else {
AppMsg.makeText(this, R.string.error_nothing_import, AppMsg.STYLE_ALERT).show();
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysClipboardFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysClipboardFragment.java
index 412fbddd8..f331358fa 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysClipboardFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysClipboardFragment.java
@@ -71,7 +71,7 @@ public class ImportKeysClipboardFragment extends Fragment {
return;
}
}
- mImportActivity.loadCallback(sendText.getBytes(), null, null, null);
+ mImportActivity.loadCallback(sendText.getBytes(), null, null, null, null);
}
});
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysFileFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysFileFragment.java
index dc5333a8f..51f961aab 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysFileFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysFileFragment.java
@@ -85,7 +85,7 @@ public class ImportKeysFileFragment extends Fragment {
if (resultCode == Activity.RESULT_OK && data != null) {
// load data
- mImportActivity.loadCallback(null, data.getData(), null, null);
+ mImportActivity.loadCallback(null, data.getData(), null, null, null);
}
break;
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysKeybaseFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysKeybaseFragment.java
new file mode 100644
index 000000000..7ae57cec3
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysKeybaseFragment.java
@@ -0,0 +1,108 @@
+package org.sufficientlysecure.keychain.ui;
+
+import android.content.Context;
+import android.support.v4.app.Fragment;
+import android.os.Bundle;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.EditText;
+import android.widget.TextView;
+
+import com.beardedhen.androidbootstrap.BootstrapButton;
+
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.util.Log;
+
+/**
+ * Import public keys from the Keybase.io directory. First cut: just raw search.
+ * TODO: make a pick list of the people you’re following on keybase
+ */
+public class ImportKeysKeybaseFragment extends Fragment {
+
+ private ImportKeysActivity mImportActivity;
+ private BootstrapButton mSearchButton;
+ private EditText mQueryEditText;
+
+ public static final String ARG_QUERY = "query";
+ public static final String ARG_DISABLE_QUERY_EDIT = "disable_query_edit";
+
+ /**
+ * Creates new instance of this fragment
+ */
+ public static ImportKeysKeybaseFragment newInstance() {
+ ImportKeysKeybaseFragment frag = new ImportKeysKeybaseFragment();
+
+ Bundle args = new Bundle();
+ frag.setArguments(args);
+
+ return frag;
+ }
+
+ /**
+ * Inflate the layout for this fragment
+ */
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.import_keys_keybase_fragment, container, false);
+
+ mQueryEditText = (EditText) view.findViewById(R.id.import_keybase_query);
+
+ mSearchButton = (BootstrapButton) view.findViewById(R.id.import_keybase_search);
+ mSearchButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ String query = mQueryEditText.getText().toString();
+ search(query);
+
+ // close keyboard after pressing search
+ InputMethodManager imm =
+ (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
+ imm.hideSoftInputFromWindow(mQueryEditText.getWindowToken(), 0);
+ }
+ });
+
+ mQueryEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
+ @Override
+ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+ if (actionId == EditorInfo.IME_ACTION_SEARCH) {
+ String query = mQueryEditText.getText().toString();
+ search(query);
+
+ // Don't return true to let the keyboard close itself after pressing search
+ return false;
+ }
+ return false;
+ }
+ });
+
+ return view;
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ mImportActivity = (ImportKeysActivity) getActivity();
+
+ // set displayed values
+ if (getArguments() != null) {
+ if (getArguments().containsKey(ARG_QUERY)) {
+ String query = getArguments().getString(ARG_QUERY);
+ mQueryEditText.setText(query, TextView.BufferType.EDITABLE);
+ }
+
+ if (getArguments().getBoolean(ARG_DISABLE_QUERY_EDIT, false)) {
+ mQueryEditText.setEnabled(false);
+ }
+ }
+ }
+
+ private void search(String query) {
+ mImportActivity.loadCallback(null, null, null, null, query);
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java
index 3a6c384e8..0580db080 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java
@@ -34,6 +34,7 @@ import org.sufficientlysecure.keychain.helper.Preferences;
import org.sufficientlysecure.keychain.ui.adapter.AsyncTaskResultWrapper;
import org.sufficientlysecure.keychain.ui.adapter.ImportKeysAdapter;
import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListEntry;
+import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListKeybaseLoader;
import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListLoader;
import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListServerLoader;
import org.sufficientlysecure.keychain.util.InputData;
@@ -60,9 +61,11 @@ public class ImportKeysListFragment extends ListFragment implements
private Uri mDataUri;
private String mServerQuery;
private String mKeyServer;
+ private String mKeybaseQuery;
private static final int LOADER_ID_BYTES = 0;
private static final int LOADER_ID_SERVER_QUERY = 1;
+ private static final int LOADER_ID_KEYBASE = 2;
public byte[] getKeyBytes() {
return mKeyBytes;
@@ -76,6 +79,10 @@ public class ImportKeysListFragment extends ListFragment implements
return mServerQuery;
}
+ public String getKeybaseQuery() {
+ return mKeybaseQuery;
+ }
+
public String getKeyServer() {
return mKeyServer;
}
@@ -148,6 +155,16 @@ public class ImportKeysListFragment extends ListFragment implements
// give arguments to onCreateLoader()
getLoaderManager().initLoader(LOADER_ID_SERVER_QUERY, null, this);
}
+
+ if (mKeybaseQuery != null) {
+ // Start out with a progress indicator.
+ setListShown(false);
+
+ // Prepare the loader. Either re-connect with an existing one,
+ // or start a new one.
+ // give arguments to onCreateLoader()
+ getLoaderManager().initLoader(LOADER_ID_KEYBASE, null, this);
+ }
}
@Override
@@ -157,16 +174,18 @@ public class ImportKeysListFragment extends ListFragment implements
// Select checkbox!
// Update underlying data and notify adapter of change. The adapter will
// update the view automatically.
+
ImportKeysListEntry entry = mAdapter.getItem(position);
entry.setSelected(!entry.isSelected());
mAdapter.notifyDataSetChanged();
}
- public void loadNew(byte[] keyBytes, Uri dataUri, String serverQuery, String keyServer) {
+ public void loadNew(byte[] keyBytes, Uri dataUri, String serverQuery, String keyServer, String keybaseQuery) {
mKeyBytes = keyBytes;
mDataUri = dataUri;
mServerQuery = serverQuery;
mKeyServer = keyServer;
+ mKeybaseQuery = keybaseQuery;
if (mKeyBytes != null || mDataUri != null) {
// Start out with a progress indicator.
@@ -181,11 +200,18 @@ public class ImportKeysListFragment extends ListFragment implements
getLoaderManager().restartLoader(LOADER_ID_SERVER_QUERY, null, this);
}
+
+ if (mKeybaseQuery != null) {
+ // Start out with a progress indicator.
+ setListShown(false);
+
+ getLoaderManager().restartLoader(LOADER_ID_KEYBASE, null, this);
+ }
}
@Override
public Loader<AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>>
- onCreateLoader(int id, Bundle args) {
+ onCreateLoader(int id, Bundle args) {
switch (id) {
case LOADER_ID_BYTES: {
InputData inputData = getInputData(mKeyBytes, mDataUri);
@@ -194,6 +220,9 @@ public class ImportKeysListFragment extends ListFragment implements
case LOADER_ID_SERVER_QUERY: {
return new ImportKeysListServerLoader(getActivity(), mServerQuery, mKeyServer);
}
+ case LOADER_ID_KEYBASE: {
+ return new ImportKeysListKeybaseLoader(getActivity(), mKeybaseQuery);
+ }
default:
return null;
@@ -248,7 +277,7 @@ public class ImportKeysListFragment extends ListFragment implements
if (error == null) {
AppMsg.makeText(
getActivity(), getResources().getQuantityString(R.plurals.keys_found,
- mAdapter.getCount(), mAdapter.getCount()),
+ mAdapter.getCount(), mAdapter.getCount()),
AppMsg.STYLE_INFO
).show();
} else if (error instanceof KeyServer.InsufficientQuery) {
@@ -263,6 +292,19 @@ public class ImportKeysListFragment extends ListFragment implements
}
break;
+ case LOADER_ID_KEYBASE:
+
+ if (error == null) {
+ AppMsg.makeText(
+ getActivity(), getResources().getQuantityString(R.plurals.keys_found,
+ mAdapter.getCount(), mAdapter.getCount()),
+ AppMsg.STYLE_INFO
+ ).show();
+ } else if (error instanceof KeyServer.QueryException) {
+ AppMsg.makeText(getActivity(), R.string.error_keyserver_query,
+ AppMsg.STYLE_ALERT).show();
+ }
+
default:
break;
}
@@ -279,6 +321,10 @@ public class ImportKeysListFragment extends ListFragment implements
// Clear the data in the adapter.
mAdapter.clear();
break;
+ case LOADER_ID_KEYBASE:
+ // Clear the data in the adapter.
+ mAdapter.clear();
+ break;
default:
break;
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysQrCodeFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysQrCodeFragment.java
index 65d463456..22b56e1ab 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysQrCodeFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysQrCodeFragment.java
@@ -117,7 +117,7 @@ public class ImportKeysQrCodeFragment extends Fragment {
// is this a full key encoded as qr code?
if (scannedContent.startsWith("-----BEGIN PGP")) {
- mImportActivity.loadCallback(scannedContent.getBytes(), null, null, null);
+ mImportActivity.loadCallback(scannedContent.getBytes(), null, null, null, null);
return;
}
@@ -197,7 +197,7 @@ public class ImportKeysQrCodeFragment extends Fragment {
for (String in : mScannedContent) {
result += in;
}
- mImportActivity.loadCallback(result.getBytes(), null, null, null);
+ mImportActivity.loadCallback(result.getBytes(), null, null, null, null);
}
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysServerFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysServerFragment.java
index 82fe2fc4c..9e3d88ff5 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysServerFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysServerFragment.java
@@ -151,7 +151,7 @@ public class ImportKeysServerFragment extends Fragment {
}
private void search(String query, String keyServer) {
- mImportActivity.loadCallback(null, null, query, keyServer);
+ mImportActivity.loadCallback(null, null, query, keyServer, null);
}
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListEntry.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListEntry.java
index b06852af4..1610bfeab 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListEntry.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListEntry.java
@@ -203,7 +203,7 @@ public class ImportKeysListEntry implements Serializable, Parcelable {
* Constructor for later querying from keyserver
*/
public ImportKeysListEntry() {
- // keys from keyserver are always public keys
+ // keys from keyserver are always public keys; from keybase too
secretKey = false;
// do not select by default
mSelected = false;
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListKeybaseLoader.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListKeybaseLoader.java
new file mode 100644
index 000000000..73ff9a8f8
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListKeybaseLoader.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
+ *
+ * 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.sufficientlysecure.keychain.ui.adapter;
+
+import android.content.Context;
+import android.support.v4.content.AsyncTaskLoader;
+
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.util.KeyServer;
+import org.sufficientlysecure.keychain.util.KeybaseKeyServer;
+import org.sufficientlysecure.keychain.util.Log;
+
+import java.util.ArrayList;
+
+public class ImportKeysListKeybaseLoader
+ extends AsyncTaskLoader<AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>> {
+ Context mContext;
+
+ String mKeybaseQuery;
+
+ private ArrayList<ImportKeysListEntry> mEntryList = new ArrayList<ImportKeysListEntry>();
+ private AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>> mEntryListWrapper;
+
+ public ImportKeysListKeybaseLoader(Context context, String keybaseQuery) {
+ super(context);
+ mContext = context;
+ mKeybaseQuery = keybaseQuery;
+ }
+
+ @Override
+ public AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>> loadInBackground() {
+
+ mEntryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>(mEntryList, null);
+
+ if (mKeybaseQuery == null) {
+ Log.e(Constants.TAG, "mKeybaseQery is null!");
+ return mEntryListWrapper;
+ }
+
+ queryServer(mKeybaseQuery);
+
+ return mEntryListWrapper;
+ }
+
+ @Override
+ protected void onReset() {
+ super.onReset();
+
+ // Ensure the loader is stopped
+ onStopLoading();
+ }
+
+ @Override
+ protected void onStartLoading() {
+ forceLoad();
+ }
+
+ @Override
+ protected void onStopLoading() {
+ cancelLoad();
+ }
+
+ @Override
+ public void deliverResult(AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>> data) {
+ super.deliverResult(data);
+ }
+
+ /**
+ * Query keybase
+ */
+ private void queryServer(String query) {
+
+ KeybaseKeyServer server = new KeybaseKeyServer();
+ try {
+ ArrayList<ImportKeysListEntry> searchResult = server.search(query);
+
+ mEntryList.clear();
+
+ mEntryList.addAll(searchResult);
+ mEntryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>(mEntryList, null);
+ } catch (KeyServer.InsufficientQuery e) {
+ Log.e(Constants.TAG, "InsufficientQuery", e);
+ mEntryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>(mEntryList, e);
+ } catch (KeyServer.QueryException e) {
+ Log.e(Constants.TAG, "QueryException", e);
+ mEntryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>(mEntryList, e);
+ } catch (KeyServer.TooManyResponses e) {
+ Log.e(Constants.TAG, "TooManyResponses", e);
+ mEntryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>(mEntryList, e);
+ }
+
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/JWalk.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/JWalk.java
new file mode 100644
index 000000000..6f9c4cfa5
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/JWalk.java
@@ -0,0 +1,56 @@
+package org.sufficientlysecure.keychain.util;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+/**
+ * Minimal hierarchy selector
+ */
+public class JWalk {
+
+ public static int getInt(JSONObject json, String... path) throws JSONException {
+ json = walk(json, path);
+ return json.getInt(path[path.length - 1]);
+ }
+
+ public static long getLong(JSONObject json, String... path) throws JSONException {
+ json = walk(json, path);
+ return json.getLong(path[path.length - 1]);
+ }
+
+ public static String getString(JSONObject json, String... path) throws JSONException {
+ json = walk(json, path);
+ return json.getString(path[path.length - 1]);
+ }
+
+ public static JSONArray getArray(JSONObject json, String... path) throws JSONException {
+ json = walk(json, path);
+ return json.getJSONArray(path[path.length - 1]);
+ }
+
+ public static JSONObject optObject(JSONObject json, String... path) throws JSONException {
+ json = walk(json, path);
+ return json.optJSONObject(path[path.length - 1]);
+ }
+
+ private static JSONObject walk(JSONObject json, String... path) throws JSONException {
+ int len = path.length - 1;
+ int pathIndex = 0;
+ try {
+ while (pathIndex < len) {
+ json = json.getJSONObject(path[pathIndex]);
+ pathIndex++;
+ }
+ } catch (JSONException e) {
+ // try to give ’em a nice-looking error
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < len; i++) {
+ sb.append(path[i]).append('.');
+ }
+ sb.append(path[len]);
+ throw new JSONException("JWalk error at step " + pathIndex + " of " + sb);
+ }
+ return json;
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/KeybaseKeyServer.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/KeybaseKeyServer.java
new file mode 100644
index 000000000..4b802c0e1
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/KeybaseKeyServer.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
+ * Copyright (C) 2011-2014 Thialfihar <thi@thialfihar.org>
+ * Copyright (C) 2011 Senecaso
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.sufficientlysecure.keychain.util;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListEntry;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.net.URLEncoder;
+import java.util.ArrayList;
+import java.util.GregorianCalendar;
+import java.util.TimeZone;
+import java.util.WeakHashMap;
+
+public class KeybaseKeyServer extends KeyServer {
+
+ private WeakHashMap<String, String> mKeyCache = new WeakHashMap<String, String>();
+
+ private static String readAll(InputStream in, String encoding) throws IOException {
+ ByteArrayOutputStream raw = new ByteArrayOutputStream();
+
+ byte buffer[] = new byte[1 << 16];
+ int n = 0;
+ while ((n = in.read(buffer)) != -1) {
+ raw.write(buffer, 0, n);
+ }
+
+ if (encoding == null) {
+ encoding = "utf8";
+ }
+ return raw.toString(encoding);
+ }
+
+ @Override
+ public ArrayList<ImportKeysListEntry> search(String query) throws QueryException, TooManyResponses,
+ InsufficientQuery {
+ ArrayList<ImportKeysListEntry> results = new ArrayList<ImportKeysListEntry>();
+
+ JSONObject fromQuery = getFromKeybase("_/api/1.0/user/autocomplete.json?q=", query);
+ try {
+
+ JSONArray matches = JWalk.getArray(fromQuery, "completions");
+ for (int i = 0; i < matches.length(); i++) {
+ JSONObject match = matches.getJSONObject(i);
+
+ // only list them if they have a key
+ if (JWalk.optObject(match, "components", "key_fingerprint") != null) {
+ results.add(makeEntry(match));
+ }
+ }
+ } catch (Exception e) {
+ throw new QueryException("Unexpected structure in keybase search result: " + e.getMessage());
+ }
+
+ return results;
+ }
+
+ private JSONObject getUser(String keybaseID) throws QueryException {
+ try {
+ return getFromKeybase("_/api/1.0/user/lookup.json?username=", keybaseID);
+ } catch (Exception e) {
+ String detail = "";
+ if (keybaseID != null) {
+ detail = ". Query was for user '" + keybaseID + "'";
+ }
+ throw new QueryException(e.getMessage() + detail);
+ }
+ }
+
+ private ImportKeysListEntry makeEntry(JSONObject match) throws QueryException, JSONException {
+
+ String keybaseID = JWalk.getString(match, "components", "username", "val");
+ String key_fingerprint = JWalk.getString(match, "components", "key_fingerprint", "val");
+ key_fingerprint = key_fingerprint.replace(" ", "").toUpperCase();
+ match = getUser(keybaseID);
+
+ final ImportKeysListEntry entry = new ImportKeysListEntry();
+
+ entry.setBitStrength(4096);
+ entry.setAlgorithm("RSA");
+ entry.setKeyIdHex("0x" + key_fingerprint);
+
+ final long creationDate = JWalk.getLong(match, "them", "public_keys", "primary", "ctime");
+ final GregorianCalendar tmpGreg = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
+ tmpGreg.setTimeInMillis(creationDate * 1000);
+ entry.setDate(tmpGreg.getTime());
+ entry.setRevoked(false);
+ mKeyCache.put(keybaseID, JWalk.getString(match,"them", "public_keys", "primary", "bundle"));
+ String name = JWalk.getString(match, "them", "profile", "full_name");
+ ArrayList<String> userIds = new ArrayList<String>();
+ userIds.add(name);
+ userIds.add("keybase.io/" + keybaseID); // TODO: Maybe should be keybaseID@keybase.io ?
+ entry.setUserIds(userIds);
+ entry.setPrimaryUserId(name);
+ return entry;
+ }
+
+ private JSONObject getFromKeybase(String path, String query) throws QueryException {
+ try {
+ String url = "https://keybase.io/" + path + URLEncoder.encode(query, "utf8");
+ Log.d(Constants.TAG, "keybase query: " + url);
+
+ URL realUrl = new URL(url);
+ HttpURLConnection conn = (HttpURLConnection) realUrl.openConnection();
+ conn.setConnectTimeout(5000); // TODO: Reasonable values for keybase
+ conn.setReadTimeout(25000);
+ conn.connect();
+ int response = conn.getResponseCode();
+ if (response >= 200 && response < 300) {
+ String text = readAll(conn.getInputStream(), conn.getContentEncoding());
+ try {
+ JSONObject json = new JSONObject(text);
+ if (JWalk.getInt(json, "status", "code") != 0) {
+ throw new QueryException("Keybase autocomplete search failed");
+ }
+ return json;
+ } catch (JSONException e) {
+ throw new QueryException("Keybase.io query returned broken JSON");
+ }
+ } else {
+ String message = readAll(conn.getErrorStream(), conn.getContentEncoding());
+ throw new QueryException("Keybase.io query error (status=" + response +
+ "): " + message);
+ }
+ } catch (Exception e) {
+ throw new QueryException("Keybase.io query error");
+ }
+ }
+
+ @Override
+ public String get(String id) throws QueryException {
+ // id is like "keybase/username"
+ String keybaseID = id.substring(id.indexOf('/') + 1);
+ String key = mKeyCache.get(keybaseID);
+ if (key == null) {
+ try {
+ JSONObject user = getUser(keybaseID);
+ key = JWalk.getString(user, "them", "public_keys", "primary", "bundle");
+ } catch (Exception e) {
+ throw new QueryException(e.getMessage());
+ }
+ }
+ return key;
+ }
+
+ @Override
+ public void add(String armoredKey) throws AddKeyException {
+ throw new AddKeyException();
+ }
+} \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/layout/import_keys_keybase_fragment.xml b/OpenKeychain/src/main/res/layout/import_keys_keybase_fragment.xml
new file mode 100644
index 000000000..248581342
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/import_keys_keybase_fragment.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:bootstrapbutton="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal" >
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+
+ <EditText
+ android:id="@+id/import_keybase_query"
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:gravity="top|left"
+ android:hint="@string/hint_keybase_search"
+ android:imeOptions="actionSearch"
+ android:inputType="textNoSuggestions"
+ android:singleLine="true"
+ android:lines="1"
+ android:maxLines="1"
+ android:minLines="1"
+ android:layout_gravity="center_vertical" />
+
+ <com.beardedhen.androidbootstrap.BootstrapButton
+ android:id="@+id/import_keybase_search"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:layout_marginLeft="10dp"
+ bootstrapbutton:bb_icon_left="fa-search"
+ bootstrapbutton:bb_roundedCorners="true"
+ bootstrapbutton:bb_size="default"
+ bootstrapbutton:bb_type="default" />
+ </LinearLayout>
+
+ <!--
+ <com.beardedhen.androidbootstrap.BootstrapButton
+ android:id="@+id/import_keybase_button"
+ android:layout_width="match_parent"
+ android:layout_height="70dp"
+ android:layout_margin="10dp"
+ android:text="@string/import_keybase_button"
+ bootstrapbutton:bb_size="default"
+ bootstrapbutton:bb_type="default" />
+ -->
+
+</LinearLayout> \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/values/arrays.xml b/OpenKeychain/src/main/res/values/arrays.xml
index 4173d49e4..699c02aff 100644
--- a/OpenKeychain/src/main/res/values/arrays.xml
+++ b/OpenKeychain/src/main/res/values/arrays.xml
@@ -54,6 +54,7 @@
<item>@string/menu_import_from_qr_code</item>
<item>@string/import_from_clipboard</item>
<item>@string/menu_import_from_nfc</item>
+ <item>@string/menu_import_from_keybase</item>
</string-array>
</resources>
diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml
index 330bc349d..21d008bb9 100644
--- a/OpenKeychain/src/main/res/values/strings.xml
+++ b/OpenKeychain/src/main/res/values/strings.xml
@@ -75,6 +75,7 @@
<string name="menu_create_key_expert">Create key (expert)</string>
<string name="menu_search">Search</string>
<string name="menu_import_from_key_server">Keyserver</string>
+ <string name="menu_import_from_keybase">Import from Keybase.io</string>
<string name="menu_key_server">Keyserver…</string>
<string name="menu_update_key">Update from keyserver</string>
<string name="menu_export_key_to_server">Upload to key server</string>
@@ -348,6 +349,7 @@
<string name="hint_public_keys">Search Public Keys</string>
<string name="hint_secret_keys">Search Secret Keys</string>
<string name="action_share_key_with">Share Key with…</string>
+ <string name="hint_keybase_search">Search Keybase.io</string>
<!-- key bit length selections -->
<string name="key_size_512">512</string>
@@ -393,6 +395,7 @@
<string name="import_nfc_text">To receive keys via NFC, the device needs to be unlocked.</string>
<string name="import_nfc_help_button">Help</string>
<string name="import_clipboard_button">Get key from clipboard</string>
+ <string name="import_keybase_button">Get key from Keybase.io</string>
<!-- Intent labels -->
<string name="intent_decrypt_file">Decrypt File with OpenKeychain</string>