diff options
| author | Dominik Schürmann <dominik@dominikschuermann.de> | 2014-05-05 10:10:47 +0200 | 
|---|---|---|
| committer | Dominik Schürmann <dominik@dominikschuermann.de> | 2014-05-05 10:10:47 +0200 | 
| commit | 6d10ca678a541dddb26f43c9d415d14f6f5eceaf (patch) | |
| tree | ee097be93497a90139f982bbf9851eeb2115dafd /OpenKeychain/src/main/java/org/sufficientlysecure | |
| parent | e48460bb7df8f2d40fe187f8c267fbba3fb43c75 (diff) | |
| parent | 90b4db07925ec4741fedfa75eed4ed6acc533696 (diff) | |
| download | open-keychain-6d10ca678a541dddb26f43c9d415d14f6f5eceaf.tar.gz open-keychain-6d10ca678a541dddb26f43c9d415d14f6f5eceaf.tar.bz2 open-keychain-6d10ca678a541dddb26f43c9d415d14f6f5eceaf.zip  | |
Merge pull request #603 from timbray/master
Adds first level of keybase support
Diffstat (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure')
15 files changed, 621 insertions, 31 deletions
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..eabcfadee 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,7 @@ 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.KeybaseKeyServer;  import org.sufficientlysecure.keychain.util.KeychainServiceListener;  import org.sufficientlysecure.keychain.util.Log;  import org.sufficientlysecure.keychain.util.ProgressScaler; @@ -103,6 +104,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 +741,55 @@ 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 keybaseID = entry.getUserIds().get(1); +                    byte[] downloadedKeyBytes = server.get(keybaseID).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) { + +                            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); @@ -767,7 +818,6 @@ public class KeychainIntentService extends IntentService                          // 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; 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..a996079c9 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysKeybaseFragment.java @@ -0,0 +1,120 @@ +/* + * 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; + +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"; + +    /** +     * 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); +            } +        } +    } + +    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..c1aa8a1f2 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,6 +200,13 @@ 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 @@ -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..e66032482 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListKeybaseLoader.java @@ -0,0 +1,106 @@ +/* + * 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) { +            mEntryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>(mEntryList, e); +        } catch (KeyServer.QueryException e) { +            mEntryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>(mEntryList, e); +        } catch (KeyServer.TooManyResponses e) { +            mEntryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>(mEntryList, e); +        } + +    } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListServerLoader.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListServerLoader.java index 838aeefee..65810064c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListServerLoader.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListServerLoader.java @@ -116,13 +116,10 @@ public class ImportKeysListServerLoader              }              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/HkpKeyServer.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/HkpKeyServer.java index cf658b0b6..b81fba05d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/HkpKeyServer.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/HkpKeyServer.java @@ -34,7 +34,6 @@ import org.sufficientlysecure.keychain.pgp.PgpHelper;  import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;  import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListEntry; -import java.io.ByteArrayOutputStream;  import java.io.IOException;  import java.io.InputStream;  import java.io.UnsupportedEncodingException; @@ -167,21 +166,6 @@ public class HkpKeyServer extends KeyServer {          mPort = port;      } -    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); -    } -      private String query(String request) throws QueryException, HttpError {          InetAddress ips[];          try { 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..7ae1d8fab --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/JWalk.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2014 Tim Bray <tbray@textuality.com> + * + * 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.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/KeyServer.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/KeyServer.java index 2b97165ac..94c02a773 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/KeyServer.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/KeyServer.java @@ -20,6 +20,9 @@ package org.sufficientlysecure.keychain.util;  import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListEntry; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream;  import java.util.List;  public abstract class KeyServer { @@ -49,4 +52,19 @@ public abstract class KeyServer {      abstract String get(String keyIdHex) throws QueryException;      abstract void add(String armoredKey) throws AddKeyException; + +    public 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); +    }  } 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..fb58e9f1d --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/KeybaseKeyServer.java @@ -0,0 +1,161 @@ +/* + * 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.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>(); + +    @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(); + +        // TODO: Fix; have suggested keybase provide this value to avoid search-time crypto calls +        entry.setBitStrength(4096); +        entry.setAlgorithm("RSA"); +        entry.setKeyIdHex("0x" + key_fingerprint); +        entry.setRevoked(false); + +        // ctime +        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()); + +        // key bits +        // we have to fetch the user object to construct the search-result list, so we might as +        //  well (weakly) remember the key, in case they try to import it +        mKeyCache.put(keybaseID, JWalk.getString(match,"them", "public_keys", "primary", "bundle")); + +        // String displayName = JWalk.getString(match, "them", "profile", "full_name"); +        ArrayList<String> userIds = new ArrayList<String>(); +        String name = "keybase.io/" + keybaseID + " <" + keybaseID + "@keybase.io>"; +        userIds.add(name); +        userIds.add(keybaseID); +        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 { +        String key = mKeyCache.get(id); +        if (key == null) { +            try { +                JSONObject user = getUser(id); +                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  | 
