implemented revocation on deletion

This commit is contained in:
Adithya Abraham Philip
2015-07-09 22:51:20 +05:30
parent bfe36019bd
commit fcd27d2600
27 changed files with 1058 additions and 304 deletions

View File

@@ -18,19 +18,29 @@
package org.sufficientlysecure.keychain.operations;
import android.content.Context;
import android.net.Uri;
import android.support.annotation.NonNull;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.operations.results.ConsolidateResult;
import org.sufficientlysecure.keychain.operations.results.DeleteResult;
import org.sufficientlysecure.keychain.operations.results.InputPendingResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
import org.sufficientlysecure.keychain.pgp.Progressable;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.service.ContactSyncAdapterService;
import org.sufficientlysecure.keychain.service.DeleteKeyringParcel;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.util.Log;
/** An operation which implements a high level keyring delete operation.
*
@@ -48,12 +58,17 @@ public class DeleteOperation extends BaseOperation<DeleteKeyringParcel> {
@NonNull
@Override
public DeleteResult execute(DeleteKeyringParcel deleteKeyringParcel,
public OperationResult execute(DeleteKeyringParcel deleteKeyringParcel,
CryptoInputParcel cryptoInputParcel) {
long[] masterKeyIds = deleteKeyringParcel.mMasterKeyIds;
boolean isSecret = deleteKeyringParcel.mIsSecret;
return onlyDeleteKey(masterKeyIds, isSecret);
}
private DeleteResult onlyDeleteKey(long[] masterKeyIds, boolean isSecret) {
OperationLog log = new OperationLog();
if (masterKeyIds.length == 0) {
@@ -113,7 +128,6 @@ public class DeleteOperation extends BaseOperation<DeleteKeyringParcel> {
}
return new DeleteResult(result, log, success, fail);
}
}

View File

@@ -21,7 +21,10 @@ import android.content.Context;
import android.support.annotation.NonNull;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.keyimport.HkpKeyserver;
import org.sufficientlysecure.keychain.operations.results.EditKeyResult;
import org.sufficientlysecure.keychain.operations.results.ExportResult;
import org.sufficientlysecure.keychain.operations.results.InputPendingResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
@@ -34,6 +37,7 @@ import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException;
import org.sufficientlysecure.keychain.service.ContactSyncAdapterService;
import org.sufficientlysecure.keychain.service.ExportKeyringParcel;
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
@@ -58,8 +62,16 @@ public class EditKeyOperation extends BaseOperation<SaveKeyringParcel> {
super(context, providerHelper, progressable, cancelled);
}
/**
* Saves an edited key, and uploads it to a server atomically or otherwise as
* specified in saveParcel
*
* @param saveParcel primary input to the operation
* @param cryptoInput input that changes if user interaction is required
* @return the result of the operation
*/
@NonNull
public OperationResult execute(SaveKeyringParcel saveParcel, CryptoInputParcel cryptoInput) {
public InputPendingResult execute(SaveKeyringParcel saveParcel, CryptoInputParcel cryptoInput) {
OperationLog log = new OperationLog();
log.add(LogType.MSG_ED, 0);
@@ -120,6 +132,24 @@ public class EditKeyOperation extends BaseOperation<SaveKeyringParcel> {
// It's a success, so this must be non-null now
UncachedKeyRing ring = modifyResult.getRing();
if (saveParcel.isUpload()) {
ExportKeyringParcel exportKeyringParcel =
new ExportKeyringParcel(saveParcel.getUploadKeyserver(), ring);
ExportResult uploadResult =
new ExportOperation(mContext, mProviderHelper, mProgressable)
.execute(exportKeyringParcel, cryptoInput);
if (uploadResult.isPending()) {
return uploadResult;
} else if (!uploadResult.success() && saveParcel.isUploadAtomic()) {
// if atomic, update fail implies edit operation should also fail and not save
log.add(uploadResult, 2);
return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, saveParcel.mMasterKeyId);
} else {
// upload succeeded or not atomic so we continue
log.add(uploadResult, 2);
}
}
// Save the new keyring.
SaveKeyringResult saveResult = mProviderHelper
.saveSecretKeyRing(ring, new ProgressScaler(mProgressable, 60, 95, 100));
@@ -160,5 +190,4 @@ public class EditKeyOperation extends BaseOperation<SaveKeyringParcel> {
return new EditKeyResult(EditKeyResult.RESULT_OK, log, ring.getMasterKeyId());
}
}

View File

@@ -34,6 +34,7 @@ import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import org.spongycastle.bcpg.ArmoredOutputStream;
@@ -42,6 +43,7 @@ import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.keyimport.HkpKeyserver;
import org.sufficientlysecure.keychain.keyimport.Keyserver.AddKeyException;
import org.sufficientlysecure.keychain.operations.results.ExportResult;
import org.sufficientlysecure.keychain.operations.results.InputPendingResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing;
@@ -128,6 +130,18 @@ public class ExportOperation extends BaseOperation<ExportKeyringParcel> {
}
}
/**
* returns null if no user input required for upload, an InputPendingResult otherwise
*/
@Nullable
public InputPendingResult getUploadPendingInput() {
if (!OrbotHelper.isOrbotInRequiredState(mContext)) {
return new ExportResult(null,
RequiredInputParcel.createOrbotRequiredOperation());
}
return null;
}
public ExportResult exportToFile(long[] masterKeyIds, boolean exportSecret, String outputFile) {
OperationLog log = new OperationLog();
@@ -351,10 +365,15 @@ public class ExportOperation extends BaseOperation<ExportKeyringParcel> {
HkpKeyserver hkpKeyserver = new HkpKeyserver(exportInput.mKeyserver);
try {
CanonicalizedPublicKeyRing keyring
= mProviderHelper.getCanonicalizedPublicKeyRing(
exportInput.mCanonicalizedPublicKeyringUri);
return uploadKeyRingToServer(hkpKeyserver, keyring, proxy);
if (exportInput.mCanonicalizedPublicKeyringUri != null) {
CanonicalizedPublicKeyRing keyring
= mProviderHelper.getCanonicalizedPublicKeyRing(
exportInput.mCanonicalizedPublicKeyringUri);
return uploadKeyRingToServer(hkpKeyserver, keyring, proxy);
} else {
return uploadKeyRingToServer(hkpKeyserver, exportInput.mUncachedKeyRing,
proxy);
}
} catch (ProviderHelper.NotFoundException e) {
Log.e(Constants.TAG, "error uploading key", e);
return new ExportResult(ExportResult.RESULT_ERROR, new OperationLog());

View File

@@ -0,0 +1,124 @@
package org.sufficientlysecure.keychain.operations;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.support.annotation.NonNull;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.operations.results.DeleteResult;
import org.sufficientlysecure.keychain.operations.results.InputPendingResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.operations.results.RevokeResult;
import org.sufficientlysecure.keychain.pgp.Progressable;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.service.RevokeKeyringParcel;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.util.Log;
public class RevokeOperation extends BaseOperation<RevokeKeyringParcel> {
public RevokeOperation(Context context, ProviderHelper providerHelper, Progressable progressable) {
super(context, providerHelper, progressable);
}
@NonNull
@Override
public OperationResult execute(RevokeKeyringParcel revokeKeyringParcel,
CryptoInputParcel cryptoInputParcel) {
long masterKeyId = revokeKeyringParcel.mMasterKeyId;
OperationResult.OperationLog log = new OperationResult.OperationLog();
log.add(OperationResult.LogType.MSG_REVOKE, 0,
KeyFormattingUtils.beautifyKeyId(masterKeyId));
try {
Uri secretUri = KeychainContract.KeyRings.buildUnifiedKeyRingUri(masterKeyId);
CachedPublicKeyRing keyRing = mProviderHelper.getCachedPublicKeyRing(secretUri);
// check if this is a master secret key we can work with
switch (keyRing.getSecretKeyType(masterKeyId)) {
case GNU_DUMMY:
log.add(OperationResult.LogType.MSG_EK_ERROR_DUMMY, 1);
return new RevokeResult(RevokeResult.RESULT_ERROR, log, masterKeyId);
}
SaveKeyringParcel saveKeyringParcel = getRevokedSaveKeyringParcel(masterKeyId,
keyRing.getFingerprint());
// all revoke operations are made atomic as of now
saveKeyringParcel.setUpdateOptions(revokeKeyringParcel.mUpload, true,
revokeKeyringParcel.mKeyserver);
InputPendingResult revokeAndUploadResult = new EditKeyOperation(mContext,
mProviderHelper, mProgressable, mCancelled)
.execute(saveKeyringParcel, cryptoInputParcel);
if (revokeAndUploadResult.isPending()) {
return revokeAndUploadResult;
}
log.add(revokeAndUploadResult, 1);
if (revokeAndUploadResult.success()) {
log.add(OperationResult.LogType.MSG_REVOKE_OK, 1);
return new RevokeResult(RevokeResult.RESULT_OK, log, masterKeyId);
} else {
log.add(OperationResult.LogType.MSG_REVOKE_KEY_FAIL, 1);
return new RevokeResult(RevokeResult.RESULT_ERROR, log, masterKeyId);
}
} catch (PgpKeyNotFoundException | ProviderHelper.NotFoundException e) {
Log.e(Constants.TAG, "could not find key to revoke", e);
log.add(OperationResult.LogType.MSG_REVOKE_KEY_FAIL, 1);
return new RevokeResult(RevokeResult.RESULT_ERROR, log, masterKeyId);
}
}
private SaveKeyringParcel getRevokedSaveKeyringParcel(long masterKeyId, byte[] fingerprint) {
final String[] SUBKEYS_PROJECTION = new String[]{
KeychainContract.Keys.KEY_ID
};
final int INDEX_KEY_ID = 0;
Uri keysUri = KeychainContract.Keys.buildKeysUri(masterKeyId);
Cursor subKeyCursor =
mContext.getContentResolver().query(keysUri, SUBKEYS_PROJECTION, null, null, null);
SaveKeyringParcel saveKeyringParcel =
new SaveKeyringParcel(masterKeyId, fingerprint);
// add all subkeys, for revocation
while (subKeyCursor != null && subKeyCursor.moveToNext()) {
saveKeyringParcel.mRevokeSubKeys.add(subKeyCursor.getLong(INDEX_KEY_ID));
}
if (subKeyCursor != null) {
subKeyCursor.close();
}
final String[] USER_IDS_PROJECTION = new String[]{
KeychainContract.UserPackets.USER_ID
};
final int INDEX_USER_ID = 0;
Uri userIdsUri = KeychainContract.UserPackets.buildUserIdsUri(masterKeyId);
Cursor userIdCursor = mContext.getContentResolver().query(
userIdsUri, USER_IDS_PROJECTION, null, null, null);
while (userIdCursor != null && userIdCursor.moveToNext()) {
saveKeyringParcel.mRevokeUserIds.add(userIdCursor.getString(INDEX_USER_ID));
}
if (userIdCursor != null) {
userIdCursor.close();
}
return saveKeyringParcel;
}
}

View File

@@ -21,8 +21,10 @@ package org.sufficientlysecure.keychain.operations.results;
import android.app.Activity;
import android.content.Intent;
import android.os.Parcel;
import android.support.annotation.Nullable;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
import org.sufficientlysecure.keychain.ui.LogDisplayActivity;
import org.sufficientlysecure.keychain.ui.LogDisplayFragment;
import org.sufficientlysecure.keychain.ui.util.Notify;
@@ -30,7 +32,7 @@ import org.sufficientlysecure.keychain.ui.util.Notify.ActionListener;
import org.sufficientlysecure.keychain.ui.util.Notify.Showable;
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
public class DeleteResult extends OperationResult {
public class DeleteResult extends InputPendingResult {
final public int mOk, mFail;
@@ -40,6 +42,18 @@ public class DeleteResult extends OperationResult {
mFail = fail;
}
/**
* used when more input is required
* @param log operation log upto point of required input, if any
* @param requiredInput represents input required
*/
public DeleteResult(@Nullable OperationLog log, RequiredInputParcel requiredInput) {
super(log, requiredInput);
// values are not to be used
mOk = -1;
mFail = -1;
}
/** Construct from a parcel - trivial because we have no extra data. */
public DeleteResult(Parcel source) {
super(source);

View File

@@ -20,7 +20,7 @@ package org.sufficientlysecure.keychain.operations.results;
import android.os.Parcel;
public class EditKeyResult extends OperationResult {
public class EditKeyResult extends InputPendingResult {
public final Long mMasterKeyId;

View File

@@ -22,6 +22,7 @@ import java.util.ArrayList;
import android.os.Parcel;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
public class InputPendingResult extends OperationResult {
@@ -30,26 +31,33 @@ public class InputPendingResult extends OperationResult {
public static final int RESULT_PENDING = RESULT_ERROR + 8;
final RequiredInputParcel mRequiredInput;
// in case operation needs to add to/changes the cryptoInputParcel sent to it
final CryptoInputParcel mCryptoInputParcel;
public InputPendingResult(int result, OperationLog log) {
super(result, log);
mRequiredInput = null;
mCryptoInputParcel = null;
}
public InputPendingResult(OperationLog log, RequiredInputParcel requiredInput) {
public InputPendingResult(OperationLog log, RequiredInputParcel requiredInput,
CryptoInputParcel cryptoInputParcel) {
super(RESULT_PENDING, log);
mRequiredInput = requiredInput;
mCryptoInputParcel = cryptoInputParcel;
}
public InputPendingResult(Parcel source) {
super(source);
mRequiredInput = source.readParcelable(getClass().getClassLoader());
mCryptoInputParcel = source.readParcelable(getClass().getClassLoader());
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeParcelable(mRequiredInput, 0);
dest.writeParcelable(mCryptoInputParcel, 0);
}
public boolean isPending() {

View File

@@ -760,6 +760,13 @@ public abstract class OperationResult implements Parcelable {
MSG_DEL_OK (LogLevel.OK, R.plurals.msg_del_ok),
MSG_DEL_FAIL (LogLevel.WARN, R.plurals.msg_del_fail),
MSG_REVOKE_ERROR_EMPTY (LogLevel.ERROR, R.string.msg_revoke_error_empty),
MSG_REVOKE_ERROR_MULTI_SECRET (LogLevel.DEBUG, R.string.msg_revoke_error_multi_secret),
MSG_REVOKE_ERROR_NOT_FOUND (LogLevel.DEBUG, R.string.msg_revoke_error_multi_secret),
MSG_REVOKE (LogLevel.DEBUG, R.string.msg_revoke_key),
MSG_REVOKE_KEY_FAIL (LogLevel.WARN, R.string.msg_revoke_key_fail),
MSG_REVOKE_OK (LogLevel.OK, R.string.msg_revoke_ok),
// keybase verification
MSG_KEYBASE_VERIFICATION(LogLevel.START, R.string.msg_keybase_verification),

View File

@@ -0,0 +1,100 @@
package org.sufficientlysecure.keychain.operations.results;
import android.app.Activity;
import android.content.Intent;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.Nullable;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
import org.sufficientlysecure.keychain.ui.LogDisplayActivity;
import org.sufficientlysecure.keychain.ui.LogDisplayFragment;
import org.sufficientlysecure.keychain.ui.util.Notify;
public class RevokeResult extends InputPendingResult {
public final long mMasterKeyId;
public RevokeResult(int result, OperationLog log, long masterKeyId) {
super(result, log);
mMasterKeyId = masterKeyId;
}
/**
* used when more input is required
* @param log operation log upto point of required input, if any
* @param requiredInput represents input required
*/
public RevokeResult(@Nullable OperationLog log, RequiredInputParcel requiredInput) {
super(log, requiredInput);
// we won't use these values
mMasterKeyId = -1;
}
/** Construct from a parcel - trivial because we have no extra data. */
public RevokeResult(Parcel source) {
super(source);
mMasterKeyId = source.readLong();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeLong(mMasterKeyId);
}
public static final Parcelable.Creator<RevokeResult> CREATOR = new Parcelable.Creator<RevokeResult>() {
@Override
public RevokeResult createFromParcel(Parcel in) {
return new RevokeResult(in);
}
@Override
public RevokeResult[] newArray(int size) {
return new RevokeResult[size];
}
};
@Override
public Notify.Showable createNotify(final Activity activity) {
int resultType = getResult();
String str;
int duration;
Notify.Style style;
// Not an overall failure
if ((resultType & OperationResult.RESULT_ERROR) == 0) {
duration = Notify.LENGTH_LONG;
// New and updated keys
if (resultType == OperationResult.RESULT_OK) {
style = Notify.Style.OK;
str = activity.getString(R.string.revoke_ok);
} else {
duration = 0;
style = Notify.Style.ERROR;
str = "internal error";
}
} else {
duration = 0;
style = Notify.Style.ERROR;
str = activity.getString(R.string.revoke_fail);
}
return Notify.create(activity, str, duration, style, new Notify.ActionListener() {
@Override
public void onAction() {
Intent intent = new Intent(
activity, LogDisplayActivity.class);
intent.putExtra(LogDisplayFragment.EXTRA_RESULT, RevokeResult.this);
activity.startActivity(intent);
}
}, R.string.snackbar_details);
}
}

View File

@@ -18,6 +18,8 @@
package org.sufficientlysecure.keychain.pgp;
import android.os.Parcelable;
import org.spongycastle.bcpg.ArmoredOutputStream;
import org.spongycastle.bcpg.PublicKeyAlgorithmTags;
import org.spongycastle.bcpg.SignatureSubpacketTags;
@@ -48,6 +50,7 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
@@ -78,7 +81,7 @@ import java.util.TreeSet;
*
*/
@SuppressWarnings("unchecked")
public class UncachedKeyRing {
public class UncachedKeyRing implements Serializable {
final PGPKeyRing mRing;
final boolean mIsSecret;

View File

@@ -23,9 +23,12 @@ import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
public class ExportKeyringParcel implements Parcelable {
public String mKeyserver;
public Uri mCanonicalizedPublicKeyringUri;
public UncachedKeyRing mUncachedKeyRing;
public boolean mExportSecret;
public long mMasterKeyIds[];
@@ -45,6 +48,12 @@ public class ExportKeyringParcel implements Parcelable {
mCanonicalizedPublicKeyringUri = keyringUri;
}
public ExportKeyringParcel(String keyserver, UncachedKeyRing uncachedKeyRing) {
mExportType = ExportType.UPLOAD_KEYSERVER;
mKeyserver = keyserver;
mUncachedKeyRing = uncachedKeyRing;
}
public ExportKeyringParcel(long[] masterKeyIds, boolean exportSecret, String outputFile) {
mExportType = ExportType.EXPORT_FILE;
mMasterKeyIds = masterKeyIds;
@@ -62,6 +71,7 @@ public class ExportKeyringParcel implements Parcelable {
protected ExportKeyringParcel(Parcel in) {
mKeyserver = in.readString();
mCanonicalizedPublicKeyringUri = (Uri) in.readValue(Uri.class.getClassLoader());
mUncachedKeyRing = (UncachedKeyRing) in.readValue(UncachedKeyRing.class.getClassLoader());
mExportSecret = in.readByte() != 0x00;
mOutputFile = in.readString();
mOutputUri = (Uri) in.readValue(Uri.class.getClassLoader());
@@ -78,6 +88,7 @@ public class ExportKeyringParcel implements Parcelable {
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(mKeyserver);
dest.writeValue(mCanonicalizedPublicKeyringUri);
dest.writeValue(mUncachedKeyRing);
dest.writeByte((byte) (mExportSecret ? 0x01 : 0x00));
dest.writeString(mOutputFile);
dest.writeValue(mOutputUri);

View File

@@ -37,6 +37,7 @@ import org.sufficientlysecure.keychain.operations.ExportOperation;
import org.sufficientlysecure.keychain.operations.ImportOperation;
import org.sufficientlysecure.keychain.operations.KeybaseVerificationOperation;
import org.sufficientlysecure.keychain.operations.PromoteKeyOperation;
import org.sufficientlysecure.keychain.operations.RevokeOperation;
import org.sufficientlysecure.keychain.operations.SignEncryptOperation;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify;
@@ -114,6 +115,8 @@ public class KeychainService extends Service implements Progressable {
} else if (inputParcel instanceof SaveKeyringParcel) {
op = new EditKeyOperation(outerThis, new ProviderHelper(outerThis), outerThis,
mActionCanceled);
} else if (inputParcel instanceof RevokeKeyringParcel) {
op = new RevokeOperation(outerThis, new ProviderHelper(outerThis), outerThis);
} else if (inputParcel instanceof CertifyActionsParcel) {
op = new CertifyOperation(outerThis, new ProviderHelper(outerThis), outerThis,
mActionCanceled);
@@ -135,7 +138,7 @@ public class KeychainService extends Service implements Progressable {
op = new KeybaseVerificationOperation(outerThis, new ProviderHelper(outerThis),
outerThis);
} else {
return;
throw new AssertionError("Unrecognized input parcel in KeychainService!");
}
@SuppressWarnings("unchecked") // this is unchecked, we make sure it's the correct op above!

View File

@@ -0,0 +1,47 @@
package org.sufficientlysecure.keychain.service;
import android.os.Parcel;
import android.os.Parcelable;
public class RevokeKeyringParcel implements Parcelable {
final public long mMasterKeyId;
final public boolean mUpload;
final public String mKeyserver;
public RevokeKeyringParcel(long masterKeyId, boolean upload, String keyserver) {
mMasterKeyId = masterKeyId;
mUpload = upload;
mKeyserver = keyserver;
}
protected RevokeKeyringParcel(Parcel in) {
mMasterKeyId = in.readLong();
mUpload = in.readByte() != 0x00;
mKeyserver = in.readString();
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeLong(mMasterKeyId);
dest.writeByte((byte) (mUpload ? 0x01 : 0x00));
dest.writeString(mKeyserver);
}
public static final Parcelable.Creator<RevokeKeyringParcel> CREATOR = new Parcelable.Creator<RevokeKeyringParcel>() {
@Override
public RevokeKeyringParcel createFromParcel(Parcel in) {
return new RevokeKeyringParcel(in);
}
@Override
public RevokeKeyringParcel[] newArray(int size) {
return new RevokeKeyringParcel[size];
}
};
}

View File

@@ -65,6 +65,11 @@ public class SaveKeyringParcel implements Parcelable {
public Passphrase mCardPin;
public Passphrase mCardAdminPin;
// private because they have to be set together with setUpdateOptions
private boolean mUpload;
private boolean mUploadAtomic;
private String mKeyserver;
public SaveKeyringParcel() {
reset();
}
@@ -86,6 +91,27 @@ public class SaveKeyringParcel implements Parcelable {
mRevokeSubKeys = new ArrayList<>();
mCardPin = null;
mCardAdminPin = null;
mUpload = false;
mUploadAtomic = false;
mKeyserver = null;
}
public void setUpdateOptions(boolean upload, boolean uploadAtomic, String keysever) {
mUpload = upload;
mUploadAtomic = uploadAtomic;
mKeyserver = keysever;
}
public boolean isUpload() {
return mUpload;
}
public boolean isUploadAtomic() {
return mUploadAtomic;
}
public String getUploadKeyserver() {
return mKeyserver;
}
public boolean isEmpty() {
@@ -234,6 +260,10 @@ public class SaveKeyringParcel implements Parcelable {
mCardPin = source.readParcelable(Passphrase.class.getClassLoader());
mCardAdminPin = source.readParcelable(Passphrase.class.getClassLoader());
mUpload = source.readByte() != 0;
mUploadAtomic = source.readByte() != 0;
mKeyserver = source.readString();
}
@Override
@@ -259,6 +289,10 @@ public class SaveKeyringParcel implements Parcelable {
destination.writeParcelable(mCardPin, flags);
destination.writeParcelable(mCardAdminPin, flags);
destination.writeByte((byte) (mUpload ? 1 : 0));
destination.writeByte((byte) (mUploadAtomic ? 1 : 0));
destination.writeString(mKeyserver);
}
public static final Creator<SaveKeyringParcel> CREATOR = new Creator<SaveKeyringParcel>() {

View File

@@ -36,6 +36,8 @@ public class CryptoInputParcel implements Parcelable {
final Date mSignatureTime;
final Passphrase mPassphrase;
// used to supply an explicit proxy to operations that require it
// this is not final so it can be added to an existing CryptoInputParcel
// (e.g) CertifyOperation with upload might require both passphrase and orbot to be enabled
private ParcelableProxy mParcelableProxy;
// this map contains both decrypted session keys and signed hashes to be
@@ -45,30 +47,25 @@ public class CryptoInputParcel implements Parcelable {
public CryptoInputParcel() {
mSignatureTime = new Date();
mPassphrase = null;
mParcelableProxy = null;
}
public CryptoInputParcel(Date signatureTime, Passphrase passphrase) {
mSignatureTime = signatureTime == null ? new Date() : signatureTime;
mPassphrase = passphrase;
mParcelableProxy = null;
}
public CryptoInputParcel(Passphrase passphrase) {
mSignatureTime = new Date();
mPassphrase = passphrase;
mParcelableProxy = null;
}
public CryptoInputParcel(Date signatureTime) {
mSignatureTime = signatureTime == null ? new Date() : signatureTime;
mPassphrase = null;
mParcelableProxy = null;
}
public CryptoInputParcel(ParcelableProxy parcelableProxy) {
mSignatureTime = new Date(); // just for compatibility with parcel-ing
mPassphrase = null;
this();
mParcelableProxy = parcelableProxy;
}

View File

@@ -15,7 +15,7 @@ import java.util.Date;
public class RequiredInputParcel implements Parcelable {
public enum RequiredInputType {
PASSPHRASE, PASSPHRASE_SYMMETRIC, NFC_SIGN, NFC_DECRYPT, NFC_MOVE_KEY_TO_CARD, ENABLE_ORBOT
PASSPHRASE, PASSPHRASE_SYMMETRIC, NFC_SIGN, NFC_DECRYPT, NFC_MOVE_KEY_TO_CARD, ENABLE_ORBOT,
}
public Date mSignatureTime;

View File

@@ -0,0 +1,231 @@
package org.sufficientlysecure.keychain.ui;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.FragmentActivity;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.operations.results.DeleteResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.pgp.KeyRing;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.service.DeleteKeyringParcel;
import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper;
import org.sufficientlysecure.keychain.ui.dialog.CustomAlertDialogBuilder;
import org.sufficientlysecure.keychain.util.Log;
import java.util.HashMap;
public class DeleteKeyDialogActivity extends FragmentActivity
implements CryptoOperationHelper.Callback<DeleteKeyringParcel, DeleteResult> {
public static final String EXTRA_DELETE_MASTER_KEY_IDS = "extra_delete_master_key_ids";
private CryptoOperationHelper<DeleteKeyringParcel, DeleteResult> mDeleteOpHelper;
private long[] mMasterKeyIds;
private boolean mHasSecret;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mDeleteOpHelper = new CryptoOperationHelper<>(DeleteKeyDialogActivity.this,
DeleteKeyDialogActivity.this, R.string.progress_deleting);
mDeleteOpHelper.onRestoreInstanceState(savedInstanceState);
mMasterKeyIds = getIntent().getLongArrayExtra(EXTRA_DELETE_MASTER_KEY_IDS);
Handler deleteDialogHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (msg.what == DeleteKeyDialogFragment.MESSAGE_PERFORM_DELETE) {
mHasSecret = msg.getData().getBoolean(DeleteKeyDialogFragment.MSG_HAS_SECRET);
mDeleteOpHelper.cryptoOperation();
}
}
};
Messenger messenger = new Messenger(deleteDialogHandler);
DeleteKeyDialogFragment deleteKeyDialogFragment
= DeleteKeyDialogFragment.newInstance(messenger, mMasterKeyIds);
deleteKeyDialogFragment.show(getSupportFragmentManager(), "deleteKeyDialog");
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
mDeleteOpHelper.handleActivityResult(requestCode, resultCode, data);
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
mDeleteOpHelper.onSaveInstanceState(outState);
}
@Override
public DeleteKeyringParcel createOperationInput() {
return new DeleteKeyringParcel(mMasterKeyIds, mHasSecret);
}
@Override
public void onCryptoOperationSuccess(DeleteResult result) {
handleResult(result);
}
@Override
public void onCryptoOperationCancelled() {
setResult(RESULT_CANCELED);
finish();
}
@Override
public void onCryptoOperationError(DeleteResult result) {
handleResult(result);
}
@Override
public boolean onCryptoSetProgress(String msg, int progress, int max) {
return false;
}
public void handleResult(DeleteResult result) {
Intent intent = new Intent();
intent.putExtra(OperationResult.EXTRA_RESULT, result);
setResult(RESULT_OK, intent);
finish();
}
public static class DeleteKeyDialogFragment extends DialogFragment {
public static final String MSG_HAS_SECRET = "msg_has_secret";
private static final String ARG_MESSENGER = "messenger";
private static final String ARG_DELETE_MASTER_KEY_IDS = "delete_master_key_ids";
public static final int MESSAGE_PERFORM_DELETE = 1;
private TextView mMainMessage;
private View mInflateView;
private Messenger mMessenger;
/**
* Creates new instance of this delete file dialog fragment
*/
public static DeleteKeyDialogFragment newInstance(Messenger messenger, long[] masterKeyIds) {
DeleteKeyDialogFragment frag = new DeleteKeyDialogFragment();
Bundle args = new Bundle();
args.putParcelable(ARG_MESSENGER, messenger);
args.putLongArray(ARG_DELETE_MASTER_KEY_IDS, masterKeyIds);
frag.setArguments(args);
return frag;
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final FragmentActivity activity = getActivity();
mMessenger = getArguments().getParcelable(ARG_MESSENGER);
final long[] masterKeyIds = getArguments().getLongArray(ARG_DELETE_MASTER_KEY_IDS);
ContextThemeWrapper theme = new ContextThemeWrapper(activity,
R.style.Theme_AppCompat_Light_Dialog);
CustomAlertDialogBuilder builder = new CustomAlertDialogBuilder(theme);
// Setup custom View to display in AlertDialog
LayoutInflater inflater = activity.getLayoutInflater();
mInflateView = inflater.inflate(R.layout.view_key_delete_fragment, null);
builder.setView(mInflateView);
mMainMessage = (TextView) mInflateView.findViewById(R.id.mainMessage);
final boolean hasSecret;
// If only a single key has been selected
if (masterKeyIds.length == 1) {
long masterKeyId = masterKeyIds[0];
try {
HashMap<String, Object> data = new ProviderHelper(activity).getUnifiedData(
masterKeyId, new String[]{
KeychainContract.KeyRings.USER_ID,
KeychainContract.KeyRings.HAS_ANY_SECRET
}, new int[]{
ProviderHelper.FIELD_TYPE_STRING,
ProviderHelper.FIELD_TYPE_INTEGER
}
);
String name;
KeyRing.UserId mainUserId = KeyRing.splitUserId((String) data.get(KeychainContract.KeyRings.USER_ID));
if (mainUserId.name != null) {
name = mainUserId.name;
} else {
name = getString(R.string.user_id_no_name);
}
hasSecret = ((Long) data.get(KeychainContract.KeyRings.HAS_ANY_SECRET)) == 1;
if (hasSecret) {
// show title only for secret key deletions,
// see http://www.google.com/design/spec/components/dialogs.html#dialogs-behavior
builder.setTitle(getString(R.string.title_delete_secret_key, name));
mMainMessage.setText(getString(R.string.secret_key_deletion_confirmation, name));
} else {
mMainMessage.setText(getString(R.string.public_key_deletetion_confirmation, name));
}
} catch (ProviderHelper.NotFoundException e) {
dismiss();
return null;
}
} else {
mMainMessage.setText(R.string.key_deletion_confirmation_multi);
hasSecret = false;
}
builder.setPositiveButton(R.string.btn_delete, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Bundle data = new Bundle();
data.putBoolean(MSG_HAS_SECRET, hasSecret);
Message msg = Message.obtain();
msg.setData(data);
msg.what = MESSAGE_PERFORM_DELETE;
try {
mMessenger.send(msg);
} catch (RemoteException e) {
Log.e(Constants.TAG, "messenger error", e);
}
}
});
builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {
getActivity().finish();
}
});
return builder.show();
}
}
}

View File

@@ -410,6 +410,7 @@ public class EditKeyFragment extends QueueingCryptoOperationFragment<SaveKeyring
editSubkeyExpiry(position);
break;
case EditSubkeyDialogFragment.MESSAGE_REVOKE:
Log.e("PHILIP", "rev subkey " + keyId + " from " + mSaveKeyringParcel.mMasterKeyId);
// toggle
if (mSaveKeyringParcel.mRevokeSubKeys.contains(keyId)) {
mSaveKeyringParcel.mRevokeSubKeys.remove(keyId);

View File

@@ -69,9 +69,9 @@ import org.sufficientlysecure.keychain.provider.KeychainDatabase;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.service.ConsolidateInputParcel;
import org.sufficientlysecure.keychain.service.ImportKeyringParcel;
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter;
import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper;
import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
@@ -91,6 +91,8 @@ public class KeyListFragment extends LoaderFragment
CryptoOperationHelper.Callback<ImportKeyringParcel, ImportKeyResult> {
static final int REQUEST_ACTION = 1;
private static final int REQUEST_DELETE = 2;
private static final int REQUEST_VIEW_KEY = 3;
private KeyListAdapter mAdapter;
private StickyListHeadersListView mStickyList;
@@ -336,7 +338,7 @@ public class KeyListFragment extends LoaderFragment
Intent viewIntent = new Intent(getActivity(), ViewKeyActivity.class);
viewIntent.setData(
KeyRings.buildGenericKeyRingUri(mAdapter.getMasterKeyId(position)));
startActivity(viewIntent);
startActivityForResult(viewIntent, REQUEST_VIEW_KEY);
}
protected void encrypt(ActionMode mode, long[] masterKeyIds) {
@@ -362,30 +364,9 @@ public class KeyListFragment extends LoaderFragment
return;
}
// Message is received after key is deleted
Handler returnHandler = new Handler() {
@Override
public void handleMessage(Message message) {
if (message.arg1 == DeleteKeyDialogFragment.MESSAGE_OKAY) {
Bundle data = message.getData();
if (data != null) {
DeleteResult result = data.getParcelable(DeleteResult.EXTRA_RESULT);
if (result != null) {
result.createNotify(getActivity()).show();
}
}
mode.finish();
}
}
};
// Create a new Messenger for the communication back
Messenger messenger = new Messenger(returnHandler);
DeleteKeyDialogFragment deleteKeyDialog = DeleteKeyDialogFragment.newInstance(messenger,
masterKeyIds);
deleteKeyDialog.show(getActivity().getSupportFragmentManager(), "deleteKeyDialog");
Intent intent = new Intent(getActivity(), DeleteKeyDialogActivity.class);
intent.putExtra(DeleteKeyDialogActivity.EXTRA_DELETE_MASTER_KEY_IDS, masterKeyIds);
startActivityForResult(intent, REQUEST_DELETE);
}
@@ -620,14 +601,35 @@ public class KeyListFragment extends LoaderFragment
mConsolidateOpHelper.handleActivityResult(requestCode, resultCode, data);
}
if (requestCode == REQUEST_ACTION) {
// if a result has been returned, display a notify
if (data != null && data.hasExtra(OperationResult.EXTRA_RESULT)) {
OperationResult result = data.getParcelableExtra(OperationResult.EXTRA_RESULT);
result.createNotify(getActivity()).show();
} else {
super.onActivityResult(requestCode, resultCode, data);
}
switch (requestCode) {
case REQUEST_DELETE:
mActionMode.finish();
if (data != null && data.hasExtra(OperationResult.EXTRA_RESULT)) {
OperationResult result = data.getParcelableExtra(OperationResult.EXTRA_RESULT);
result.createNotify(getActivity()).show();
} else {
super.onActivityResult(requestCode, resultCode, data);
}
break;
case REQUEST_ACTION:
// if a result has been returned, display a notify
if (data != null && data.hasExtra(OperationResult.EXTRA_RESULT)) {
OperationResult result = data.getParcelableExtra(OperationResult.EXTRA_RESULT);
result.createNotify(getActivity()).show();
} else {
super.onActivityResult(requestCode, resultCode, data);
}
break;
case REQUEST_VIEW_KEY:
if (data != null && data.hasExtra(OperationResult.EXTRA_RESULT)) {
OperationResult result = data.getParcelableExtra(OperationResult.EXTRA_RESULT);
result.createNotify(getActivity()).show();
} else {
super.onActivityResult(requestCode, resultCode, data);
}
break;
}
}

View File

@@ -0,0 +1,258 @@
/*
* Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.de>
* Copyright (C) 2015 Adithya Abraham Philip <adithyaphilip@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.ui;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.FragmentActivity;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.AdapterView;
import android.widget.Spinner;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.operations.results.DeleteResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.operations.results.RevokeResult;
import org.sufficientlysecure.keychain.service.DeleteKeyringParcel;
import org.sufficientlysecure.keychain.service.RevokeKeyringParcel;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper;
import org.sufficientlysecure.keychain.ui.dialog.CustomAlertDialogBuilder;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.Preferences;
public class RevokeDeleteDialogActivity extends FragmentActivity {
public static final String EXTRA_MASTER_KEY_ID = "extra_master_key_id";
public static final String EXTRA_KEYSERVER = "extra_keyserver";
private final int REVOKE_OP_ID = 1;
private final int DELETE_OP_ID = 2;
private CryptoOperationHelper<RevokeKeyringParcel, RevokeResult> mRevokeOpHelper;
private CryptoOperationHelper<DeleteKeyringParcel, DeleteResult> mDeleteOpHelper;
private long mMasterKeyId;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mRevokeOpHelper = new CryptoOperationHelper<>(this,
getRevocationCallback(), R.string.progress_revoking_uploading, REVOKE_OP_ID);
mRevokeOpHelper.onRestoreInstanceState(savedInstanceState);
mDeleteOpHelper = new CryptoOperationHelper<>(this,
getDeletionCallback(), R.string.progress_deleting, DELETE_OP_ID);
mDeleteOpHelper.onRestoreInstanceState(savedInstanceState);
mMasterKeyId = getIntent().getLongExtra(EXTRA_MASTER_KEY_ID, -1);
RevokeDeleteDialogFragment fragment = RevokeDeleteDialogFragment.newInstance();
fragment.show(getSupportFragmentManager(), "deleteRevokeDialog");
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
mDeleteOpHelper.handleActivityResult(requestCode, resultCode, data);
mRevokeOpHelper.handleActivityResult(requestCode, resultCode, data);
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
mRevokeOpHelper.onSaveInstanceState(outState);
mDeleteOpHelper.onSaveInstanceState(outState);
}
private void returnResult(OperationResult result) {
Intent intent = new Intent();
intent.putExtra(OperationResult.EXTRA_RESULT, result);
setResult(RESULT_OK, intent);
finish();
}
private CryptoOperationHelper.Callback<RevokeKeyringParcel, RevokeResult> getRevocationCallback() {
CryptoOperationHelper.Callback<RevokeKeyringParcel, RevokeResult> callback
= new CryptoOperationHelper.Callback<RevokeKeyringParcel, RevokeResult>() {
@Override
public RevokeKeyringParcel createOperationInput() {
return new RevokeKeyringParcel(mMasterKeyId, true,
getIntent().getStringExtra(EXTRA_KEYSERVER));
}
@Override
public void onCryptoOperationSuccess(RevokeResult result) {
returnResult(result);
}
@Override
public void onCryptoOperationCancelled() {
setResult(RESULT_CANCELED);
finish();
}
@Override
public void onCryptoOperationError(RevokeResult result) {
returnResult(result);
}
@Override
public boolean onCryptoSetProgress(String msg, int progress, int max) {
return false;
}
};
return callback;
}
private CryptoOperationHelper.Callback<DeleteKeyringParcel, DeleteResult> getDeletionCallback() {
CryptoOperationHelper.Callback<DeleteKeyringParcel, DeleteResult> callback
= new CryptoOperationHelper.Callback<DeleteKeyringParcel, DeleteResult>() {
@Override
public DeleteKeyringParcel createOperationInput() {
return new DeleteKeyringParcel(new long[]{mMasterKeyId}, true);
}
@Override
public void onCryptoOperationSuccess(DeleteResult result) {
returnResult(result);
}
@Override
public void onCryptoOperationCancelled() {
setResult(RESULT_CANCELED);
finish();
}
@Override
public void onCryptoOperationError(DeleteResult result) {
returnResult(result);
}
@Override
public boolean onCryptoSetProgress(String msg, int progress, int max) {
return false;
}
};
return callback;
}
private void startRevocationOperation() {
mRevokeOpHelper.cryptoOperation();
}
private void startDeletionOperation() {
mDeleteOpHelper.cryptoOperation();
}
public static class RevokeDeleteDialogFragment extends DialogFragment {
public static RevokeDeleteDialogFragment newInstance() {
RevokeDeleteDialogFragment frag = new RevokeDeleteDialogFragment();
return frag;
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final Activity activity = getActivity();
final String CHOICE_REVOKE = getString(R.string.del_rev_dialog_choice_rev_upload);
final String CHOICE_DELETE = getString(R.string.del_rev_dialog_choice_delete);
// if the dialog is displayed from the application class, design is missing
// hack to get holo design (which is not automatically applied due to activity's Theme.NoDisplay
ContextThemeWrapper theme = new ContextThemeWrapper(activity,
R.style.Theme_AppCompat_Light_Dialog);
CustomAlertDialogBuilder alert = new CustomAlertDialogBuilder(theme);
alert.setTitle(R.string.del_rev_dialog_title);
LayoutInflater inflater = LayoutInflater.from(theme);
View view = inflater.inflate(R.layout.del_rev_dialog, null);
alert.setView(view);
final Spinner spinner = (Spinner) view.findViewById(R.id.spinner);
alert.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {
dialog.cancel();
activity.setResult(RESULT_CANCELED);
activity.finish();
}
});
alert.setPositiveButton(R.string.del_rev_dialog_btn_revoke,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
String choice = spinner.getSelectedItem().toString();
if (choice.equals(CHOICE_REVOKE)) {
((RevokeDeleteDialogActivity) activity)
.startRevocationOperation();
} else if (choice.equals(CHOICE_DELETE)) {
((RevokeDeleteDialogActivity) activity)
.startDeletionOperation();
} else {
throw new AssertionError(
"Unsupported delete type in RevokeDeleteDialogFragment");
}
}
});
final AlertDialog alertDialog = alert.show();
spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
String choice = parent.getItemAtPosition(pos).toString();
if (choice.equals(CHOICE_REVOKE)) {
alertDialog.getButton(AlertDialog.BUTTON_POSITIVE)
.setText(R.string.del_rev_dialog_btn_revoke);
} else if (choice.equals(CHOICE_DELETE)) {
alertDialog.getButton(AlertDialog.BUTTON_POSITIVE)
.setText(R.string.del_rev_dialog_btn_delete);
} else {
throw new AssertionError(
"Unsupported delete type in RevokeDeleteDialogFragment");
}
}
public void onNothingSelected(AdapterView<?> parent) {
}
});
return alertDialog;
}
}
}

View File

@@ -68,7 +68,6 @@ import org.sufficientlysecure.keychain.service.ImportKeyringParcel;
import org.sufficientlysecure.keychain.service.ServiceProgressHandler.MessageStatus;
import org.sufficientlysecure.keychain.ui.base.BaseNfcActivity;
import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper;
import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment;
import org.sufficientlysecure.keychain.ui.util.FormattingUtils;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State;
@@ -85,7 +84,6 @@ import org.sufficientlysecure.keychain.util.Preferences;
import java.io.IOException;
import java.util.ArrayList;
public class ViewKeyActivity extends BaseNfcActivity implements
LoaderManager.LoaderCallbacks<Cursor>,
CryptoOperationHelper.Callback<ImportKeyringParcel, ImportKeyResult> {
@@ -97,6 +95,8 @@ public class ViewKeyActivity extends BaseNfcActivity implements
static final int REQUEST_QR_FINGERPRINT = 1;
static final int REQUEST_BACKUP = 2;
static final int REQUEST_CERTIFY = 3;
static final int REQUEST_DELETE = 4;
static final int REQUEST_REVOKE_DELETE = 5;
public static final String EXTRA_DISPLAY_RESULT = "display_result";
@@ -419,27 +419,24 @@ public class ViewKeyActivity extends BaseNfcActivity implements
}
private void deleteKey() {
new Handler().post(new Runnable() {
@Override
public void run() {
// Message is received after key is deleted
Handler returnHandler = new Handler() {
@Override
public void handleMessage(Message message) {
if (message.arg1 == MessageStatus.OKAY.ordinal()) {
setResult(RESULT_CANCELED);
finish();
}
}
};
if (mIsSecret && !mIsRevoked) {
Intent revokeDeleteIntent = new Intent(this, RevokeDeleteDialogActivity.class);
// Create a new Messenger for the communication back
Messenger messenger = new Messenger(returnHandler);
DeleteKeyDialogFragment deleteKeyDialog = DeleteKeyDialogFragment.newInstance(messenger,
new long[]{ mMasterKeyId });
deleteKeyDialog.show(getSupportFragmentManager(), "deleteKeyDialog");
}
});
revokeDeleteIntent.putExtra(RevokeDeleteDialogActivity.EXTRA_MASTER_KEY_ID,
mMasterKeyId);
revokeDeleteIntent.putExtra(RevokeDeleteDialogActivity.EXTRA_KEYSERVER,
Preferences.getPreferences(this).getPreferredKeyserver());
startActivityForResult(revokeDeleteIntent, REQUEST_REVOKE_DELETE);
} else {
Intent deleteIntent = new Intent(this, DeleteKeyDialogActivity.class);
deleteIntent.putExtra(DeleteKeyDialogActivity.EXTRA_DELETE_MASTER_KEY_IDS,
new long[]{mMasterKeyId});
startActivityForResult(deleteIntent, REQUEST_DELETE);
}
}
@Override
@@ -487,6 +484,13 @@ public class ViewKeyActivity extends BaseNfcActivity implements
}
return;
}
case REQUEST_REVOKE_DELETE:
case REQUEST_DELETE: {
setResult(RESULT_OK, data);
finish();
return;
}
}
super.onActivityResult(requestCode, resultCode, data);

View File

@@ -40,6 +40,7 @@ import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
import org.sufficientlysecure.keychain.ui.NfcOperationActivity;
import org.sufficientlysecure.keychain.ui.OrbotRequiredDialogActivity;
import org.sufficientlysecure.keychain.ui.PassphraseDialogActivity;
import org.sufficientlysecure.keychain.ui.RevokeDeleteDialogActivity;
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
import org.sufficientlysecure.keychain.util.Log;
@@ -119,17 +120,13 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu
switch (requiredInput.mType) {
// TODO: Verify that all started activities add to cryptoInputParcel if necessary (like OrbotRequiredDialogActivity)
// don't forget to set mRequestedCode!
// always use CryptoOperationHelper.startActivityForResult!
case NFC_MOVE_KEY_TO_CARD:
case NFC_DECRYPT:
case NFC_SIGN: {
Intent intent = new Intent(activity, NfcOperationActivity.class);
intent.putExtra(NfcOperationActivity.EXTRA_REQUIRED_INPUT, requiredInput);
if (mUseFragment) {
mFragment.startActivityForResult(intent, mId + REQUEST_CODE_NFC);
} else {
activity.startActivityForResult(intent, mId + REQUEST_CODE_NFC);
}
startActivityForResult(intent, REQUEST_CODE_NFC);
return;
}
@@ -137,22 +134,14 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu
case PASSPHRASE_SYMMETRIC: {
Intent intent = new Intent(activity, PassphraseDialogActivity.class);
intent.putExtra(PassphraseDialogActivity.EXTRA_REQUIRED_INPUT, requiredInput);
if (mUseFragment) {
mFragment.startActivityForResult(intent, mId + REQUEST_CODE_PASSPHRASE);
} else {
activity.startActivityForResult(intent, mId + REQUEST_CODE_PASSPHRASE);
}
startActivityForResult(intent, REQUEST_CODE_PASSPHRASE);
return;
}
case ENABLE_ORBOT: {
Intent intent = new Intent(activity, OrbotRequiredDialogActivity.class);
intent.putExtra(OrbotRequiredDialogActivity.EXTRA_CRYPTO_INPUT, cryptoInputParcel);
if (mUseFragment) {
mFragment.startActivityForResult(intent, mId + REQUEST_CODE_ENABLE_ORBOT);
} else {
activity.startActivityForResult(intent, mId + REQUEST_CODE_ENABLE_ORBOT);
}
startActivityForResult(intent, REQUEST_CODE_ENABLE_ORBOT);
return;
}
@@ -162,13 +151,24 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu
}
}
protected void startActivityForResult(Intent intent, int requestCode) {
mRequestedCode = requestCode;
if (mUseFragment) {
mFragment.startActivityForResult(intent, mRequestedCode);
} else {
mActivity.startActivityForResult(intent, mRequestedCode);
}
Log.e("PHILIP", "mRequestedCode: " + mRequestedCode);
}
/**
* Attempts the result of an activity started by this helper. Returns true if requestCode is
* recognized, false otherwise.
* @return true if requestCode was recognized, false otherwise
*/
public boolean handleActivityResult(int requestCode, int resultCode, Intent data) {
Log.d(Constants.TAG, "received activity result in OperationHelper");
Log.d(Constants.TAG, "received activity result in OperationHelper with code: "
+ requestCode + " while waiting for: " + mRequestedCode);
if ((requestCode & mId) != mId) {
// this wasn't meant for us to handle

View File

@@ -1,211 +0,0 @@
/*
* Copyright (C) 2013-2014 Dominik Schürmann <dominik@dominikschuermann.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.ui.dialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.FragmentActivity;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.operations.results.DeleteResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.pgp.KeyRing;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.service.DeleteKeyringParcel;
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper;
import org.sufficientlysecure.keychain.util.Log;
import java.util.HashMap;
public class DeleteKeyDialogFragment extends DialogFragment
implements CryptoOperationHelper.Callback<DeleteKeyringParcel, DeleteResult> {
private static final String ARG_MESSENGER = "messenger";
private static final String ARG_DELETE_MASTER_KEY_IDS = "delete_master_key_ids";
public static final int MESSAGE_OKAY = 1;
public static final int MESSAGE_ERROR = 0;
private TextView mMainMessage;
private View mInflateView;
private Messenger mMessenger;
// for CryptoOperationHelper.Callback
private long[] mMasterKeyIds;
private boolean mHasSecret;
private CryptoOperationHelper<DeleteKeyringParcel, DeleteResult> mDeleteOpHelper;
/**
* Creates new instance of this delete file dialog fragment
*/
public static DeleteKeyDialogFragment newInstance(Messenger messenger, long[] masterKeyIds) {
DeleteKeyDialogFragment frag = new DeleteKeyDialogFragment();
Bundle args = new Bundle();
args.putParcelable(ARG_MESSENGER, messenger);
args.putLongArray(ARG_DELETE_MASTER_KEY_IDS, masterKeyIds);
frag.setArguments(args);
return frag;
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (mDeleteOpHelper != null) {
mDeleteOpHelper.handleActivityResult(requestCode, resultCode, data);
}
super.onActivityResult(requestCode, resultCode, data);
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final FragmentActivity activity = getActivity();
mMessenger = getArguments().getParcelable(ARG_MESSENGER);
final long[] masterKeyIds = getArguments().getLongArray(ARG_DELETE_MASTER_KEY_IDS);
CustomAlertDialogBuilder builder = new CustomAlertDialogBuilder(activity);
// Setup custom View to display in AlertDialog
LayoutInflater inflater = activity.getLayoutInflater();
mInflateView = inflater.inflate(R.layout.view_key_delete_fragment, null);
builder.setView(mInflateView);
mMainMessage = (TextView) mInflateView.findViewById(R.id.mainMessage);
final boolean hasSecret;
// If only a single key has been selected
if (masterKeyIds.length == 1) {
long masterKeyId = masterKeyIds[0];
try {
HashMap<String, Object> data = new ProviderHelper(activity).getUnifiedData(
masterKeyId, new String[]{
KeyRings.USER_ID,
KeyRings.HAS_ANY_SECRET
}, new int[]{
ProviderHelper.FIELD_TYPE_STRING,
ProviderHelper.FIELD_TYPE_INTEGER
}
);
String name;
KeyRing.UserId mainUserId = KeyRing.splitUserId((String) data.get(KeyRings.USER_ID));
if (mainUserId.name != null) {
name = mainUserId.name;
} else {
name = getString(R.string.user_id_no_name);
}
hasSecret = ((Long) data.get(KeyRings.HAS_ANY_SECRET)) == 1;
if (hasSecret) {
// show title only for secret key deletions,
// see http://www.google.com/design/spec/components/dialogs.html#dialogs-behavior
builder.setTitle(getString(R.string.title_delete_secret_key, name));
mMainMessage.setText(getString(R.string.secret_key_deletion_confirmation, name));
} else {
mMainMessage.setText(getString(R.string.public_key_deletetion_confirmation, name));
}
} catch (ProviderHelper.NotFoundException e) {
dismiss();
return null;
}
} else {
mMainMessage.setText(R.string.key_deletion_confirmation_multi);
hasSecret = false;
}
builder.setPositiveButton(R.string.btn_delete, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
mMasterKeyIds = masterKeyIds;
mHasSecret = hasSecret;
mDeleteOpHelper = new CryptoOperationHelper<>
(1, DeleteKeyDialogFragment.this, DeleteKeyDialogFragment.this,
R.string.progress_deleting);
mDeleteOpHelper.cryptoOperation();
// do NOT dismiss here, it'll give
// OperationHelper a null fragmentManager
// dismiss();
}
});
builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {
dismiss();
}
});
return builder.show();
}
@Override
public DeleteKeyringParcel createOperationInput() {
return new DeleteKeyringParcel(mMasterKeyIds, mHasSecret);
}
@Override
public void onCryptoOperationSuccess(DeleteResult result) {
handleResult(result);
}
@Override
public void onCryptoOperationCancelled() {
}
@Override
public void onCryptoOperationError(DeleteResult result) {
handleResult(result);
}
@Override
public boolean onCryptoSetProgress(String msg, int progress, int max) {
return false;
}
public void handleResult(DeleteResult result) {
try {
Bundle data = new Bundle();
data.putParcelable(OperationResult.EXTRA_RESULT, result);
Message msg = Message.obtain();
msg.arg1 = ServiceProgressHandler.MessageStatus.OKAY.ordinal();
msg.setData(data);
mMessenger.send(msg);
} catch (RemoteException e) {
Log.e(Constants.TAG, "messenger error", e);
}
dismiss();
}
}