1130 lines
53 KiB
Java
1130 lines
53 KiB
Java
/*
|
|
* Copyright (C) 2017 Schürmann & Breitmoser GbR
|
|
*
|
|
* 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.remote;
|
|
|
|
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.io.OutputStream;
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.Collections;
|
|
import java.util.Date;
|
|
import java.util.HashSet;
|
|
import java.util.List;
|
|
|
|
import android.app.PendingIntent;
|
|
import android.app.Service;
|
|
import android.content.Intent;
|
|
import android.os.Bundle;
|
|
import android.os.IBinder;
|
|
import android.os.Message;
|
|
import android.os.Messenger;
|
|
import android.os.ParcelFileDescriptor;
|
|
import android.os.RemoteException;
|
|
import android.os.SystemClock;
|
|
import androidx.annotation.NonNull;
|
|
import androidx.annotation.Nullable;
|
|
import android.text.TextUtils;
|
|
|
|
import org.bouncycastle.bcpg.ArmoredOutputStream;
|
|
import org.openintents.openpgp.AutocryptPeerUpdate;
|
|
import org.openintents.openpgp.IOpenPgpService;
|
|
import org.openintents.openpgp.OpenPgpDecryptionResult;
|
|
import org.openintents.openpgp.OpenPgpError;
|
|
import org.openintents.openpgp.OpenPgpMetadata;
|
|
import org.openintents.openpgp.OpenPgpSignatureResult;
|
|
import org.openintents.openpgp.OpenPgpSignatureResult.AutocryptPeerResult;
|
|
import org.openintents.openpgp.util.OpenPgpApi;
|
|
import org.sufficientlysecure.keychain.Constants;
|
|
import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo;
|
|
import org.sufficientlysecure.keychain.KeychainApplication;
|
|
import org.sufficientlysecure.keychain.analytics.AnalyticsManager;
|
|
import org.sufficientlysecure.keychain.operations.BackupOperation;
|
|
import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
|
|
import org.sufficientlysecure.keychain.operations.results.ExportResult;
|
|
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogEntryParcel;
|
|
import org.sufficientlysecure.keychain.operations.results.PgpSignEncryptResult;
|
|
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
|
|
import org.sufficientlysecure.keychain.pgp.DecryptVerifySecurityProblem;
|
|
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel;
|
|
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyOperation;
|
|
import org.sufficientlysecure.keychain.pgp.PgpSecurityConstants.OpenKeychainCompressionAlgorithmTags;
|
|
import org.sufficientlysecure.keychain.pgp.PgpSignEncryptData;
|
|
import org.sufficientlysecure.keychain.pgp.PgpSignEncryptOperation;
|
|
import org.sufficientlysecure.keychain.pgp.Progressable;
|
|
import org.sufficientlysecure.keychain.pgp.SecurityProblem;
|
|
import org.sufficientlysecure.keychain.daos.ApiAppDao;
|
|
import org.sufficientlysecure.keychain.daos.AutocryptPeerDao;
|
|
import org.sufficientlysecure.keychain.daos.KeyRepository;
|
|
import org.sufficientlysecure.keychain.daos.KeyRepository.NotFoundException;
|
|
import org.sufficientlysecure.keychain.provider.KeychainExternalContract.AutocryptStatus;
|
|
import org.sufficientlysecure.keychain.daos.OverriddenWarningsDao;
|
|
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.input.CryptoInputParcel;
|
|
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
|
|
import org.sufficientlysecure.keychain.util.InputData;
|
|
import org.sufficientlysecure.keychain.util.Numeric9x4PassphraseUtil;
|
|
import org.sufficientlysecure.keychain.util.Passphrase;
|
|
import timber.log.Timber;
|
|
|
|
|
|
public class OpenPgpService extends Service {
|
|
public static final int API_VERSION_WITH_KEY_INVALID_INSECURE = 8;
|
|
public static final int API_VERSION_WITHOUT_SIGNATURE_ONLY_FLAG = 8;
|
|
public static final int API_VERSION_WITH_DECRYPTION_RESULT = 8;
|
|
public static final int API_VERSION_WITH_RESULT_NO_SIGNATURE = 8;
|
|
public static final int API_VERSION_WITH_AUTOCRYPT = 12;
|
|
|
|
public static final List<Integer> SUPPORTED_VERSIONS =
|
|
Collections.unmodifiableList(Arrays.asList(7, 8, 9, 10, 11, 12));
|
|
|
|
private ApiPermissionHelper mApiPermissionHelper;
|
|
private KeyRepository mKeyRepository;
|
|
private ApiAppDao mApiAppDao;
|
|
private OpenPgpServiceKeyIdExtractor mKeyIdExtractor;
|
|
private ApiPendingIntentFactory mApiPendingIntentFactory;
|
|
private AnalyticsManager analyticsManager;
|
|
|
|
@Override
|
|
public void onCreate() {
|
|
super.onCreate();
|
|
mKeyRepository = KeyRepository.create(this);
|
|
mApiAppDao = ApiAppDao.getInstance(this);
|
|
mApiPermissionHelper = new ApiPermissionHelper(this, mApiAppDao);
|
|
mApiPendingIntentFactory = new ApiPendingIntentFactory(getBaseContext());
|
|
mKeyIdExtractor = OpenPgpServiceKeyIdExtractor.getInstance(getContentResolver(), mApiPendingIntentFactory);
|
|
|
|
analyticsManager = ((KeychainApplication) getApplication()).getAnalyticsManager();
|
|
}
|
|
|
|
private Intent signImpl(Intent data, InputStream inputStream,
|
|
OutputStream outputStream, boolean cleartextSign) {
|
|
try {
|
|
boolean asciiArmor = cleartextSign || data.getBooleanExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
|
|
|
|
// sign-only
|
|
PgpSignEncryptData.Builder pgpData = PgpSignEncryptData.builder();
|
|
pgpData.setEnableAsciiArmorOutput(asciiArmor)
|
|
.setCleartextSignature(cleartextSign)
|
|
.setDetachedSignature(!cleartextSign)
|
|
.setVersionHeader(null);
|
|
|
|
|
|
Intent signKeyIdIntent = getSignKeyMasterId(data);
|
|
// NOTE: Fallback to return account settings (Old API)
|
|
if (signKeyIdIntent.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS)
|
|
== OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED) {
|
|
return signKeyIdIntent;
|
|
}
|
|
|
|
long signKeyId = signKeyIdIntent.getLongExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, Constants.key.none);
|
|
if (signKeyId == Constants.key.none) {
|
|
throw new Exception("No signing key given");
|
|
} else {
|
|
pgpData.setSignatureMasterKeyId(signKeyId);
|
|
|
|
// get first usable subkey capable of signing
|
|
try {
|
|
long signSubKeyId = mKeyRepository.getSecretSignId(signKeyId);
|
|
pgpData.setSignatureSubKeyId(signSubKeyId);
|
|
} catch (NotFoundException e) {
|
|
throw new Exception("signing subkey not found!", e);
|
|
}
|
|
}
|
|
pgpData.setAllowedSigningKeyIds(getAllowedKeyIds());
|
|
|
|
// Get Input- and OutputStream from ParcelFileDescriptor
|
|
if (!cleartextSign) {
|
|
// output stream only needed for cleartext signatures,
|
|
// detached signatures are returned as extra
|
|
outputStream = null;
|
|
}
|
|
long inputLength = inputStream.available();
|
|
InputData inputData = new InputData(inputStream, inputLength);
|
|
|
|
CryptoInputParcel inputParcel = CryptoInputParcelCacheService.getCryptoInputParcel(this, data);
|
|
if (inputParcel == null) {
|
|
inputParcel = CryptoInputParcel.createCryptoInputParcel(new Date());
|
|
}
|
|
// override passphrase in input parcel if given by API call
|
|
if (data.hasExtra(OpenPgpApi.EXTRA_PASSPHRASE)) {
|
|
inputParcel = inputParcel.withPassphrase(
|
|
new Passphrase(data.getCharArrayExtra(OpenPgpApi.EXTRA_PASSPHRASE)), null);
|
|
}
|
|
|
|
// execute PGP operation!
|
|
PgpSignEncryptOperation pse = new PgpSignEncryptOperation(this, mKeyRepository, null);
|
|
PgpSignEncryptResult pgpResult = pse.execute(pgpData.build(), inputParcel, inputData, outputStream);
|
|
|
|
if (pgpResult.isPending()) {
|
|
RequiredInputParcel requiredInput = pgpResult.getRequiredInputParcel();
|
|
PendingIntent pIntent = mApiPendingIntentFactory.requiredInputPi(data,
|
|
requiredInput, pgpResult.mCryptoInputParcel);
|
|
|
|
// return PendingIntent to be executed by client
|
|
Intent result = new Intent();
|
|
result.putExtra(OpenPgpApi.RESULT_INTENT, pIntent);
|
|
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED);
|
|
return result;
|
|
|
|
} else if (pgpResult.success()) {
|
|
Intent result = new Intent();
|
|
if (pgpResult.getDetachedSignature() != null && !cleartextSign) {
|
|
result.putExtra(OpenPgpApi.RESULT_DETACHED_SIGNATURE, pgpResult.getDetachedSignature());
|
|
result.putExtra(OpenPgpApi.RESULT_SIGNATURE_MICALG, pgpResult.getMicAlgDigestName());
|
|
}
|
|
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
|
|
return result;
|
|
} else {
|
|
LogEntryParcel errorMsg = pgpResult.getLog().getLast();
|
|
throw new Exception(getString(errorMsg.mType.getMsgId()));
|
|
}
|
|
} catch (Exception e) {
|
|
Timber.d(e, "signImpl");
|
|
return createErrorResultIntent(OpenPgpError.GENERIC_ERROR, e.getMessage());
|
|
}
|
|
}
|
|
|
|
private Intent autocryptQueryImpl(Intent data) {
|
|
try {
|
|
KeyIdResult keyIdResult = mKeyIdExtractor.returnKeyIdsFromIntent(data, false,
|
|
mApiPermissionHelper.getCurrentCallingPackage());
|
|
Intent resultIntent = getAutocryptStatusResult(keyIdResult);
|
|
|
|
return resultIntent;
|
|
} catch (Exception e) {
|
|
Timber.d(e, "encryptAndSignImpl");
|
|
return createErrorResultIntent(OpenPgpError.GENERIC_ERROR, e.getMessage());
|
|
}
|
|
}
|
|
|
|
private Intent encryptAndSignImpl(Intent data, InputStream inputStream,
|
|
OutputStream outputStream, boolean sign) {
|
|
try {
|
|
PgpSignEncryptData.Builder pgpData = PgpSignEncryptData.builder()
|
|
.setVersionHeader(null);
|
|
|
|
if (sign) {
|
|
Intent signKeyIdIntent = getSignKeyMasterId(data);
|
|
// NOTE: Fallback to return account settings (Old API)
|
|
if (signKeyIdIntent.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)
|
|
== OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED) {
|
|
return signKeyIdIntent;
|
|
}
|
|
|
|
long signKeyId = signKeyIdIntent.getLongExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, Constants.key.none);
|
|
if (signKeyId == Constants.key.none) {
|
|
throw new Exception("No signing key given");
|
|
}
|
|
long signSubKeyId = mKeyRepository.getSecretSignId(signKeyId);
|
|
|
|
pgpData.setSignatureMasterKeyId(signKeyId)
|
|
.setSignatureSubKeyId(signSubKeyId)
|
|
.setAdditionalEncryptId(signKeyId);
|
|
}
|
|
|
|
KeyIdResult keyIdResult = mKeyIdExtractor.returnKeyIdsFromIntent(data, false,
|
|
mApiPermissionHelper.getCurrentCallingPackage());
|
|
|
|
KeyIdResultStatus keyIdResultStatus = keyIdResult.getStatus();
|
|
|
|
boolean asciiArmor = data.getBooleanExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
|
|
pgpData.setEnableAsciiArmorOutput(asciiArmor);
|
|
|
|
boolean enableCompression = data.getBooleanExtra(OpenPgpApi.EXTRA_ENABLE_COMPRESSION, true);
|
|
pgpData.setCompressionAlgorithm(enableCompression ? OpenKeychainCompressionAlgorithmTags.USE_DEFAULT :
|
|
OpenKeychainCompressionAlgorithmTags.UNCOMPRESSED);
|
|
|
|
String originalFilename = data.getStringExtra(OpenPgpApi.EXTRA_ORIGINAL_FILENAME);
|
|
if (originalFilename == null) {
|
|
originalFilename = "";
|
|
}
|
|
|
|
if (keyIdResult.hasKeySelectionPendingIntent()) {
|
|
boolean isOpportunistic = data.getBooleanExtra(OpenPgpApi.EXTRA_OPPORTUNISTIC_ENCRYPTION, false);
|
|
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");
|
|
}
|
|
|
|
Intent result = new Intent();
|
|
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED);
|
|
result.putExtra(OpenPgpApi.RESULT_INTENT, keyIdResult.getKeySelectionPendingIntent());
|
|
return result;
|
|
}
|
|
pgpData.setEncryptionMasterKeyIds(keyIdResult.getKeyIds());
|
|
pgpData.setAllowedSigningKeyIds(getAllowedKeyIds());
|
|
|
|
CryptoInputParcel inputParcel = CryptoInputParcelCacheService.getCryptoInputParcel(this, data);
|
|
if (inputParcel == null) {
|
|
inputParcel = CryptoInputParcel.createCryptoInputParcel(new Date());
|
|
}
|
|
// override passphrase in input parcel if given by API call
|
|
if (data.hasExtra(OpenPgpApi.EXTRA_PASSPHRASE)) {
|
|
inputParcel = inputParcel.withPassphrase(
|
|
new Passphrase(data.getCharArrayExtra(OpenPgpApi.EXTRA_PASSPHRASE)), null);
|
|
}
|
|
|
|
// TODO this is not correct!
|
|
long inputLength = inputStream.available();
|
|
InputData inputData = new InputData(inputStream, inputLength, originalFilename);
|
|
|
|
// execute PGP operation!
|
|
PgpSignEncryptOperation op = new PgpSignEncryptOperation(this, mKeyRepository, null);
|
|
PgpSignEncryptResult pgpResult = op.execute(pgpData.build(), inputParcel, inputData, outputStream);
|
|
|
|
if (pgpResult.isPending()) {
|
|
RequiredInputParcel requiredInput = pgpResult.getRequiredInputParcel();
|
|
PendingIntent pIntent = mApiPendingIntentFactory.requiredInputPi(data,
|
|
requiredInput, pgpResult.mCryptoInputParcel);
|
|
|
|
// return PendingIntent to be executed by client
|
|
Intent result = new Intent();
|
|
result.putExtra(OpenPgpApi.RESULT_INTENT, pIntent);
|
|
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED);
|
|
return result;
|
|
} else if (pgpResult.success()) {
|
|
Intent result = new Intent();
|
|
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
|
|
return result;
|
|
} else {
|
|
LogEntryParcel errorMsg = pgpResult.getLog().getLast();
|
|
throw new Exception(getString(errorMsg.mType.getMsgId()));
|
|
}
|
|
} catch (Exception e) {
|
|
Timber.d(e, "encryptAndSignImpl");
|
|
return createErrorResultIntent(OpenPgpError.GENERIC_ERROR, e.getMessage());
|
|
}
|
|
}
|
|
|
|
@NonNull
|
|
private Intent getAutocryptStatusResult(KeyIdResult keyIdResult) {
|
|
Intent result = new Intent();
|
|
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
|
|
result.putExtra(OpenPgpApi.RESULT_KEYS_CONFIRMED, keyIdResult.isAllKeysConfirmed());
|
|
|
|
int combinedAutocryptState = keyIdResult.getAutocryptRecommendation();
|
|
if (combinedAutocryptState == AutocryptStatus.AUTOCRYPT_PEER_DISABLED) {
|
|
switch (keyIdResult.getStatus()) {
|
|
case NO_KEYS:
|
|
case NO_KEYS_ERROR:
|
|
case MISSING: {
|
|
result.putExtra(OpenPgpApi.RESULT_AUTOCRYPT_STATUS, OpenPgpApi.AUTOCRYPT_STATUS_UNAVAILABLE);
|
|
break;
|
|
}
|
|
case DUPLICATE: {
|
|
if (keyIdResult.hasKeySelectionPendingIntent()) {
|
|
result.putExtra(OpenPgpApi.RESULT_INTENT, keyIdResult.getKeySelectionPendingIntent());
|
|
}
|
|
result.putExtra(OpenPgpApi.RESULT_AUTOCRYPT_STATUS, OpenPgpApi.AUTOCRYPT_STATUS_DISCOURAGE);
|
|
break;
|
|
}
|
|
case OK: {
|
|
result.putExtra(OpenPgpApi.RESULT_AUTOCRYPT_STATUS, OpenPgpApi.AUTOCRYPT_STATUS_DISCOURAGE);
|
|
break;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
switch (combinedAutocryptState) {
|
|
case AutocryptStatus.AUTOCRYPT_PEER_DISCOURAGED_OLD:
|
|
case AutocryptStatus.AUTOCRYPT_PEER_GOSSIP: {
|
|
result.putExtra(OpenPgpApi.RESULT_AUTOCRYPT_STATUS, OpenPgpApi.AUTOCRYPT_STATUS_DISCOURAGE);
|
|
break;
|
|
}
|
|
case AutocryptStatus.AUTOCRYPT_PEER_AVAILABLE_EXTERNAL:
|
|
case AutocryptStatus.AUTOCRYPT_PEER_AVAILABLE: {
|
|
result.putExtra(OpenPgpApi.RESULT_AUTOCRYPT_STATUS, OpenPgpApi.AUTOCRYPT_STATUS_AVAILABLE);
|
|
break;
|
|
}
|
|
case AutocryptStatus.AUTOCRYPT_PEER_MUTUAL: {
|
|
result.putExtra(OpenPgpApi.RESULT_AUTOCRYPT_STATUS, OpenPgpApi.AUTOCRYPT_STATUS_MUTUAL);
|
|
break;
|
|
}
|
|
default: {
|
|
throw new IllegalStateException("unhandled case!");
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private Intent decryptAndVerifyImpl(Intent data, InputStream inputStream, OutputStream outputStream,
|
|
boolean decryptMetadataOnly, Progressable progressable) {
|
|
try {
|
|
// output is optional, e.g., for verifying detached signatures
|
|
if (decryptMetadataOnly) {
|
|
outputStream = null;
|
|
}
|
|
|
|
int targetApiVersion = data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1);
|
|
|
|
CryptoInputParcel cryptoInput = CryptoInputParcelCacheService.getCryptoInputParcel(this, data);
|
|
if (cryptoInput == null) {
|
|
cryptoInput = CryptoInputParcel.createCryptoInputParcel();
|
|
}
|
|
// override passphrase in input parcel if given by API call
|
|
if (data.hasExtra(OpenPgpApi.EXTRA_PASSPHRASE)) {
|
|
cryptoInput = cryptoInput.withPassphrase(
|
|
new Passphrase(data.getCharArrayExtra(OpenPgpApi.EXTRA_PASSPHRASE)), null);
|
|
}
|
|
if (data.hasExtra(OpenPgpApi.EXTRA_DECRYPTION_RESULT)) {
|
|
OpenPgpDecryptionResult decryptionResult = data.getParcelableExtra(OpenPgpApi.EXTRA_DECRYPTION_RESULT);
|
|
if (decryptionResult != null && decryptionResult.hasDecryptedSessionKey()) {
|
|
cryptoInput = cryptoInput.withCryptoData(
|
|
decryptionResult.getSessionKey(), decryptionResult.getDecryptedSessionKey());
|
|
}
|
|
}
|
|
|
|
byte[] detachedSignature = data.getByteArrayExtra(OpenPgpApi.EXTRA_DETACHED_SIGNATURE);
|
|
String senderAddress = data.getStringExtra(OpenPgpApi.EXTRA_SENDER_ADDRESS);
|
|
|
|
updateAutocryptPeerImpl(data);
|
|
|
|
PgpDecryptVerifyOperation op = new PgpDecryptVerifyOperation(this, mKeyRepository, progressable);
|
|
|
|
long inputLength = data.getLongExtra(OpenPgpApi.EXTRA_DATA_LENGTH, InputData.UNKNOWN_FILESIZE);
|
|
InputData inputData = new InputData(inputStream, inputLength);
|
|
|
|
// allow only private keys associated with accounts of this app
|
|
// no support for symmetric encryption
|
|
PgpDecryptVerifyInputParcel input = PgpDecryptVerifyInputParcel.builder()
|
|
.setAllowSymmetricDecryption(false)
|
|
.setAllowedKeyIds(new ArrayList<>(getAllowedKeyIds()))
|
|
.setDecryptMetadataOnly(decryptMetadataOnly)
|
|
.setDetachedSignature(detachedSignature)
|
|
.setSenderAddress(senderAddress)
|
|
.build();
|
|
|
|
DecryptVerifyResult pgpResult = op.execute(input, cryptoInput, inputData, outputStream);
|
|
|
|
if (pgpResult.isPending()) {
|
|
// prepare and return PendingIntent to be executed by client
|
|
RequiredInputParcel requiredInput = pgpResult.getRequiredInputParcel();
|
|
PendingIntent pIntent = mApiPendingIntentFactory.requiredInputPi(data,
|
|
requiredInput, pgpResult.mCryptoInputParcel);
|
|
|
|
Intent result = new Intent();
|
|
result.putExtra(OpenPgpApi.RESULT_INTENT, pIntent);
|
|
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED);
|
|
return result;
|
|
|
|
} else if (pgpResult.success()) {
|
|
Intent result = new Intent();
|
|
|
|
processDecryptionResultForResultIntent(targetApiVersion, result, pgpResult.getDecryptionResult());
|
|
processMetadataForResultIntent(result, pgpResult.getDecryptionMetadata());
|
|
processSignatureResultForResultIntent(targetApiVersion, data, result, pgpResult);
|
|
processSecurityProblemsPendingIntent(data, result, pgpResult);
|
|
|
|
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
|
|
return result;
|
|
} else {
|
|
long[] skippedDisallowedEncryptionKeys = pgpResult.getSkippedDisallowedKeys();
|
|
if (pgpResult.isKeysDisallowed() &&
|
|
skippedDisallowedEncryptionKeys != null && skippedDisallowedEncryptionKeys.length > 0) {
|
|
// allow user to select allowed keys
|
|
Intent result = new Intent();
|
|
String packageName = mApiPermissionHelper.getCurrentCallingPackage();
|
|
result.putExtra(OpenPgpApi.RESULT_INTENT,
|
|
mApiPendingIntentFactory.createRequestKeyPermissionPendingIntent(
|
|
data, packageName, skippedDisallowedEncryptionKeys));
|
|
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED);
|
|
return result;
|
|
}
|
|
|
|
String errorMsg = getString(pgpResult.getLog().getLast().mType.getMsgId());
|
|
return createErrorResultIntent(OpenPgpError.GENERIC_ERROR, errorMsg);
|
|
}
|
|
|
|
} catch (Exception e) {
|
|
Timber.e(e, "decryptAndVerifyImpl");
|
|
return createErrorResultIntent(OpenPgpError.GENERIC_ERROR, e.getMessage());
|
|
}
|
|
}
|
|
|
|
private void processSecurityProblemsPendingIntent(Intent data, Intent result,
|
|
DecryptVerifyResult decryptVerifyResult) {
|
|
DecryptVerifySecurityProblem securityProblem = decryptVerifyResult.getSecurityProblem();
|
|
if (securityProblem == null) {
|
|
return;
|
|
}
|
|
|
|
boolean supportOverride = data.getBooleanExtra(OpenPgpApi.EXTRA_SUPPORT_OVERRIDE_CRYPTO_WARNING, false);
|
|
if (supportOverride) {
|
|
SecurityProblem prioritySecurityProblem = securityProblem.getPrioritySecurityProblem();
|
|
if (prioritySecurityProblem.isIdentifiable()) {
|
|
String identifier = prioritySecurityProblem.getIdentifier();
|
|
boolean isOverridden = OverriddenWarningsDao.create(this)
|
|
.isWarningOverridden(identifier);
|
|
result.putExtra(OpenPgpApi.RESULT_OVERRIDE_CRYPTO_WARNING, isOverridden);
|
|
}
|
|
}
|
|
|
|
String packageName = mApiPermissionHelper.getCurrentCallingPackage();
|
|
result.putExtra(OpenPgpApi.RESULT_INSECURE_DETAIL_INTENT,
|
|
mApiPendingIntentFactory.createSecurityProblemIntent(packageName, securityProblem, supportOverride));
|
|
}
|
|
|
|
private void processDecryptionResultForResultIntent(int targetApiVersion, Intent result,
|
|
OpenPgpDecryptionResult decryptionResult) {
|
|
if (targetApiVersion < API_VERSION_WITH_DECRYPTION_RESULT) {
|
|
return;
|
|
}
|
|
|
|
if (decryptionResult != null) {
|
|
result.putExtra(OpenPgpApi.RESULT_DECRYPTION, decryptionResult);
|
|
}
|
|
}
|
|
|
|
private OpenPgpSignatureResult getSignatureResultWithApiCompatibilityFallbacks(
|
|
int targetApiVersion, DecryptVerifyResult pgpResult) {
|
|
OpenPgpSignatureResult signatureResult = pgpResult.getSignatureResult();
|
|
|
|
if (targetApiVersion < API_VERSION_WITH_KEY_INVALID_INSECURE) {
|
|
// RESULT_INVALID_INSECURE has been added in version 8, fallback to RESULT_INVALID_SIGNATURE
|
|
if (signatureResult.getResult() == OpenPgpSignatureResult.RESULT_INVALID_KEY_INSECURE) {
|
|
signatureResult = OpenPgpSignatureResult.createWithInvalidSignature();
|
|
}
|
|
}
|
|
|
|
if (targetApiVersion < API_VERSION_WITHOUT_SIGNATURE_ONLY_FLAG) {
|
|
// this info was kept in OpenPgpSignatureResult, so put it there for compatibility
|
|
OpenPgpDecryptionResult decryptionResult = pgpResult.getDecryptionResult();
|
|
boolean signatureOnly = decryptionResult.getResult() == OpenPgpDecryptionResult.RESULT_NOT_ENCRYPTED
|
|
&& signatureResult.getResult() != OpenPgpSignatureResult.RESULT_NO_SIGNATURE;
|
|
// noinspection deprecation, this is for backwards compatibility
|
|
signatureResult = signatureResult.withSignatureOnlyFlag(signatureOnly);
|
|
}
|
|
|
|
return signatureResult;
|
|
}
|
|
|
|
private void processMetadataForResultIntent(Intent result, OpenPgpMetadata metadata) {
|
|
String charset = metadata != null ? metadata.getCharset() : null;
|
|
if (charset != null) {
|
|
result.putExtra(OpenPgpApi.RESULT_CHARSET, charset);
|
|
}
|
|
|
|
if (metadata != null) {
|
|
result.putExtra(OpenPgpApi.RESULT_METADATA, metadata);
|
|
}
|
|
}
|
|
|
|
private void processSignatureResultForResultIntent(int targetApiVersion, Intent data,
|
|
Intent result, DecryptVerifyResult pgpResult) {
|
|
OpenPgpSignatureResult signatureResult =
|
|
getSignatureResultWithApiCompatibilityFallbacks(targetApiVersion, pgpResult);
|
|
|
|
switch (signatureResult.getResult()) {
|
|
case OpenPgpSignatureResult.RESULT_KEY_MISSING: {
|
|
// If signature key is missing we return a PendingIntent to retrieve the key
|
|
result.putExtra(OpenPgpApi.RESULT_INTENT,
|
|
mApiPendingIntentFactory.createImportFromKeyserverPendingIntent(data,
|
|
signatureResult.getKeyId()));
|
|
break;
|
|
}
|
|
case OpenPgpSignatureResult.RESULT_VALID_KEY_CONFIRMED:
|
|
case OpenPgpSignatureResult.RESULT_VALID_KEY_UNCONFIRMED:
|
|
case OpenPgpSignatureResult.RESULT_INVALID_KEY_REVOKED:
|
|
case OpenPgpSignatureResult.RESULT_INVALID_KEY_EXPIRED:
|
|
case OpenPgpSignatureResult.RESULT_INVALID_KEY_INSECURE: {
|
|
// If signature key is known, return PendingIntent to show key
|
|
result.putExtra(OpenPgpApi.RESULT_INTENT,
|
|
mApiPendingIntentFactory.createShowKeyPendingIntent(data, signatureResult.getKeyId()));
|
|
break;
|
|
}
|
|
default:
|
|
case OpenPgpSignatureResult.RESULT_NO_SIGNATURE: {
|
|
if (targetApiVersion < API_VERSION_WITH_RESULT_NO_SIGNATURE) {
|
|
// RESULT_NO_SIGNATURE has been added in version 8, before the signatureResult was null
|
|
signatureResult = null;
|
|
}
|
|
// otherwise, no pending intent
|
|
}
|
|
|
|
case OpenPgpSignatureResult.RESULT_INVALID_SIGNATURE: {
|
|
// no key id -> no PendingIntent
|
|
}
|
|
}
|
|
|
|
String autocryptPeerentity = data.getStringExtra(OpenPgpApi.EXTRA_AUTOCRYPT_PEER_ID);
|
|
if (autocryptPeerentity != null) {
|
|
if (targetApiVersion < API_VERSION_WITH_AUTOCRYPT) {
|
|
throw new IllegalStateException("API version conflict, autocrypt is supported v12 and up!");
|
|
}
|
|
signatureResult = processAutocryptPeerInfoToSignatureResult(signatureResult, autocryptPeerentity);
|
|
}
|
|
|
|
result.putExtra(OpenPgpApi.RESULT_SIGNATURE, signatureResult);
|
|
}
|
|
|
|
private OpenPgpSignatureResult processAutocryptPeerInfoToSignatureResult(OpenPgpSignatureResult signatureResult,
|
|
String autocryptPeerId) {
|
|
boolean hasValidSignature =
|
|
signatureResult.getResult() == OpenPgpSignatureResult.RESULT_VALID_KEY_CONFIRMED ||
|
|
signatureResult.getResult() == OpenPgpSignatureResult.RESULT_VALID_KEY_UNCONFIRMED;
|
|
if (!hasValidSignature) {
|
|
return signatureResult;
|
|
}
|
|
|
|
AutocryptPeerDao autocryptPeerentityDao =
|
|
AutocryptPeerDao.getInstance(getBaseContext());
|
|
Long autocryptPeerMasterKeyId = autocryptPeerentityDao.getMasterKeyIdForAutocryptPeer(autocryptPeerId);
|
|
|
|
long masterKeyId = signatureResult.getKeyId();
|
|
if (autocryptPeerMasterKeyId == null) {
|
|
Date now = new Date();
|
|
Date effectiveTime = signatureResult.getSignatureTimestamp();
|
|
if (effectiveTime.after(now)) {
|
|
effectiveTime = now;
|
|
}
|
|
AutocryptInteractor autocryptInteractor =
|
|
AutocryptInteractor.getInstance(this, mApiPermissionHelper.getCurrentCallingPackage());
|
|
autocryptInteractor.updateKeyGossipFromSignature(autocryptPeerId, effectiveTime, masterKeyId);
|
|
return signatureResult.withAutocryptPeerResult(AutocryptPeerResult.NEW);
|
|
} else if (masterKeyId == autocryptPeerMasterKeyId) {
|
|
return signatureResult.withAutocryptPeerResult(AutocryptPeerResult.OK);
|
|
} else {
|
|
return signatureResult.withAutocryptPeerResult(AutocryptPeerResult.MISMATCH);
|
|
}
|
|
}
|
|
|
|
private Intent getKeyImpl(Intent data, OutputStream outputStream) {
|
|
try {
|
|
long masterKeyId;
|
|
if (data.hasExtra(OpenPgpApi.EXTRA_KEY_ID)) {
|
|
masterKeyId = data.getLongExtra(OpenPgpApi.EXTRA_KEY_ID, 0);
|
|
} else if (data.hasExtra(OpenPgpApi.EXTRA_USER_ID)) {
|
|
KeyIdResult keyIdResult = mKeyIdExtractor.returnKeyIdsFromEmails(
|
|
null, new String[] { data.getStringExtra(OpenPgpApi.EXTRA_USER_ID) },
|
|
mApiPermissionHelper.getCurrentCallingPackage());
|
|
if (keyIdResult.getStatus() != KeyIdResultStatus.OK) {
|
|
Intent result = getAutocryptStatusResult(keyIdResult);
|
|
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR);
|
|
return result;
|
|
}
|
|
|
|
masterKeyId = keyIdResult.getKeyIds()[0];
|
|
} else {
|
|
throw new IllegalArgumentException("Missing argument key_id or user_id!");
|
|
}
|
|
|
|
try {
|
|
CanonicalizedPublicKeyRing keyRing =
|
|
mKeyRepository.getCanonicalizedPublicKeyRing(masterKeyId);
|
|
|
|
Intent result = new Intent();
|
|
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
|
|
|
|
if (data.getBooleanExtra(OpenPgpApi.EXTRA_MINIMIZE, false)) {
|
|
String userIdToKeep = data.getStringExtra(OpenPgpApi.EXTRA_MINIMIZE_USER_ID);
|
|
keyRing = keyRing.minimize(userIdToKeep);
|
|
}
|
|
|
|
boolean requestedKeyData = outputStream != null;
|
|
if (requestedKeyData) {
|
|
boolean requestAsciiArmor = data.getBooleanExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, false);
|
|
|
|
try {
|
|
if (requestAsciiArmor) {
|
|
outputStream = new ArmoredOutputStream(outputStream);
|
|
}
|
|
keyRing.encode(outputStream);
|
|
} finally {
|
|
try {
|
|
outputStream.close();
|
|
} catch (IOException e) {
|
|
Timber.e(e, "IOException when closing OutputStream");
|
|
}
|
|
}
|
|
}
|
|
|
|
// also return PendingIntent that opens the key view activity
|
|
result.putExtra(OpenPgpApi.RESULT_INTENT,
|
|
mApiPendingIntentFactory.createShowKeyPendingIntent(data, masterKeyId));
|
|
|
|
return result;
|
|
} catch (KeyRepository.NotFoundException e) {
|
|
// If keys are not in db we return an additional PendingIntent
|
|
// to retrieve the missing key
|
|
Intent result = new Intent();
|
|
result.putExtra(OpenPgpApi.RESULT_INTENT,
|
|
mApiPendingIntentFactory.createImportFromKeyserverPendingIntent(data, masterKeyId));
|
|
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED);
|
|
return result;
|
|
}
|
|
} catch (Exception e) {
|
|
Timber.d(e, "getKeyImpl");
|
|
return createErrorResultIntent(OpenPgpError.GENERIC_ERROR, e.getMessage());
|
|
}
|
|
}
|
|
|
|
@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;
|
|
}
|
|
|
|
/* Signing key choose dialog for older API versions. We keep it around to make sure those don't break */
|
|
private Intent getSignKeyIdImplLegacy(Intent data) {
|
|
// 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!
|
|
if (data.hasExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID)) {
|
|
long signKeyId = data.getLongExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, Constants.key.none);
|
|
|
|
Intent result = new Intent();
|
|
result.putExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, signKeyId);
|
|
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
|
|
return result;
|
|
} else {
|
|
String currentPkg = mApiPermissionHelper.getCurrentCallingPackage();
|
|
byte[] packageSignature = mApiPermissionHelper.getPackageCertificateOrError(currentPkg);
|
|
String preferredUserId = data.getStringExtra(OpenPgpApi.EXTRA_USER_ID);
|
|
|
|
PendingIntent pi = mApiPendingIntentFactory.createSelectSignKeyIdLegacyPendingIntent(
|
|
data, currentPkg, packageSignature, preferredUserId);
|
|
|
|
// return PendingIntent to be executed by client
|
|
Intent result = new Intent();
|
|
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED);
|
|
result.putExtra(OpenPgpApi.RESULT_INTENT, pi);
|
|
|
|
return result;
|
|
}
|
|
}
|
|
|
|
private Intent getSignKeyIdImpl(Intent data) {
|
|
Intent result = new Intent();
|
|
data.setAction(OpenPgpApi.ACTION_GET_SIGN_KEY_ID);
|
|
|
|
{ // return PendingIntent to be executed by client
|
|
String currentPkg = mApiPermissionHelper.getCurrentCallingPackage();
|
|
byte[] packageSignature = mApiPermissionHelper.getPackageCertificateOrError(currentPkg);
|
|
String preferredUserId = data.getStringExtra(OpenPgpApi.EXTRA_USER_ID);
|
|
PendingIntent pi;
|
|
// the new dialog doesn't really work if we don't have a user id to work with. just show the old...
|
|
if (TextUtils.isEmpty(preferredUserId)) {
|
|
pi = mApiPendingIntentFactory.createSelectSignKeyIdLegacyPendingIntent(
|
|
data, currentPkg, packageSignature, null);
|
|
} else {
|
|
boolean showAutocryptHint = data.getBooleanExtra(OpenPgpApi.EXTRA_SHOW_AUTOCRYPT_HINT, false);
|
|
pi = mApiPendingIntentFactory.createSelectSignKeyIdPendingIntent(
|
|
data, currentPkg, packageSignature, preferredUserId, showAutocryptHint);
|
|
}
|
|
result.putExtra(OpenPgpApi.RESULT_INTENT, pi);
|
|
}
|
|
|
|
long signKeyId;
|
|
if (data.hasExtra(OpenPgpApi.RESULT_SIGN_KEY_ID)) {
|
|
signKeyId = data.getLongExtra(OpenPgpApi.RESULT_SIGN_KEY_ID, Constants.key.none);
|
|
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
|
|
} else {
|
|
signKeyId = data.getLongExtra(OpenPgpApi.EXTRA_PRESELECT_KEY_ID, Constants.key.none);
|
|
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED);
|
|
}
|
|
result.putExtra(OpenPgpApi.RESULT_SIGN_KEY_ID, signKeyId);
|
|
|
|
if (signKeyId != Constants.key.none) {
|
|
UnifiedKeyInfo unifiedKeyInfo = mKeyRepository.getUnifiedKeyInfo(signKeyId);
|
|
if (unifiedKeyInfo == null) {
|
|
Timber.e("Error loading key info");
|
|
return createErrorResultIntent(OpenPgpError.GENERIC_ERROR, "Signing key not found!");
|
|
}
|
|
String userId = unifiedKeyInfo.user_id();
|
|
long creationTime = unifiedKeyInfo.creation() * 1000;
|
|
|
|
result.putExtra(OpenPgpApi.RESULT_PRIMARY_USER_ID, userId);
|
|
result.putExtra(OpenPgpApi.RESULT_KEY_CREATION_TIME, creationTime);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private Intent getKeyIdsImpl(Intent data) {
|
|
KeyIdResult keyIdResult = mKeyIdExtractor.returnKeyIdsFromIntent(data, true,
|
|
mApiPermissionHelper.getCurrentCallingPackage());
|
|
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();
|
|
|
|
Intent result = new Intent();
|
|
result.putExtra(OpenPgpApi.RESULT_KEY_IDS, keyIds);
|
|
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
|
|
return result;
|
|
}
|
|
|
|
private Intent backupImpl(Intent data, OutputStream outputStream) {
|
|
try {
|
|
long[] masterKeyIds = data.getLongArrayExtra(OpenPgpApi.EXTRA_KEY_IDS);
|
|
boolean backupSecret = data.getBooleanExtra(OpenPgpApi.EXTRA_BACKUP_SECRET, false);
|
|
boolean enableAsciiArmorOutput = data.getBooleanExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
|
|
|
|
CryptoInputParcel inputParcel = CryptoInputParcelCacheService.getCryptoInputParcel(this, data);
|
|
if (inputParcel == null) {
|
|
Intent result = new Intent();
|
|
result.putExtra(OpenPgpApi.RESULT_INTENT, mApiPendingIntentFactory.createBackupPendingIntent(data, masterKeyIds, backupSecret));
|
|
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED);
|
|
return result;
|
|
}
|
|
// after user interaction with RemoteBackupActivity,
|
|
// the backup code is cached in CryptoInputParcelCacheService, now we can proceed
|
|
|
|
BackupKeyringParcel input = BackupKeyringParcel
|
|
.create(masterKeyIds, backupSecret, true, enableAsciiArmorOutput, null);
|
|
BackupOperation op = new BackupOperation(this, mKeyRepository, null);
|
|
ExportResult pgpResult = op.execute(input, inputParcel, outputStream);
|
|
|
|
if (pgpResult.success()) {
|
|
Intent result = new Intent();
|
|
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
|
|
return result;
|
|
} else {
|
|
// should not happen normally...
|
|
String errorMsg = getString(pgpResult.getLog().getLast().mType.getMsgId());
|
|
return createErrorResultIntent(OpenPgpError.GENERIC_ERROR, errorMsg);
|
|
}
|
|
} catch (Exception e) {
|
|
Timber.d(e, "backupImpl");
|
|
return createErrorResultIntent(OpenPgpError.GENERIC_ERROR, e.getMessage());
|
|
}
|
|
}
|
|
|
|
private Intent autocryptKeyTransferImpl(Intent data, OutputStream outputStream) {
|
|
try {
|
|
long[] masterKeyIds = data.getLongArrayExtra(OpenPgpApi.EXTRA_KEY_IDS);
|
|
|
|
HashSet<Long> allowedKeyIds = getAllowedKeyIds();
|
|
for (long masterKeyId : masterKeyIds) {
|
|
if (!allowedKeyIds.contains(masterKeyId)) {
|
|
Intent result = new Intent();
|
|
String packageName = mApiPermissionHelper.getCurrentCallingPackage();
|
|
result.putExtra(OpenPgpApi.RESULT_INTENT,
|
|
mApiPendingIntentFactory.createRequestKeyPermissionPendingIntent(
|
|
data, packageName, masterKeyId));
|
|
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED);
|
|
return result;
|
|
}
|
|
}
|
|
|
|
List<String> headerLines = data.getStringArrayListExtra(OpenPgpApi.EXTRA_CUSTOM_HEADERS);
|
|
|
|
Passphrase autocryptTransferCode = Numeric9x4PassphraseUtil.generateNumeric9x4Passphrase();
|
|
CryptoInputParcel inputParcel = CryptoInputParcel.createCryptoInputParcel(autocryptTransferCode);
|
|
|
|
BackupKeyringParcel input = BackupKeyringParcel.createExportAutocryptSetupMessage(masterKeyIds, headerLines);
|
|
BackupOperation op = new BackupOperation(this, mKeyRepository, null);
|
|
ExportResult pgpResult = op.execute(input, inputParcel, outputStream);
|
|
|
|
PendingIntent displayTransferCodePendingIntent =
|
|
mApiPendingIntentFactory.createDisplayTransferCodePendingIntent(autocryptTransferCode);
|
|
|
|
if (pgpResult.success()) {
|
|
Intent result = new Intent();
|
|
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
|
|
result.putExtra(OpenPgpApi.RESULT_INTENT, displayTransferCodePendingIntent);
|
|
return result;
|
|
} else {
|
|
// should not happen normally...
|
|
String errorMsg = getString(pgpResult.getLog().getLast().mType.getMsgId());
|
|
return createErrorResultIntent(OpenPgpError.GENERIC_ERROR, errorMsg);
|
|
}
|
|
} catch (Exception e) {
|
|
Timber.d(e);
|
|
return createErrorResultIntent(OpenPgpError.GENERIC_ERROR, e.getMessage());
|
|
}
|
|
}
|
|
|
|
private Intent updateAutocryptPeerImpl(Intent data) {
|
|
try {
|
|
AutocryptInteractor autocryptInteractor = AutocryptInteractor.getInstance(
|
|
getBaseContext(), mApiPermissionHelper.getCurrentCallingPackage());
|
|
|
|
if (data.hasExtra(OpenPgpApi.EXTRA_AUTOCRYPT_PEER_ID) &&
|
|
data.hasExtra(OpenPgpApi.EXTRA_AUTOCRYPT_PEER_UPDATE)) {
|
|
String autocryptPeerId = data.getStringExtra(OpenPgpApi.EXTRA_AUTOCRYPT_PEER_ID);
|
|
AutocryptPeerUpdate autocryptPeerUpdate = data.getParcelableExtra(OpenPgpApi.EXTRA_AUTOCRYPT_PEER_UPDATE);
|
|
|
|
if (autocryptPeerUpdate != null) {
|
|
autocryptInteractor.updateAutocryptPeerState(autocryptPeerId, autocryptPeerUpdate);
|
|
}
|
|
}
|
|
|
|
if (data.hasExtra(OpenPgpApi.EXTRA_AUTOCRYPT_PEER_GOSSIP_UPDATES)) {
|
|
Bundle updates = data.getBundleExtra(OpenPgpApi.EXTRA_AUTOCRYPT_PEER_GOSSIP_UPDATES);
|
|
for (String address : updates.keySet()) {
|
|
Timber.d(Constants.TAG, "Updating gossip state: " + address);
|
|
AutocryptPeerUpdate update = updates.getParcelable(address);
|
|
if (update != null) {
|
|
autocryptInteractor.updateAutocryptPeerGossipState(address, update);
|
|
}
|
|
}
|
|
}
|
|
|
|
Intent result = new Intent();
|
|
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
|
|
return result;
|
|
} catch (Exception e) {
|
|
Timber.d(e, "exception in updateAutocryptPeerImpl");
|
|
return createErrorResultIntent(OpenPgpError.GENERIC_ERROR, e.getMessage());
|
|
}
|
|
}
|
|
|
|
private Intent checkPermissionImpl(@NonNull Intent data) {
|
|
Intent permissionIntent = mApiPermissionHelper.isAllowedOrReturnIntent(data);
|
|
if (permissionIntent != null) {
|
|
return permissionIntent;
|
|
}
|
|
Intent result = new Intent();
|
|
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
|
|
return result;
|
|
}
|
|
|
|
private Intent getSignKeyMasterId(Intent data) {
|
|
long signKeyId = data.getLongExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, Constants.key.none);
|
|
if (signKeyId == Constants.key.none) {
|
|
return getSignKeyIdImpl(data);
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
private HashSet<Long> getAllowedKeyIds() {
|
|
String currentPkg = mApiPermissionHelper.getCurrentCallingPackage();
|
|
return mApiAppDao.getAllowedKeyIdsForApp(currentPkg);
|
|
}
|
|
|
|
/**
|
|
* Check requirements:
|
|
* - params != null
|
|
* - has supported API version
|
|
* - is allowed to call the service (access has been granted)
|
|
*
|
|
* @return null if everything is okay, or a Bundle with an createErrorPendingIntent/PendingIntent
|
|
*/
|
|
private Intent checkRequirements(Intent data) {
|
|
// params Bundle is required!
|
|
if (data == null) {
|
|
Intent result = new Intent();
|
|
OpenPgpError error = new OpenPgpError(OpenPgpError.GENERIC_ERROR, "params Bundle required!");
|
|
result.putExtra(OpenPgpApi.RESULT_ERROR, error);
|
|
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR);
|
|
return result;
|
|
}
|
|
|
|
// version code is required and needs to correspond to version code of service!
|
|
// History of versions in openpgp-api's CHANGELOG.md
|
|
if (!SUPPORTED_VERSIONS.contains(data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1))) {
|
|
Intent result = new Intent();
|
|
OpenPgpError error = new OpenPgpError
|
|
(OpenPgpError.INCOMPATIBLE_API_VERSIONS, "Incompatible API versions!\n"
|
|
+ "used API version: " + data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) + "\n"
|
|
+ "supported API versions: " + SUPPORTED_VERSIONS);
|
|
result.putExtra(OpenPgpApi.RESULT_ERROR, error);
|
|
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR);
|
|
return result;
|
|
}
|
|
|
|
// special exception: getting a sign key id will also register the app
|
|
if (OpenPgpApi.ACTION_GET_SIGN_KEY_ID.equals(data.getAction())) {
|
|
return null;
|
|
}
|
|
|
|
// check if caller is allowed to access OpenKeychain
|
|
Intent result = mApiPermissionHelper.isAllowedOrReturnIntent(data);
|
|
if (result != null) {
|
|
return result;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private final IOpenPgpService.Stub mBinder = new IOpenPgpService.Stub() {
|
|
@Override
|
|
public Intent execute(Intent data, ParcelFileDescriptor input, ParcelFileDescriptor output) {
|
|
Timber.w(
|
|
"You are using a deprecated service which may lead to truncated data on return, please use IOpenPgpService2!");
|
|
return executeInternal(data, input, output);
|
|
}
|
|
|
|
};
|
|
|
|
@Override
|
|
public IBinder onBind(Intent intent) {
|
|
return mBinder;
|
|
}
|
|
|
|
@Nullable
|
|
protected Intent executeInternal(
|
|
@NonNull Intent data,
|
|
@Nullable ParcelFileDescriptor input,
|
|
@Nullable ParcelFileDescriptor output) {
|
|
|
|
OutputStream outputStream =
|
|
(output != null) ? new ParcelFileDescriptor.AutoCloseOutputStream(output) : null;
|
|
InputStream inputStream =
|
|
(input != null) ? new ParcelFileDescriptor.AutoCloseInputStream(input) : null;
|
|
|
|
try {
|
|
long startTime = SystemClock.elapsedRealtime();
|
|
Timber.i("API call: %s", data.getAction());
|
|
Intent result = executeInternalWithStreams(data, inputStream, outputStream);
|
|
long elapsedTime = SystemClock.elapsedRealtime() - startTime;
|
|
Timber.i("Elapsed time: %d", elapsedTime);
|
|
return result;
|
|
} finally {
|
|
// always close input and output file descriptors even in createErrorPendingIntent cases
|
|
if (inputStream != null) {
|
|
try {
|
|
inputStream.close();
|
|
} catch (IOException e) {
|
|
Timber.e(e, "IOException when closing input ParcelFileDescriptor");
|
|
}
|
|
}
|
|
if (outputStream != null) {
|
|
try {
|
|
outputStream.close();
|
|
} catch (IOException e) {
|
|
Timber.e(e, "IOException when closing output ParcelFileDescriptor");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@Nullable
|
|
protected Intent executeInternalWithStreams(
|
|
@NonNull Intent data,
|
|
@Nullable InputStream inputStream,
|
|
@Nullable OutputStream outputStream) {
|
|
|
|
// We need to be able to load our own parcelables
|
|
data.setExtrasClassLoader(getClassLoader());
|
|
|
|
Intent errorResult = checkRequirements(data);
|
|
if (errorResult != null) {
|
|
return errorResult;
|
|
}
|
|
|
|
analyticsManager.trackApiServiceCall(data.getAction(), mApiPermissionHelper.getCurrentCallingPackage());
|
|
|
|
Progressable progressable = null;
|
|
if (data.hasExtra(OpenPgpApi.EXTRA_PROGRESS_MESSENGER)) {
|
|
Messenger messenger = data.getParcelableExtra(OpenPgpApi.EXTRA_PROGRESS_MESSENGER);
|
|
progressable = createMessengerProgressable(messenger);
|
|
}
|
|
|
|
String action = data.getAction();
|
|
switch (action) {
|
|
case OpenPgpApi.ACTION_CHECK_PERMISSION: {
|
|
return checkPermissionImpl(data);
|
|
}
|
|
case OpenPgpApi.ACTION_CLEARTEXT_SIGN: {
|
|
return signImpl(data, inputStream, outputStream, true);
|
|
}
|
|
case OpenPgpApi.ACTION_SIGN: {
|
|
// DEPRECATED: same as ACTION_CLEARTEXT_SIGN
|
|
Timber.w(
|
|
"You are using a deprecated API call, please use ACTION_CLEARTEXT_SIGN instead of ACTION_SIGN!");
|
|
return signImpl(data, inputStream, outputStream, true);
|
|
}
|
|
case OpenPgpApi.ACTION_DETACHED_SIGN: {
|
|
return signImpl(data, inputStream, outputStream, false);
|
|
}
|
|
case OpenPgpApi.ACTION_QUERY_AUTOCRYPT_STATUS: {
|
|
return autocryptQueryImpl(data);
|
|
}
|
|
case OpenPgpApi.ACTION_ENCRYPT:
|
|
case OpenPgpApi.ACTION_SIGN_AND_ENCRYPT: {
|
|
boolean enableSign = action.equals(OpenPgpApi.ACTION_SIGN_AND_ENCRYPT);
|
|
return encryptAndSignImpl(data, inputStream, outputStream, enableSign);
|
|
}
|
|
case OpenPgpApi.ACTION_DECRYPT_VERIFY: {
|
|
return decryptAndVerifyImpl(data, inputStream, outputStream, false, progressable);
|
|
}
|
|
case OpenPgpApi.ACTION_DECRYPT_METADATA: {
|
|
return decryptAndVerifyImpl(data, inputStream, outputStream, true, null);
|
|
}
|
|
case OpenPgpApi.ACTION_GET_SIGN_KEY_ID: {
|
|
return getSignKeyIdImpl(data);
|
|
}
|
|
case OpenPgpApi.ACTION_GET_SIGN_KEY_ID_LEGACY: {
|
|
return getSignKeyIdImplLegacy(data);
|
|
}
|
|
case OpenPgpApi.ACTION_GET_KEY_IDS: {
|
|
return getKeyIdsImpl(data);
|
|
}
|
|
case OpenPgpApi.ACTION_GET_KEY: {
|
|
return getKeyImpl(data, outputStream);
|
|
}
|
|
case OpenPgpApi.ACTION_BACKUP: {
|
|
return backupImpl(data, outputStream);
|
|
}
|
|
case OpenPgpApi.ACTION_AUTOCRYPT_KEY_TRANSFER: {
|
|
return autocryptKeyTransferImpl(data, outputStream);
|
|
}
|
|
case OpenPgpApi.ACTION_UPDATE_AUTOCRYPT_PEER: {
|
|
return updateAutocryptPeerImpl(data);
|
|
}
|
|
default: {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
@NonNull
|
|
private static Progressable createMessengerProgressable(final Messenger messenger) {
|
|
return new Progressable() {
|
|
boolean errorState = false;
|
|
@Override
|
|
public void setProgress(Integer ignored, int current, int total) {
|
|
if (errorState) {
|
|
return;
|
|
}
|
|
Message m = Message.obtain();
|
|
m.arg1 = current;
|
|
m.arg2 = total;
|
|
try {
|
|
messenger.send(m);
|
|
} catch (RemoteException e) {
|
|
e.printStackTrace();
|
|
errorState = true;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void setPreventCancel() {
|
|
|
|
}
|
|
};
|
|
}
|
|
|
|
}
|