From 765ec094c9415fcaddd65b7b743179b2ea7dc098 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Fri, 28 Aug 2015 15:18:11 +0200 Subject: Make keybase an experimental feature --- .../org/sufficientlysecure/keychain/Constants.java | 1 + .../keychain/ui/SettingsActivity.java | 15 + .../keychain/ui/ViewKeyActivity.java | 7 + .../keychain/ui/ViewKeyAdvActivity.java | 6 - .../keychain/ui/ViewKeyKeybaseFragment.java | 520 ++++++++++++++++++++ .../keychain/ui/ViewKeyTrustFragment.java | 531 --------------------- .../keychain/util/Preferences.java | 10 + .../src/main/res/layout/view_key_activity.xml | 22 +- .../res/layout/view_key_adv_keybase_fragment.xml | 169 +++---- .../main/res/layout/view_key_adv_keybase_proof.xml | 4 +- .../src/main/res/layout/view_key_fragment.xml | 4 +- OpenKeychain/src/main/res/values/strings.xml | 10 +- .../src/main/res/xml/experimental_preferences.xml | 7 + 13 files changed, 653 insertions(+), 653 deletions(-) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyKeybaseFragment.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyTrustFragment.java diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java index 60bcf187d..6a9656b28 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java @@ -111,6 +111,7 @@ public final class Constants { // other settings public static final String EXPERIMENTAL_ENABLE_WORD_CONFIRM = "experimentalEnableWordConfirm"; public static final String EXPERIMENTAL_ENABLE_LINKED_IDENTITIES = "experimentalEnableLinkedIdentities"; + public static final String EXPERIMENTAL_ENABLE_KEYBASE = "experimentalEnableKeybase"; public static final class Theme { public static final String LIGHT = "light"; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java index 43edc8220..4077f1c84 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java @@ -570,7 +570,11 @@ public class SettingsActivity extends AppCompatPreferenceActivity { initializeExperimentalEnableLinkedIdentities( (SwitchPreference) findPreference(Constants.Pref.EXPERIMENTAL_ENABLE_LINKED_IDENTITIES)); + initializeExperimentalEnableKeybase( + (SwitchPreference) findPreference(Constants.Pref.EXPERIMENTAL_ENABLE_KEYBASE)); + initializeTheme((ListPreference) findPreference(Constants.Pref.THEME)); + } } @@ -702,4 +706,15 @@ public class SettingsActivity extends AppCompatPreferenceActivity { } }); } + + private static void initializeExperimentalEnableKeybase(final SwitchPreference mExperimentalKeybase) { + mExperimentalKeybase.setChecked(sPreferences.getExperimentalEnableKeybase()); + mExperimentalKeybase.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + public boolean onPreferenceChange(Preference preference, Object newValue) { + mExperimentalKeybase.setChecked((Boolean) newValue); + sPreferences.setExperimentalEnableKeybase((Boolean) newValue); + return false; + } + }); + } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java index 4ae568d28..06126ebc4 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java @@ -300,6 +300,13 @@ public class ViewKeyActivity extends BaseNfcActivity implements .replace(R.id.view_key_fragment, frag) .commit(); + if (Preferences.getPreferences(this).getExperimentalEnableKeybase()) { + final ViewKeyKeybaseFragment keybaseFrag = ViewKeyKeybaseFragment.newInstance(mDataUri); + manager.beginTransaction() + .replace(R.id.view_key_keybase_fragment, keybaseFrag) + .commit(); + } + // need to postpone loading of the yubikey fragment until after mMasterKeyId // is available, but we mark here that this should be done mShowYubikeyAfterCreation = true; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvActivity.java index 673092e61..edd9feec9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvActivity.java @@ -57,7 +57,6 @@ public class ViewKeyAdvActivity extends BaseActivity implements public static final int TAB_IDENTITIES = 1; public static final int TAB_SUBKEYS = 2; public static final int TAB_CERTS = 3; - public static final int TAB_KEYBASE = 4; // view private ViewPager mViewPager; @@ -140,11 +139,6 @@ public class ViewKeyAdvActivity extends BaseActivity implements adapter.addTab(ViewKeyAdvCertsFragment.class, certsBundle, getString(R.string.key_view_tab_certs)); - Bundle trustBundle = new Bundle(); - trustBundle.putParcelable(ViewKeyTrustFragment.ARG_DATA_URI, dataUri); - adapter.addTab(ViewKeyTrustFragment.class, - trustBundle, getString(R.string.key_view_tab_keybase)); - // update layout after operations mSlidingTabLayout.setViewPager(mViewPager); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyKeybaseFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyKeybaseFragment.java new file mode 100644 index 000000000..266633061 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyKeybaseFragment.java @@ -0,0 +1,520 @@ +/* + * Copyright (C) 2014 Tim Bray + * + * 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 . + */ + +package org.sufficientlysecure.keychain.ui; + +import android.content.Intent; +import android.database.Cursor; +import android.graphics.Typeface; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.Bundle; +import android.support.v4.app.LoaderManager; +import android.support.v4.content.CursorLoader; +import android.support.v4.content.Loader; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.method.LinkMovementMethod; +import android.text.style.ClickableSpan; +import android.text.style.StyleSpan; +import android.text.style.URLSpan; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TableLayout; +import android.widget.TableRow; +import android.widget.TextView; + +import com.textuality.keybase.lib.KeybaseException; +import com.textuality.keybase.lib.Proof; +import com.textuality.keybase.lib.User; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.operations.results.KeybaseVerificationResult; +import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; +import org.sufficientlysecure.keychain.service.KeybaseVerificationParcel; +import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; +import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.ParcelableProxy; +import org.sufficientlysecure.keychain.util.Preferences; +import org.sufficientlysecure.keychain.util.orbot.OrbotHelper; + +import java.util.ArrayList; +import java.util.Hashtable; +import java.util.List; + +public class ViewKeyKeybaseFragment extends LoaderFragment implements + LoaderManager.LoaderCallbacks, + CryptoOperationHelper.Callback { + + public static final String ARG_DATA_URI = "uri"; + + private TextView mReportHeader; + private TableLayout mProofListing; + private LayoutInflater mInflater; + private View mProofVerifyHeader; + private TextView mProofVerifyDetail; + + private static final int LOADER_ID_DATABASE = 1; + + // for retrieving the key we’re working on + private Uri mDataUri; + + private Proof mProof; + + // for CryptoOperationHelper,Callback + private String mKeybaseProof; + private String mKeybaseFingerprint; + private CryptoOperationHelper + mKeybaseOpHelper; + + /** + * Creates new instance of this fragment + */ + public static ViewKeyKeybaseFragment newInstance(Uri dataUri) { + ViewKeyKeybaseFragment frag = new ViewKeyKeybaseFragment(); + Bundle args = new Bundle(); + args.putParcelable(ARG_DATA_URI, dataUri); + + frag.setArguments(args); + + return frag; + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) { + View root = super.onCreateView(inflater, superContainer, savedInstanceState); + View view = inflater.inflate(R.layout.view_key_adv_keybase_fragment, getContainer()); + mInflater = inflater; + + mReportHeader = (TextView) view.findViewById(R.id.view_key_trust_cloud_narrative); + mProofListing = (TableLayout) view.findViewById(R.id.view_key_proof_list); + mProofVerifyHeader = view.findViewById(R.id.view_key_proof_verify_header); + mProofVerifyDetail = (TextView) view.findViewById(R.id.view_key_proof_verify_detail); + mReportHeader.setVisibility(View.GONE); + mProofListing.setVisibility(View.GONE); + mProofVerifyHeader.setVisibility(View.GONE); + mProofVerifyDetail.setVisibility(View.GONE); + + return root; + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + Uri dataUri = getArguments().getParcelable(ARG_DATA_URI); + if (dataUri == null) { + Log.e(Constants.TAG, "Data missing. Should be Uri of key!"); + getActivity().finish(); + return; + } + mDataUri = dataUri; + + // retrieve the key from the database + getLoaderManager().initLoader(LOADER_ID_DATABASE, null, this); + } + + static final String[] TRUST_PROJECTION = new String[]{ + KeyRings._ID, KeyRings.FINGERPRINT, KeyRings.IS_REVOKED, KeyRings.IS_EXPIRED, + KeyRings.HAS_ANY_SECRET, KeyRings.VERIFIED + }; + static final int INDEX_TRUST_FINGERPRINT = 1; + static final int INDEX_TRUST_IS_REVOKED = 2; + static final int INDEX_TRUST_IS_EXPIRED = 3; + static final int INDEX_UNIFIED_HAS_ANY_SECRET = 4; + static final int INDEX_VERIFIED = 5; + + public Loader onCreateLoader(int id, Bundle args) { + setContentShown(false); + + switch (id) { + case LOADER_ID_DATABASE: { + Uri baseUri = KeyRings.buildUnifiedKeyRingUri(mDataUri); + return new CursorLoader(getActivity(), baseUri, TRUST_PROJECTION, null, null, null); + } + // decided to just use an AsyncTask for keybase, but maybe later + default: + return null; + } + } + + public void onLoadFinished(Loader loader, Cursor data) { + /* TODO better error handling? May cause problems when a key is deleted, + * because the notification triggers faster than the activity closes. + */ + // Avoid NullPointerExceptions... + if (data.getCount() == 0) { + return; + } + + boolean nothingSpecial = true; + + // Swap the new cursor in. (The framework will take care of closing the + // old cursor once we return.) + if (data.moveToFirst()) { + + final byte[] fp = data.getBlob(INDEX_TRUST_FINGERPRINT); + final String fingerprint = KeyFormattingUtils.convertFingerprintToHex(fp); + + startSearch(fingerprint); + } + + setContentShown(true); + } + + private void startSearch(final String fingerprint) { + final Preferences.ProxyPrefs proxyPrefs = + Preferences.getPreferences(getActivity()).getProxyPrefs(); + + OrbotHelper.DialogActions dialogActions = new OrbotHelper.DialogActions() { + @Override + public void onOrbotStarted() { + new DescribeKey(proxyPrefs.parcelableProxy).execute(fingerprint); + } + + @Override + public void onNeutralButton() { + new DescribeKey(ParcelableProxy.getForNoProxy()) + .execute(fingerprint); + } + + @Override + public void onCancel() { + + } + }; + + if (OrbotHelper.putOrbotInRequiredState(dialogActions, getActivity())) { + new DescribeKey(proxyPrefs.parcelableProxy).execute(fingerprint); + } + } + + /** + * This is called when the last Cursor provided to onLoadFinished() above is about to be closed. + * We need to make sure we are no longer using it. + */ + public void onLoaderReset(Loader loader) { + // no-op in this case I think + } + + class ResultPage { + String mHeader; + final List mProofs; + + public ResultPage(String header, List proofs) { + mHeader = header; + mProofs = proofs; + } + } + + // look for evidence from keybase in the background, make tabular version of result + // + private class DescribeKey extends AsyncTask { + ParcelableProxy mParcelableProxy; + + public DescribeKey(ParcelableProxy parcelableProxy) { + mParcelableProxy = parcelableProxy; + } + + @Override + protected ResultPage doInBackground(String... args) { + String fingerprint = args[0]; + + final ArrayList proofList = new ArrayList(); + final Hashtable> proofs = new Hashtable>(); + try { + User keybaseUser = User.findByFingerprint(fingerprint, mParcelableProxy.getProxy()); + for (Proof proof : keybaseUser.getProofs()) { + Integer proofType = proof.getType(); + appendIfOK(proofs, proofType, proof); + } + + // a one-liner in a modern programming language + for (Integer proofType : proofs.keySet()) { + Proof[] x = {}; + Proof[] proofsFor = proofs.get(proofType).toArray(x); + if (proofsFor.length > 0) { + SpannableStringBuilder ssb = new SpannableStringBuilder(); + int i = 0; + while (i < proofsFor.length - 1) { + appendProofLinks(ssb, fingerprint, proofsFor[i]); + ssb.append(", "); + i++; + } + appendProofLinks(ssb, fingerprint, proofsFor[i]); + proofList.add(formatSpannableString(ssb, getProofNarrative(proofType))); + } + } + + } catch (KeybaseException ignored) { + } + + return new ResultPage(getString(R.string.key_trust_results_prefix), proofList); + } + + private SpannableStringBuilder formatSpannableString(SpannableStringBuilder proofLinks, String proofType) { + //Formatting SpannableStringBuilder with String.format() causes the links to stop working. + //This method is to insert the links while reserving the links + + SpannableStringBuilder ssb = new SpannableStringBuilder(); + ssb.append(proofType); + if (proofType.contains("%s")) { + int i = proofType.indexOf("%s"); + ssb.replace(i, i + 2, proofLinks); + } else ssb.append(proofLinks); + + return ssb; + } + + private SpannableStringBuilder appendProofLinks(SpannableStringBuilder ssb, final String fingerprint, final Proof proof) throws KeybaseException { + int startAt = ssb.length(); + String handle = proof.getHandle(); + ssb.append(handle); + ssb.setSpan(new URLSpan(proof.getServiceUrl()), startAt, startAt + handle.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + if (haveProofFor(proof.getType())) { + ssb.append("\u00a0["); + startAt = ssb.length(); + String verify = getString(R.string.keybase_verify); + ssb.append(verify); + ClickableSpan clicker = new ClickableSpan() { + @Override + public void onClick(View view) { + verify(proof, fingerprint); + } + }; + ssb.setSpan(clicker, startAt, startAt + verify.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + ssb.append("]"); + } + return ssb; + } + + @Override + protected void onPostExecute(ResultPage result) { + super.onPostExecute(result); + if (result.mProofs.isEmpty()) { + result.mHeader = getActivity().getString(R.string.key_trust_no_cloud_evidence); + } + + mReportHeader.setVisibility(View.VISIBLE); + mProofListing.setVisibility(View.VISIBLE); + mReportHeader.setText(result.mHeader); + + int rowNumber = 1; + for (CharSequence s : result.mProofs) { + TableRow row = (TableRow) mInflater.inflate(R.layout.view_key_adv_keybase_proof, null); + TextView number = (TextView) row.findViewById(R.id.proof_number); + TextView text = (TextView) row.findViewById(R.id.proof_text); + number.setText(Integer.toString(rowNumber++) + ". "); + text.setText(s); + text.setMovementMethod(LinkMovementMethod.getInstance()); + mProofListing.addView(row); + } + } + } + + private String getProofNarrative(int proofType) { + int stringIndex; + switch (proofType) { + case Proof.PROOF_TYPE_TWITTER: + stringIndex = R.string.keybase_narrative_twitter; + break; + case Proof.PROOF_TYPE_GITHUB: + stringIndex = R.string.keybase_narrative_github; + break; + case Proof.PROOF_TYPE_DNS: + stringIndex = R.string.keybase_narrative_dns; + break; + case Proof.PROOF_TYPE_WEB_SITE: + stringIndex = R.string.keybase_narrative_web_site; + break; + case Proof.PROOF_TYPE_HACKERNEWS: + stringIndex = R.string.keybase_narrative_hackernews; + break; + case Proof.PROOF_TYPE_COINBASE: + stringIndex = R.string.keybase_narrative_coinbase; + break; + case Proof.PROOF_TYPE_REDDIT: + stringIndex = R.string.keybase_narrative_reddit; + break; + default: + stringIndex = R.string.keybase_narrative_unknown; + } + return getActivity().getString(stringIndex); + } + + private void appendIfOK(Hashtable> table, Integer proofType, Proof proof) throws KeybaseException { + ArrayList list = table.get(proofType); + if (list == null) { + list = new ArrayList(); + table.put(proofType, list); + } + list.add(proof); + } + + // which proofs do we have working verifiers for? + private boolean haveProofFor(int proofType) { + switch (proofType) { + case Proof.PROOF_TYPE_TWITTER: + return true; + case Proof.PROOF_TYPE_GITHUB: + return true; + case Proof.PROOF_TYPE_DNS: + return true; + case Proof.PROOF_TYPE_WEB_SITE: + return true; + case Proof.PROOF_TYPE_HACKERNEWS: + return true; + case Proof.PROOF_TYPE_COINBASE: + return true; + case Proof.PROOF_TYPE_REDDIT: + return true; + default: + return false; + } + } + + private void verify(final Proof proof, final String fingerprint) { + + mProof = proof; + mKeybaseProof = proof.toString(); + mKeybaseFingerprint = fingerprint; + + mProofVerifyDetail.setVisibility(View.GONE); + + mKeybaseOpHelper = new CryptoOperationHelper<>(1, this, this, + R.string.progress_verifying_signature); + mKeybaseOpHelper.cryptoOperation(); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (mKeybaseOpHelper != null) { + mKeybaseOpHelper.handleActivityResult(requestCode, resultCode, data); + } + } + + // CryptoOperationHelper.Callback methods + @Override + public KeybaseVerificationParcel createOperationInput() { + return new KeybaseVerificationParcel(mKeybaseProof, mKeybaseFingerprint); + } + + @Override + public void onCryptoOperationSuccess(KeybaseVerificationResult result) { + + result.createNotify(getActivity()).show(); + + String proofUrl = result.mProofUrl; + String presenceUrl = result.mPresenceUrl; + String presenceLabel = result.mPresenceLabel; + + Proof proof = mProof; // TODO: should ideally be contained in result + + String proofLabel; + switch (proof.getType()) { + case Proof.PROOF_TYPE_TWITTER: + proofLabel = getString(R.string.keybase_twitter_proof); + break; + case Proof.PROOF_TYPE_DNS: + proofLabel = getString(R.string.keybase_dns_proof); + break; + case Proof.PROOF_TYPE_WEB_SITE: + proofLabel = getString(R.string.keybase_web_site_proof); + break; + case Proof.PROOF_TYPE_GITHUB: + proofLabel = getString(R.string.keybase_github_proof); + break; + case Proof.PROOF_TYPE_REDDIT: + proofLabel = getString(R.string.keybase_reddit_proof); + break; + default: + proofLabel = getString(R.string.keybase_a_post); + break; + } + + SpannableStringBuilder ssb = new SpannableStringBuilder(); + + ssb.append(getString(R.string.keybase_proof_succeeded)); + StyleSpan bold = new StyleSpan(Typeface.BOLD); + ssb.setSpan(bold, 0, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + ssb.append("\n\n"); + int length = ssb.length(); + ssb.append(proofLabel); + if (proofUrl != null) { + URLSpan postLink = new URLSpan(proofUrl); + ssb.setSpan(postLink, length, length + proofLabel.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + if (Proof.PROOF_TYPE_DNS == proof.getType()) { + ssb.append(" ").append(getString(R.string.keybase_for_the_domain)).append(" "); + } else { + ssb.append(" ").append(getString(R.string.keybase_fetched_from)).append(" "); + } + length = ssb.length(); + URLSpan presenceLink = new URLSpan(presenceUrl); + ssb.append(presenceLabel); + ssb.setSpan(presenceLink, length, length + presenceLabel.length(), Spanned + .SPAN_EXCLUSIVE_EXCLUSIVE); + if (Proof.PROOF_TYPE_REDDIT == proof.getType()) { + ssb.append(", "). + append(getString(R.string.keybase_reddit_attribution)). + append(" “").append(proof.getHandle()).append("”, "); + } + ssb.append(" ").append(getString(R.string.keybase_contained_signature)); + + displaySpannableResult(ssb); + } + + @Override + public void onCryptoOperationCancelled() { + + } + + @Override + public void onCryptoOperationError(KeybaseVerificationResult result) { + + result.createNotify(getActivity()).show(); + + SpannableStringBuilder ssb = new SpannableStringBuilder(); + + ssb.append(getString(R.string.keybase_proof_failure)); + String msg = getString(result.getLog().getLast().mType.mMsgId); + if (msg == null) { + msg = getString(R.string.keybase_unknown_proof_failure); + } + StyleSpan bold = new StyleSpan(Typeface.BOLD); + ssb.setSpan(bold, 0, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + ssb.append("\n\n").append(msg); + + displaySpannableResult(ssb); + } + + @Override + public boolean onCryptoSetProgress(String msg, int progress, int max) { + return false; + } + + private void displaySpannableResult(SpannableStringBuilder ssb) { + mProofVerifyHeader.setVisibility(View.VISIBLE); + mProofVerifyDetail.setVisibility(View.VISIBLE); + mProofVerifyDetail.setMovementMethod(LinkMovementMethod.getInstance()); + mProofVerifyDetail.setText(ssb); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyTrustFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyTrustFragment.java deleted file mode 100644 index 150acdc90..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyTrustFragment.java +++ /dev/null @@ -1,531 +0,0 @@ -/* - * Copyright (C) 2014 Tim Bray - * - * 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 . - */ - -package org.sufficientlysecure.keychain.ui; - -import android.content.Intent; -import android.database.Cursor; -import android.graphics.Typeface; -import android.net.Uri; -import android.os.AsyncTask; -import android.os.Bundle; -import android.support.v4.app.LoaderManager; -import android.support.v4.content.CursorLoader; -import android.support.v4.content.Loader; -import android.text.SpannableStringBuilder; -import android.text.Spanned; -import android.text.method.LinkMovementMethod; -import android.text.style.ClickableSpan; -import android.text.style.StyleSpan; -import android.text.style.URLSpan; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.TableLayout; -import android.widget.TableRow; -import android.widget.TextView; - -import com.textuality.keybase.lib.KeybaseException; -import com.textuality.keybase.lib.Proof; -import com.textuality.keybase.lib.User; - -import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.operations.results.KeybaseVerificationResult; -import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; -import org.sufficientlysecure.keychain.service.KeybaseVerificationParcel; -import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; -import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; -import org.sufficientlysecure.keychain.util.Log; -import org.sufficientlysecure.keychain.util.ParcelableProxy; -import org.sufficientlysecure.keychain.util.Preferences; -import org.sufficientlysecure.keychain.util.orbot.OrbotHelper; - -import java.util.ArrayList; -import java.util.Hashtable; -import java.util.List; - -public class ViewKeyTrustFragment extends LoaderFragment implements - LoaderManager.LoaderCallbacks, - CryptoOperationHelper.Callback { - - public static final String ARG_DATA_URI = "uri"; - - private View mStartSearch; - private TextView mTrustReadout; - private TextView mReportHeader; - private TableLayout mProofListing; - private LayoutInflater mInflater; - private View mProofVerifyHeader; - private TextView mProofVerifyDetail; - - private static final int LOADER_ID_DATABASE = 1; - - // for retrieving the key we’re working on - private Uri mDataUri; - - private Proof mProof; - - // for CryptoOperationHelper,Callback - private String mKeybaseProof; - private String mKeybaseFingerprint; - private CryptoOperationHelper - mKeybaseOpHelper; - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) { - View root = super.onCreateView(inflater, superContainer, savedInstanceState); - View view = inflater.inflate(R.layout.view_key_adv_keybase_fragment, getContainer()); - mInflater = inflater; - - mTrustReadout = (TextView) view.findViewById(R.id.view_key_trust_readout); - mStartSearch = view.findViewById(R.id.view_key_trust_search_cloud); - mStartSearch.setEnabled(false); - mReportHeader = (TextView) view.findViewById(R.id.view_key_trust_cloud_narrative); - mProofListing = (TableLayout) view.findViewById(R.id.view_key_proof_list); - mProofVerifyHeader = view.findViewById(R.id.view_key_proof_verify_header); - mProofVerifyDetail = (TextView) view.findViewById(R.id.view_key_proof_verify_detail); - mReportHeader.setVisibility(View.GONE); - mProofListing.setVisibility(View.GONE); - mProofVerifyHeader.setVisibility(View.GONE); - mProofVerifyDetail.setVisibility(View.GONE); - - return root; - } - - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - - Uri dataUri = getArguments().getParcelable(ARG_DATA_URI); - if (dataUri == null) { - Log.e(Constants.TAG, "Data missing. Should be Uri of key!"); - getActivity().finish(); - return; - } - mDataUri = dataUri; - - // retrieve the key from the database - getLoaderManager().initLoader(LOADER_ID_DATABASE, null, this); - } - - static final String[] TRUST_PROJECTION = new String[]{ - KeyRings._ID, KeyRings.FINGERPRINT, KeyRings.IS_REVOKED, KeyRings.IS_EXPIRED, - KeyRings.HAS_ANY_SECRET, KeyRings.VERIFIED - }; - static final int INDEX_TRUST_FINGERPRINT = 1; - static final int INDEX_TRUST_IS_REVOKED = 2; - static final int INDEX_TRUST_IS_EXPIRED = 3; - static final int INDEX_UNIFIED_HAS_ANY_SECRET = 4; - static final int INDEX_VERIFIED = 5; - - public Loader onCreateLoader(int id, Bundle args) { - setContentShown(false); - - switch (id) { - case LOADER_ID_DATABASE: { - Uri baseUri = KeyRings.buildUnifiedKeyRingUri(mDataUri); - return new CursorLoader(getActivity(), baseUri, TRUST_PROJECTION, null, null, null); - } - // decided to just use an AsyncTask for keybase, but maybe later - default: - return null; - } - } - - public void onLoadFinished(Loader loader, Cursor data) { - /* TODO better error handling? May cause problems when a key is deleted, - * because the notification triggers faster than the activity closes. - */ - // Avoid NullPointerExceptions... - if (data.getCount() == 0) { - return; - } - - boolean nothingSpecial = true; - StringBuilder message = new StringBuilder(); - - // Swap the new cursor in. (The framework will take care of closing the - // old cursor once we return.) - if (data.moveToFirst()) { - - if (data.getInt(INDEX_UNIFIED_HAS_ANY_SECRET) != 0) { - message.append(getString(R.string.key_trust_it_is_yours)).append("\n"); - nothingSpecial = false; - } else if (data.getInt(INDEX_VERIFIED) != 0) { - message.append(getString(R.string.key_trust_already_verified)).append("\n"); - nothingSpecial = false; - } - - // If this key is revoked, don’t trust it! - if (data.getInt(INDEX_TRUST_IS_REVOKED) != 0) { - message.append(getString(R.string.key_trust_revoked)). - append(getString(R.string.key_trust_old_keys)); - - nothingSpecial = false; - } else { - if (data.getInt(INDEX_TRUST_IS_EXPIRED) != 0) { - - // if expired, don’t trust it! - message.append(getString(R.string.key_trust_expired)). - append(getString(R.string.key_trust_old_keys)); - - nothingSpecial = false; - } - } - - if (nothingSpecial) { - message.append(getString(R.string.key_trust_maybe)); - } - - final byte[] fp = data.getBlob(INDEX_TRUST_FINGERPRINT); - final String fingerprint = KeyFormattingUtils.convertFingerprintToHex(fp); - if (fingerprint != null) { - - mStartSearch.setEnabled(true); - mStartSearch.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - final Preferences.ProxyPrefs proxyPrefs = - Preferences.getPreferences(getActivity()).getProxyPrefs(); - - OrbotHelper.DialogActions dialogActions = new OrbotHelper.DialogActions() { - @Override - public void onOrbotStarted() { - mStartSearch.setEnabled(false); - new DescribeKey(proxyPrefs.parcelableProxy).execute(fingerprint); - } - - @Override - public void onNeutralButton() { - mStartSearch.setEnabled(false); - new DescribeKey(ParcelableProxy.getForNoProxy()) - .execute(fingerprint); - } - - @Override - public void onCancel() { - - } - }; - - if (OrbotHelper.putOrbotInRequiredState(dialogActions, getActivity())) { - mStartSearch.setEnabled(false); - new DescribeKey(proxyPrefs.parcelableProxy).execute(fingerprint); - } - } - }); - } - } - - mTrustReadout.setText(message); - setContentShown(true); - } - - /** - * This is called when the last Cursor provided to onLoadFinished() above is about to be closed. - * We need to make sure we are no longer using it. - */ - public void onLoaderReset(Loader loader) { - // no-op in this case I think - } - - class ResultPage { - String mHeader; - final List mProofs; - - public ResultPage(String header, List proofs) { - mHeader = header; - mProofs = proofs; - } - } - - // look for evidence from keybase in the background, make tabular version of result - // - private class DescribeKey extends AsyncTask { - ParcelableProxy mParcelableProxy; - - public DescribeKey(ParcelableProxy parcelableProxy) { - mParcelableProxy = parcelableProxy; - } - - @Override - protected ResultPage doInBackground(String... args) { - String fingerprint = args[0]; - - final ArrayList proofList = new ArrayList(); - final Hashtable> proofs = new Hashtable>(); - try { - User keybaseUser = User.findByFingerprint(fingerprint, mParcelableProxy.getProxy()); - for (Proof proof : keybaseUser.getProofs()) { - Integer proofType = proof.getType(); - appendIfOK(proofs, proofType, proof); - } - - // a one-liner in a modern programming language - for (Integer proofType : proofs.keySet()) { - Proof[] x = {}; - Proof[] proofsFor = proofs.get(proofType).toArray(x); - if (proofsFor.length > 0) { - SpannableStringBuilder ssb = new SpannableStringBuilder(); - int i = 0; - while (i < proofsFor.length - 1) { - appendProofLinks(ssb, fingerprint, proofsFor[i]); - ssb.append(", "); - i++; - } - appendProofLinks(ssb, fingerprint, proofsFor[i]); - proofList.add(formatSpannableString(ssb, getProofNarrative(proofType))); - } - } - - } catch (KeybaseException ignored) { - } - - return new ResultPage(getString(R.string.key_trust_results_prefix), proofList); - } - - private SpannableStringBuilder formatSpannableString(SpannableStringBuilder proofLinks,String proofType){ - //Formatting SpannableStringBuilder with String.format() causes the links to stop working. - //This method is to insert the links while reserving the links - - SpannableStringBuilder ssb = new SpannableStringBuilder(); - ssb.append(proofType); - if(proofType.contains("%s")){ - int i = proofType.indexOf("%s"); - ssb.replace(i,i+2,proofLinks); - } - else ssb.append(proofLinks); - - return ssb; - } - - private SpannableStringBuilder appendProofLinks(SpannableStringBuilder ssb, final String fingerprint, final Proof proof) throws KeybaseException { - int startAt = ssb.length(); - String handle = proof.getHandle(); - ssb.append(handle); - ssb.setSpan(new URLSpan(proof.getServiceUrl()), startAt, startAt + handle.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - if (haveProofFor(proof.getType())) { - ssb.append("\u00a0["); - startAt = ssb.length(); - String verify = getString(R.string.keybase_verify); - ssb.append(verify); - ClickableSpan clicker = new ClickableSpan() { - @Override - public void onClick(View view) { - verify(proof, fingerprint); - } - }; - ssb.setSpan(clicker, startAt, startAt + verify.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - ssb.append("]"); - } - return ssb; - } - - @Override - protected void onPostExecute(ResultPage result) { - super.onPostExecute(result); - if (result.mProofs.isEmpty()) { - result.mHeader = getActivity().getString(R.string.key_trust_no_cloud_evidence); - } - - mStartSearch.setVisibility(View.GONE); - mReportHeader.setVisibility(View.VISIBLE); - mProofListing.setVisibility(View.VISIBLE); - mReportHeader.setText(result.mHeader); - - int rowNumber = 1; - for (CharSequence s : result.mProofs) { - TableRow row = (TableRow) mInflater.inflate(R.layout.view_key_adv_keybase_proof, null); - TextView number = (TextView) row.findViewById(R.id.proof_number); - TextView text = (TextView) row.findViewById(R.id.proof_text); - number.setText(Integer.toString(rowNumber++) + ". "); - text.setText(s); - text.setMovementMethod(LinkMovementMethod.getInstance()); - mProofListing.addView(row); - } - - // mSearchReport.loadDataWithBaseURL("file:///android_res/drawable/", s, "text/html", "UTF-8", null); - } - } - - private String getProofNarrative(int proofType) { - int stringIndex; - switch (proofType) { - case Proof.PROOF_TYPE_TWITTER: stringIndex = R.string.keybase_narrative_twitter; break; - case Proof.PROOF_TYPE_GITHUB: stringIndex = R.string.keybase_narrative_github; break; - case Proof.PROOF_TYPE_DNS: stringIndex = R.string.keybase_narrative_dns; break; - case Proof.PROOF_TYPE_WEB_SITE: stringIndex = R.string.keybase_narrative_web_site; break; - case Proof.PROOF_TYPE_HACKERNEWS: stringIndex = R.string.keybase_narrative_hackernews; break; - case Proof.PROOF_TYPE_COINBASE: stringIndex = R.string.keybase_narrative_coinbase; break; - case Proof.PROOF_TYPE_REDDIT: stringIndex = R.string.keybase_narrative_reddit; break; - default: stringIndex = R.string.keybase_narrative_unknown; - } - return getActivity().getString(stringIndex); - } - - private void appendIfOK(Hashtable> table, Integer proofType, Proof proof) throws KeybaseException { - ArrayList list = table.get(proofType); - if (list == null) { - list = new ArrayList(); - table.put(proofType, list); - } - list.add(proof); - } - - // which proofs do we have working verifiers for? - private boolean haveProofFor(int proofType) { - switch (proofType) { - case Proof.PROOF_TYPE_TWITTER: return true; - case Proof.PROOF_TYPE_GITHUB: return true; - case Proof.PROOF_TYPE_DNS: return true; - case Proof.PROOF_TYPE_WEB_SITE: return true; - case Proof.PROOF_TYPE_HACKERNEWS: return true; - case Proof.PROOF_TYPE_COINBASE: return true; - case Proof.PROOF_TYPE_REDDIT: return true; - default: return false; - } - } - - private void verify(final Proof proof, final String fingerprint) { - - mProof = proof; - mKeybaseProof = proof.toString(); - mKeybaseFingerprint = fingerprint; - - mProofVerifyDetail.setVisibility(View.GONE); - - mKeybaseOpHelper = new CryptoOperationHelper<>(1, this, this, - R.string.progress_verifying_signature); - mKeybaseOpHelper.cryptoOperation(); - } - - @Override - public void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - if (mKeybaseOpHelper != null) { - mKeybaseOpHelper.handleActivityResult(requestCode, resultCode, data); - } - } - - // CryptoOperationHelper.Callback methods - @Override - public KeybaseVerificationParcel createOperationInput() { - return new KeybaseVerificationParcel(mKeybaseProof, mKeybaseFingerprint); - } - - @Override - public void onCryptoOperationSuccess(KeybaseVerificationResult result) { - - result.createNotify(getActivity()).show(); - - String proofUrl = result.mProofUrl; - String presenceUrl = result.mPresenceUrl; - String presenceLabel = result.mPresenceLabel; - - Proof proof = mProof; // TODO: should ideally be contained in result - - String proofLabel; - switch (proof.getType()) { - case Proof.PROOF_TYPE_TWITTER: - proofLabel = getString(R.string.keybase_twitter_proof); - break; - case Proof.PROOF_TYPE_DNS: - proofLabel = getString(R.string.keybase_dns_proof); - break; - case Proof.PROOF_TYPE_WEB_SITE: - proofLabel = getString(R.string.keybase_web_site_proof); - break; - case Proof.PROOF_TYPE_GITHUB: - proofLabel = getString(R.string.keybase_github_proof); - break; - case Proof.PROOF_TYPE_REDDIT: - proofLabel = getString(R.string.keybase_reddit_proof); - break; - default: - proofLabel = getString(R.string.keybase_a_post); - break; - } - - SpannableStringBuilder ssb = new SpannableStringBuilder(); - - ssb.append(getString(R.string.keybase_proof_succeeded)); - StyleSpan bold = new StyleSpan(Typeface.BOLD); - ssb.setSpan(bold, 0, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - ssb.append("\n\n"); - int length = ssb.length(); - ssb.append(proofLabel); - if (proofUrl != null) { - URLSpan postLink = new URLSpan(proofUrl); - ssb.setSpan(postLink, length, length + proofLabel.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - } - if (Proof.PROOF_TYPE_DNS == proof.getType()) { - ssb.append(" ").append(getString(R.string.keybase_for_the_domain)).append(" "); - } else { - ssb.append(" ").append(getString(R.string.keybase_fetched_from)).append(" "); - } - length = ssb.length(); - URLSpan presenceLink = new URLSpan(presenceUrl); - ssb.append(presenceLabel); - ssb.setSpan(presenceLink, length, length + presenceLabel.length(), Spanned - .SPAN_EXCLUSIVE_EXCLUSIVE); - if (Proof.PROOF_TYPE_REDDIT == proof.getType()) { - ssb.append(", "). - append(getString(R.string.keybase_reddit_attribution)). - append(" “").append(proof.getHandle()).append("”, "); - } - ssb.append(" ").append(getString(R.string.keybase_contained_signature)); - - displaySpannableResult(ssb); - } - - @Override - public void onCryptoOperationCancelled() { - - } - - @Override - public void onCryptoOperationError(KeybaseVerificationResult result) { - - result.createNotify(getActivity()).show(); - - SpannableStringBuilder ssb = new SpannableStringBuilder(); - - ssb.append(getString(R.string.keybase_proof_failure)); - String msg = getString(result.getLog().getLast().mType.mMsgId); - if (msg == null) { - msg = getString(R.string.keybase_unknown_proof_failure); - } - StyleSpan bold = new StyleSpan(Typeface.BOLD); - ssb.setSpan(bold, 0, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - ssb.append("\n\n").append(msg); - - displaySpannableResult(ssb); - } - - @Override - public boolean onCryptoSetProgress(String msg, int progress, int max) { - return false; - } - - private void displaySpannableResult(SpannableStringBuilder ssb) { - mProofVerifyHeader.setVisibility(View.VISIBLE); - mProofVerifyDetail.setVisibility(View.VISIBLE); - mProofVerifyDetail.setMovementMethod(LinkMovementMethod.getInstance()); - mProofVerifyDetail.setText(ssb); - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java index 3225c3765..4ef215036 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java @@ -381,6 +381,16 @@ public class Preferences { return mSharedPreferences.getBoolean(Pref.EXPERIMENTAL_ENABLE_LINKED_IDENTITIES, false); } + public void setExperimentalEnableKeybase(boolean enableKeybase) { + SharedPreferences.Editor editor = mSharedPreferences.edit(); + editor.putBoolean(Pref.EXPERIMENTAL_ENABLE_KEYBASE, enableKeybase); + editor.commit(); + } + + public boolean getExperimentalEnableKeybase() { + return mSharedPreferences.getBoolean(Pref.EXPERIMENTAL_ENABLE_KEYBASE, false); + } + public void upgradePreferences(Context context) { if (mSharedPreferences.getInt(Constants.Pref.PREF_DEFAULT_VERSION, 0) != Constants.Defaults.PREF_VERSION) { diff --git a/OpenKeychain/src/main/res/layout/view_key_activity.xml b/OpenKeychain/src/main/res/layout/view_key_activity.xml index 1c29ad759..3913122bc 100644 --- a/OpenKeychain/src/main/res/layout/view_key_activity.xml +++ b/OpenKeychain/src/main/res/layout/view_key_activity.xml @@ -182,18 +182,32 @@ android:layout_gravity="fill_vertical" app:layout_behavior="@string/appbar_scrolling_view_behavior"> - + android:layout_height="match_parent" + android:orientation="vertical"> + + + + + - + diff --git a/OpenKeychain/src/main/res/layout/view_key_adv_keybase_fragment.xml b/OpenKeychain/src/main/res/layout/view_key_adv_keybase_fragment.xml index eecb19000..75d56e092 100644 --- a/OpenKeychain/src/main/res/layout/view_key_adv_keybase_fragment.xml +++ b/OpenKeychain/src/main/res/layout/view_key_adv_keybase_fragment.xml @@ -1,126 +1,95 @@ - - - - + + - - + android:layout_height="wrap_content" + android:layout_gravity="center" + card_view:cardBackgroundColor="?attr/colorCardViewBackground" + card_view:cardCornerRadius="4dp" + card_view:cardElevation="2dp" + card_view:cardUseCompatPadding="true"> - - - - + android:orientation="vertical"> + android:layout_height="wrap_content" + android:text="@string/section_keybase_proofs" /> + android:layout_height="wrap_content" + android:background="?attr/colorButtonRow" + android:gravity="center_horizontal" + android:padding="8dp" + android:text="@string/key_trust_header_text" + android:textAppearance="?android:attr/textAppearanceSmall" /> + + + android:layout_height="wrap_content" + android:orientation="vertical" + android:paddingBottom="16dp" + android:paddingLeft="16dp" + android:paddingRight="16dp" + android:paddingTop="16dp"> + android:id="@+id/view_key_trust_cloud_narrative" + style="?android:attr/textAppearanceMedium" + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_marginBottom="14dp" + android:layout_marginLeft="8dp" + android:layout_weight="1" /> + + - - - - - - - - + android:layout_height="0dp" + android:layout_marginTop="16dp" + android:layout_weight="1" + android:text="@string/section_proof_details" /> - + - + - - + + + \ No newline at end of file diff --git a/OpenKeychain/src/main/res/layout/view_key_adv_keybase_proof.xml b/OpenKeychain/src/main/res/layout/view_key_adv_keybase_proof.xml index 0ffd151c1..033282305 100644 --- a/OpenKeychain/src/main/res/layout/view_key_adv_keybase_proof.xml +++ b/OpenKeychain/src/main/res/layout/view_key_adv_keybase_proof.xml @@ -7,7 +7,7 @@ android:layout_height="wrap_content" android:paddingLeft="6dip" android:text="1." - style="?android:attr/textAppearanceMedium" /> + style="?android:attr/textAppearanceSmall" /> + style="?android:attr/textAppearanceSmall" /> diff --git a/OpenKeychain/src/main/res/layout/view_key_fragment.xml b/OpenKeychain/src/main/res/layout/view_key_fragment.xml index 42c8f617b..0d15ba7dc 100644 --- a/OpenKeychain/src/main/res/layout/view_key_fragment.xml +++ b/OpenKeychain/src/main/res/layout/view_key_fragment.xml @@ -3,12 +3,10 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" - android:paddingBottom="230dp" + android:paddingBottom="16dp" android:paddingLeft="16dp" android:paddingRight="16dp" android:paddingTop="16dp"> - "Identities" "YubiKey" "Linked System Contact" + "Keybase.io Proofs" "Should you trust this key?" Proof verification - "Proofs from the Internet" "Subkeys" "Key Search" "Keyserver, keybase.io" @@ -197,6 +197,8 @@ "Allows to compare fingerprints with words instead of hexadecimal representation." "Linked Identities" "Linked Identities" + "Keybase.io Proofs" + "Every time a key is displayed, this will contact keybase.io for key proofs" "Enable Tor" @@ -674,12 +676,6 @@ "Something is wrong with this identity!" - "You have already confirmed this key!" - "This is one of your keys!" - "This key is neither revoked nor expired.\nYou haven’t confirmed it, but you may choose to trust it." - "This key has been revoked by its owner. You should not trust it." - "This key has expired. You should not trust it." - "It may be OK to use this to decrypt an old message dating from the time when this key was valid." "No proof from the Internet on this key’s trustworthiness." "Start search" "Keybase.io offers “proofs” which assert that the owner of this key: " diff --git a/OpenKeychain/src/main/res/xml/experimental_preferences.xml b/OpenKeychain/src/main/res/xml/experimental_preferences.xml index eb88e3ed9..33ab92697 100644 --- a/OpenKeychain/src/main/res/xml/experimental_preferences.xml +++ b/OpenKeychain/src/main/res/xml/experimental_preferences.xml @@ -18,6 +18,13 @@ android:summary="@string/label_experimental_settings_linked_identities_summary" android:title="@string/label_experimental_settings_linked_identities_title" /> + +