Merge pull request #2088 from open-keychain/dry-run
Use KeychainExternalProvider for resolving encryption keys in api
This commit is contained in:
commit
b0f343d082
|
@ -25,6 +25,8 @@ import org.sufficientlysecure.keychain.Constants;
|
||||||
|
|
||||||
|
|
||||||
public class KeychainExternalContract {
|
public class KeychainExternalContract {
|
||||||
|
public static final int KEY_STATUS_UNVERIFIED = 1;
|
||||||
|
public static final int KEY_STATUS_VERIFIED = 2;
|
||||||
|
|
||||||
// this is in KeychainExternalContract already, but we want to be double
|
// this is in KeychainExternalContract already, but we want to be double
|
||||||
// sure this isn't mixed up with the internal one!
|
// sure this isn't mixed up with the internal one!
|
||||||
|
@ -37,8 +39,9 @@ public class KeychainExternalContract {
|
||||||
|
|
||||||
public static class EmailStatus implements BaseColumns {
|
public static class EmailStatus implements BaseColumns {
|
||||||
public static final String EMAIL_ADDRESS = "email_address";
|
public static final String EMAIL_ADDRESS = "email_address";
|
||||||
public static final String EMAIL_STATUS = "email_status";
|
|
||||||
public static final String USER_ID = "user_id";
|
public static final String USER_ID = "user_id";
|
||||||
|
public static final String USER_ID_STATUS = "email_status";
|
||||||
|
public static final String MASTER_KEY_ID = "master_key_id";
|
||||||
|
|
||||||
public static final Uri CONTENT_URI = BASE_CONTENT_URI_EXTERNAL.buildUpon()
|
public static final Uri CONTENT_URI = BASE_CONTENT_URI_EXTERNAL.buildUpon()
|
||||||
.appendPath(BASE_EMAIL_STATUS).build();
|
.appendPath(BASE_EMAIL_STATUS).build();
|
||||||
|
|
|
@ -34,6 +34,7 @@ import android.os.Binder;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
import org.sufficientlysecure.keychain.BuildConfig;
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
import org.sufficientlysecure.keychain.provider.ApiDataAccessObject;
|
import org.sufficientlysecure.keychain.provider.ApiDataAccessObject;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||||
|
@ -50,6 +51,7 @@ import org.sufficientlysecure.keychain.util.Log;
|
||||||
|
|
||||||
public class KeychainExternalProvider extends ContentProvider implements SimpleContentResolverInterface {
|
public class KeychainExternalProvider extends ContentProvider implements SimpleContentResolverInterface {
|
||||||
private static final int EMAIL_STATUS = 101;
|
private static final int EMAIL_STATUS = 101;
|
||||||
|
private static final int EMAIL_STATUS_INTERNAL = 102;
|
||||||
private static final int API_APPS = 301;
|
private static final int API_APPS = 301;
|
||||||
private static final int API_APPS_BY_PACKAGE_NAME = 302;
|
private static final int API_APPS_BY_PACKAGE_NAME = 302;
|
||||||
|
|
||||||
|
@ -78,8 +80,9 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC
|
||||||
* </pre>
|
* </pre>
|
||||||
*/
|
*/
|
||||||
matcher.addURI(authority, KeychainExternalContract.BASE_EMAIL_STATUS, EMAIL_STATUS);
|
matcher.addURI(authority, KeychainExternalContract.BASE_EMAIL_STATUS, EMAIL_STATUS);
|
||||||
|
matcher.addURI(authority, KeychainExternalContract.BASE_EMAIL_STATUS + "/*", EMAIL_STATUS_INTERNAL);
|
||||||
|
|
||||||
matcher.addURI(KeychainContract.CONTENT_AUTHORITY, KeychainContract.BASE_API_APPS, API_APPS);
|
// can only query status of calling app - for internal use only!
|
||||||
matcher.addURI(KeychainContract.CONTENT_AUTHORITY, KeychainContract.BASE_API_APPS + "/*", API_APPS_BY_PACKAGE_NAME);
|
matcher.addURI(KeychainContract.CONTENT_AUTHORITY, KeychainContract.BASE_API_APPS + "/*", API_APPS_BY_PACKAGE_NAME);
|
||||||
|
|
||||||
return matcher;
|
return matcher;
|
||||||
|
@ -134,9 +137,19 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC
|
||||||
|
|
||||||
SQLiteDatabase db = getDb().getReadableDatabase();
|
SQLiteDatabase db = getDb().getReadableDatabase();
|
||||||
|
|
||||||
|
String callingPackageName = mApiPermissionHelper.getCurrentCallingPackage();
|
||||||
|
|
||||||
switch (match) {
|
switch (match) {
|
||||||
|
case EMAIL_STATUS_INTERNAL:
|
||||||
|
if (!BuildConfig.APPLICATION_ID.equals(callingPackageName)) {
|
||||||
|
throw new AccessControlException("This URI can only be called internally!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// override package name to use any external
|
||||||
|
// callingPackageName = uri.getLastPathSegment();
|
||||||
|
|
||||||
case EMAIL_STATUS: {
|
case EMAIL_STATUS: {
|
||||||
boolean callerIsAllowed = mApiPermissionHelper.isAllowedIgnoreErrors();
|
boolean callerIsAllowed = (match == EMAIL_STATUS_INTERNAL) || mApiPermissionHelper.isAllowedIgnoreErrors();
|
||||||
if (!callerIsAllowed) {
|
if (!callerIsAllowed) {
|
||||||
throw new AccessControlException("An application must register before use of KeychainExternalProvider!");
|
throw new AccessControlException("An application must register before use of KeychainExternalProvider!");
|
||||||
}
|
}
|
||||||
|
@ -152,14 +165,18 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC
|
||||||
projectionMap.put(EmailStatus._ID, "email AS _id");
|
projectionMap.put(EmailStatus._ID, "email AS _id");
|
||||||
projectionMap.put(EmailStatus.EMAIL_ADDRESS, // this is actually the queried address
|
projectionMap.put(EmailStatus.EMAIL_ADDRESS, // this is actually the queried address
|
||||||
TEMP_TABLE_QUERIED_ADDRESSES + "." + TEMP_TABLE_COLUMN_ADDRES + " AS " + EmailStatus.EMAIL_ADDRESS);
|
TEMP_TABLE_QUERIED_ADDRESSES + "." + TEMP_TABLE_COLUMN_ADDRES + " AS " + EmailStatus.EMAIL_ADDRESS);
|
||||||
|
projectionMap.put(EmailStatus.USER_ID,
|
||||||
|
Tables.USER_PACKETS + "." + UserPackets.USER_ID + " AS " + EmailStatus.USER_ID);
|
||||||
// we take the minimum (>0) here, where "1" is "verified by known secret key", "2" is "self-certified"
|
// we take the minimum (>0) here, where "1" is "verified by known secret key", "2" is "self-certified"
|
||||||
projectionMap.put(EmailStatus.EMAIL_STATUS, "CASE ( MIN (" + Certs.VERIFIED + " ) ) "
|
projectionMap.put(EmailStatus.USER_ID_STATUS, "CASE ( MIN (" + Certs.VERIFIED + " ) ) "
|
||||||
// remap to keep this provider contract independent from our internal representation
|
// remap to keep this provider contract independent from our internal representation
|
||||||
+ " WHEN NULL THEN 1"
|
+ " WHEN " + Certs.VERIFIED_SELF + " THEN " + KeychainExternalContract.KEY_STATUS_UNVERIFIED
|
||||||
+ " WHEN " + Certs.VERIFIED_SELF + " THEN 1"
|
+ " WHEN " + Certs.VERIFIED_SECRET + " THEN " + KeychainExternalContract.KEY_STATUS_VERIFIED
|
||||||
+ " WHEN " + Certs.VERIFIED_SECRET + " THEN 2"
|
+ " WHEN NULL THEN " + KeychainExternalContract.KEY_STATUS_UNVERIFIED
|
||||||
+ " END AS " + EmailStatus.EMAIL_STATUS);
|
+ " END AS " + EmailStatus.USER_ID_STATUS);
|
||||||
projectionMap.put(EmailStatus.USER_ID, Tables.USER_PACKETS + "." + UserPackets.USER_ID + " AS " + EmailStatus.USER_ID);
|
projectionMap.put(EmailStatus.USER_ID, Tables.USER_PACKETS + "." + UserPackets.USER_ID + " AS " + EmailStatus.USER_ID);
|
||||||
|
projectionMap.put(EmailStatus.MASTER_KEY_ID,
|
||||||
|
Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID + " AS " + EmailStatus.MASTER_KEY_ID);
|
||||||
qb.setProjectionMap(projectionMap);
|
qb.setProjectionMap(projectionMap);
|
||||||
|
|
||||||
if (projection == null) {
|
if (projection == null) {
|
||||||
|
|
|
@ -66,6 +66,7 @@ import org.sufficientlysecure.keychain.provider.KeyRepository;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||||
import org.sufficientlysecure.keychain.remote.OpenPgpServiceKeyIdExtractor.KeyIdResult;
|
import org.sufficientlysecure.keychain.remote.OpenPgpServiceKeyIdExtractor.KeyIdResult;
|
||||||
|
import org.sufficientlysecure.keychain.remote.OpenPgpServiceKeyIdExtractor.KeyIdResultStatus;
|
||||||
import org.sufficientlysecure.keychain.service.BackupKeyringParcel;
|
import org.sufficientlysecure.keychain.service.BackupKeyringParcel;
|
||||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||||
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
|
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
|
||||||
|
@ -115,7 +116,7 @@ public class OpenPgpService extends Service {
|
||||||
|
|
||||||
Intent signKeyIdIntent = getSignKeyMasterId(data);
|
Intent signKeyIdIntent = getSignKeyMasterId(data);
|
||||||
// NOTE: Fallback to return account settings (Old API)
|
// NOTE: Fallback to return account settings (Old API)
|
||||||
if (signKeyIdIntent.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)
|
if (signKeyIdIntent.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS)
|
||||||
== OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED) {
|
== OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED) {
|
||||||
return signKeyIdIntent;
|
return signKeyIdIntent;
|
||||||
}
|
}
|
||||||
|
@ -188,11 +189,7 @@ public class OpenPgpService extends Service {
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.d(Constants.TAG, "signImpl", e);
|
Log.d(Constants.TAG, "signImpl", e);
|
||||||
Intent result = new Intent();
|
return createErrorResultIntent(OpenPgpError.GENERIC_ERROR, e.getMessage());
|
||||||
result.putExtra(OpenPgpApi.RESULT_ERROR,
|
|
||||||
new OpenPgpError(OpenPgpError.GENERIC_ERROR, e.getMessage()));
|
|
||||||
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR);
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -213,51 +210,53 @@ public class OpenPgpService extends Service {
|
||||||
compressionId = PgpSecurityConstants.OpenKeychainCompressionAlgorithmTags.UNCOMPRESSED;
|
compressionId = PgpSecurityConstants.OpenKeychainCompressionAlgorithmTags.UNCOMPRESSED;
|
||||||
}
|
}
|
||||||
|
|
||||||
KeyIdResult keyIdResult = mKeyIdExtractor.returnKeyIdsFromIntent(data, false);
|
|
||||||
if (keyIdResult.hasResultIntent()) {
|
|
||||||
return keyIdResult.getResultIntent();
|
|
||||||
}
|
|
||||||
long[] keyIds = keyIdResult.getKeyIds();
|
|
||||||
|
|
||||||
// TODO this is not correct!
|
|
||||||
long inputLength = inputStream.available();
|
|
||||||
InputData inputData = new InputData(inputStream, inputLength, originalFilename);
|
|
||||||
|
|
||||||
PgpSignEncryptData pgpData = new PgpSignEncryptData();
|
PgpSignEncryptData pgpData = new PgpSignEncryptData();
|
||||||
pgpData.setEnableAsciiArmorOutput(asciiArmor)
|
pgpData.setEnableAsciiArmorOutput(asciiArmor)
|
||||||
.setVersionHeader(null)
|
.setVersionHeader(null)
|
||||||
.setCompressionAlgorithm(compressionId)
|
.setCompressionAlgorithm(compressionId);
|
||||||
.setSymmetricEncryptionAlgorithm(PgpSecurityConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_DEFAULT)
|
|
||||||
.setEncryptionMasterKeyIds(keyIds);
|
|
||||||
|
|
||||||
if (sign) {
|
if (sign) {
|
||||||
|
|
||||||
Intent signKeyIdIntent = getSignKeyMasterId(data);
|
Intent signKeyIdIntent = getSignKeyMasterId(data);
|
||||||
// NOTE: Fallback to return account settings (Old API)
|
// NOTE: Fallback to return account settings (Old API)
|
||||||
if (signKeyIdIntent.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)
|
if (signKeyIdIntent.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)
|
||||||
== OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED) {
|
!= OpenPgpApi.RESULT_CODE_SUCCESS) {
|
||||||
return signKeyIdIntent;
|
return signKeyIdIntent;
|
||||||
}
|
}
|
||||||
|
|
||||||
long signKeyId = signKeyIdIntent.getLongExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, Constants.key.none);
|
long signKeyId = signKeyIdIntent.getLongExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, Constants.key.none);
|
||||||
if (signKeyId == Constants.key.none) {
|
if (signKeyId == Constants.key.none) {
|
||||||
throw new Exception("No signing key given");
|
throw new Exception("No signing key given");
|
||||||
} else {
|
}
|
||||||
pgpData.setSignatureMasterKeyId(signKeyId);
|
long signSubKeyId = mKeyRepository.getCachedPublicKeyRing(signKeyId).getSecretSignId();
|
||||||
|
|
||||||
// get first usable subkey capable of signing
|
pgpData.setSignatureMasterKeyId(signKeyId)
|
||||||
try {
|
.setSignatureSubKeyId(signSubKeyId)
|
||||||
long signSubKeyId = mKeyRepository.getCachedPublicKeyRing(
|
.setAdditionalEncryptId(signKeyId);
|
||||||
pgpData.getSignatureMasterKeyId()).getSecretSignId();
|
}
|
||||||
pgpData.setSignatureSubKeyId(signSubKeyId);
|
|
||||||
} catch (PgpKeyNotFoundException e) {
|
KeyIdResult keyIdResult = mKeyIdExtractor.returnKeyIdsFromIntent(data, false,
|
||||||
throw new Exception("signing subkey not found!", e);
|
mApiPermissionHelper.getCurrentCallingPackage());
|
||||||
}
|
|
||||||
|
boolean isDryRun = data.getBooleanExtra(OpenPgpApi.EXTRA_DRY_RUN, false);
|
||||||
|
boolean isOpportunistic = data.getBooleanExtra(OpenPgpApi.EXTRA_OPPORTUNISTIC_ENCRYPTION, false);
|
||||||
|
KeyIdResultStatus keyIdResultStatus = keyIdResult.getStatus();
|
||||||
|
if (isDryRun) {
|
||||||
|
return getDryRunStatusResult(keyIdResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keyIdResult.hasKeySelectionPendingIntent()) {
|
||||||
|
if ((keyIdResultStatus == KeyIdResultStatus.MISSING || keyIdResultStatus == KeyIdResultStatus.NO_KEYS ||
|
||||||
|
keyIdResultStatus == KeyIdResultStatus.NO_KEYS_ERROR) && isOpportunistic) {
|
||||||
|
return createErrorResultIntent(OpenPgpError.OPPORTUNISTIC_MISSING_KEYS,
|
||||||
|
"missing keys in opportunistic mode");
|
||||||
}
|
}
|
||||||
|
|
||||||
// sign and encrypt
|
Intent result = new Intent();
|
||||||
pgpData.setSignatureHashAlgorithm(PgpSecurityConstants.OpenKeychainHashAlgorithmTags.USE_DEFAULT)
|
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED);
|
||||||
.setAdditionalEncryptId(signKeyId); // add sign key for encryption
|
result.putExtra(OpenPgpApi.RESULT_INTENT, keyIdResult.getKeySelectionPendingIntent());
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
pgpData.setEncryptionMasterKeyIds(keyIdResult.getKeyIds());
|
||||||
|
|
||||||
PgpSignEncryptInputParcel pseInput = new PgpSignEncryptInputParcel(pgpData);
|
PgpSignEncryptInputParcel pseInput = new PgpSignEncryptInputParcel(pgpData);
|
||||||
pseInput.setAllowedKeyIds(getAllowedKeyIds());
|
pseInput.setAllowedKeyIds(getAllowedKeyIds());
|
||||||
|
@ -268,13 +267,15 @@ public class OpenPgpService extends Service {
|
||||||
}
|
}
|
||||||
// override passphrase in input parcel if given by API call
|
// override passphrase in input parcel if given by API call
|
||||||
if (data.hasExtra(OpenPgpApi.EXTRA_PASSPHRASE)) {
|
if (data.hasExtra(OpenPgpApi.EXTRA_PASSPHRASE)) {
|
||||||
inputParcel.mPassphrase =
|
inputParcel.mPassphrase = new Passphrase(data.getCharArrayExtra(OpenPgpApi.EXTRA_PASSPHRASE));
|
||||||
new Passphrase(data.getCharArrayExtra(OpenPgpApi.EXTRA_PASSPHRASE));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PgpSignEncryptOperation op = new PgpSignEncryptOperation(this, mKeyRepository, null);
|
// TODO this is not correct!
|
||||||
|
long inputLength = inputStream.available();
|
||||||
|
InputData inputData = new InputData(inputStream, inputLength, originalFilename);
|
||||||
|
|
||||||
// execute PGP operation!
|
// execute PGP operation!
|
||||||
|
PgpSignEncryptOperation op = new PgpSignEncryptOperation(this, mKeyRepository, null);
|
||||||
PgpSignEncryptResult pgpResult = op.execute(pseInput, inputParcel, inputData, outputStream);
|
PgpSignEncryptResult pgpResult = op.execute(pseInput, inputParcel, inputData, outputStream);
|
||||||
|
|
||||||
if (pgpResult.isPending()) {
|
if (pgpResult.isPending()) {
|
||||||
|
@ -297,11 +298,43 @@ public class OpenPgpService extends Service {
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.d(Constants.TAG, "encryptAndSignImpl", e);
|
Log.d(Constants.TAG, "encryptAndSignImpl", e);
|
||||||
Intent result = new Intent();
|
return createErrorResultIntent(OpenPgpError.GENERIC_ERROR, e.getMessage());
|
||||||
result.putExtra(OpenPgpApi.RESULT_ERROR,
|
}
|
||||||
new OpenPgpError(OpenPgpError.GENERIC_ERROR, e.getMessage()));
|
}
|
||||||
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR);
|
|
||||||
return result;
|
@NonNull
|
||||||
|
private Intent getDryRunStatusResult(KeyIdResult keyIdResult) {
|
||||||
|
switch (keyIdResult.getStatus()) {
|
||||||
|
case MISSING: {
|
||||||
|
Intent result = new Intent();
|
||||||
|
result.putExtra(OpenPgpApi.RESULT_ERROR,
|
||||||
|
new OpenPgpError(OpenPgpError.OPPORTUNISTIC_MISSING_KEYS, "missing keys in opportunistic mode"));
|
||||||
|
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
case NO_KEYS:
|
||||||
|
case NO_KEYS_ERROR: {
|
||||||
|
Intent result = new Intent();
|
||||||
|
result.putExtra(OpenPgpApi.RESULT_ERROR,
|
||||||
|
new OpenPgpError(OpenPgpError.NO_USER_IDS, "empty recipient list"));
|
||||||
|
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
case DUPLICATE: {
|
||||||
|
Intent result = new Intent();
|
||||||
|
result.putExtra(OpenPgpApi.RESULT_KEYS_CONFIRMED, false);
|
||||||
|
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
case OK: {
|
||||||
|
Intent result = new Intent();
|
||||||
|
result.putExtra(OpenPgpApi.RESULT_KEYS_CONFIRMED, keyIdResult.isAllKeysConfirmed());
|
||||||
|
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
throw new IllegalStateException("unhandled case!");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -385,18 +418,12 @@ public class OpenPgpService extends Service {
|
||||||
}
|
}
|
||||||
|
|
||||||
String errorMsg = getString(pgpResult.getLog().getLast().mType.getMsgId());
|
String errorMsg = getString(pgpResult.getLog().getLast().mType.getMsgId());
|
||||||
Intent result = new Intent();
|
return createErrorResultIntent(OpenPgpError.GENERIC_ERROR, errorMsg);
|
||||||
result.putExtra(OpenPgpApi.RESULT_ERROR, new OpenPgpError(OpenPgpError.GENERIC_ERROR, errorMsg));
|
|
||||||
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR);
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.e(Constants.TAG, "decryptAndVerifyImpl", e);
|
Log.e(Constants.TAG, "decryptAndVerifyImpl", e);
|
||||||
Intent result = new Intent();
|
return createErrorResultIntent(OpenPgpError.GENERIC_ERROR, e.getMessage());
|
||||||
result.putExtra(OpenPgpApi.RESULT_ERROR, new OpenPgpError(OpenPgpError.GENERIC_ERROR, e.getMessage()));
|
|
||||||
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR);
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -532,20 +559,24 @@ public class OpenPgpService extends Service {
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.d(Constants.TAG, "getKeyImpl", e);
|
Log.d(Constants.TAG, "getKeyImpl", e);
|
||||||
Intent result = new Intent();
|
return createErrorResultIntent(OpenPgpError.GENERIC_ERROR, e.getMessage());
|
||||||
result.putExtra(OpenPgpApi.RESULT_ERROR,
|
|
||||||
new OpenPgpError(OpenPgpError.GENERIC_ERROR, e.getMessage()));
|
|
||||||
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR);
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private Intent createErrorResultIntent(int errorCode, String errorMsg) {
|
||||||
|
Intent result = new Intent();
|
||||||
|
result.putExtra(OpenPgpApi.RESULT_ERROR,
|
||||||
|
new OpenPgpError(errorCode, errorMsg));
|
||||||
|
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
private Intent getSignKeyIdImpl(Intent data) {
|
private Intent getSignKeyIdImpl(Intent data) {
|
||||||
// if data already contains EXTRA_SIGN_KEY_ID, it has been executed again
|
// if data already contains EXTRA_SIGN_KEY_ID, it has been executed again
|
||||||
// after user interaction. Then, we just need to return the long again!
|
// after user interaction. Then, we just need to return the long again!
|
||||||
if (data.hasExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID)) {
|
if (data.hasExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID)) {
|
||||||
long signKeyId = data.getLongExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID,
|
long signKeyId = data.getLongExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, Constants.key.none);
|
||||||
Constants.key.none);
|
|
||||||
|
|
||||||
Intent result = new Intent();
|
Intent result = new Intent();
|
||||||
result.putExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, signKeyId);
|
result.putExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, signKeyId);
|
||||||
|
@ -567,9 +598,13 @@ public class OpenPgpService extends Service {
|
||||||
}
|
}
|
||||||
|
|
||||||
private Intent getKeyIdsImpl(Intent data) {
|
private Intent getKeyIdsImpl(Intent data) {
|
||||||
KeyIdResult keyIdResult = mKeyIdExtractor.returnKeyIdsFromIntent(data, true);
|
KeyIdResult keyIdResult = mKeyIdExtractor.returnKeyIdsFromIntent(data, true,
|
||||||
if (keyIdResult.hasResultIntent()) {
|
mApiPermissionHelper.getCurrentCallingPackage());
|
||||||
return keyIdResult.getResultIntent();
|
if (keyIdResult.hasKeySelectionPendingIntent()) {
|
||||||
|
Intent result = new Intent();
|
||||||
|
result.putExtra(OpenPgpApi.RESULT_INTENT, keyIdResult.getKeySelectionPendingIntent());
|
||||||
|
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
long[] keyIds = keyIdResult.getKeyIds();
|
long[] keyIds = keyIdResult.getKeyIds();
|
||||||
|
|
||||||
|
@ -606,18 +641,11 @@ public class OpenPgpService extends Service {
|
||||||
} else {
|
} else {
|
||||||
// should not happen normally...
|
// should not happen normally...
|
||||||
String errorMsg = getString(pgpResult.getLog().getLast().mType.getMsgId());
|
String errorMsg = getString(pgpResult.getLog().getLast().mType.getMsgId());
|
||||||
Intent result = new Intent();
|
return createErrorResultIntent(OpenPgpError.GENERIC_ERROR, errorMsg);
|
||||||
result.putExtra(OpenPgpApi.RESULT_ERROR, new OpenPgpError(OpenPgpError.GENERIC_ERROR, errorMsg));
|
|
||||||
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR);
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.d(Constants.TAG, "backupImpl", e);
|
Log.d(Constants.TAG, "backupImpl", e);
|
||||||
Intent result = new Intent();
|
return createErrorResultIntent(OpenPgpError.GENERIC_ERROR, e.getMessage());
|
||||||
result.putExtra(OpenPgpApi.RESULT_ERROR,
|
|
||||||
new OpenPgpError(OpenPgpError.GENERIC_ERROR, e.getMessage()));
|
|
||||||
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR);
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,40 +2,36 @@ package org.sufficientlysecure.keychain.remote;
|
||||||
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
import android.content.ContentResolver;
|
import android.content.ContentResolver;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
import android.support.annotation.VisibleForTesting;
|
import android.support.annotation.VisibleForTesting;
|
||||||
|
|
||||||
import org.openintents.openpgp.OpenPgpError;
|
|
||||||
import org.openintents.openpgp.util.OpenPgpApi;
|
import org.openintents.openpgp.util.OpenPgpApi;
|
||||||
import org.openintents.openpgp.util.OpenPgpUtils;
|
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
import org.sufficientlysecure.keychain.pgp.KeyRing;
|
import org.sufficientlysecure.keychain.provider.KeychainExternalContract;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
import org.sufficientlysecure.keychain.provider.KeychainExternalContract.EmailStatus;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables;
|
|
||||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
|
|
||||||
|
|
||||||
class OpenPgpServiceKeyIdExtractor {
|
class OpenPgpServiceKeyIdExtractor {
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
static final String[] KEY_SEARCH_PROJECTION = new String[]{
|
static final String[] PROJECTION_KEY_SEARCH = {
|
||||||
KeyRings._ID,
|
"email_address",
|
||||||
KeyRings.MASTER_KEY_ID,
|
"master_key_id",
|
||||||
KeyRings.IS_EXPIRED, // referenced in where clause!
|
"email_status",
|
||||||
KeyRings.IS_REVOKED, // referenced in where clause!
|
|
||||||
};
|
};
|
||||||
|
private static final int INDEX_EMAIL_ADDRESS = 0;
|
||||||
private static final int INDEX_MASTER_KEY_ID = 1;
|
private static final int INDEX_MASTER_KEY_ID = 1;
|
||||||
|
private static final int INDEX_EMAIL_STATUS = 2;
|
||||||
// do not pre-select revoked or expired keys
|
|
||||||
private static final String KEY_SEARCH_WHERE = Tables.KEYS + "." + KeychainContract.KeyRings.IS_REVOKED
|
|
||||||
+ " = 0 AND " + KeychainContract.KeyRings.IS_EXPIRED + " = 0";
|
|
||||||
|
|
||||||
|
|
||||||
private final ApiPendingIntentFactory apiPendingIntentFactory;
|
private final ApiPendingIntentFactory apiPendingIntentFactory;
|
||||||
|
@ -53,150 +49,249 @@ class OpenPgpServiceKeyIdExtractor {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
KeyIdResult returnKeyIdsFromIntent(Intent data, boolean askIfNoUserIdsProvided) {
|
KeyIdResult returnKeyIdsFromIntent(Intent data, boolean askIfNoUserIdsProvided, String callingPackageName) {
|
||||||
HashSet<Long> encryptKeyIds = new HashSet<>();
|
|
||||||
|
|
||||||
boolean hasKeysFromSelectPubkeyActivity = data.hasExtra(OpenPgpApi.EXTRA_KEY_IDS_SELECTED);
|
boolean hasKeysFromSelectPubkeyActivity = data.hasExtra(OpenPgpApi.EXTRA_KEY_IDS_SELECTED);
|
||||||
|
|
||||||
|
KeyIdResult result;
|
||||||
if (hasKeysFromSelectPubkeyActivity) {
|
if (hasKeysFromSelectPubkeyActivity) {
|
||||||
|
HashSet<Long> encryptKeyIds = new HashSet<>();
|
||||||
for (long keyId : data.getLongArrayExtra(OpenPgpApi.EXTRA_KEY_IDS_SELECTED)) {
|
for (long keyId : data.getLongArrayExtra(OpenPgpApi.EXTRA_KEY_IDS_SELECTED)) {
|
||||||
encryptKeyIds.add(keyId);
|
encryptKeyIds.add(keyId);
|
||||||
}
|
}
|
||||||
|
result = createKeysOkResult(encryptKeyIds, false);
|
||||||
} else if (data.hasExtra(OpenPgpApi.EXTRA_USER_IDS) || askIfNoUserIdsProvided) {
|
} else if (data.hasExtra(OpenPgpApi.EXTRA_USER_IDS) || askIfNoUserIdsProvided) {
|
||||||
String[] userIds = data.getStringArrayExtra(OpenPgpApi.EXTRA_USER_IDS);
|
String[] userIds = data.getStringArrayExtra(OpenPgpApi.EXTRA_USER_IDS);
|
||||||
boolean isOpportunistic = data.getBooleanExtra(OpenPgpApi.EXTRA_OPPORTUNISTIC_ENCRYPTION, false);
|
result = returnKeyIdsFromEmails(data, userIds, callingPackageName);
|
||||||
KeyIdResult result = returnKeyIdsFromEmails(data, userIds, isOpportunistic);
|
} else {
|
||||||
|
result = createNoKeysResult();
|
||||||
if (result.mResultIntent != null) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
encryptKeyIds.addAll(result.mKeyIds);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// add key ids from non-ambiguous key id extra
|
// add key ids from non-ambiguous key id extra
|
||||||
if (data.hasExtra(OpenPgpApi.EXTRA_KEY_IDS)) {
|
if (data.hasExtra(OpenPgpApi.EXTRA_KEY_IDS)) {
|
||||||
|
HashSet<Long> explicitKeyIds = new HashSet<>();
|
||||||
for (long keyId : data.getLongArrayExtra(OpenPgpApi.EXTRA_KEY_IDS)) {
|
for (long keyId : data.getLongArrayExtra(OpenPgpApi.EXTRA_KEY_IDS)) {
|
||||||
encryptKeyIds.add(keyId);
|
explicitKeyIds.add(keyId);
|
||||||
}
|
}
|
||||||
|
result = result.withExplicitKeyIds(explicitKeyIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (encryptKeyIds.isEmpty()) {
|
return result;
|
||||||
Intent result = new Intent();
|
|
||||||
result.putExtra(OpenPgpApi.RESULT_ERROR,
|
|
||||||
new OpenPgpError(OpenPgpError.NO_USER_IDS, "No encryption keys or user ids specified!" +
|
|
||||||
"(pass empty user id array to get dialog without preselection)"));
|
|
||||||
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR);
|
|
||||||
return new KeyIdResult(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new KeyIdResult(encryptKeyIds);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private KeyIdResult returnKeyIdsFromEmails(Intent data, String[] encryptionUserIds, boolean isOpportunistic) {
|
private KeyIdResult returnKeyIdsFromEmails(Intent data, String[] encryptionAddresses, String callingPackageName) {
|
||||||
boolean hasUserIds = (encryptionUserIds != null && encryptionUserIds.length > 0);
|
boolean hasAddresses = (encryptionAddresses != null && encryptionAddresses.length > 0);
|
||||||
|
|
||||||
|
boolean allKeysConfirmed = false;
|
||||||
HashSet<Long> keyIds = new HashSet<>();
|
HashSet<Long> keyIds = new HashSet<>();
|
||||||
ArrayList<String> missingEmails = new ArrayList<>();
|
ArrayList<String> missingEmails = new ArrayList<>();
|
||||||
ArrayList<String> duplicateEmails = new ArrayList<>();
|
ArrayList<String> duplicateEmails = new ArrayList<>();
|
||||||
if (hasUserIds) {
|
|
||||||
for (String rawUserId : encryptionUserIds) {
|
if (hasAddresses) {
|
||||||
OpenPgpUtils.UserId userId = KeyRing.splitUserId(rawUserId);
|
HashMap<String, UserIdStatus> keyRows = getStatusMapForQueriedAddresses(encryptionAddresses, callingPackageName);
|
||||||
String email = userId.email != null ? userId.email : rawUserId;
|
|
||||||
// try to find the key for this specific email
|
boolean anyKeyNotVerified = false;
|
||||||
Uri uri = KeyRings.buildUnifiedKeyRingsFindByEmailUri(email);
|
for (Entry<String, UserIdStatus> entry : keyRows.entrySet()) {
|
||||||
Cursor cursor = contentResolver.query(uri, KEY_SEARCH_PROJECTION, KEY_SEARCH_WHERE, null, null);
|
String queriedAddress = entry.getKey();
|
||||||
if (cursor == null) {
|
UserIdStatus userIdStatus = entry.getValue();
|
||||||
throw new IllegalStateException("Internal error, received null cursor!");
|
|
||||||
|
if (userIdStatus.masterKeyId == null) {
|
||||||
|
missingEmails.add(queriedAddress);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
try {
|
|
||||||
// result should be one entry containing the key id
|
|
||||||
if (cursor.moveToFirst()) {
|
|
||||||
long id = cursor.getLong(INDEX_MASTER_KEY_ID);
|
|
||||||
keyIds.add(id);
|
|
||||||
|
|
||||||
// another entry for this email -> two keys with the same email inside user id
|
keyIds.add(userIdStatus.masterKeyId);
|
||||||
if (!cursor.isLast()) {
|
|
||||||
Log.d(Constants.TAG, "more than one user id with the same email");
|
|
||||||
duplicateEmails.add(email);
|
|
||||||
|
|
||||||
// also pre-select
|
if (userIdStatus.hasDuplicate) {
|
||||||
while (cursor.moveToNext()) {
|
duplicateEmails.add(queriedAddress);
|
||||||
long duplicateId = cursor.getLong(INDEX_MASTER_KEY_ID);
|
}
|
||||||
keyIds.add(duplicateId);
|
|
||||||
}
|
if (!userIdStatus.verified) {
|
||||||
}
|
anyKeyNotVerified = true;
|
||||||
} else {
|
|
||||||
missingEmails.add(email);
|
|
||||||
Log.d(Constants.TAG, "user id missing");
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
cursor.close();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (keyRows.size() != encryptionAddresses.length) {
|
||||||
|
Log.e(Constants.TAG, "Number of rows doesn't match number of retrieved rows! Probably a bug?");
|
||||||
|
}
|
||||||
|
|
||||||
|
allKeysConfirmed = !anyKeyNotVerified;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean hasMissingUserIds = !missingEmails.isEmpty();
|
if (!missingEmails.isEmpty()) {
|
||||||
boolean hasDuplicateUserIds = !duplicateEmails.isEmpty();
|
return createMissingKeysResult(data, keyIds, missingEmails, duplicateEmails);
|
||||||
if (isOpportunistic && (!hasUserIds || hasMissingUserIds)) {
|
|
||||||
Intent result = new Intent();
|
|
||||||
result.putExtra(OpenPgpApi.RESULT_ERROR,
|
|
||||||
new OpenPgpError(OpenPgpError.OPPORTUNISTIC_MISSING_KEYS, "missing keys in opportunistic mode"));
|
|
||||||
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR);
|
|
||||||
return new KeyIdResult(result);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!hasUserIds || hasMissingUserIds || hasDuplicateUserIds) {
|
if (!duplicateEmails.isEmpty()) {
|
||||||
long[] keyIdsArray = KeyFormattingUtils.getUnboxedLongArray(keyIds);
|
return createDuplicateKeysResult(data, keyIds, missingEmails, duplicateEmails);
|
||||||
PendingIntent pi = apiPendingIntentFactory.createSelectPublicKeyPendingIntent(data, keyIdsArray,
|
|
||||||
missingEmails, duplicateEmails, hasUserIds);
|
|
||||||
|
|
||||||
// return PendingIntent to be executed by client
|
|
||||||
Intent result = new Intent();
|
|
||||||
result.putExtra(OpenPgpApi.RESULT_INTENT, pi);
|
|
||||||
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED);
|
|
||||||
return new KeyIdResult(result);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (keyIds.isEmpty()) {
|
if (keyIds.isEmpty()) {
|
||||||
throw new AssertionError("keyIdsArray.length == 0, should never happen!");
|
return createNoKeysResult(data, keyIds, missingEmails, duplicateEmails);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new KeyIdResult(keyIds);
|
return createKeysOkResult(keyIds, allKeysConfirmed);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** This method queries the KeychainExternalProvider for all addresses given in encryptionUserIds.
|
||||||
|
* It returns a map with one UserIdStatus per queried address. If multiple key candidates exist,
|
||||||
|
* the one with the highest verification status is selected. If two candidates with the same
|
||||||
|
* verification status exist, the first one is returned and marked as having a duplicate.
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
private HashMap<String, UserIdStatus> getStatusMapForQueriedAddresses(String[] encryptionUserIds, String callingPackageName) {
|
||||||
|
HashMap<String,UserIdStatus> keyRows = new HashMap<>();
|
||||||
|
Uri queryUri = EmailStatus.CONTENT_URI.buildUpon().appendPath(callingPackageName).build();
|
||||||
|
Cursor cursor = contentResolver.query(queryUri, PROJECTION_KEY_SEARCH, null, encryptionUserIds, null);
|
||||||
|
if (cursor == null) {
|
||||||
|
throw new IllegalStateException("Internal error, received null cursor!");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
while (cursor.moveToNext()) {
|
||||||
|
String queryAddress = cursor.getString(INDEX_EMAIL_ADDRESS);
|
||||||
|
Long masterKeyId = cursor.isNull(INDEX_MASTER_KEY_ID) ? null : cursor.getLong(INDEX_MASTER_KEY_ID);
|
||||||
|
boolean isVerified = cursor.getInt(INDEX_EMAIL_STATUS) == KeychainExternalContract.KEY_STATUS_VERIFIED;
|
||||||
|
UserIdStatus userIdStatus = new UserIdStatus(masterKeyId, isVerified);
|
||||||
|
|
||||||
|
boolean seenBefore = keyRows.containsKey(queryAddress);
|
||||||
|
if (!seenBefore) {
|
||||||
|
keyRows.put(queryAddress, userIdStatus);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
UserIdStatus previousUserIdStatus = keyRows.get(queryAddress);
|
||||||
|
if (previousUserIdStatus.masterKeyId == null) {
|
||||||
|
keyRows.put(queryAddress, userIdStatus);
|
||||||
|
} else if (!previousUserIdStatus.verified && userIdStatus.verified) {
|
||||||
|
keyRows.put(queryAddress, userIdStatus);
|
||||||
|
} else if (previousUserIdStatus.verified == userIdStatus.verified) {
|
||||||
|
previousUserIdStatus.hasDuplicate = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
cursor.close();
|
||||||
|
}
|
||||||
|
return keyRows;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class UserIdStatus {
|
||||||
|
private final Long masterKeyId;
|
||||||
|
private final boolean verified;
|
||||||
|
private boolean hasDuplicate;
|
||||||
|
|
||||||
|
UserIdStatus(Long masterKeyId, boolean verified) {
|
||||||
|
this.masterKeyId = masterKeyId;
|
||||||
|
this.verified = verified;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static class KeyIdResult {
|
static class KeyIdResult {
|
||||||
private final Intent mResultIntent;
|
private final PendingIntent mKeySelectionPendingIntent;
|
||||||
private final HashSet<Long> mKeyIds;
|
private final HashSet<Long> mUserKeyIds;
|
||||||
|
private final HashSet<Long> mExplicitKeyIds;
|
||||||
|
private final KeyIdResultStatus mStatus;
|
||||||
|
private final boolean mAllKeysConfirmed;
|
||||||
|
|
||||||
private KeyIdResult(Intent resultIntent) {
|
private KeyIdResult(PendingIntent keySelectionPendingIntent, KeyIdResultStatus keyIdResultStatus) {
|
||||||
mResultIntent = resultIntent;
|
mKeySelectionPendingIntent = keySelectionPendingIntent;
|
||||||
mKeyIds = null;
|
mUserKeyIds = null;
|
||||||
|
mAllKeysConfirmed = false;
|
||||||
|
mStatus = keyIdResultStatus;
|
||||||
|
mExplicitKeyIds = null;
|
||||||
}
|
}
|
||||||
private KeyIdResult(HashSet<Long> keyIds) {
|
private KeyIdResult(HashSet<Long> keyIds, boolean allKeysConfirmed, KeyIdResultStatus keyIdResultStatus) {
|
||||||
mResultIntent = null;
|
mKeySelectionPendingIntent = null;
|
||||||
mKeyIds = keyIds;
|
mUserKeyIds = keyIds;
|
||||||
|
mAllKeysConfirmed = allKeysConfirmed;
|
||||||
|
mStatus = keyIdResultStatus;
|
||||||
|
mExplicitKeyIds = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean hasResultIntent() {
|
private KeyIdResult(KeyIdResult keyIdResult, HashSet<Long> explicitKeyIds) {
|
||||||
return mResultIntent != null;
|
mKeySelectionPendingIntent = keyIdResult.mKeySelectionPendingIntent;
|
||||||
|
mUserKeyIds = keyIdResult.mUserKeyIds;
|
||||||
|
mAllKeysConfirmed = keyIdResult.mAllKeysConfirmed;
|
||||||
|
mStatus = keyIdResult.mStatus;
|
||||||
|
mExplicitKeyIds = explicitKeyIds;
|
||||||
}
|
}
|
||||||
Intent getResultIntent() {
|
|
||||||
if (mResultIntent == null) {
|
boolean hasKeySelectionPendingIntent() {
|
||||||
|
return mKeySelectionPendingIntent != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
PendingIntent getKeySelectionPendingIntent() {
|
||||||
|
if (mKeySelectionPendingIntent == null) {
|
||||||
throw new AssertionError("result intent must not be null when getResultIntent is called!");
|
throw new AssertionError("result intent must not be null when getResultIntent is called!");
|
||||||
}
|
}
|
||||||
if (mKeyIds != null) {
|
if (mUserKeyIds != null) {
|
||||||
throw new AssertionError("key ids must be null when getKeyIds is called!");
|
throw new AssertionError("key ids must be null when getKeyIds is called!");
|
||||||
}
|
}
|
||||||
return mResultIntent;
|
return mKeySelectionPendingIntent;
|
||||||
}
|
}
|
||||||
|
|
||||||
long[] getKeyIds() {
|
long[] getKeyIds() {
|
||||||
if (mResultIntent != null) {
|
if (mKeySelectionPendingIntent != null) {
|
||||||
throw new AssertionError("result intent must be null when getKeyIds is called!");
|
throw new AssertionError("result intent must be null when getKeyIds is called!");
|
||||||
}
|
}
|
||||||
if (mKeyIds == null) {
|
HashSet<Long> allKeyIds = new HashSet<>();
|
||||||
throw new AssertionError("key ids must not be null when getKeyIds is called!");
|
if (mUserKeyIds != null) {
|
||||||
|
allKeyIds.addAll(mUserKeyIds);
|
||||||
}
|
}
|
||||||
return KeyFormattingUtils.getUnboxedLongArray(mKeyIds);
|
if (mExplicitKeyIds != null) {
|
||||||
|
allKeyIds.addAll(mExplicitKeyIds);
|
||||||
|
}
|
||||||
|
return KeyFormattingUtils.getUnboxedLongArray(allKeyIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isAllKeysConfirmed() {
|
||||||
|
return mAllKeysConfirmed;
|
||||||
|
}
|
||||||
|
|
||||||
|
private KeyIdResult withExplicitKeyIds(HashSet<Long> explicitKeyIds) {
|
||||||
|
return new KeyIdResult(this, explicitKeyIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyIdResultStatus getStatus() {
|
||||||
|
return mStatus;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum KeyIdResultStatus {
|
||||||
|
OK, MISSING, DUPLICATE, NO_KEYS, NO_KEYS_ERROR
|
||||||
|
}
|
||||||
|
|
||||||
|
private KeyIdResult createKeysOkResult(HashSet<Long> encryptKeyIds, boolean allKeysConfirmed) {
|
||||||
|
return new KeyIdResult(encryptKeyIds, allKeysConfirmed, KeyIdResultStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
private KeyIdResult createNoKeysResult(Intent data,
|
||||||
|
HashSet<Long> selectedKeyIds, ArrayList<String> missingEmails, ArrayList<String> duplicateEmails) {
|
||||||
|
long[] keyIdsArray = KeyFormattingUtils.getUnboxedLongArray(selectedKeyIds);
|
||||||
|
PendingIntent selectKeyPendingIntent = apiPendingIntentFactory.createSelectPublicKeyPendingIntent(
|
||||||
|
data, keyIdsArray, missingEmails, duplicateEmails, false);
|
||||||
|
|
||||||
|
return new KeyIdResult(selectKeyPendingIntent, KeyIdResultStatus.NO_KEYS);
|
||||||
|
}
|
||||||
|
|
||||||
|
private KeyIdResult createDuplicateKeysResult(Intent data,
|
||||||
|
HashSet<Long> selectedKeyIds, ArrayList<String> missingEmails, ArrayList<String> duplicateEmails) {
|
||||||
|
long[] keyIdsArray = KeyFormattingUtils.getUnboxedLongArray(selectedKeyIds);
|
||||||
|
PendingIntent selectKeyPendingIntent = apiPendingIntentFactory.createSelectPublicKeyPendingIntent(
|
||||||
|
data, keyIdsArray, missingEmails, duplicateEmails, false);
|
||||||
|
|
||||||
|
return new KeyIdResult(selectKeyPendingIntent, KeyIdResultStatus.DUPLICATE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private KeyIdResult createMissingKeysResult(Intent data,
|
||||||
|
HashSet<Long> selectedKeyIds, ArrayList<String> missingEmails, ArrayList<String> duplicateEmails) {
|
||||||
|
long[] keyIdsArray = KeyFormattingUtils.getUnboxedLongArray(selectedKeyIds);
|
||||||
|
PendingIntent selectKeyPendingIntent = apiPendingIntentFactory.createSelectPublicKeyPendingIntent(
|
||||||
|
data, keyIdsArray, missingEmails, duplicateEmails, false);
|
||||||
|
|
||||||
|
return new KeyIdResult(selectKeyPendingIntent, KeyIdResultStatus.MISSING);
|
||||||
|
}
|
||||||
|
|
||||||
|
private KeyIdResult createNoKeysResult() {
|
||||||
|
return new KeyIdResult(null, KeyIdResultStatus.NO_KEYS_ERROR);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,6 +78,15 @@ public class KeychainExternalProviderTest {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test(expected = AccessControlException.class)
|
||||||
|
public void testPermission__withExplicitPackage() throws Exception {
|
||||||
|
contentResolver.query(
|
||||||
|
EmailStatus.CONTENT_URI.buildUpon().appendPath("fake_pkg").build(),
|
||||||
|
new String[] { EmailStatus.EMAIL_ADDRESS, EmailStatus.EMAIL_ADDRESS, EmailStatus.USER_ID },
|
||||||
|
null, new String [] { }, null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@Test(expected = AccessControlException.class)
|
@Test(expected = AccessControlException.class)
|
||||||
public void testPermission__withWrongPackageCert() throws Exception {
|
public void testPermission__withWrongPackageCert() throws Exception {
|
||||||
apiDao.deleteApiApp(PACKAGE_NAME);
|
apiDao.deleteApiApp(PACKAGE_NAME);
|
||||||
|
@ -94,7 +103,7 @@ public class KeychainExternalProviderTest {
|
||||||
public void testQuery__withNonExistentAddress() throws Exception {
|
public void testQuery__withNonExistentAddress() throws Exception {
|
||||||
Cursor cursor = contentResolver.query(
|
Cursor cursor = contentResolver.query(
|
||||||
EmailStatus.CONTENT_URI, new String[] {
|
EmailStatus.CONTENT_URI, new String[] {
|
||||||
EmailStatus.EMAIL_ADDRESS, EmailStatus.EMAIL_STATUS, EmailStatus.USER_ID },
|
EmailStatus.EMAIL_ADDRESS, EmailStatus.USER_ID_STATUS, EmailStatus.USER_ID },
|
||||||
null, new String [] { MAIL_ADDRESS_1 }, null
|
null, new String [] { MAIL_ADDRESS_1 }, null
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -111,7 +120,7 @@ public class KeychainExternalProviderTest {
|
||||||
|
|
||||||
Cursor cursor = contentResolver.query(
|
Cursor cursor = contentResolver.query(
|
||||||
EmailStatus.CONTENT_URI, new String[] {
|
EmailStatus.CONTENT_URI, new String[] {
|
||||||
EmailStatus.EMAIL_ADDRESS, EmailStatus.EMAIL_STATUS, EmailStatus.USER_ID },
|
EmailStatus.EMAIL_ADDRESS, EmailStatus.USER_ID_STATUS, EmailStatus.USER_ID },
|
||||||
null, new String [] { MAIL_ADDRESS_1 }, null
|
null, new String [] { MAIL_ADDRESS_1 }, null
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -129,7 +138,7 @@ public class KeychainExternalProviderTest {
|
||||||
|
|
||||||
Cursor cursor = contentResolver.query(
|
Cursor cursor = contentResolver.query(
|
||||||
EmailStatus.CONTENT_URI, new String[] {
|
EmailStatus.CONTENT_URI, new String[] {
|
||||||
EmailStatus.EMAIL_ADDRESS, EmailStatus.EMAIL_STATUS, EmailStatus.USER_ID },
|
EmailStatus.EMAIL_ADDRESS, EmailStatus.USER_ID_STATUS, EmailStatus.USER_ID },
|
||||||
null, new String [] { MAIL_ADDRESS_1, MAIL_ADDRESS_2 }, null
|
null, new String [] { MAIL_ADDRESS_1, MAIL_ADDRESS_2 }, null
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -151,7 +160,7 @@ public class KeychainExternalProviderTest {
|
||||||
|
|
||||||
Cursor cursor = contentResolver.query(
|
Cursor cursor = contentResolver.query(
|
||||||
EmailStatus.CONTENT_URI, new String[] {
|
EmailStatus.CONTENT_URI, new String[] {
|
||||||
EmailStatus.EMAIL_ADDRESS, EmailStatus.EMAIL_STATUS, EmailStatus.USER_ID },
|
EmailStatus.EMAIL_ADDRESS, EmailStatus.USER_ID_STATUS, EmailStatus.USER_ID },
|
||||||
null, new String [] { MAIL_ADDRESS_SEC_1 }, null
|
null, new String [] { MAIL_ADDRESS_SEC_1 }, null
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -172,7 +181,7 @@ public class KeychainExternalProviderTest {
|
||||||
|
|
||||||
Cursor cursor = contentResolver.query(
|
Cursor cursor = contentResolver.query(
|
||||||
EmailStatus.CONTENT_URI, new String[] {
|
EmailStatus.CONTENT_URI, new String[] {
|
||||||
EmailStatus.EMAIL_ADDRESS, EmailStatus.EMAIL_STATUS, EmailStatus.USER_ID },
|
EmailStatus.EMAIL_ADDRESS, EmailStatus.USER_ID_STATUS, EmailStatus.USER_ID },
|
||||||
null, new String [] { MAIL_ADDRESS_1 }, null
|
null, new String [] { MAIL_ADDRESS_1 }, null
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -1,27 +1,28 @@
|
||||||
package org.sufficientlysecure.keychain.remote;
|
package org.sufficientlysecure.keychain.remote;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
import android.content.ContentResolver;
|
import android.content.ContentResolver;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.database.Cursor;
|
|
||||||
import android.database.MatrixCursor;
|
import android.database.MatrixCursor;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.openintents.openpgp.OpenPgpError;
|
|
||||||
import org.openintents.openpgp.util.OpenPgpApi;
|
import org.openintents.openpgp.util.OpenPgpApi;
|
||||||
|
import org.sufficientlysecure.keychain.BuildConfig;
|
||||||
import org.sufficientlysecure.keychain.KeychainTestRunner;
|
import org.sufficientlysecure.keychain.KeychainTestRunner;
|
||||||
import org.sufficientlysecure.keychain.remote.OpenPgpServiceKeyIdExtractor.KeyIdResult;
|
import org.sufficientlysecure.keychain.remote.OpenPgpServiceKeyIdExtractor.KeyIdResult;
|
||||||
|
import org.sufficientlysecure.keychain.remote.OpenPgpServiceKeyIdExtractor.KeyIdResultStatus;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertArrayEquals;
|
import static org.junit.Assert.assertArrayEquals;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertSame;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
import static org.mockito.Matchers.any;
|
import static org.mockito.Matchers.any;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
|
@ -51,9 +52,11 @@ public class OpenPgpServiceKeyIdExtractorTest {
|
||||||
Intent intent = new Intent();
|
Intent intent = new Intent();
|
||||||
intent.putExtra(OpenPgpApi.EXTRA_KEY_IDS, KEY_IDS);
|
intent.putExtra(OpenPgpApi.EXTRA_KEY_IDS, KEY_IDS);
|
||||||
|
|
||||||
KeyIdResult keyIdResult = openPgpServiceKeyIdExtractor.returnKeyIdsFromIntent(intent, false);
|
KeyIdResult keyIdResult = openPgpServiceKeyIdExtractor.returnKeyIdsFromIntent(intent, false,
|
||||||
|
BuildConfig.APPLICATION_ID);
|
||||||
|
|
||||||
assertFalse(keyIdResult.hasResultIntent());
|
assertEquals(KeyIdResultStatus.NO_KEYS_ERROR, keyIdResult.getStatus());
|
||||||
|
assertFalse(keyIdResult.hasKeySelectionPendingIntent());
|
||||||
assertArrayEqualsSorted(KEY_IDS, keyIdResult.getKeyIds());
|
assertArrayEqualsSorted(KEY_IDS, keyIdResult.getKeyIds());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,9 +66,11 @@ public class OpenPgpServiceKeyIdExtractorTest {
|
||||||
intent.putExtra(OpenPgpApi.EXTRA_KEY_IDS_SELECTED, KEY_IDS);
|
intent.putExtra(OpenPgpApi.EXTRA_KEY_IDS_SELECTED, KEY_IDS);
|
||||||
intent.putExtra(OpenPgpApi.EXTRA_USER_IDS, USER_IDS); // should be ignored
|
intent.putExtra(OpenPgpApi.EXTRA_USER_IDS, USER_IDS); // should be ignored
|
||||||
|
|
||||||
KeyIdResult keyIdResult = openPgpServiceKeyIdExtractor.returnKeyIdsFromIntent(intent, false);
|
KeyIdResult keyIdResult = openPgpServiceKeyIdExtractor.returnKeyIdsFromIntent(intent, false,
|
||||||
|
BuildConfig.APPLICATION_ID);
|
||||||
|
|
||||||
assertFalse(keyIdResult.hasResultIntent());
|
assertEquals(KeyIdResultStatus.OK, keyIdResult.getStatus());
|
||||||
|
assertFalse(keyIdResult.hasKeySelectionPendingIntent());
|
||||||
assertArrayEqualsSorted(KEY_IDS, keyIdResult.getKeyIds());
|
assertArrayEqualsSorted(KEY_IDS, keyIdResult.getKeyIds());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,30 +85,30 @@ public class OpenPgpServiceKeyIdExtractorTest {
|
||||||
setupPendingIntentFactoryResult(pendingIntent);
|
setupPendingIntentFactoryResult(pendingIntent);
|
||||||
|
|
||||||
|
|
||||||
KeyIdResult keyIdResult = openPgpServiceKeyIdExtractor.returnKeyIdsFromIntent(intent, false);
|
KeyIdResult keyIdResult = openPgpServiceKeyIdExtractor.returnKeyIdsFromIntent(intent, false,
|
||||||
|
BuildConfig.APPLICATION_ID);
|
||||||
|
|
||||||
|
|
||||||
assertTrue(keyIdResult.hasResultIntent());
|
assertEquals(KeyIdResultStatus.NO_KEYS, keyIdResult.getStatus());
|
||||||
Intent resultIntent = keyIdResult.getResultIntent();
|
assertTrue(keyIdResult.hasKeySelectionPendingIntent());
|
||||||
assertEquals(OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED,
|
|
||||||
resultIntent.getIntExtra(OpenPgpApi.RESULT_CODE, Integer.MAX_VALUE));
|
|
||||||
assertEquals(pendingIntent, resultIntent.getParcelableExtra(OpenPgpApi.RESULT_INTENT));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void returnKeyIdsFromIntent__withNoData() throws Exception {
|
public void returnKeyIdsFromIntent__withNoData() throws Exception {
|
||||||
Intent intent = new Intent();
|
Intent intent = new Intent();
|
||||||
|
intent.putExtra(OpenPgpApi.EXTRA_USER_IDS, new String[] { });
|
||||||
|
|
||||||
|
PendingIntent pendingIntent = mock(PendingIntent.class);
|
||||||
|
setupPendingIntentFactoryResult(pendingIntent);
|
||||||
|
|
||||||
|
|
||||||
KeyIdResult keyIdResult = openPgpServiceKeyIdExtractor.returnKeyIdsFromIntent(intent, false);
|
KeyIdResult keyIdResult = openPgpServiceKeyIdExtractor.returnKeyIdsFromIntent(intent, false,
|
||||||
|
BuildConfig.APPLICATION_ID);
|
||||||
|
|
||||||
|
|
||||||
assertTrue(keyIdResult.hasResultIntent());
|
assertEquals(KeyIdResultStatus.NO_KEYS, keyIdResult.getStatus());
|
||||||
Intent resultIntent = keyIdResult.getResultIntent();
|
assertTrue(keyIdResult.hasKeySelectionPendingIntent());
|
||||||
assertEquals(OpenPgpApi.RESULT_CODE_ERROR,
|
assertSame(pendingIntent, keyIdResult.getKeySelectionPendingIntent());
|
||||||
resultIntent.getIntExtra(OpenPgpApi.RESULT_CODE, Integer.MAX_VALUE));
|
|
||||||
assertEquals(OpenPgpError.NO_USER_IDS,
|
|
||||||
resultIntent.<OpenPgpError>getParcelableExtra(OpenPgpApi.RESULT_ERROR).getErrorId());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -114,14 +119,13 @@ public class OpenPgpServiceKeyIdExtractorTest {
|
||||||
PendingIntent pendingIntent = mock(PendingIntent.class);
|
PendingIntent pendingIntent = mock(PendingIntent.class);
|
||||||
setupPendingIntentFactoryResult(pendingIntent);
|
setupPendingIntentFactoryResult(pendingIntent);
|
||||||
|
|
||||||
KeyIdResult keyIdResult = openPgpServiceKeyIdExtractor.returnKeyIdsFromIntent(intent, false);
|
KeyIdResult keyIdResult = openPgpServiceKeyIdExtractor.returnKeyIdsFromIntent(intent, false,
|
||||||
|
BuildConfig.APPLICATION_ID);
|
||||||
|
|
||||||
|
|
||||||
assertTrue(keyIdResult.hasResultIntent());
|
assertEquals(KeyIdResultStatus.NO_KEYS, keyIdResult.getStatus());
|
||||||
Intent resultIntent = keyIdResult.getResultIntent();
|
assertTrue(keyIdResult.hasKeySelectionPendingIntent());
|
||||||
assertEquals(OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED,
|
assertSame(pendingIntent, keyIdResult.getKeySelectionPendingIntent());
|
||||||
resultIntent.getIntExtra(OpenPgpApi.RESULT_CODE, Integer.MAX_VALUE));
|
|
||||||
assertEquals(pendingIntent, resultIntent.getParcelableExtra(OpenPgpApi.RESULT_INTENT));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -134,33 +138,13 @@ public class OpenPgpServiceKeyIdExtractorTest {
|
||||||
setupPendingIntentFactoryResult(pendingIntent);
|
setupPendingIntentFactoryResult(pendingIntent);
|
||||||
|
|
||||||
|
|
||||||
KeyIdResult keyIdResult = openPgpServiceKeyIdExtractor.returnKeyIdsFromIntent(intent, true);
|
KeyIdResult keyIdResult = openPgpServiceKeyIdExtractor.returnKeyIdsFromIntent(intent, true,
|
||||||
|
BuildConfig.APPLICATION_ID);
|
||||||
|
|
||||||
|
|
||||||
assertTrue(keyIdResult.hasResultIntent());
|
assertEquals(KeyIdResultStatus.NO_KEYS, keyIdResult.getStatus());
|
||||||
Intent resultIntent = keyIdResult.getResultIntent();
|
assertTrue(keyIdResult.hasKeySelectionPendingIntent());
|
||||||
assertEquals(OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED,
|
assertSame(pendingIntent, keyIdResult.getKeySelectionPendingIntent());
|
||||||
resultIntent.getIntExtra(OpenPgpApi.RESULT_CODE, Integer.MAX_VALUE));
|
|
||||||
assertEquals(pendingIntent, resultIntent.getParcelableExtra(OpenPgpApi.RESULT_INTENT));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void returnKeyIdsFromIntent__withUserIds__withEmptyQueryResult__inOpportunisticMode() throws Exception {
|
|
||||||
Intent intent = new Intent();
|
|
||||||
intent.putExtra(OpenPgpApi.EXTRA_USER_IDS, USER_IDS);
|
|
||||||
intent.putExtra(OpenPgpApi.EXTRA_OPPORTUNISTIC_ENCRYPTION, true);
|
|
||||||
|
|
||||||
setupContentResolverResult();
|
|
||||||
|
|
||||||
|
|
||||||
KeyIdResult keyIdResult = openPgpServiceKeyIdExtractor.returnKeyIdsFromIntent(intent, false);
|
|
||||||
|
|
||||||
|
|
||||||
assertTrue(keyIdResult.hasResultIntent());
|
|
||||||
Intent resultIntent = keyIdResult.getResultIntent();
|
|
||||||
assertEquals(OpenPgpApi.RESULT_CODE_ERROR, resultIntent.getIntExtra(OpenPgpApi.RESULT_CODE, Integer.MAX_VALUE));
|
|
||||||
assertEquals(OpenPgpError.OPPORTUNISTIC_MISSING_KEYS,
|
|
||||||
resultIntent.<OpenPgpError>getParcelableExtra(OpenPgpApi.RESULT_ERROR).getErrorId());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -168,16 +152,15 @@ public class OpenPgpServiceKeyIdExtractorTest {
|
||||||
Intent intent = new Intent();
|
Intent intent = new Intent();
|
||||||
intent.putExtra(OpenPgpApi.EXTRA_USER_IDS, USER_IDS);
|
intent.putExtra(OpenPgpApi.EXTRA_USER_IDS, USER_IDS);
|
||||||
|
|
||||||
setupContentResolverResult(new long[][] {
|
setupContentResolverResult(USER_IDS, new Long[] { 123L, 234L }, new int[] { 0, 0 });
|
||||||
new long[] { 123L },
|
|
||||||
new long[] { 234L }
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
KeyIdResult keyIdResult = openPgpServiceKeyIdExtractor.returnKeyIdsFromIntent(intent, false);
|
KeyIdResult keyIdResult = openPgpServiceKeyIdExtractor.returnKeyIdsFromIntent(intent, false,
|
||||||
|
BuildConfig.APPLICATION_ID);
|
||||||
|
|
||||||
|
|
||||||
assertFalse(keyIdResult.hasResultIntent());
|
assertEquals(KeyIdResultStatus.OK, keyIdResult.getStatus());
|
||||||
|
assertFalse(keyIdResult.hasKeySelectionPendingIntent());
|
||||||
assertArrayEqualsSorted(KEY_IDS, keyIdResult.getKeyIds());
|
assertArrayEqualsSorted(KEY_IDS, keyIdResult.getKeyIds());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -186,23 +169,20 @@ public class OpenPgpServiceKeyIdExtractorTest {
|
||||||
Intent intent = new Intent();
|
Intent intent = new Intent();
|
||||||
intent.putExtra(OpenPgpApi.EXTRA_USER_IDS, USER_IDS);
|
intent.putExtra(OpenPgpApi.EXTRA_USER_IDS, USER_IDS);
|
||||||
|
|
||||||
setupContentResolverResult(new long[][] {
|
setupContentResolverResult(new String[] {
|
||||||
new long[] { 123L, 345L },
|
USER_IDS[0], USER_IDS[0], USER_IDS[1]
|
||||||
new long[] { 234L }
|
}, new Long[] { 123L, 345L, 234L }, new int[] { 0, 0, 0 });
|
||||||
});
|
|
||||||
|
|
||||||
PendingIntent pendingIntent = mock(PendingIntent.class);
|
PendingIntent pendingIntent = mock(PendingIntent.class);
|
||||||
setupPendingIntentFactoryResult(pendingIntent);
|
setupPendingIntentFactoryResult(pendingIntent);
|
||||||
|
|
||||||
|
|
||||||
KeyIdResult keyIdResult = openPgpServiceKeyIdExtractor.returnKeyIdsFromIntent(intent, false);
|
KeyIdResult keyIdResult = openPgpServiceKeyIdExtractor.returnKeyIdsFromIntent(intent, false,
|
||||||
|
BuildConfig.APPLICATION_ID);
|
||||||
|
|
||||||
|
|
||||||
assertTrue(keyIdResult.hasResultIntent());
|
assertEquals(KeyIdResultStatus.DUPLICATE, keyIdResult.getStatus());
|
||||||
Intent resultIntent = keyIdResult.getResultIntent();
|
assertTrue(keyIdResult.hasKeySelectionPendingIntent());
|
||||||
assertEquals(OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED,
|
|
||||||
resultIntent.getIntExtra(OpenPgpApi.RESULT_CODE, Integer.MAX_VALUE));
|
|
||||||
assertEquals(pendingIntent, resultIntent.getParcelableExtra(OpenPgpApi.RESULT_INTENT));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -210,46 +190,36 @@ public class OpenPgpServiceKeyIdExtractorTest {
|
||||||
Intent intent = new Intent();
|
Intent intent = new Intent();
|
||||||
intent.putExtra(OpenPgpApi.EXTRA_USER_IDS, USER_IDS);
|
intent.putExtra(OpenPgpApi.EXTRA_USER_IDS, USER_IDS);
|
||||||
|
|
||||||
setupContentResolverResult(new long[][] {
|
setupContentResolverResult(USER_IDS, new Long[] { null, 234L }, new int[] { 0, 0 });
|
||||||
new long[] { },
|
|
||||||
new long[] { 234L }
|
|
||||||
});
|
|
||||||
|
|
||||||
PendingIntent pendingIntent = mock(PendingIntent.class);
|
PendingIntent pendingIntent = mock(PendingIntent.class);
|
||||||
setupPendingIntentFactoryResult(pendingIntent);
|
setupPendingIntentFactoryResult(pendingIntent);
|
||||||
|
|
||||||
|
|
||||||
KeyIdResult keyIdResult = openPgpServiceKeyIdExtractor.returnKeyIdsFromIntent(intent, false);
|
KeyIdResult keyIdResult = openPgpServiceKeyIdExtractor.returnKeyIdsFromIntent(intent, false,
|
||||||
|
BuildConfig.APPLICATION_ID);
|
||||||
|
|
||||||
|
|
||||||
assertTrue(keyIdResult.hasResultIntent());
|
assertEquals(KeyIdResultStatus.MISSING, keyIdResult.getStatus());
|
||||||
Intent resultIntent = keyIdResult.getResultIntent();
|
assertTrue(keyIdResult.hasKeySelectionPendingIntent());
|
||||||
assertEquals(OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED,
|
|
||||||
resultIntent.getIntExtra(OpenPgpApi.RESULT_CODE, Integer.MAX_VALUE));
|
|
||||||
assertEquals(pendingIntent, resultIntent.getParcelableExtra(OpenPgpApi.RESULT_INTENT));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupContentResolverResult() {
|
private void setupContentResolverResult() {
|
||||||
MatrixCursor resultCursor = new MatrixCursor(OpenPgpServiceKeyIdExtractor.KEY_SEARCH_PROJECTION);
|
MatrixCursor resultCursor = new MatrixCursor(OpenPgpServiceKeyIdExtractor.PROJECTION_KEY_SEARCH);
|
||||||
when(contentResolver.query(
|
when(contentResolver.query(
|
||||||
any(Uri.class), any(String[].class), any(String.class), any(String[].class), any(String.class)))
|
any(Uri.class), any(String[].class), any(String.class), any(String[].class), any(String.class)))
|
||||||
.thenReturn(resultCursor);
|
.thenReturn(resultCursor);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupContentResolverResult(long[][] resultKeyIds) {
|
private void setupContentResolverResult(String[] userIds, Long[] resultKeyIds, int[] verified) {
|
||||||
Cursor[] resultCursors = new MatrixCursor[resultKeyIds.length];
|
MatrixCursor resultCursor = new MatrixCursor(OpenPgpServiceKeyIdExtractor.PROJECTION_KEY_SEARCH);
|
||||||
for (int i = 0; i < resultKeyIds.length; i++) {
|
for (int i = 0; i < userIds.length; i++) {
|
||||||
MatrixCursor resultCursor = new MatrixCursor(OpenPgpServiceKeyIdExtractor.KEY_SEARCH_PROJECTION);
|
resultCursor.addRow(new Object[] { userIds[i], resultKeyIds[i], verified[i] });
|
||||||
for (long keyId : resultKeyIds[i]) {
|
|
||||||
resultCursor.addRow(new Object[] { keyId, keyId, 0L, 0L });
|
|
||||||
}
|
|
||||||
|
|
||||||
resultCursors[i] = resultCursor;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
when(contentResolver.query(
|
when(contentResolver.query(
|
||||||
any(Uri.class), any(String[].class), any(String.class), any(String[].class), any(String.class)))
|
any(Uri.class), any(String[].class), any(String.class), any(String[].class), any(String.class)))
|
||||||
.thenReturn(resultCursors[0], Arrays.copyOfRange(resultCursors, 1, resultCursors.length));
|
.thenReturn(resultCursor);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupPendingIntentFactoryResult(PendingIntent pendingIntent) {
|
private void setupPendingIntentFactoryResult(PendingIntent pendingIntent) {
|
||||||
|
|
2
extern/openpgp-api-lib
vendored
2
extern/openpgp-api-lib
vendored
|
@ -1 +1 @@
|
||||||
Subproject commit 5aa2affb752c066232bd7e7bb7c22739fa9f3526
|
Subproject commit f28cb92944efbf56fa0582769a97b23c4272f6ac
|
Loading…
Reference in a new issue