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