/* * Copyright (C) 2014 Dominik Schürmann * * 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 java.util.ArrayList; import android.app.Activity; import android.content.Intent; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.support.v4.app.Fragment; import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; import android.util.Log; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; import android.widget.ViewAnimator; import org.openintents.openpgp.OpenPgpDecryptionResult; import org.openintents.openpgp.OpenPgpSignatureResult; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.ImportKeyringParcel; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.util.Notify.Style; import org.sufficientlysecure.keychain.util.Preferences; public abstract class DecryptFragment extends Fragment implements LoaderManager.LoaderCallbacks { public static final int LOADER_ID_UNIFIED = 0; public static final String ARG_DECRYPT_VERIFY_RESULT = "decrypt_verify_result"; protected LinearLayout mResultLayout; protected ImageView mEncryptionIcon; protected TextView mEncryptionText; protected ImageView mSignatureIcon; protected TextView mSignatureText; protected View mSignatureLayout; protected TextView mSignatureName; protected TextView mSignatureEmail; protected TextView mSignatureAction; private OpenPgpSignatureResult mSignatureResult; private DecryptVerifyResult mDecryptVerifyResult; private ViewAnimator mOverlayAnimator; private CryptoOperationHelper mImportOpHelper; @Override public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); // NOTE: These views are inside the activity! mResultLayout = (LinearLayout) getActivity().findViewById(R.id.result_main_layout); mResultLayout.setVisibility(View.GONE); mEncryptionIcon = (ImageView) getActivity().findViewById(R.id.result_encryption_icon); mEncryptionText = (TextView) getActivity().findViewById(R.id.result_encryption_text); mSignatureIcon = (ImageView) getActivity().findViewById(R.id.result_signature_icon); mSignatureText = (TextView) getActivity().findViewById(R.id.result_signature_text); mSignatureLayout = getActivity().findViewById(R.id.result_signature_layout); mSignatureName = (TextView) getActivity().findViewById(R.id.result_signature_name); mSignatureEmail = (TextView) getActivity().findViewById(R.id.result_signature_email); mSignatureAction = (TextView) getActivity().findViewById(R.id.result_signature_action); // Overlay mOverlayAnimator = (ViewAnimator) view; Button vErrorOverlayButton = (Button) view.findViewById(R.id.decrypt_error_overlay_button); vErrorOverlayButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { mOverlayAnimator.setDisplayedChild(0); } }); } private void showErrorOverlay(boolean overlay) { int child = overlay ? 1 : 0; if (mOverlayAnimator.getDisplayedChild() != child) { mOverlayAnimator.setDisplayedChild(child); } } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putParcelable(ARG_DECRYPT_VERIFY_RESULT, mDecryptVerifyResult); } @Override public void onViewStateRestored(Bundle savedInstanceState) { super.onViewStateRestored(savedInstanceState); if (savedInstanceState == null) { return; } DecryptVerifyResult result = savedInstanceState.getParcelable(ARG_DECRYPT_VERIFY_RESULT); if (result != null) { loadVerifyResult(result); } } private void lookupUnknownKey(long unknownKeyId) { final ArrayList keyList; final String keyserver; // search config keyserver = Preferences.getPreferences(getActivity()).getPreferredKeyserver(); { ParcelableKeyRing keyEntry = new ParcelableKeyRing(null, KeyFormattingUtils.convertKeyIdToHex(unknownKeyId)); ArrayList selectedEntries = new ArrayList<>(); selectedEntries.add(keyEntry); keyList = selectedEntries; } CryptoOperationHelper.Callback callback = new CryptoOperationHelper.Callback() { @Override public ImportKeyringParcel createOperationInput() { return new ImportKeyringParcel(keyList, keyserver); } @Override public void onCryptoOperationSuccess(ImportKeyResult result) { result.createNotify(getActivity()).show(); getLoaderManager().restartLoader(LOADER_ID_UNIFIED, null, DecryptFragment.this); } @Override public void onCryptoOperationCancelled() { // do nothing } @Override public void onCryptoOperationError(ImportKeyResult result) { result.createNotify(getActivity()).show(); } @Override public boolean onCryptoSetProgress(String msg, int progress, int max) { return false; } }; mImportOpHelper = new CryptoOperationHelper<>(1, this, callback, R.string.progress_importing); mImportOpHelper.cryptoOperation(); } private void showKey(long keyId) { try { Intent viewKeyIntent = new Intent(getActivity(), ViewKeyActivity.class); long masterKeyId = new ProviderHelper(getActivity()).getCachedPublicKeyRing( KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(keyId) ).getMasterKeyId(); viewKeyIntent.setData(KeyRings.buildGenericKeyRingUri(masterKeyId)); startActivity(viewKeyIntent); } catch (PgpKeyNotFoundException e) { Notify.create(getActivity(), R.string.error_key_not_found, Style.ERROR); } } protected void loadVerifyResult(DecryptVerifyResult decryptVerifyResult) { mDecryptVerifyResult = decryptVerifyResult; mSignatureResult = decryptVerifyResult.getSignatureResult(); OpenPgpDecryptionResult decryptionResult = decryptVerifyResult.getDecryptionResult(); mResultLayout.setVisibility(View.VISIBLE); switch (decryptionResult.getResult()) { case OpenPgpDecryptionResult.RESULT_ENCRYPTED: { mEncryptionText.setText(R.string.decrypt_result_encrypted); KeyFormattingUtils.setStatusImage(getActivity(), mEncryptionIcon, mEncryptionText, State.ENCRYPTED); break; } case OpenPgpDecryptionResult.RESULT_INSECURE: { mEncryptionText.setText(R.string.decrypt_result_insecure); KeyFormattingUtils.setStatusImage(getActivity(), mEncryptionIcon, mEncryptionText, State.INSECURE); break; } default: case OpenPgpDecryptionResult.RESULT_NOT_ENCRYPTED: { mEncryptionText.setText(R.string.decrypt_result_not_encrypted); KeyFormattingUtils.setStatusImage(getActivity(), mEncryptionIcon, mEncryptionText, State.NOT_ENCRYPTED); break; } } if (mSignatureResult.getResult() == OpenPgpSignatureResult.RESULT_NO_SIGNATURE) { // no signature setSignatureLayoutVisibility(View.GONE); mSignatureText.setText(R.string.decrypt_result_no_signature); KeyFormattingUtils.setStatusImage(getActivity(), mSignatureIcon, mSignatureText, State.NOT_SIGNED); getLoaderManager().destroyLoader(LOADER_ID_UNIFIED); showErrorOverlay(false); onVerifyLoaded(true); } else { // signature present // after loader is restarted signature results are checked getLoaderManager().restartLoader(LOADER_ID_UNIFIED, null, this); } } private void setSignatureLayoutVisibility(int visibility) { mSignatureLayout.setVisibility(visibility); } private void setShowAction(final long signatureKeyId) { mSignatureAction.setText(R.string.decrypt_result_action_show); mSignatureAction.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.ic_vpn_key_grey_24dp, 0); mSignatureLayout.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { showKey(signatureKeyId); } }); } // These are the rows that we will retrieve. static final String[] UNIFIED_PROJECTION = new String[]{ KeychainContract.KeyRings._ID, KeychainContract.KeyRings.MASTER_KEY_ID, KeychainContract.KeyRings.USER_ID, KeychainContract.KeyRings.VERIFIED, KeychainContract.KeyRings.HAS_ANY_SECRET, }; @SuppressWarnings("unused") static final int INDEX_MASTER_KEY_ID = 1; static final int INDEX_USER_ID = 2; static final int INDEX_VERIFIED = 3; static final int INDEX_HAS_ANY_SECRET = 4; @Override public Loader onCreateLoader(int id, Bundle args) { if (id != LOADER_ID_UNIFIED) { return null; } Uri baseUri = KeychainContract.KeyRings.buildUnifiedKeyRingsFindBySubkeyUri( mSignatureResult.getKeyId()); return new CursorLoader(getActivity(), baseUri, UNIFIED_PROJECTION, null, null, null); } @Override public void onLoadFinished(Loader loader, Cursor data) { if (loader.getId() != LOADER_ID_UNIFIED) { return; } // If the key is unknown, show it as such if (data.getCount() == 0 || !data.moveToFirst()) { showUnknownKeyStatus(); return; } long signatureKeyId = mSignatureResult.getKeyId(); String userId = data.getString(INDEX_USER_ID); KeyRing.UserId userIdSplit = KeyRing.splitUserId(userId); if (userIdSplit.name != null) { mSignatureName.setText(userIdSplit.name); } else { mSignatureName.setText(R.string.user_id_no_name); } if (userIdSplit.email != null) { mSignatureEmail.setText(userIdSplit.email); } else { mSignatureEmail.setText(KeyFormattingUtils.beautifyKeyIdWithPrefix( getActivity(), mSignatureResult.getKeyId())); } // NOTE: Don't use revoked and expired fields from database, they don't show // revoked/expired subkeys boolean isRevoked = mSignatureResult.getResult() == OpenPgpSignatureResult.RESULT_INVALID_KEY_REVOKED; boolean isExpired = mSignatureResult.getResult() == OpenPgpSignatureResult.RESULT_INVALID_KEY_EXPIRED; boolean isInsecure = mSignatureResult.getResult() == OpenPgpSignatureResult.RESULT_INVALID_INSECURE; boolean isVerified = data.getInt(INDEX_VERIFIED) > 0; boolean isYours = data.getInt(INDEX_HAS_ANY_SECRET) != 0; if (isRevoked) { mSignatureText.setText(R.string.decrypt_result_signature_revoked_key); KeyFormattingUtils.setStatusImage(getActivity(), mSignatureIcon, mSignatureText, State.REVOKED); setSignatureLayoutVisibility(View.VISIBLE); setShowAction(signatureKeyId); onVerifyLoaded(true); } else if (isExpired) { mSignatureText.setText(R.string.decrypt_result_signature_expired_key); KeyFormattingUtils.setStatusImage(getActivity(), mSignatureIcon, mSignatureText, State.EXPIRED); setSignatureLayoutVisibility(View.VISIBLE); setShowAction(signatureKeyId); showErrorOverlay(false); onVerifyLoaded(true); } else if (isInsecure) { mSignatureText.setText(R.string.decrypt_result_insecure_cryptography); KeyFormattingUtils.setStatusImage(getActivity(), mSignatureIcon, mSignatureText, State.INSECURE); setSignatureLayoutVisibility(View.VISIBLE); setShowAction(signatureKeyId); showErrorOverlay(false); onVerifyLoaded(true); } else if (isYours) { mSignatureText.setText(R.string.decrypt_result_signature_secret); KeyFormattingUtils.setStatusImage(getActivity(), mSignatureIcon, mSignatureText, State.VERIFIED); setSignatureLayoutVisibility(View.VISIBLE); setShowAction(signatureKeyId); showErrorOverlay(false); onVerifyLoaded(true); } else if (isVerified) { mSignatureText.setText(R.string.decrypt_result_signature_certified); KeyFormattingUtils.setStatusImage(getActivity(), mSignatureIcon, mSignatureText, State.VERIFIED); setSignatureLayoutVisibility(View.VISIBLE); setShowAction(signatureKeyId); showErrorOverlay(false); onVerifyLoaded(true); } else { mSignatureText.setText(R.string.decrypt_result_signature_uncertified); KeyFormattingUtils.setStatusImage(getActivity(), mSignatureIcon, mSignatureText, State.UNVERIFIED); setSignatureLayoutVisibility(View.VISIBLE); setShowAction(signatureKeyId); showErrorOverlay(false); onVerifyLoaded(true); } } @Override public void onLoaderReset(Loader loader) { if (loader.getId() != LOADER_ID_UNIFIED) { return; } setSignatureLayoutVisibility(View.GONE); } private void showUnknownKeyStatus() { final long signatureKeyId = mSignatureResult.getKeyId(); int result = mSignatureResult.getResult(); if (result != OpenPgpSignatureResult.RESULT_KEY_MISSING && result != OpenPgpSignatureResult.RESULT_INVALID_SIGNATURE) { Log.e(Constants.TAG, "got missing status for non-missing key, shouldn't happen!"); } String userId = mSignatureResult.getPrimaryUserId(); KeyRing.UserId userIdSplit = KeyRing.splitUserId(userId); if (userIdSplit.name != null) { mSignatureName.setText(userIdSplit.name); } else { mSignatureName.setText(R.string.user_id_no_name); } if (userIdSplit.email != null) { mSignatureEmail.setText(userIdSplit.email); } else { mSignatureEmail.setText(KeyFormattingUtils.beautifyKeyIdWithPrefix( getActivity(), mSignatureResult.getKeyId())); } switch (mSignatureResult.getResult()) { case OpenPgpSignatureResult.RESULT_KEY_MISSING: { mSignatureText.setText(R.string.decrypt_result_signature_missing_key); KeyFormattingUtils.setStatusImage(getActivity(), mSignatureIcon, mSignatureText, State.UNKNOWN_KEY); setSignatureLayoutVisibility(View.VISIBLE); mSignatureAction.setText(R.string.decrypt_result_action_Lookup); mSignatureAction .setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.ic_file_download_grey_24dp, 0); mSignatureLayout.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { lookupUnknownKey(signatureKeyId); } }); showErrorOverlay(false); onVerifyLoaded(true); break; } case OpenPgpSignatureResult.RESULT_INVALID_SIGNATURE: { mSignatureText.setText(R.string.decrypt_result_invalid_signature); KeyFormattingUtils.setStatusImage(getActivity(), mSignatureIcon, mSignatureText, State.INVALID); setSignatureLayoutVisibility(View.GONE); showErrorOverlay(true); onVerifyLoaded(false); break; } } } protected abstract void onVerifyLoaded(boolean hideErrorOverlay); public void startDisplayLogActivity() { Activity activity = getActivity(); if (activity == null) { return; } Intent intent = new Intent(activity, LogDisplayActivity.class); intent.putExtra(LogDisplayFragment.EXTRA_RESULT, mDecryptVerifyResult); activity.startActivity(intent); } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { if (mImportOpHelper != null) { mImportOpHelper.handleActivityResult(requestCode, resultCode, data); } super.onActivityResult(requestCode, resultCode, data); } }