866 lines
37 KiB
Java
866 lines
37 KiB
Java
/*
|
|
* Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
|
* Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com>
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
package org.sufficientlysecure.keychain.service;
|
|
|
|
import android.app.Service;
|
|
import android.content.Intent;
|
|
import android.net.Uri;
|
|
import android.os.Bundle;
|
|
import android.os.IBinder;
|
|
import android.os.Message;
|
|
import android.os.Messenger;
|
|
import android.os.RemoteException;
|
|
|
|
import com.textuality.keybase.lib.Proof;
|
|
import com.textuality.keybase.lib.prover.Prover;
|
|
|
|
import org.json.JSONObject;
|
|
import org.spongycastle.openpgp.PGPUtil;
|
|
import org.sufficientlysecure.keychain.Constants;
|
|
import org.sufficientlysecure.keychain.R;
|
|
import org.sufficientlysecure.keychain.keyimport.HkpKeyserver;
|
|
import org.sufficientlysecure.keychain.keyimport.Keyserver;
|
|
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
|
|
import org.sufficientlysecure.keychain.operations.CertifyOperation;
|
|
import org.sufficientlysecure.keychain.operations.DeleteOperation;
|
|
import org.sufficientlysecure.keychain.operations.EditKeyOperation;
|
|
import org.sufficientlysecure.keychain.operations.ImportExportOperation;
|
|
import org.sufficientlysecure.keychain.operations.PromoteKeyOperation;
|
|
import org.sufficientlysecure.keychain.operations.SignEncryptOperation;
|
|
import org.sufficientlysecure.keychain.operations.results.CertifyResult;
|
|
import org.sufficientlysecure.keychain.operations.results.ConsolidateResult;
|
|
import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
|
|
import org.sufficientlysecure.keychain.operations.results.DeleteResult;
|
|
import org.sufficientlysecure.keychain.operations.results.ExportResult;
|
|
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
|
|
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
|
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
|
|
import org.sufficientlysecure.keychain.operations.results.PromoteKeyResult;
|
|
import org.sufficientlysecure.keychain.operations.results.SignEncryptResult;
|
|
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
|
|
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify;
|
|
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel;
|
|
import org.sufficientlysecure.keychain.pgp.Progressable;
|
|
import org.sufficientlysecure.keychain.pgp.SignEncryptParcel;
|
|
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
|
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralMsgIdException;
|
|
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
|
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
|
import org.sufficientlysecure.keychain.service.ServiceProgressHandler.MessageStatus;
|
|
import org.sufficientlysecure.keychain.util.Log;
|
|
import org.sufficientlysecure.keychain.util.ParcelableFileCache;
|
|
|
|
import java.io.ByteArrayInputStream;
|
|
import java.io.InputStream;
|
|
import java.util.ArrayList;
|
|
import java.util.HashSet;
|
|
import java.util.Iterator;
|
|
import java.util.List;
|
|
import java.util.concurrent.ExecutorService;
|
|
import java.util.concurrent.SynchronousQueue;
|
|
import java.util.concurrent.ThreadPoolExecutor;
|
|
import java.util.concurrent.TimeUnit;
|
|
import java.util.concurrent.atomic.AtomicBoolean;
|
|
|
|
import de.measite.minidns.Client;
|
|
import de.measite.minidns.DNSMessage;
|
|
import de.measite.minidns.Question;
|
|
import de.measite.minidns.Record;
|
|
import de.measite.minidns.record.Data;
|
|
import de.measite.minidns.record.TXT;
|
|
|
|
/**
|
|
* This Service contains all important long lasting operations for OpenKeychain. It receives Intents with
|
|
* data from the activities or other apps, executes them, and stops itself after doing them.
|
|
*/
|
|
public class KeychainService extends Service implements Progressable {
|
|
|
|
/* extras that can be given by intent */
|
|
public static final String EXTRA_MESSENGER = "messenger";
|
|
public static final String EXTRA_DATA = "data";
|
|
|
|
/* possible actions */
|
|
public static final String ACTION_SIGN_ENCRYPT = Constants.INTENT_PREFIX + "SIGN_ENCRYPT";
|
|
|
|
public static final String ACTION_DECRYPT_VERIFY = Constants.INTENT_PREFIX + "DECRYPT_VERIFY";
|
|
|
|
public static final String ACTION_VERIFY_KEYBASE_PROOF = Constants.INTENT_PREFIX + "VERIFY_KEYBASE_PROOF";
|
|
|
|
public static final String ACTION_DECRYPT_METADATA = Constants.INTENT_PREFIX + "DECRYPT_METADATA";
|
|
|
|
public static final String ACTION_EDIT_KEYRING = Constants.INTENT_PREFIX + "EDIT_KEYRING";
|
|
|
|
public static final String ACTION_PROMOTE_KEYRING = Constants.INTENT_PREFIX + "PROMOTE_KEYRING";
|
|
|
|
public static final String ACTION_IMPORT_KEYRING = Constants.INTENT_PREFIX + "IMPORT_KEYRING";
|
|
public static final String ACTION_EXPORT_KEYRING = Constants.INTENT_PREFIX + "EXPORT_KEYRING";
|
|
|
|
public static final String ACTION_UPLOAD_KEYRING = Constants.INTENT_PREFIX + "UPLOAD_KEYRING";
|
|
|
|
public static final String ACTION_CERTIFY_KEYRING = Constants.INTENT_PREFIX + "SIGN_KEYRING";
|
|
|
|
public static final String ACTION_DELETE = Constants.INTENT_PREFIX + "DELETE";
|
|
|
|
public static final String ACTION_CONSOLIDATE = Constants.INTENT_PREFIX + "CONSOLIDATE";
|
|
|
|
public static final String ACTION_CANCEL = Constants.INTENT_PREFIX + "CANCEL";
|
|
|
|
/* keys for data bundle */
|
|
|
|
// encrypt
|
|
public static final String ENCRYPT_DECRYPT_INPUT_URI = "input_uri";
|
|
public static final String ENCRYPT_DECRYPT_OUTPUT_URI = "output_uri";
|
|
public static final String SIGN_ENCRYPT_PARCEL = "sign_encrypt_parcel";
|
|
|
|
// decrypt/verify
|
|
public static final String DECRYPT_VERIFY_PARCEL = "decrypt_verify_parcel";
|
|
|
|
// keybase proof
|
|
public static final String KEYBASE_REQUIRED_FINGERPRINT = "keybase_required_fingerprint";
|
|
public static final String KEYBASE_PROOF = "keybase_proof";
|
|
|
|
// save keyring
|
|
public static final String EDIT_KEYRING_PARCEL = "save_parcel";
|
|
public static final String EDIT_KEYRING_PASSPHRASE = "passphrase";
|
|
public static final String EXTRA_CRYPTO_INPUT = "crypto_input";
|
|
|
|
// delete keyring(s)
|
|
public static final String DELETE_KEY_LIST = "delete_list";
|
|
public static final String DELETE_IS_SECRET = "delete_is_secret";
|
|
|
|
// import key
|
|
public static final String IMPORT_KEY_LIST = "import_key_list";
|
|
public static final String IMPORT_KEY_SERVER = "import_key_server";
|
|
|
|
// export key
|
|
public static final String EXPORT_FILENAME = "export_filename";
|
|
public static final String EXPORT_URI = "export_uri";
|
|
public static final String EXPORT_SECRET = "export_secret";
|
|
public static final String EXPORT_ALL = "export_all";
|
|
public static final String EXPORT_KEY_RING_MASTER_KEY_ID = "export_key_ring_id";
|
|
|
|
// upload key
|
|
public static final String UPLOAD_KEY_SERVER = "upload_key_server";
|
|
|
|
// certify key
|
|
public static final String CERTIFY_PARCEL = "certify_parcel";
|
|
|
|
// promote key
|
|
public static final String PROMOTE_MASTER_KEY_ID = "promote_master_key_id";
|
|
public static final String PROMOTE_CARD_AID = "promote_card_aid";
|
|
public static final String PROMOTE_SUBKEY_IDS = "promote_fingerprints";
|
|
|
|
// consolidate
|
|
public static final String CONSOLIDATE_RECOVERY = "consolidate_recovery";
|
|
|
|
Messenger mMessenger;
|
|
|
|
// this attribute can possibly merged with the one above? not sure...
|
|
private AtomicBoolean mActionCanceled = new AtomicBoolean(false);
|
|
|
|
|
|
private KeyImportAccumulator mKeyImportAccumulator;
|
|
|
|
private KeychainService mKeychainService;
|
|
|
|
@Override
|
|
public IBinder onBind(Intent intent) {
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* This is run on the main thread, we need to spawn a runnable which runs on another thread for the actual operation
|
|
*/
|
|
@Override
|
|
public int onStartCommand(final Intent intent, int flags, int startId) {
|
|
mKeychainService = this;
|
|
|
|
if (ACTION_CANCEL.equals(intent.getAction())) {
|
|
mActionCanceled.set(true);
|
|
return START_NOT_STICKY;
|
|
}
|
|
|
|
Runnable actionRunnable = new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
// We have not been cancelled! (yet)
|
|
mActionCanceled.set(false);
|
|
|
|
Bundle extras = intent.getExtras();
|
|
if (extras == null) {
|
|
Log.e(Constants.TAG, "Extras bundle is null!");
|
|
return;
|
|
}
|
|
|
|
if (!(extras.containsKey(EXTRA_MESSENGER) || extras.containsKey(EXTRA_DATA) || (intent
|
|
.getAction() == null))) {
|
|
Log.e(Constants.TAG,
|
|
"Extra bundle must contain a messenger, a data bundle, and an action!");
|
|
return;
|
|
}
|
|
|
|
Uri dataUri = intent.getData();
|
|
|
|
mMessenger = (Messenger) extras.get(EXTRA_MESSENGER);
|
|
Bundle data = extras.getBundle(EXTRA_DATA);
|
|
if (data == null) {
|
|
Log.e(Constants.TAG, "data extra is null!");
|
|
return;
|
|
}
|
|
|
|
Log.logDebugBundle(data, "EXTRA_DATA");
|
|
|
|
ProviderHelper providerHelper = new ProviderHelper(mKeychainService);
|
|
|
|
String action = intent.getAction();
|
|
|
|
// executeServiceMethod action from extra bundle
|
|
switch (action) {
|
|
case ACTION_CERTIFY_KEYRING: {
|
|
|
|
// Input
|
|
CertifyActionsParcel parcel = data.getParcelable(CERTIFY_PARCEL);
|
|
CryptoInputParcel cryptoInput = data.getParcelable(EXTRA_CRYPTO_INPUT);
|
|
String keyServerUri = data.getString(UPLOAD_KEY_SERVER);
|
|
|
|
// Operation
|
|
CertifyOperation op = new CertifyOperation(mKeychainService, providerHelper, mKeychainService,
|
|
mActionCanceled);
|
|
CertifyResult result = op.certify(parcel, cryptoInput, keyServerUri);
|
|
|
|
// Result
|
|
sendMessageToHandler(MessageStatus.OKAY, result);
|
|
|
|
break;
|
|
}
|
|
case ACTION_CONSOLIDATE: {
|
|
|
|
// Operation
|
|
ConsolidateResult result;
|
|
if (data.containsKey(CONSOLIDATE_RECOVERY) && data.getBoolean(CONSOLIDATE_RECOVERY)) {
|
|
result = providerHelper.consolidateDatabaseStep2(mKeychainService);
|
|
} else {
|
|
result = providerHelper.consolidateDatabaseStep1(mKeychainService);
|
|
}
|
|
|
|
// Result
|
|
sendMessageToHandler(MessageStatus.OKAY, result);
|
|
|
|
break;
|
|
}
|
|
case ACTION_DECRYPT_METADATA: {
|
|
|
|
// Input
|
|
CryptoInputParcel cryptoInput = data.getParcelable(EXTRA_CRYPTO_INPUT);
|
|
PgpDecryptVerifyInputParcel input = data.getParcelable(DECRYPT_VERIFY_PARCEL);
|
|
|
|
// this action is here for compatibility only
|
|
input.setDecryptMetadataOnly(true);
|
|
|
|
// Operation
|
|
PgpDecryptVerify op = new PgpDecryptVerify(mKeychainService, providerHelper, mKeychainService);
|
|
DecryptVerifyResult decryptVerifyResult = op.execute(input, cryptoInput);
|
|
|
|
// Result
|
|
sendMessageToHandler(MessageStatus.OKAY, decryptVerifyResult);
|
|
|
|
break;
|
|
}
|
|
case ACTION_VERIFY_KEYBASE_PROOF: {
|
|
|
|
try {
|
|
Proof proof = new Proof(new JSONObject(data.getString(KEYBASE_PROOF)));
|
|
setProgress(R.string.keybase_message_fetching_data, 0, 100);
|
|
|
|
Prover prover = Prover.findProverFor(proof);
|
|
|
|
if (prover == null) {
|
|
sendProofError(getString(R.string.keybase_no_prover_found) + ": " + proof
|
|
.getPrettyName());
|
|
return;
|
|
}
|
|
|
|
if (!prover.fetchProofData()) {
|
|
sendProofError(prover.getLog(), getString(R.string.keybase_problem_fetching_evidence));
|
|
return;
|
|
}
|
|
String requiredFingerprint = data.getString(KEYBASE_REQUIRED_FINGERPRINT);
|
|
if (!prover.checkFingerprint(requiredFingerprint)) {
|
|
sendProofError(getString(R.string.keybase_key_mismatch));
|
|
return;
|
|
}
|
|
|
|
String domain = prover.dnsTxtCheckRequired();
|
|
if (domain != null) {
|
|
DNSMessage dnsQuery = new Client().query(new Question(domain, Record.TYPE.TXT));
|
|
if (dnsQuery == null) {
|
|
sendProofError(prover.getLog(), getString(R.string.keybase_dns_query_failure));
|
|
return;
|
|
}
|
|
Record[] records = dnsQuery.getAnswers();
|
|
List<List<byte[]>> extents = new ArrayList<List<byte[]>>();
|
|
for (Record r : records) {
|
|
Data d = r.getPayload();
|
|
if (d instanceof TXT) {
|
|
extents.add(((TXT) d).getExtents());
|
|
}
|
|
}
|
|
if (!prover.checkDnsTxt(extents)) {
|
|
sendProofError(prover.getLog(), null);
|
|
return;
|
|
}
|
|
}
|
|
|
|
byte[] messageBytes = prover.getPgpMessage().getBytes();
|
|
if (prover.rawMessageCheckRequired()) {
|
|
InputStream messageByteStream = PGPUtil.getDecoderStream(new ByteArrayInputStream
|
|
(messageBytes));
|
|
if (!prover.checkRawMessageBytes(messageByteStream)) {
|
|
sendProofError(prover.getLog(), null);
|
|
return;
|
|
}
|
|
}
|
|
|
|
PgpDecryptVerify op = new PgpDecryptVerify(mKeychainService, providerHelper,
|
|
mKeychainService);
|
|
|
|
PgpDecryptVerifyInputParcel input = new PgpDecryptVerifyInputParcel(messageBytes)
|
|
.setSignedLiteralData(true)
|
|
.setRequiredSignerFingerprint(requiredFingerprint);
|
|
|
|
DecryptVerifyResult decryptVerifyResult = op.execute(input, new CryptoInputParcel());
|
|
|
|
if (!decryptVerifyResult.success()) {
|
|
OperationLog log = decryptVerifyResult.getLog();
|
|
OperationResult.LogEntryParcel lastEntry = null;
|
|
for (OperationResult.LogEntryParcel entry : log) {
|
|
lastEntry = entry;
|
|
}
|
|
sendProofError(getString(lastEntry.mType.getMsgId()));
|
|
return;
|
|
}
|
|
|
|
if (!prover.validate(new String(decryptVerifyResult.getOutputBytes()))) {
|
|
sendProofError(getString(R.string.keybase_message_payload_mismatch));
|
|
return;
|
|
}
|
|
|
|
Bundle resultData = new Bundle();
|
|
resultData.putString(ServiceProgressHandler.DATA_MESSAGE, "OK");
|
|
|
|
// these help the handler construct a useful human-readable message
|
|
resultData.putString(ServiceProgressHandler.KEYBASE_PROOF_URL, prover.getProofUrl());
|
|
resultData.putString(ServiceProgressHandler.KEYBASE_PRESENCE_URL, prover.getPresenceUrl());
|
|
resultData.putString(ServiceProgressHandler.KEYBASE_PRESENCE_LABEL, prover
|
|
.getPresenceLabel());
|
|
sendMessageToHandler(MessageStatus.OKAY, resultData);
|
|
} catch (Exception e) {
|
|
sendErrorToHandler(e);
|
|
}
|
|
|
|
break;
|
|
}
|
|
case ACTION_DECRYPT_VERIFY: {
|
|
|
|
// Input
|
|
CryptoInputParcel cryptoInput = data.getParcelable(EXTRA_CRYPTO_INPUT);
|
|
PgpDecryptVerifyInputParcel input = data.getParcelable(DECRYPT_VERIFY_PARCEL);
|
|
|
|
// for compatibility
|
|
// TODO merge with ACTION_DECRYPT_METADATA
|
|
input.setDecryptMetadataOnly(false);
|
|
|
|
// Operation
|
|
PgpDecryptVerify op = new PgpDecryptVerify(mKeychainService, providerHelper, mKeychainService);
|
|
DecryptVerifyResult decryptVerifyResult = op.execute(input, cryptoInput);
|
|
|
|
// Output
|
|
sendMessageToHandler(MessageStatus.OKAY, decryptVerifyResult);
|
|
|
|
break;
|
|
}
|
|
case ACTION_DELETE: {
|
|
|
|
// Input
|
|
long[] masterKeyIds = data.getLongArray(DELETE_KEY_LIST);
|
|
boolean isSecret = data.getBoolean(DELETE_IS_SECRET);
|
|
|
|
// Operation
|
|
DeleteOperation op = new DeleteOperation(mKeychainService, providerHelper, mKeychainService);
|
|
DeleteResult result = op.execute(masterKeyIds, isSecret);
|
|
|
|
// Result
|
|
sendMessageToHandler(MessageStatus.OKAY, result);
|
|
|
|
break;
|
|
}
|
|
case ACTION_EDIT_KEYRING: {
|
|
|
|
// Input
|
|
SaveKeyringParcel saveParcel = data.getParcelable(EDIT_KEYRING_PARCEL);
|
|
CryptoInputParcel cryptoInput = data.getParcelable(EXTRA_CRYPTO_INPUT);
|
|
|
|
// Operation
|
|
EditKeyOperation op = new EditKeyOperation(mKeychainService, providerHelper,
|
|
mKeychainService, mActionCanceled);
|
|
OperationResult result = op.execute(saveParcel, cryptoInput);
|
|
|
|
// Result
|
|
sendMessageToHandler(MessageStatus.OKAY, result);
|
|
|
|
break;
|
|
}
|
|
case ACTION_PROMOTE_KEYRING: {
|
|
|
|
// Input
|
|
long keyRingId = data.getLong(PROMOTE_MASTER_KEY_ID);
|
|
byte[] cardAid = data.getByteArray(PROMOTE_CARD_AID);
|
|
long[] subKeyIds = data.getLongArray(PROMOTE_SUBKEY_IDS);
|
|
|
|
// Operation
|
|
PromoteKeyOperation op = new PromoteKeyOperation(
|
|
mKeychainService, providerHelper, mKeychainService,
|
|
mActionCanceled);
|
|
PromoteKeyResult result = op.execute(keyRingId, cardAid, subKeyIds);
|
|
|
|
// Result
|
|
sendMessageToHandler(MessageStatus.OKAY, result);
|
|
|
|
break;
|
|
}
|
|
case ACTION_EXPORT_KEYRING: {
|
|
|
|
// Input
|
|
boolean exportSecret = data.getBoolean(EXPORT_SECRET, false);
|
|
String outputFile = data.getString(EXPORT_FILENAME);
|
|
Uri outputUri = data.getParcelable(EXPORT_URI);
|
|
|
|
boolean exportAll = data.getBoolean(EXPORT_ALL);
|
|
long[] masterKeyIds = exportAll ? null : data.getLongArray(EXPORT_KEY_RING_MASTER_KEY_ID);
|
|
|
|
// Operation
|
|
ImportExportOperation importExportOperation = new ImportExportOperation(
|
|
mKeychainService, providerHelper, mKeychainService);
|
|
ExportResult result;
|
|
if (outputFile != null) {
|
|
result = importExportOperation.exportToFile(masterKeyIds, exportSecret, outputFile);
|
|
} else {
|
|
result = importExportOperation.exportToUri(masterKeyIds, exportSecret, outputUri);
|
|
}
|
|
|
|
// Result
|
|
sendMessageToHandler(MessageStatus.OKAY, result);
|
|
|
|
break;
|
|
}
|
|
case ACTION_IMPORT_KEYRING: {
|
|
|
|
// Input
|
|
String keyServer = data.getString(IMPORT_KEY_SERVER);
|
|
ArrayList<ParcelableKeyRing> keyList = data.getParcelableArrayList(IMPORT_KEY_LIST);
|
|
|
|
// either keyList or cache must be null, no guarantees otherwise
|
|
if (keyList == null) {// import from file, do serially
|
|
serialKeyImport(null, keyServer, providerHelper);
|
|
} else {
|
|
// if there is more than one key with the same fingerprint, we do a serial import to prevent
|
|
// https://github.com/open-keychain/open-keychain/issues/1221
|
|
HashSet<String> keyFingerprintSet = new HashSet<>();
|
|
for (int i = 0; i < keyList.size(); i++) {
|
|
keyFingerprintSet.add(keyList.get(i).mExpectedFingerprint);
|
|
}
|
|
if (keyFingerprintSet.size() == keyList.size()) {
|
|
// all keys have unique fingerprints
|
|
multiThreadedKeyImport(keyList.iterator(), keyList.size(), keyServer);
|
|
} else {
|
|
serialKeyImport(keyList, keyServer, providerHelper);
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
case ACTION_SIGN_ENCRYPT: {
|
|
|
|
// Input
|
|
SignEncryptParcel inputParcel = data.getParcelable(SIGN_ENCRYPT_PARCEL);
|
|
CryptoInputParcel cryptoInput = data.getParcelable(EXTRA_CRYPTO_INPUT);
|
|
|
|
// Operation
|
|
SignEncryptOperation op = new SignEncryptOperation(
|
|
mKeychainService, providerHelper, mKeychainService, mActionCanceled);
|
|
SignEncryptResult result = op.execute(inputParcel, cryptoInput);
|
|
|
|
// Result
|
|
sendMessageToHandler(MessageStatus.OKAY, result);
|
|
|
|
break;
|
|
}
|
|
case ACTION_UPLOAD_KEYRING: {
|
|
try {
|
|
|
|
// Input
|
|
String keyServer = data.getString(UPLOAD_KEY_SERVER);
|
|
// and dataUri!
|
|
|
|
// Operation
|
|
HkpKeyserver server = new HkpKeyserver(keyServer);
|
|
|
|
CanonicalizedPublicKeyRing keyring = providerHelper.getCanonicalizedPublicKeyRing(dataUri);
|
|
ImportExportOperation importExportOperation = new ImportExportOperation(mKeychainService,
|
|
providerHelper, mKeychainService);
|
|
|
|
try {
|
|
importExportOperation.uploadKeyRingToServer(server, keyring);
|
|
} catch (Keyserver.AddKeyException e) {
|
|
throw new PgpGeneralException("Unable to export key to selected server");
|
|
}
|
|
|
|
sendMessageToHandler(MessageStatus.OKAY);
|
|
} catch (Exception e) {
|
|
sendErrorToHandler(e);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
if (!intent.getAction().equals(ACTION_IMPORT_KEYRING)) {
|
|
// import keyring handles stopping service on its own
|
|
stopSelf();
|
|
}
|
|
}
|
|
};
|
|
|
|
Thread actionThread = new Thread(actionRunnable);
|
|
actionThread.start();
|
|
|
|
return START_NOT_STICKY;
|
|
}
|
|
|
|
private void sendProofError(List<String> log, String label) {
|
|
String msg = null;
|
|
label = (label == null) ? "" : label + ": ";
|
|
for (String m : log) {
|
|
Log.e(Constants.TAG, label + m);
|
|
msg = m;
|
|
}
|
|
sendProofError(label + msg);
|
|
}
|
|
|
|
private void sendProofError(String msg) {
|
|
Bundle bundle = new Bundle();
|
|
bundle.putString(ServiceProgressHandler.DATA_ERROR, msg);
|
|
sendMessageToHandler(MessageStatus.OKAY, bundle);
|
|
}
|
|
|
|
private void sendErrorToHandler(Exception e) {
|
|
// TODO: Implement a better exception handling here
|
|
// contextualize the exception, if necessary
|
|
String message;
|
|
if (e instanceof PgpGeneralMsgIdException) {
|
|
e = ((PgpGeneralMsgIdException) e).getContextualized(mKeychainService);
|
|
message = e.getMessage();
|
|
} else {
|
|
message = e.getMessage();
|
|
}
|
|
Log.d(Constants.TAG, "KeychainService Exception: ", e);
|
|
|
|
Bundle data = new Bundle();
|
|
data.putString(ServiceProgressHandler.DATA_ERROR, message);
|
|
sendMessageToHandler(MessageStatus.EXCEPTION, null, data);
|
|
}
|
|
|
|
private void sendMessageToHandler(MessageStatus status, Integer arg2, Bundle data) {
|
|
|
|
Message msg = Message.obtain();
|
|
assert msg != null;
|
|
msg.arg1 = status.ordinal();
|
|
if (arg2 != null) {
|
|
msg.arg2 = arg2;
|
|
}
|
|
if (data != null) {
|
|
msg.setData(data);
|
|
}
|
|
|
|
try {
|
|
mMessenger.send(msg);
|
|
} catch (RemoteException e) {
|
|
Log.w(Constants.TAG, "Exception sending message, Is handler present?", e);
|
|
} catch (NullPointerException e) {
|
|
Log.w(Constants.TAG, "Messenger is null!", e);
|
|
}
|
|
}
|
|
|
|
private void sendMessageToHandler(MessageStatus status, OperationResult data) {
|
|
Bundle bundle = new Bundle();
|
|
bundle.putParcelable(OperationResult.EXTRA_RESULT, data);
|
|
sendMessageToHandler(status, null, bundle);
|
|
}
|
|
|
|
private void sendMessageToHandler(MessageStatus status, Bundle data) {
|
|
sendMessageToHandler(status, null, data);
|
|
}
|
|
|
|
private void sendMessageToHandler(MessageStatus status) {
|
|
sendMessageToHandler(status, null, null);
|
|
}
|
|
|
|
/**
|
|
* Set progress of ProgressDialog by sending message to handler on UI thread
|
|
*/
|
|
@Override
|
|
public void setProgress(String message, int progress, int max) {
|
|
Log.d(Constants.TAG, "Send message by setProgress with progress=" + progress + ", max="
|
|
+ max);
|
|
|
|
Bundle data = new Bundle();
|
|
if (message != null) {
|
|
data.putString(ServiceProgressHandler.DATA_MESSAGE, message);
|
|
}
|
|
data.putInt(ServiceProgressHandler.DATA_PROGRESS, progress);
|
|
data.putInt(ServiceProgressHandler.DATA_PROGRESS_MAX, max);
|
|
|
|
sendMessageToHandler(MessageStatus.UPDATE_PROGRESS, null, data);
|
|
}
|
|
|
|
@Override
|
|
public void setProgress(int resourceId, int progress, int max) {
|
|
setProgress(getString(resourceId), progress, max);
|
|
}
|
|
|
|
@Override
|
|
public void setProgress(int progress, int max) {
|
|
setProgress(null, progress, max);
|
|
}
|
|
|
|
@Override
|
|
public void setPreventCancel() {
|
|
sendMessageToHandler(MessageStatus.PREVENT_CANCEL);
|
|
}
|
|
|
|
public void serialKeyImport(ArrayList<ParcelableKeyRing> keyList, final String keyServer,
|
|
ProviderHelper providerHelper) {
|
|
Log.d(Constants.TAG, "serial key import starting");
|
|
ParcelableFileCache<ParcelableKeyRing> cache =
|
|
new ParcelableFileCache<>(mKeychainService, "key_import.pcl");
|
|
|
|
// Operation
|
|
ImportExportOperation importExportOperation = new ImportExportOperation(
|
|
mKeychainService, providerHelper, mKeychainService,
|
|
mActionCanceled);
|
|
// Either list or cache must be null, no guarantees otherwise.
|
|
ImportKeyResult result = keyList != null
|
|
? importExportOperation.importKeyRings(keyList, keyServer)
|
|
: importExportOperation.importKeyRings(cache, keyServer);
|
|
|
|
ContactSyncAdapterService.requestSync();
|
|
// Result
|
|
sendMessageToHandler(MessageStatus.OKAY, result);
|
|
|
|
stopSelf();
|
|
}
|
|
|
|
public void multiThreadedKeyImport(Iterator<ParcelableKeyRing> keyListIterator, int totKeys, final String
|
|
keyServer) {
|
|
Log.d(Constants.TAG, "Multi-threaded key import starting");
|
|
if (keyListIterator != null) {
|
|
mKeyImportAccumulator = new KeyImportAccumulator(totKeys, mKeychainService);
|
|
setProgress(0, totKeys);
|
|
|
|
final int maxThreads = 200;
|
|
ExecutorService importExecutor = new ThreadPoolExecutor(0, maxThreads,
|
|
30L, TimeUnit.SECONDS,
|
|
new SynchronousQueue<Runnable>());
|
|
|
|
while (keyListIterator.hasNext()) {
|
|
|
|
final ParcelableKeyRing pkRing = keyListIterator.next();
|
|
|
|
Runnable importOperationRunnable = new Runnable() {
|
|
|
|
@Override
|
|
public void run() {
|
|
ImportKeyResult result = null;
|
|
try {
|
|
ImportExportOperation importExportOperation = new ImportExportOperation(
|
|
mKeychainService,
|
|
new ProviderHelper(mKeychainService),
|
|
mKeyImportAccumulator.getImportProgressable(),
|
|
mActionCanceled);
|
|
|
|
ArrayList<ParcelableKeyRing> list = new ArrayList<>();
|
|
list.add(pkRing);
|
|
|
|
result = importExportOperation.importKeyRings(list,
|
|
keyServer);
|
|
} finally {
|
|
// in the off-chance that importKeyRings does something to crash the
|
|
// thread before it can call singleKeyRingImportCompleted, our imported
|
|
// key count will go wrong. This will cause the service to never die,
|
|
// and the progress dialog to stay displayed. The finally block was
|
|
// originally meant to ensure singleKeyRingImportCompleted was called,
|
|
// and checks for null were to be introduced, but in such a scenario,
|
|
// knowing an uncaught error exists in importKeyRings is more important.
|
|
|
|
// if a null gets passed, something wrong is happening. We want a crash.
|
|
|
|
mKeyImportAccumulator.singleKeyRingImportCompleted(result);
|
|
}
|
|
}
|
|
};
|
|
|
|
importExecutor.execute(importOperationRunnable);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Used to accumulate the results of individual key imports
|
|
*/
|
|
private class KeyImportAccumulator {
|
|
private OperationResult.OperationLog mImportLog = new OperationResult.OperationLog();
|
|
private int mTotalKeys;
|
|
private int mImportedKeys = 0;
|
|
private Progressable mInternalProgressable;
|
|
ArrayList<Long> mImportedMasterKeyIds = new ArrayList<Long>();
|
|
private int mBadKeys = 0;
|
|
private int mNewKeys = 0;
|
|
private int mUpdatedKeys = 0;
|
|
private int mSecret = 0;
|
|
private int mResultType = 0;
|
|
|
|
/**
|
|
* meant to be used with a service due to stopSelf() in singleKeyRingImportCompleted. Remove this if
|
|
* generalising.
|
|
*
|
|
* @param totalKeys total number of keys to be imported
|
|
* @param externalProgressable the external progressable to be updated every time a key is imported
|
|
*/
|
|
public KeyImportAccumulator(int totalKeys, Progressable externalProgressable) {
|
|
mTotalKeys = totalKeys;
|
|
// ignore updates from ImportExportOperation for now
|
|
mInternalProgressable = new Progressable() {
|
|
@Override
|
|
public void setProgress(String message, int current, int total) {
|
|
|
|
}
|
|
|
|
@Override
|
|
public void setProgress(int resourceId, int current, int total) {
|
|
|
|
}
|
|
|
|
@Override
|
|
public void setProgress(int current, int total) {
|
|
|
|
}
|
|
|
|
@Override
|
|
public void setPreventCancel() {
|
|
|
|
}
|
|
};
|
|
}
|
|
|
|
private synchronized void singleKeyRingImportCompleted(ImportKeyResult result) {
|
|
// increase imported key count and accumulate log and bad, new etc. key counts from result
|
|
mKeyImportAccumulator.accumulateKeyImport(result);
|
|
|
|
setProgress(mKeyImportAccumulator.getImportedKeys(), mKeyImportAccumulator.getTotalKeys());
|
|
|
|
if (mKeyImportAccumulator.isImportFinished()) {
|
|
ContactSyncAdapterService.requestSync();
|
|
|
|
sendMessageToHandler(ServiceProgressHandler.MessageStatus.OKAY,
|
|
mKeyImportAccumulator.getConsolidatedImportKeyResult());
|
|
|
|
stopSelf();//we're done here
|
|
}
|
|
}
|
|
|
|
public Progressable getImportProgressable() {
|
|
return mInternalProgressable;
|
|
}
|
|
|
|
public int getTotalKeys() {
|
|
return mTotalKeys;
|
|
}
|
|
|
|
public int getImportedKeys() {
|
|
return mImportedKeys;
|
|
}
|
|
|
|
public synchronized void accumulateKeyImport(ImportKeyResult result) {
|
|
mImportedKeys++;
|
|
mImportLog.addAll(result.getLog().toList());//accumulates log
|
|
mBadKeys += result.mBadKeys;
|
|
mNewKeys += result.mNewKeys;
|
|
mUpdatedKeys += result.mUpdatedKeys;
|
|
mSecret += result.mSecret;
|
|
|
|
long[] masterKeyIds = result.getImportedMasterKeyIds();
|
|
for (long masterKeyId : masterKeyIds) {
|
|
mImportedMasterKeyIds.add(masterKeyId);
|
|
}
|
|
|
|
// if any key import has been cancelled, set result type to cancelled
|
|
// resultType is added to in getConsolidatedKayImport to account for remaining factors
|
|
mResultType |= result.getResult() & ImportKeyResult.RESULT_CANCELLED;
|
|
}
|
|
|
|
/**
|
|
* returns accumulated result of all imports so far
|
|
*/
|
|
public ImportKeyResult getConsolidatedImportKeyResult() {
|
|
|
|
// adding required information to mResultType
|
|
// special case,no keys requested for import
|
|
if (mBadKeys == 0 && mNewKeys == 0 && mUpdatedKeys == 0) {
|
|
mResultType = ImportKeyResult.RESULT_FAIL_NOTHING;
|
|
} else {
|
|
if (mNewKeys > 0) {
|
|
mResultType |= ImportKeyResult.RESULT_OK_NEWKEYS;
|
|
}
|
|
if (mUpdatedKeys > 0) {
|
|
mResultType |= ImportKeyResult.RESULT_OK_UPDATED;
|
|
}
|
|
if (mBadKeys > 0) {
|
|
mResultType |= ImportKeyResult.RESULT_WITH_ERRORS;
|
|
if (mNewKeys == 0 && mUpdatedKeys == 0) {
|
|
mResultType |= ImportKeyResult.RESULT_ERROR;
|
|
}
|
|
}
|
|
if (mImportLog.containsWarnings()) {
|
|
mResultType |= ImportKeyResult.RESULT_WARNINGS;
|
|
}
|
|
}
|
|
|
|
long masterKeyIds[] = new long[mImportedMasterKeyIds.size()];
|
|
for (int i = 0; i < masterKeyIds.length; i++) {
|
|
masterKeyIds[i] = mImportedMasterKeyIds.get(i);
|
|
}
|
|
|
|
return new ImportKeyResult(mResultType, mImportLog, mNewKeys, mUpdatedKeys, mBadKeys,
|
|
mSecret, masterKeyIds);
|
|
}
|
|
|
|
public boolean isImportFinished() {
|
|
return mTotalKeys == mImportedKeys;
|
|
}
|
|
}
|
|
}
|