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

@@ -682,6 +682,12 @@
<activity
android:name=".ui.PassphraseDialogActivity"
android:theme="@android:style/Theme.NoDisplay" />
<activity
android:name=".ui.DeleteKeyDialogActivity"
android:theme="@android:style/Theme.NoDisplay" />
<activity
android:name=".ui.RevokeDeleteDialogActivity"
android:theme="@android:style/Theme.NoDisplay" />
<activity
android:name=".ui.OrbotRequiredDialogActivity"
android:theme="@android:style/Theme.NoDisplay" />

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();
}
}

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="16dp"
android:paddingLeft="24dp"
android:paddingRight="24dp"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/del_rev_dialog_message"
android:textAppearance="@style/TextAppearance.AppCompat.Medium"/>
<Spinner
android:id="@+id/spinner"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:drawSelectorOnTop="true"
android:entries="@array/rev_del_dialog_entries"
android:textAppearance="@style/TextAppearance.AppCompat.Medium"/>
</LinearLayout>

View File

@@ -37,6 +37,10 @@
<item>@string/pref_proxy_type_value_http</item>
<item>@string/pref_proxy_type_value_socks</item>
</string-array>
<string-array name="rev_del_dialog_entries" translatable="true">
<item>@string/del_rev_dialog_choice_rev_upload</item>
<item>@string/del_rev_dialog_choice_delete</item>
</string-array>
<string-array name="rsa_key_size_spinner_values" translatable="false">
<item>@string/key_size_2048</item>
<item>@string/key_size_4096</item>

View File

@@ -361,6 +361,7 @@
<string name="progress_cancelling">"cancelling…"</string>
<string name="progress_saving">"saving…"</string>
<string name="progress_importing">"importing…"</string>
<string name="progress_revoking_uploading">"Revoking and uploading key…"</string>
<string name="progress_updating">"Updating keys…"</string>
<string name="progress_exporting">"exporting…"</string>
<string name="progress_uploading">"uploading…"</string>
@@ -524,6 +525,12 @@
<string name="delete_nothing">"Nothing to delete."</string>
<string name="delete_cancelled">"Delete operation cancelled."</string>
<!-- Revoke result toast (snackbar) -->
<string name="revoke_ok">"Successfully revoked key."</string>
<string name="revoke_fail">"Error revoking key!"</string>
<string name="revoke_nothing">"Nothing to revoke."</string>
<string name="revoke_cancelled">"Revoke operation cancelled."</string>
<!-- Certify result toast -->
<plurals name="certify_keys_ok">
<item quantity="one">"Successfully certified key%2$s."</item>
@@ -584,6 +591,17 @@
<string name="share_qr_code_dialog_title">"Share with QR Code"</string>
<string name="share_nfc_dialog">"Share with NFC"</string>
<!-- Delete or revoke private key dialog -->
<string name="del_rev_dialog_message">"If you would no longer like to use this key, it should be revoked and uploaded. Select delete only if you wish to remove the key from OpenKeychain but continue to use it from somewhere else."</string>
<string name="del_rev_dialog_title">"Revoke/Delete key"</string>
<string name="del_rev_dialog_btn_revoke">"Revoke and upload"</string>
<string name="del_rev_dialog_btn_delete">"Delete only"</string>
<!-- Delete Or Revoke Dialog spinner -->
<string name="del_rev_dialog_choice_delete">"Delete only"</string>
<string name="del_rev_dialog_choice_rev_upload">"Revoke and Upload"</string>
<!-- Key list -->
<plurals name="key_list_selected_keys">
<item quantity="one">"1 key selected."</item>
@@ -1270,6 +1288,13 @@
<item quantity="other">"Failed to delete %d keys"</item>
</plurals>
<string name="msg_revoke_error_empty">"Nothing to revoke!"</string>
<string name="msg_revoke_error_multi_secret">"Secret keys can only be revoked individually!"</string>
<string name="msg_revoke_error_not_found">"Cannot find key to revoke!"</string>
<string name="msg_revoke_key">"Revoking key %s"</string>
<string name="msg_revoke_key_fail">"Failed revoking key"</string>
<string name="msg_revoke_ok">"Successfully revoked key"</string>
<string name="msg_acc_saved">"Account saved"</string>
<string name="msg_download_success">"Downloaded successfully!"</string>