token-import: first iteration

This commit is contained in:
Vincent Breitmoser
2017-09-04 23:54:56 +02:00
parent 21bc6f12f0
commit af7d36c038
35 changed files with 1098 additions and 77 deletions

View File

@@ -76,6 +76,7 @@ public class CryptoInputParcelCacheService extends Service {
data.setExtrasClassLoader(CryptoInputParcelCacheService.class.getClassLoader());
// And write out the UUID most and least significant bits.
data.setExtrasClassLoader(CryptoInputParcelCacheService.class.getClassLoader());
data.putExtra(OpenPgpApi.EXTRA_CALL_UUID1, mTicket.getMostSignificantBits());
data.putExtra(OpenPgpApi.EXTRA_CALL_UUID2, mTicket.getLeastSignificantBits());
}

View File

@@ -587,6 +587,11 @@ public class SecurityTokenHelper {
return getData(0x00, 0x4F);
}
public String getUrl() throws IOException {
byte[] data = getData(0x5F, 0x50);
return new String(data);
}
public String getUserId() throws IOException {
return getHolderName(getData(0x00, 0x65));
}

View File

@@ -19,7 +19,6 @@
package org.sufficientlysecure.keychain.service;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

View File

@@ -77,6 +77,7 @@ public class CreateKeyActivity extends BaseSecurityTokenActivity {
byte[] mScannedFingerprints;
byte[] mSecurityTokenAid;
String mSecurityTokenUserId;
private String mSecurityTokenUrl;
@Override
public void onCreate(Bundle savedInstanceState) {
@@ -162,6 +163,7 @@ public class CreateKeyActivity extends BaseSecurityTokenActivity {
mScannedFingerprints = mSecurityTokenHelper.getFingerprints();
mSecurityTokenAid = mSecurityTokenHelper.getAid();
mSecurityTokenUserId = mSecurityTokenHelper.getUserId();
mSecurityTokenUrl = mSecurityTokenHelper.getUrl();
}
@Override
@@ -194,8 +196,8 @@ public class CreateKeyActivity extends BaseSecurityTokenActivity {
finish();
} catch (PgpKeyNotFoundException e) {
Fragment frag = CreateSecurityTokenImportResetFragment.newInstance(
mScannedFingerprints, mSecurityTokenAid, mSecurityTokenUserId);
Fragment frag = CreateSecurityTokenImportFragment.newInstance(
mScannedFingerprints, mSecurityTokenAid, mSecurityTokenUserId, mSecurityTokenUrl);
loadFragment(frag, FragAction.TO_RIGHT);
}
} else {

View File

@@ -0,0 +1,310 @@
/*
* Copyright (C) 2017 Vincent Breitmoser <look@my.amazin.horse>
*
* 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.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.annotation.StringRes;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.TextView;
import org.bouncycastle.util.encoders.Hex;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
import org.sufficientlysecure.keychain.operations.results.PromoteKeyResult;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.service.ImportKeyringParcel;
import org.sufficientlysecure.keychain.service.PromoteKeyringParcel;
import org.sufficientlysecure.keychain.ui.CreateSecurityTokenImportPresenter.CreateSecurityTokenImportMvpView;
import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper;
import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper.AbstractCallback;
import org.sufficientlysecure.keychain.ui.keyview.ViewKeyActivity;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.ui.widget.StatusIndicator;
import org.sufficientlysecure.keychain.ui.widget.StatusIndicator.Status;
import org.sufficientlysecure.keychain.ui.widget.ToolableViewAnimator;
public class CreateSecurityTokenImportFragment extends Fragment implements CreateSecurityTokenImportMvpView,
OnClickListener {
private static final String ARG_FINGERPRINTS = "fingerprint";
private static final String ARG_AID = "aid";
private static final String ARG_USER_ID = "user_ids";
private static final String ARG_URL = "key_uri";
CreateSecurityTokenImportPresenter presenter;
private ViewGroup statusLayoutGroup;
private ToolableViewAnimator actionAnimator;
ImportKeyringParcel currentImportKeyringParcel;
PromoteKeyringParcel currentPromoteKeyringParcel;
private LayoutInflater layoutInflater;
private StatusIndicator latestStatusIndicator;
public static Fragment newInstanceForDebug() {
// byte[] scannedFps = KeyFormattingUtils.convertFingerprintHexFingerprint("4700BA1AC417ABEF3CC7765AD686905837779C3E");
byte[] scannedFps = KeyFormattingUtils.convertFingerprintHexFingerprint("1efdb4845ca242ca6977fddb1f788094fd3b430a");
return newInstance(scannedFps, Hex.decode("010203040506"), "yubinu2@mugenguild.com", "http://valodim.stratum0.net/mryubinu3.asc");
}
public static Fragment newInstance(byte[] scannedFingerprints, byte[] nfcAid, String userId, String tokenUrl) {
CreateSecurityTokenImportFragment frag = new CreateSecurityTokenImportFragment();
Bundle args = new Bundle();
args.putByteArray(ARG_FINGERPRINTS, scannedFingerprints);
args.putByteArray(ARG_AID, nfcAid);
args.putString(ARG_USER_ID, userId);
args.putString(ARG_URL, tokenUrl);
frag.setArguments(args);
return frag;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle args = getArguments();
byte[] tokenFingerprints = args.getByteArray(ARG_FINGERPRINTS);
byte[] tokenAid = args.getByteArray(ARG_AID);
String tokenUserId = args.getString(ARG_USER_ID);
String tokenUrl = args.getString(ARG_URL);
presenter = new CreateSecurityTokenImportPresenter(
getContext(), tokenFingerprints, tokenAid, tokenUserId, tokenUrl, getLoaderManager());
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
this.layoutInflater = inflater;
View view = inflater.inflate(R.layout.create_security_token_import_fragment, container, false);
statusLayoutGroup = (ViewGroup) view.findViewById(R.id.status_indicator_layout);
actionAnimator = (ToolableViewAnimator) view.findViewById(R.id.action_animator);
view.findViewById(R.id.button_import).setOnClickListener(this);
view.findViewById(R.id.button_view_key).setOnClickListener(this);
view.findViewById(R.id.button_retry).setOnClickListener(this);
view.findViewById(R.id.button_reset_token_1).setOnClickListener(this);
view.findViewById(R.id.button_reset_token_2).setOnClickListener(this);
view.findViewById(R.id.button_reset_token_3).setOnClickListener(this);
setHasOptionsMenu(true);
presenter.setView(this);
return view;
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.token_setup, menu);
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
presenter.onActivityCreated();
}
@Override
public void finishAndShowKey(long masterKeyId) {
Activity activity = getActivity();
Intent viewKeyIntent = new Intent(activity, ViewKeyActivity.class);
// use the imported masterKeyId, not the one from the token, because
// that one might* just have been a subkey of the imported key
viewKeyIntent.setData(KeyRings.buildGenericKeyRingUri(masterKeyId));
if (activity instanceof CreateKeyActivity) {
((CreateKeyActivity) activity).finishWithFirstTimeHandling(viewKeyIntent);
} else {
activity.startActivity(viewKeyIntent);
activity.finish();
}
}
@Override
public void statusLineAdd(StatusLine statusLine) {
if (latestStatusIndicator != null) {
throw new IllegalStateException("Cannot set next status line before completing previous!");
}
View line = layoutInflater.inflate(R.layout.status_indicator_line, statusLayoutGroup, false);
latestStatusIndicator = (StatusIndicator) line.findViewById(R.id.status_indicator);
latestStatusIndicator.setDisplayedChild(Status.PROGRESS);
TextView latestStatusText = (TextView) line.findViewById(R.id.status_text);
latestStatusText.setText(statusLine.stringRes);
statusLayoutGroup.addView(line);
}
@Override
public void statusLineOk() {
latestStatusIndicator.setDisplayedChild(Status.OK);
latestStatusIndicator = null;
}
@Override
public void statusLineError() {
latestStatusIndicator.setDisplayedChild(Status.ERROR);
latestStatusIndicator = null;
}
@Override
public void resetStatusLines() {
latestStatusIndicator = null;
statusLayoutGroup.removeAllViews();
}
@Override
public void showActionImport() {
actionAnimator.setDisplayedChildId(R.id.token_layout_import);
}
@Override
public void showActionViewKey() {
actionAnimator.setDisplayedChildId(R.id.token_layout_ok);
}
@Override
public void showActionRetryOrFromFile() {
actionAnimator.setDisplayedChildId(R.id.token_layout_not_found);
}
@Override
public void hideAction() {
actionAnimator.setDisplayedChild(0);
}
@Override
public void operationImportKey(byte[] importKeyData) {
if (currentImportKeyringParcel != null) {
throw new IllegalStateException("Cannot trigger import operation twice!");
}
currentImportKeyringParcel =
ImportKeyringParcel.createImportKeyringParcel(ParcelableKeyRing.createFromEncodedBytes(importKeyData));
cryptoImportOperationHelper.setOperationMinimumDelay(1000L);
cryptoImportOperationHelper.cryptoOperation();
}
@Override
public void operationPromote(long masterKeyId, byte[] cardAid) {
if (currentImportKeyringParcel != null) {
throw new IllegalStateException("Cannot trigger import operation twice!");
}
currentPromoteKeyringParcel = PromoteKeyringParcel.createPromoteKeyringParcel(masterKeyId, cardAid, null);
cryptoPromoteOperationHelper.setOperationMinimumDelay(1000L);
cryptoPromoteOperationHelper.cryptoOperation();
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.button_import: {
presenter.onClickImport();
break;
}
case R.id.button_retry: {
presenter.onClickRetry();
break;
}
case R.id.button_view_key: {
presenter.onClickViewKey();
break;
}
case R.id.button_reset_token_1:
case R.id.button_reset_token_2:
case R.id.button_reset_token_3: {
presenter.onClickResetToken();
break;
}
}
}
CryptoOperationHelper<ImportKeyringParcel, ImportKeyResult> cryptoImportOperationHelper =
new CryptoOperationHelper<>(0, this, new AbstractCallback<ImportKeyringParcel, ImportKeyResult>() {
@Override
public ImportKeyringParcel createOperationInput() {
return currentImportKeyringParcel;
}
@Override
public void onCryptoOperationSuccess(ImportKeyResult result) {
currentImportKeyringParcel = null;
presenter.onImportSuccess();
}
@Override
public void onCryptoOperationError(ImportKeyResult result) {
currentImportKeyringParcel = null;
presenter.onImportError();
}
}, null);
CryptoOperationHelper<PromoteKeyringParcel, PromoteKeyResult> cryptoPromoteOperationHelper =
new CryptoOperationHelper<>(1, this, new AbstractCallback<PromoteKeyringParcel, PromoteKeyResult>() {
@Override
public PromoteKeyringParcel createOperationInput() {
return currentPromoteKeyringParcel;
}
@Override
public void onCryptoOperationSuccess(PromoteKeyResult result) {
currentPromoteKeyringParcel = null;
presenter.onPromoteSuccess();
}
@Override
public void onCryptoOperationError(PromoteKeyResult result) {
currentPromoteKeyringParcel = null;
presenter.onPromoteError();
}
}, null);
enum StatusLine {
SEARCH_LOCAL (R.string.status_search_local),
SEARCH_URI (R.string.status_search_uri),
SEARCH_KEYSERVER (R.string.status_search_keyserver),
IMPORT (R.string.status_import),
TOKEN_PROMOTE(R.string.status_token_promote),
TOKEN_CHECK (R.string.status_token_check);
@StringRes
private int stringRes;
StatusLine(@StringRes int stringRes) {
this.stringRes = stringRes;
}
}
}

View File

@@ -0,0 +1,240 @@
/*
* Copyright (C) 2017 Vincent Breitmoser <look@my.amazin.horse>
*
* 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.os.Bundle;
import android.support.v4.app.LoaderManager;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.content.Loader;
import org.sufficientlysecure.keychain.provider.KeyRepository;
import org.sufficientlysecure.keychain.ui.CreateSecurityTokenImportFragment.StatusLine;
import org.sufficientlysecure.keychain.ui.PublicKeyRetrievalLoader.KeyRetrievalResult;
import org.sufficientlysecure.keychain.ui.PublicKeyRetrievalLoader.KeyserverRetrievalLoader;
import org.sufficientlysecure.keychain.ui.PublicKeyRetrievalLoader.LocalKeyLookupLoader;
import org.sufficientlysecure.keychain.ui.PublicKeyRetrievalLoader.UriKeyRetrievalLoader;
class CreateSecurityTokenImportPresenter {
private static final int LOADER_LOCAL = 0;
private static final int LOADER_URI = 1;
private static final int LOADER_KEYSERVER = 2;
private Context context;
private byte[][] tokenFingerprints;
private byte[] tokenAid;
private double tokenVersion;
private String tokenUserId;
private final String tokenUrl;
private LoaderManager loaderManager;
private CreateSecurityTokenImportMvpView view;
private boolean searchedLocally;
private boolean searchedAtUri;
private boolean searchedKeyservers;
private byte[] importKeyData;
private Long masterKeyId;
public CreateSecurityTokenImportPresenter(Context context, byte[] tokenFingerprints, byte[] tokenAid,
String tokenUserId, String tokenUrl, LoaderManager loaderManager) {
this.context = context.getApplicationContext();
this.tokenAid = tokenAid;
this.tokenUserId = tokenUserId;
this.tokenUrl = tokenUrl;
this.loaderManager = loaderManager;
if (tokenFingerprints.length % 20 != 0) {
throw new IllegalArgumentException("fingerprints must be multiple of 20 bytes!");
}
this.tokenFingerprints = new byte[tokenFingerprints.length / 20][];
for (int i = 0; i < tokenFingerprints.length / 20; i++) {
this.tokenFingerprints[i] = new byte[20];
System.arraycopy(tokenFingerprints, i*20, this.tokenFingerprints[i], 0, 20);
}
}
public void setView(CreateSecurityTokenImportMvpView view) {
this.view = view;
}
public void onActivityCreated() {
continueSearch();
}
private void continueSearchAfterError() {
view.statusLineError();
continueSearch();
}
private void continueSearch() {
if (!searchedLocally) {
view.statusLineAdd(StatusLine.SEARCH_LOCAL);
loaderManager.restartLoader(LOADER_LOCAL, null, loaderCallbacks);
return;
}
if (!searchedAtUri) {
view.statusLineAdd(StatusLine.SEARCH_URI);
loaderManager.restartLoader(LOADER_URI, null, loaderCallbacks);
return;
}
if (!searchedKeyservers) {
view.statusLineAdd(StatusLine.SEARCH_KEYSERVER);
loaderManager.restartLoader(LOADER_KEYSERVER, null, loaderCallbacks);
return;
}
view.showActionRetryOrFromFile();
}
private LoaderCallbacks<KeyRetrievalResult> loaderCallbacks = new LoaderCallbacks<KeyRetrievalResult>() {
@Override
public Loader<KeyRetrievalResult> onCreateLoader(int id, Bundle args) {
switch (id) {
case LOADER_LOCAL:
return new LocalKeyLookupLoader(context, tokenFingerprints);
case LOADER_URI:
return new UriKeyRetrievalLoader(context, tokenUrl, tokenFingerprints);
case LOADER_KEYSERVER:
return new KeyserverRetrievalLoader(context, tokenFingerprints[0]);
}
throw new IllegalArgumentException("called with unknown loader id!");
}
@Override
public void onLoadFinished(Loader<KeyRetrievalResult> loader, KeyRetrievalResult data) {
switch (loader.getId()) {
case LOADER_LOCAL: {
searchedLocally = true;
break;
}
case LOADER_URI: {
searchedAtUri = true;
break;
}
case LOADER_KEYSERVER: {
searchedKeyservers = true;
break;
}
default: {
throw new IllegalArgumentException("called with unknown loader id!");
}
}
if (data.isSuccess()) {
processResult(data);
} else {
continueSearchAfterError();
}
}
@Override
public void onLoaderReset(Loader<KeyRetrievalResult> loader) {
}
};
private void processResult(KeyRetrievalResult result) {
view.statusLineOk();
byte[] importKeyData = result.getKeyData();
Long masterKeyId = result.getMasterKeyId();
if (importKeyData != null && masterKeyId != null) {
view.showActionImport();
this.importKeyData = importKeyData;
this.masterKeyId = masterKeyId;
return;
}
if (masterKeyId != null) {
this.masterKeyId = masterKeyId;
view.statusLineAdd(StatusLine.TOKEN_CHECK);
view.operationPromote(masterKeyId, tokenAid);
return;
}
throw new IllegalArgumentException("Method can only be called with successful result!");
}
void onClickImport() {
view.statusLineAdd(StatusLine.IMPORT);
view.hideAction();
view.operationImportKey(importKeyData);
}
void onImportSuccess() {
view.statusLineOk();
view.statusLineAdd(StatusLine.TOKEN_PROMOTE);
view.operationPromote(masterKeyId, tokenAid);
}
void onImportError() {
view.statusLineError();
}
void onPromoteSuccess() {
view.statusLineOk();
view.showActionViewKey();
}
void onPromoteError() {
view.statusLineError();
}
void onClickRetry() {
searchedLocally = false;
searchedAtUri = false;
searchedKeyservers = false;
view.hideAction();
view.resetStatusLines();
continueSearch();
}
void onClickViewKey() {
view.finishAndShowKey(masterKeyId);
}
public void onClickResetToken() {
}
interface CreateSecurityTokenImportMvpView {
void statusLineAdd(StatusLine statusLine);
void statusLineOk();
void statusLineError();
void resetStatusLines();
void showActionImport();
void showActionViewKey();
void showActionRetryOrFromFile();
void hideAction();
void operationImportKey(byte[] importKeyData);
void operationPromote(long masterKeyId, byte[] cardAid);
void finishAndShowKey(long masterKeyId);
}
}

View File

@@ -44,7 +44,6 @@ import org.sufficientlysecure.keychain.ui.CreateKeyActivity.SecurityTokenListene
import org.sufficientlysecure.keychain.ui.base.QueueingCryptoOperationFragment;
import org.sufficientlysecure.keychain.ui.keyview.ViewKeyActivity;
import org.sufficientlysecure.keychain.keyimport.HkpKeyserverAddress;
import org.sufficientlysecure.keychain.util.Preferences;
import java.io.IOException;
import java.nio.ByteBuffer;
@@ -72,7 +71,6 @@ public class CreateSecurityTokenImportResetFragment
private TextView vUserId;
private TextView mNextButton;
private RadioButton mRadioImport;
private RadioButton mRadioFile;
private RadioButton mRadioReset;
private View mResetWarning;
@@ -117,7 +115,6 @@ public class CreateSecurityTokenImportResetFragment
vUserId = (TextView) view.findViewById(R.id.token_userid);
mNextButton = (TextView) view.findViewById(R.id.create_key_next_button);
mRadioImport = (RadioButton) view.findViewById(R.id.token_decision_import);
mRadioFile = (RadioButton) view.findViewById(R.id.token_decision_file);
mRadioReset = (RadioButton) view.findViewById(R.id.token_decision_reset);
mResetWarning = view.findViewById(R.id.token_import_reset_warning);
@@ -141,8 +138,6 @@ public class CreateSecurityTokenImportResetFragment
resetCard();
} else if (mRadioImport.isChecked()){
importKey();
} else {
importFile();
}
}
});
@@ -158,17 +153,6 @@ public class CreateSecurityTokenImportResetFragment
}
}
});
mRadioFile.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (isChecked) {
mNextButton.setText(R.string.key_list_fab_import);
mNextButton.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.ic_folder_grey_24dp, 0);
mNextButton.setVisibility(View.VISIBLE);
mResetWarning.setVisibility(View.GONE);
}
}
});
mRadioReset.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
@@ -183,7 +167,6 @@ public class CreateSecurityTokenImportResetFragment
setData();
return view;
}
@@ -214,21 +197,9 @@ public class CreateSecurityTokenImportResetFragment
}
public void importKey() {
ArrayList<ParcelableKeyRing> keyList = new ArrayList<>();
keyList.add(ParcelableKeyRing.createFromReference(mTokenFingerprint, null, null, null));
mKeyList = keyList;
mKeyserver = Preferences.getPreferences(getActivity()).getPreferredKeyserver();
super.setProgressMessageResource(R.string.progress_importing);
super.cryptoOperation();
}
public void importFile() {
Intent intent = new Intent(getActivity(), ImportKeysActivity.class);
intent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_FILE);
startActivity(intent);
// Fragment frag = CreateSecurityTokenImportFragment.newInstance(mTokenFingerprints, mTokenAid, mTokenUserId,
// keyUdi);
// mCreateKeyActivity.loadFragment(frag, FragAction.TO_RIGHT);
}
public void resetCard() {

View File

@@ -19,6 +19,7 @@ package org.sufficientlysecure.keychain.ui;
import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
@@ -26,9 +27,12 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Animation;
import org.bouncycastle.util.encoders.Hex;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction;
import org.sufficientlysecure.keychain.ui.base.BaseSecurityTokenActivity;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
public class CreateSecurityTokenWaitFragment extends Fragment {

View File

@@ -0,0 +1,227 @@
/*
* Copyright (C) 2017 Vincent Breitmoser <look@my.amazin.horse>
*
* 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 java.io.IOException;
import java.util.Arrays;
import android.content.Context;
import android.os.SystemClock;
import android.support.annotation.Nullable;
import android.support.v4.content.AsyncTaskLoader;
import android.util.Log;
import com.google.auto.value.AutoValue;
import okhttp3.Request.Builder;
import okhttp3.Response;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.keyimport.HkpKeyserverAddress;
import org.sufficientlysecure.keychain.keyimport.HkpKeyserverClient;
import org.sufficientlysecure.keychain.keyimport.KeyserverClient.QueryFailedException;
import org.sufficientlysecure.keychain.network.OkHttpClientFactory;
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
import org.sufficientlysecure.keychain.provider.KeyRepository;
import org.sufficientlysecure.keychain.provider.KeyRepository.NotFoundException;
import org.sufficientlysecure.keychain.ui.PublicKeyRetrievalLoader.KeyRetrievalResult;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.util.ParcelableProxy;
import org.sufficientlysecure.keychain.util.Preferences;
public abstract class PublicKeyRetrievalLoader extends AsyncTaskLoader<KeyRetrievalResult> {
private static final long MIN_OPERATION_TIME_MILLIS = 1500;
private KeyRetrievalResult cachedResult;
public PublicKeyRetrievalLoader(Context context) {
super(context);
}
@Override
protected KeyRetrievalResult onLoadInBackground() {
long startTime = SystemClock.elapsedRealtime();
KeyRetrievalResult keyRetrievalResult = super.onLoadInBackground();
try {
long elapsedTime = startTime - SystemClock.elapsedRealtime();
if (elapsedTime < MIN_OPERATION_TIME_MILLIS) {
Thread.sleep(MIN_OPERATION_TIME_MILLIS - elapsedTime);
}
} catch (InterruptedException e) {
// nvm
}
return keyRetrievalResult;
}
public static class LocalKeyLookupLoader extends PublicKeyRetrievalLoader {
private final KeyRepository keyRepository;
private final byte[][] fingerprints;
public LocalKeyLookupLoader(Context context, byte[][] fingerprints) {
super(context);
this.fingerprints = fingerprints;
this.keyRepository = KeyRepository.createDatabaseInteractor(context);
}
@Override
public KeyRetrievalResult loadInBackground() {
try {
// TODO check other fingerprints
long masterKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(fingerprints[0]);
CachedPublicKeyRing cachedPublicKeyRing = keyRepository.getCachedPublicKeyRing(masterKeyId);
switch (cachedPublicKeyRing.getSecretKeyType(masterKeyId)) {
case PASSPHRASE:
case PASSPHRASE_EMPTY: {
return KeyRetrievalResult.createWithMasterKeyIdAndSecretAvailable(masterKeyId);
}
case GNU_DUMMY:
case DIVERT_TO_CARD:
case UNAVAILABLE: {
return KeyRetrievalResult.createWithMasterKeyId(masterKeyId);
}
default: {
throw new IllegalStateException("Unhandled SecretKeyType!");
}
}
} catch (NotFoundException e) {
return KeyRetrievalResult.createWithError();
}
}
}
public static class UriKeyRetrievalLoader extends PublicKeyRetrievalLoader {
byte[][] fingerprints;
String yubikeyUri;
public UriKeyRetrievalLoader(Context context, String yubikeyUri, byte[][] fingerprints) {
super(context);
this.yubikeyUri = yubikeyUri;
this.fingerprints = fingerprints;
}
@Override
public KeyRetrievalResult loadInBackground() {
try {
Response execute =
OkHttpClientFactory.getSimpleClient().newCall(new Builder().url(yubikeyUri).build()).execute();
if (execute.isSuccessful()) {
UncachedKeyRing keyRing = UncachedKeyRing.decodeFromData(execute.body().bytes());
if (Arrays.equals(fingerprints[0], keyRing.getFingerprint())) {
return KeyRetrievalResult.createWithKeyringdata(keyRing.getMasterKeyId(), keyRing.getEncoded());
}
}
} catch (IOException | PgpGeneralException e) {
Log.e(Constants.TAG, "error retrieving key from uri", e);
}
return KeyRetrievalResult.createWithError();
}
}
public static class KeyserverRetrievalLoader extends PublicKeyRetrievalLoader {
byte[] fingerprint;
public KeyserverRetrievalLoader(Context context, byte[] fingerprint) {
super(context);
this.fingerprint = fingerprint;
}
@Override
public KeyRetrievalResult loadInBackground() {
HkpKeyserverAddress preferredKeyserver = Preferences.getPreferences(getContext()).getPreferredKeyserver();
ParcelableProxy parcelableProxy = Preferences.getPreferences(getContext()).getParcelableProxy();
HkpKeyserverClient keyserverClient = HkpKeyserverClient.fromHkpKeyserverAddress(preferredKeyserver);
try {
String keyString =
keyserverClient.get("0x" + KeyFormattingUtils.convertFingerprintToHex(fingerprint), parcelableProxy);
UncachedKeyRing keyRing = UncachedKeyRing.decodeFromData(keyString.getBytes());
return KeyRetrievalResult.createWithKeyringdata(keyRing.getMasterKeyId(), keyRing.getEncoded());
} catch (QueryFailedException | IOException | PgpGeneralException e) {
Log.e(Constants.TAG, "error retrieving key from keyserver", e);
}
return KeyRetrievalResult.createWithError();
}
}
@Override
public void deliverResult(KeyRetrievalResult result) {
cachedResult = result;
if (isStarted()) {
super.deliverResult(result);
}
}
@Override
protected void onStartLoading() {
if (cachedResult != null) {
deliverResult(cachedResult);
}
if (takeContentChanged() || cachedResult == null) {
forceLoad();
}
}
@AutoValue
static abstract class KeyRetrievalResult {
@Nullable
abstract Long getMasterKeyId();
@Nullable
abstract byte[] getKeyData();
abstract boolean isSecretKeyAvailable();
boolean isSuccess() {
return getMasterKeyId() != null || getKeyData() != null;
}
static KeyRetrievalResult createWithError() {
return new AutoValue_PublicKeyRetrievalLoader_KeyRetrievalResult(null, null, false);
}
static KeyRetrievalResult createWithKeyringdata(long masterKeyId, byte[] keyringData) {
return new AutoValue_PublicKeyRetrievalLoader_KeyRetrievalResult(masterKeyId, keyringData, false);
}
static KeyRetrievalResult createWithMasterKeyIdAndSecretAvailable(long masterKeyId) {
return new AutoValue_PublicKeyRetrievalLoader_KeyRetrievalResult(masterKeyId, null, true);
}
static KeyRetrievalResult createWithMasterKeyId(long masterKeyId) {
return new AutoValue_PublicKeyRetrievalLoader_KeyRetrievalResult(masterKeyId, null, false);
}
}
}

View File

@@ -99,7 +99,6 @@ public class ViewKeySecurityTokenFragment
mMasterKeyId = args.getLong(ARG_MASTER_KEY_ID);
getLoaderManager().initLoader(0, null, this);
}
@Override

View File

@@ -25,9 +25,11 @@ import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.Messenger;
import android.os.Parcelable;
import android.os.SystemClock;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
@@ -55,6 +57,8 @@ import org.sufficientlysecure.keychain.util.Log;
*/
public class CryptoOperationHelper<T extends Parcelable, S extends OperationResult> {
private long operationStartTime;
public interface Callback<T extends Parcelable, S extends OperationResult> {
T createOperationInput();
@@ -67,6 +71,19 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu
boolean onCryptoSetProgress(String msg, int progress, int max);
}
public static abstract class AbstractCallback<T extends Parcelable, S extends OperationResult>
implements Callback<T,S> {
@Override
public void onCryptoOperationCancelled() {
throw new UnsupportedOperationException("Unexpectedly cancelled operation!!");
}
@Override
public boolean onCryptoSetProgress(String msg, int progress, int max) {
return false;
}
}
// request codes from CryptoOperationHelper are created essentially
// a static property, used to identify requestCodes meant for this
// particular helper. a request code looks as follows:
@@ -85,6 +102,7 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu
private Integer mProgressMessageResource;
private boolean mCancellable = false;
private Long minimumOperationDelay;
private FragmentActivity mActivity;
private Fragment mFragment;
@@ -119,6 +137,10 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu
mProgressMessageResource = id;
}
public void setOperationMinimumDelay(Long delay) {
this.minimumOperationDelay = delay;
}
public void setProgressCancellable(boolean cancellable) {
mCancellable = cancellable;
}
@@ -323,10 +345,11 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu
}
public void cryptoOperation() {
operationStartTime = SystemClock.elapsedRealtime();
cryptoOperation(CryptoInputParcel.createCryptoInputParcel(new Date()));
}
public void onHandleResult(OperationResult result) {
private void onHandleResult(final OperationResult result) {
Log.d(Constants.TAG, "Handling result in OperationHelper success: " + result.success());
if (result instanceof InputPendingResult) {
@@ -340,6 +363,21 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu
dismissProgress();
long elapsedTime = SystemClock.elapsedRealtime() - operationStartTime;
if (minimumOperationDelay == null || elapsedTime > minimumOperationDelay) {
returnResultToCallback(result);
return;
}
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
returnResultToCallback(result);
}
}, minimumOperationDelay - elapsedTime);
}
private void returnResultToCallback(OperationResult result) {
try {
if (result.success()) {
// noinspection unchecked, because type erasure :(

View File

@@ -151,7 +151,8 @@ public class IdentityLoader extends AsyncTaskLoader<List<IdentityInfo>> {
private Intent getTrustIdActivityIntentIfResolvable(String packageName, String autocryptPeer) {
Intent intent = new Intent();
intent.setAction(packageName + ".AUTOCRYPT_PEER_ACTION");
intent.setAction("org.autocrypt.PEER_ACTION");
intent.setPackage(packageName);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra(OpenPgpApi.EXTRA_AUTOCRYPT_PEER_ID, autocryptPeer);