New Gradle project structure
This commit is contained in:
@@ -0,0 +1,869 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2013 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.service;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.spongycastle.openpgp.PGPKeyRing;
|
||||
import org.spongycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.spongycastle.openpgp.PGPSecretKey;
|
||||
import org.spongycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.Id;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.helper.FileHelper;
|
||||
import org.sufficientlysecure.keychain.helper.OtherHelper;
|
||||
import org.sufficientlysecure.keychain.helper.Preferences;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpConversionHelper;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpHelper;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpImportExport;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpKeyOperation;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpOperation;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.DataStream;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListEntry;
|
||||
import org.sufficientlysecure.keychain.util.HkpKeyServer;
|
||||
import org.sufficientlysecure.keychain.util.InputData;
|
||||
import org.sufficientlysecure.keychain.util.KeyServer.KeyInfo;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.ProgressDialogUpdater;
|
||||
|
||||
import android.app.IntentService;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.os.RemoteException;
|
||||
|
||||
/**
|
||||
* This Service contains all important long lasting operations for APG. It receives Intents with
|
||||
* data from the activities or other apps, queues these intents, executes them, and stops itself
|
||||
* after doing them.
|
||||
*/
|
||||
public class KeychainIntentService extends IntentService implements ProgressDialogUpdater {
|
||||
|
||||
/* 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_ENCRYPT_SIGN = Constants.INTENT_PREFIX + "ENCRYPT_SIGN";
|
||||
|
||||
public static final String ACTION_DECRYPT_VERIFY = Constants.INTENT_PREFIX + "DECRYPT_VERIFY";
|
||||
|
||||
public static final String ACTION_SAVE_KEYRING = Constants.INTENT_PREFIX + "SAVE_KEYRING";
|
||||
public static final String ACTION_GENERATE_KEY = Constants.INTENT_PREFIX + "GENERATE_KEY";
|
||||
public static final String ACTION_GENERATE_DEFAULT_RSA_KEYS = Constants.INTENT_PREFIX
|
||||
+ "GENERATE_DEFAULT_RSA_KEYS";
|
||||
|
||||
public static final String ACTION_DELETE_FILE_SECURELY = Constants.INTENT_PREFIX
|
||||
+ "DELETE_FILE_SECURELY";
|
||||
|
||||
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_QUERY_KEYRING = Constants.INTENT_PREFIX + "QUERY_KEYRING";
|
||||
|
||||
public static final String ACTION_SIGN_KEYRING = Constants.INTENT_PREFIX + "SIGN_KEYRING";
|
||||
|
||||
/* keys for data bundle */
|
||||
|
||||
// encrypt, decrypt, import export
|
||||
public static final String TARGET = "target";
|
||||
// possible targets:
|
||||
public static final int TARGET_BYTES = 1;
|
||||
public static final int TARGET_FILE = 2;
|
||||
public static final int TARGET_STREAM = 3;
|
||||
|
||||
// encrypt
|
||||
public static final String ENCRYPT_SECRET_KEY_ID = "secret_key_id";
|
||||
public static final String ENCRYPT_USE_ASCII_ARMOR = "use_ascii_armor";
|
||||
public static final String ENCRYPT_ENCRYPTION_KEYS_IDS = "encryption_keys_ids";
|
||||
public static final String ENCRYPT_COMPRESSION_ID = "compression_id";
|
||||
public static final String ENCRYPT_GENERATE_SIGNATURE = "generate_signature";
|
||||
public static final String ENCRYPT_SIGN_ONLY = "sign_only";
|
||||
public static final String ENCRYPT_MESSAGE_BYTES = "message_bytes";
|
||||
public static final String ENCRYPT_INPUT_FILE = "input_file";
|
||||
public static final String ENCRYPT_OUTPUT_FILE = "output_file";
|
||||
public static final String ENCRYPT_PROVIDER_URI = "provider_uri";
|
||||
|
||||
// decrypt/verify
|
||||
public static final String DECRYPT_SIGNED_ONLY = "signed_only";
|
||||
public static final String DECRYPT_RETURN_BYTES = "return_binary";
|
||||
public static final String DECRYPT_CIPHERTEXT_BYTES = "ciphertext_bytes";
|
||||
public static final String DECRYPT_ASSUME_SYMMETRIC = "assume_symmetric";
|
||||
public static final String DECRYPT_LOOKUP_UNKNOWN_KEY = "lookup_unknownKey";
|
||||
|
||||
// save keyring
|
||||
public static final String SAVE_KEYRING_NEW_PASSPHRASE = "new_passphrase";
|
||||
public static final String SAVE_KEYRING_CURRENT_PASSPHRASE = "current_passphrase";
|
||||
public static final String SAVE_KEYRING_USER_IDS = "user_ids";
|
||||
public static final String SAVE_KEYRING_KEYS = "keys";
|
||||
public static final String SAVE_KEYRING_KEYS_USAGES = "keys_usages";
|
||||
public static final String SAVE_KEYRING_MASTER_KEY_ID = "master_key_id";
|
||||
public static final String SAVE_KEYRING_CAN_SIGN = "can_sign";
|
||||
|
||||
// generate key
|
||||
public static final String GENERATE_KEY_ALGORITHM = "algorithm";
|
||||
public static final String GENERATE_KEY_KEY_SIZE = "key_size";
|
||||
public static final String GENERATE_KEY_SYMMETRIC_PASSPHRASE = "passphrase";
|
||||
public static final String GENERATE_KEY_MASTER_KEY = "master_key";
|
||||
|
||||
// delete file securely
|
||||
public static final String DELETE_FILE = "deleteFile";
|
||||
|
||||
// import key
|
||||
public static final String IMPORT_INPUT_STREAM = "import_input_stream";
|
||||
public static final String IMPORT_FILENAME = "import_filename";
|
||||
public static final String IMPORT_BYTES = "import_bytes";
|
||||
public static final String IMPORT_KEY_LIST = "import_key_list";
|
||||
|
||||
// export key
|
||||
public static final String EXPORT_OUTPUT_STREAM = "export_output_stream";
|
||||
public static final String EXPORT_FILENAME = "export_filename";
|
||||
public static final String EXPORT_KEY_TYPE = "export_key_type";
|
||||
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";
|
||||
public static final String UPLOAD_KEY_KEYRING_ROW_ID = "upload_key_ring_id";
|
||||
|
||||
// query key
|
||||
public static final String QUERY_KEY_SERVER = "query_key_server";
|
||||
public static final String QUERY_KEY_TYPE = "query_key_type";
|
||||
public static final String QUERY_KEY_STRING = "query_key_string";
|
||||
public static final String QUERY_KEY_ID = "query_key_id";
|
||||
|
||||
// sign key
|
||||
public static final String SIGN_KEY_MASTER_KEY_ID = "sign_key_master_key_id";
|
||||
public static final String SIGN_KEY_PUB_KEY_ID = "sign_key_pub_key_id";
|
||||
|
||||
/*
|
||||
* possible data keys as result send over messenger
|
||||
*/
|
||||
// keys
|
||||
public static final String RESULT_NEW_KEY = "new_key";
|
||||
public static final String RESULT_NEW_KEY2 = "new_key2";
|
||||
|
||||
// encrypt
|
||||
public static final String RESULT_SIGNATURE_BYTES = "signature_data";
|
||||
public static final String RESULT_SIGNATURE_STRING = "signature_text";
|
||||
public static final String RESULT_ENCRYPTED_STRING = "encrypted_message";
|
||||
public static final String RESULT_ENCRYPTED_BYTES = "encrypted_data";
|
||||
public static final String RESULT_URI = "result_uri";
|
||||
|
||||
// decrypt/verify
|
||||
public static final String RESULT_DECRYPTED_STRING = "decrypted_message";
|
||||
public static final String RESULT_DECRYPTED_BYTES = "decrypted_data";
|
||||
public static final String RESULT_SIGNATURE = "signature";
|
||||
public static final String RESULT_SIGNATURE_KEY_ID = "signature_key_id";
|
||||
public static final String RESULT_SIGNATURE_USER_ID = "signature_user_id";
|
||||
|
||||
public static final String RESULT_SIGNATURE_SUCCESS = "signature_success";
|
||||
public static final String RESULT_SIGNATURE_UNKNOWN = "signature_unknown";
|
||||
public static final String RESULT_SIGNATURE_LOOKUP_KEY = "lookup_key";
|
||||
|
||||
// import
|
||||
public static final String RESULT_IMPORT_ADDED = "added";
|
||||
public static final String RESULT_IMPORT_UPDATED = "updated";
|
||||
public static final String RESULT_IMPORT_BAD = "bad";
|
||||
|
||||
// export
|
||||
public static final String RESULT_EXPORT = "exported";
|
||||
|
||||
// query
|
||||
public static final String RESULT_QUERY_KEY_DATA = "query_key_data";
|
||||
public static final String RESULT_QUERY_KEY_SEARCH_RESULT = "query_key_search_result";
|
||||
|
||||
Messenger mMessenger;
|
||||
|
||||
public KeychainIntentService() {
|
||||
super("ApgService");
|
||||
}
|
||||
|
||||
/**
|
||||
* The IntentService calls this method from the default worker thread with the intent that
|
||||
* started the service. When this method returns, IntentService stops the service, as
|
||||
* appropriate.
|
||||
*/
|
||||
@Override
|
||||
protected void onHandleIntent(Intent intent) {
|
||||
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;
|
||||
}
|
||||
|
||||
mMessenger = (Messenger) extras.get(EXTRA_MESSENGER);
|
||||
Bundle data = extras.getBundle(EXTRA_DATA);
|
||||
|
||||
OtherHelper.logDebugBundle(data, "EXTRA_DATA");
|
||||
|
||||
String action = intent.getAction();
|
||||
|
||||
// execute action from extra bundle
|
||||
if (ACTION_ENCRYPT_SIGN.equals(action)) {
|
||||
try {
|
||||
/* Input */
|
||||
int target = data.getInt(TARGET);
|
||||
|
||||
long secretKeyId = data.getLong(ENCRYPT_SECRET_KEY_ID);
|
||||
String encryptionPassphrase = data.getString(GENERATE_KEY_SYMMETRIC_PASSPHRASE);
|
||||
|
||||
boolean useAsciiArmor = data.getBoolean(ENCRYPT_USE_ASCII_ARMOR);
|
||||
long encryptionKeyIds[] = data.getLongArray(ENCRYPT_ENCRYPTION_KEYS_IDS);
|
||||
int compressionId = data.getInt(ENCRYPT_COMPRESSION_ID);
|
||||
boolean generateSignature = data.getBoolean(ENCRYPT_GENERATE_SIGNATURE);
|
||||
boolean signOnly = data.getBoolean(ENCRYPT_SIGN_ONLY);
|
||||
|
||||
InputStream inStream = null;
|
||||
long inLength = -1;
|
||||
InputData inputData = null;
|
||||
OutputStream outStream = null;
|
||||
String streamFilename = null;
|
||||
switch (target) {
|
||||
case TARGET_BYTES: /* encrypting bytes directly */
|
||||
byte[] bytes = data.getByteArray(ENCRYPT_MESSAGE_BYTES);
|
||||
|
||||
inStream = new ByteArrayInputStream(bytes);
|
||||
inLength = bytes.length;
|
||||
|
||||
inputData = new InputData(inStream, inLength);
|
||||
outStream = new ByteArrayOutputStream();
|
||||
|
||||
break;
|
||||
case TARGET_FILE: /* encrypting file */
|
||||
String inputFile = data.getString(ENCRYPT_INPUT_FILE);
|
||||
String outputFile = data.getString(ENCRYPT_OUTPUT_FILE);
|
||||
|
||||
// check if storage is ready
|
||||
if (!FileHelper.isStorageMounted(inputFile)
|
||||
|| !FileHelper.isStorageMounted(outputFile)) {
|
||||
throw new PgpGeneralException(
|
||||
getString(R.string.error_external_storage_not_ready));
|
||||
}
|
||||
|
||||
inStream = new FileInputStream(inputFile);
|
||||
File file = new File(inputFile);
|
||||
inLength = file.length();
|
||||
inputData = new InputData(inStream, inLength);
|
||||
|
||||
outStream = new FileOutputStream(outputFile);
|
||||
|
||||
break;
|
||||
|
||||
case TARGET_STREAM: /* Encrypting stream from content uri */
|
||||
Uri providerUri = (Uri) data.getParcelable(ENCRYPT_PROVIDER_URI);
|
||||
|
||||
// InputStream
|
||||
InputStream in = getContentResolver().openInputStream(providerUri);
|
||||
inLength = PgpHelper.getLengthOfStream(in);
|
||||
inputData = new InputData(in, inLength);
|
||||
|
||||
// OutputStream
|
||||
try {
|
||||
while (true) {
|
||||
streamFilename = PgpHelper.generateRandomFilename(32);
|
||||
if (streamFilename == null) {
|
||||
throw new PgpGeneralException("couldn't generate random file name");
|
||||
}
|
||||
openFileInput(streamFilename).close();
|
||||
}
|
||||
} catch (FileNotFoundException e) {
|
||||
// found a name that isn't used yet
|
||||
}
|
||||
outStream = openFileOutput(streamFilename, Context.MODE_PRIVATE);
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new PgpGeneralException("No target choosen!");
|
||||
|
||||
}
|
||||
|
||||
/* Operation */
|
||||
PgpOperation operation = new PgpOperation(this, this, inputData, outStream);
|
||||
if (generateSignature) {
|
||||
Log.d(Constants.TAG, "generating signature...");
|
||||
operation.generateSignature(useAsciiArmor, false, secretKeyId,
|
||||
PassphraseCacheService.getCachedPassphrase(this, secretKeyId),
|
||||
Preferences.getPreferences(this).getDefaultHashAlgorithm(), Preferences
|
||||
.getPreferences(this).getForceV3Signatures());
|
||||
} else if (signOnly) {
|
||||
Log.d(Constants.TAG, "sign only...");
|
||||
operation.signText(secretKeyId, PassphraseCacheService.getCachedPassphrase(
|
||||
this, secretKeyId), Preferences.getPreferences(this)
|
||||
.getDefaultHashAlgorithm(), Preferences.getPreferences(this)
|
||||
.getForceV3Signatures());
|
||||
} else {
|
||||
Log.d(Constants.TAG, "encrypt...");
|
||||
operation.signAndEncrypt(useAsciiArmor, compressionId, encryptionKeyIds,
|
||||
encryptionPassphrase, Preferences.getPreferences(this)
|
||||
.getDefaultEncryptionAlgorithm(), secretKeyId, Preferences
|
||||
.getPreferences(this).getDefaultHashAlgorithm(), Preferences
|
||||
.getPreferences(this).getForceV3Signatures(),
|
||||
PassphraseCacheService.getCachedPassphrase(this, secretKeyId));
|
||||
}
|
||||
|
||||
outStream.close();
|
||||
|
||||
/* Output */
|
||||
|
||||
Bundle resultData = new Bundle();
|
||||
|
||||
switch (target) {
|
||||
case TARGET_BYTES:
|
||||
if (useAsciiArmor) {
|
||||
String output = new String(
|
||||
((ByteArrayOutputStream) outStream).toByteArray());
|
||||
if (generateSignature) {
|
||||
resultData.putString(RESULT_SIGNATURE_STRING, output);
|
||||
} else {
|
||||
resultData.putString(RESULT_ENCRYPTED_STRING, output);
|
||||
}
|
||||
} else {
|
||||
byte output[] = ((ByteArrayOutputStream) outStream).toByteArray();
|
||||
if (generateSignature) {
|
||||
resultData.putByteArray(RESULT_SIGNATURE_BYTES, output);
|
||||
} else {
|
||||
resultData.putByteArray(RESULT_ENCRYPTED_BYTES, output);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case TARGET_FILE:
|
||||
// nothing, file was written, just send okay
|
||||
|
||||
break;
|
||||
case TARGET_STREAM:
|
||||
String uri = DataStream.buildDataStreamUri(streamFilename).toString();
|
||||
resultData.putString(RESULT_URI, uri);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
OtherHelper.logDebugBundle(resultData, "resultData");
|
||||
|
||||
sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, resultData);
|
||||
} catch (Exception e) {
|
||||
sendErrorToHandler(e);
|
||||
}
|
||||
} else if (ACTION_DECRYPT_VERIFY.equals(action)) {
|
||||
try {
|
||||
/* Input */
|
||||
int target = data.getInt(TARGET);
|
||||
|
||||
long secretKeyId = data.getLong(ENCRYPT_SECRET_KEY_ID);
|
||||
byte[] bytes = data.getByteArray(DECRYPT_CIPHERTEXT_BYTES);
|
||||
boolean signedOnly = data.getBoolean(DECRYPT_SIGNED_ONLY);
|
||||
boolean returnBytes = data.getBoolean(DECRYPT_RETURN_BYTES);
|
||||
boolean assumeSymmetricEncryption = data.getBoolean(DECRYPT_ASSUME_SYMMETRIC);
|
||||
|
||||
boolean lookupUnknownKey = data.getBoolean(DECRYPT_LOOKUP_UNKNOWN_KEY);
|
||||
|
||||
InputStream inStream = null;
|
||||
long inLength = -1;
|
||||
InputData inputData = null;
|
||||
OutputStream outStream = null;
|
||||
String streamFilename = null;
|
||||
switch (target) {
|
||||
case TARGET_BYTES: /* decrypting bytes directly */
|
||||
inStream = new ByteArrayInputStream(bytes);
|
||||
inLength = bytes.length;
|
||||
|
||||
inputData = new InputData(inStream, inLength);
|
||||
outStream = new ByteArrayOutputStream();
|
||||
|
||||
break;
|
||||
|
||||
case TARGET_FILE: /* decrypting file */
|
||||
String inputFile = data.getString(ENCRYPT_INPUT_FILE);
|
||||
String outputFile = data.getString(ENCRYPT_OUTPUT_FILE);
|
||||
|
||||
// check if storage is ready
|
||||
if (!FileHelper.isStorageMounted(inputFile)
|
||||
|| !FileHelper.isStorageMounted(outputFile)) {
|
||||
throw new PgpGeneralException(
|
||||
getString(R.string.error_external_storage_not_ready));
|
||||
}
|
||||
|
||||
// InputStream
|
||||
inLength = -1;
|
||||
inStream = new FileInputStream(inputFile);
|
||||
File file = new File(inputFile);
|
||||
inLength = file.length();
|
||||
inputData = new InputData(inStream, inLength);
|
||||
|
||||
// OutputStream
|
||||
outStream = new FileOutputStream(outputFile);
|
||||
|
||||
break;
|
||||
|
||||
case TARGET_STREAM: /* decrypting stream from content uri */
|
||||
Uri providerUri = (Uri) data.getParcelable(ENCRYPT_PROVIDER_URI);
|
||||
|
||||
// InputStream
|
||||
InputStream in = getContentResolver().openInputStream(providerUri);
|
||||
inLength = PgpHelper.getLengthOfStream(in);
|
||||
inputData = new InputData(in, inLength);
|
||||
|
||||
// OutputStream
|
||||
try {
|
||||
while (true) {
|
||||
streamFilename = PgpHelper.generateRandomFilename(32);
|
||||
if (streamFilename == null) {
|
||||
throw new PgpGeneralException("couldn't generate random file name");
|
||||
}
|
||||
openFileInput(streamFilename).close();
|
||||
}
|
||||
} catch (FileNotFoundException e) {
|
||||
// found a name that isn't used yet
|
||||
}
|
||||
outStream = openFileOutput(streamFilename, Context.MODE_PRIVATE);
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new PgpGeneralException("No target choosen!");
|
||||
|
||||
}
|
||||
|
||||
/* Operation */
|
||||
|
||||
Bundle resultData = new Bundle();
|
||||
|
||||
// verifyText and decrypt returning additional resultData values for the
|
||||
// verification of signatures
|
||||
PgpOperation operation = new PgpOperation(this, this, inputData, outStream);
|
||||
if (signedOnly) {
|
||||
resultData = operation.verifyText(lookupUnknownKey);
|
||||
} else {
|
||||
resultData = operation.decryptAndVerify(
|
||||
PassphraseCacheService.getCachedPassphrase(this, secretKeyId),
|
||||
assumeSymmetricEncryption);
|
||||
}
|
||||
|
||||
outStream.close();
|
||||
|
||||
/* Output */
|
||||
|
||||
switch (target) {
|
||||
case TARGET_BYTES:
|
||||
if (returnBytes) {
|
||||
byte output[] = ((ByteArrayOutputStream) outStream).toByteArray();
|
||||
resultData.putByteArray(RESULT_DECRYPTED_BYTES, output);
|
||||
} else {
|
||||
String output = new String(
|
||||
((ByteArrayOutputStream) outStream).toByteArray());
|
||||
resultData.putString(RESULT_DECRYPTED_STRING, output);
|
||||
}
|
||||
|
||||
break;
|
||||
case TARGET_FILE:
|
||||
// nothing, file was written, just send okay and verification bundle
|
||||
|
||||
break;
|
||||
case TARGET_STREAM:
|
||||
String uri = DataStream.buildDataStreamUri(streamFilename).toString();
|
||||
resultData.putString(RESULT_URI, uri);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
OtherHelper.logDebugBundle(resultData, "resultData");
|
||||
|
||||
sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, resultData);
|
||||
} catch (Exception e) {
|
||||
sendErrorToHandler(e);
|
||||
}
|
||||
} else if (ACTION_SAVE_KEYRING.equals(action)) {
|
||||
try {
|
||||
/* Input */
|
||||
String oldPassPhrase = data.getString(SAVE_KEYRING_CURRENT_PASSPHRASE);
|
||||
String newPassPhrase = data.getString(SAVE_KEYRING_NEW_PASSPHRASE);
|
||||
boolean canSign = true;
|
||||
|
||||
if (data.containsKey(SAVE_KEYRING_CAN_SIGN)) {
|
||||
canSign = data.getBoolean(SAVE_KEYRING_CAN_SIGN);
|
||||
}
|
||||
|
||||
if (newPassPhrase == null) {
|
||||
newPassPhrase = oldPassPhrase;
|
||||
}
|
||||
ArrayList<String> userIds = data.getStringArrayList(SAVE_KEYRING_USER_IDS);
|
||||
ArrayList<PGPSecretKey> keys = PgpConversionHelper.BytesToPGPSecretKeyList(data
|
||||
.getByteArray(SAVE_KEYRING_KEYS));
|
||||
ArrayList<Integer> keysUsages = data.getIntegerArrayList(SAVE_KEYRING_KEYS_USAGES);
|
||||
long masterKeyId = data.getLong(SAVE_KEYRING_MASTER_KEY_ID);
|
||||
|
||||
PgpKeyOperation keyOperations = new PgpKeyOperation(this, this);
|
||||
/* Operation */
|
||||
if (!canSign) {
|
||||
keyOperations.changeSecretKeyPassphrase(
|
||||
ProviderHelper.getPGPSecretKeyRingByKeyId(this, masterKeyId),
|
||||
oldPassPhrase, newPassPhrase);
|
||||
} else {
|
||||
keyOperations.buildSecretKey(userIds, keys, keysUsages, masterKeyId,
|
||||
oldPassPhrase, newPassPhrase);
|
||||
}
|
||||
PassphraseCacheService.addCachedPassphrase(this, masterKeyId, newPassPhrase);
|
||||
|
||||
/* Output */
|
||||
sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY);
|
||||
} catch (Exception e) {
|
||||
sendErrorToHandler(e);
|
||||
}
|
||||
} else if (ACTION_GENERATE_KEY.equals(action)) {
|
||||
try {
|
||||
/* Input */
|
||||
int algorithm = data.getInt(GENERATE_KEY_ALGORITHM);
|
||||
String passphrase = data.getString(GENERATE_KEY_SYMMETRIC_PASSPHRASE);
|
||||
int keysize = data.getInt(GENERATE_KEY_KEY_SIZE);
|
||||
PGPSecretKey masterKey = null;
|
||||
if (data.containsKey(GENERATE_KEY_MASTER_KEY)) {
|
||||
masterKey = PgpConversionHelper.BytesToPGPSecretKey(data
|
||||
.getByteArray(GENERATE_KEY_MASTER_KEY));
|
||||
}
|
||||
|
||||
/* Operation */
|
||||
PgpKeyOperation keyOperations = new PgpKeyOperation(this, this);
|
||||
PGPSecretKeyRing newKeyRing = keyOperations.createKey(algorithm, keysize,
|
||||
passphrase, masterKey);
|
||||
|
||||
/* Output */
|
||||
Bundle resultData = new Bundle();
|
||||
resultData.putByteArray(RESULT_NEW_KEY,
|
||||
PgpConversionHelper.PGPSecretKeyRingToBytes(newKeyRing));
|
||||
|
||||
OtherHelper.logDebugBundle(resultData, "resultData");
|
||||
|
||||
sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, resultData);
|
||||
} catch (Exception e) {
|
||||
sendErrorToHandler(e);
|
||||
}
|
||||
} else if (ACTION_GENERATE_DEFAULT_RSA_KEYS.equals(action)) {
|
||||
// generate one RSA 4096 key for signing and one subkey for encrypting!
|
||||
try {
|
||||
/* Input */
|
||||
String passphrase = data.getString(GENERATE_KEY_SYMMETRIC_PASSPHRASE);
|
||||
|
||||
/* Operation */
|
||||
PgpKeyOperation keyOperations = new PgpKeyOperation(this, this);
|
||||
|
||||
PGPSecretKeyRing masterKeyRing = keyOperations.createKey(Id.choice.algorithm.rsa,
|
||||
4096, passphrase, null);
|
||||
|
||||
PGPSecretKeyRing subKeyRing = keyOperations.createKey(Id.choice.algorithm.rsa,
|
||||
4096, passphrase, masterKeyRing.getSecretKey());
|
||||
|
||||
/* Output */
|
||||
Bundle resultData = new Bundle();
|
||||
resultData.putByteArray(RESULT_NEW_KEY,
|
||||
PgpConversionHelper.PGPSecretKeyRingToBytes(masterKeyRing));
|
||||
resultData.putByteArray(RESULT_NEW_KEY2,
|
||||
PgpConversionHelper.PGPSecretKeyRingToBytes(subKeyRing));
|
||||
|
||||
OtherHelper.logDebugBundle(resultData, "resultData");
|
||||
|
||||
sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, resultData);
|
||||
} catch (Exception e) {
|
||||
sendErrorToHandler(e);
|
||||
}
|
||||
} else if (ACTION_DELETE_FILE_SECURELY.equals(action)) {
|
||||
try {
|
||||
/* Input */
|
||||
String deleteFile = data.getString(DELETE_FILE);
|
||||
|
||||
/* Operation */
|
||||
try {
|
||||
PgpHelper.deleteFileSecurely(this, this, new File(deleteFile));
|
||||
} catch (FileNotFoundException e) {
|
||||
throw new PgpGeneralException(
|
||||
getString(R.string.error_file_not_found, deleteFile));
|
||||
} catch (IOException e) {
|
||||
throw new PgpGeneralException(getString(R.string.error_file_delete_failed,
|
||||
deleteFile));
|
||||
}
|
||||
|
||||
/* Output */
|
||||
sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY);
|
||||
} catch (Exception e) {
|
||||
sendErrorToHandler(e);
|
||||
}
|
||||
} else if (ACTION_IMPORT_KEYRING.equals(action)) {
|
||||
try {
|
||||
|
||||
/* Input */
|
||||
int target = data.getInt(TARGET);
|
||||
|
||||
/* Operation */
|
||||
InputStream inStream = null;
|
||||
long inLength = -1;
|
||||
InputData inputData = null;
|
||||
switch (target) {
|
||||
case TARGET_BYTES: /* import key from bytes directly */
|
||||
byte[] bytes = data.getByteArray(IMPORT_BYTES);
|
||||
|
||||
inStream = new ByteArrayInputStream(bytes);
|
||||
inLength = bytes.length;
|
||||
|
||||
inputData = new InputData(inStream, inLength);
|
||||
|
||||
break;
|
||||
case TARGET_FILE: /* import key from file */
|
||||
String inputFile = data.getString(IMPORT_FILENAME);
|
||||
|
||||
inStream = new FileInputStream(inputFile);
|
||||
File file = new File(inputFile);
|
||||
inLength = file.length();
|
||||
inputData = new InputData(inStream, inLength);
|
||||
|
||||
break;
|
||||
|
||||
case TARGET_STREAM:
|
||||
// TODO: not implemented
|
||||
break;
|
||||
}
|
||||
|
||||
Bundle resultData = new Bundle();
|
||||
|
||||
ArrayList<Long> keyIds = (ArrayList<Long>) data.getSerializable(IMPORT_KEY_LIST);
|
||||
|
||||
PgpImportExport pgpImportExport = new PgpImportExport(this, this);
|
||||
resultData = pgpImportExport.importKeyRings(inputData, keyIds);
|
||||
|
||||
sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, resultData);
|
||||
} catch (Exception e) {
|
||||
sendErrorToHandler(e);
|
||||
}
|
||||
} else if (ACTION_EXPORT_KEYRING.equals(action)) {
|
||||
try {
|
||||
|
||||
/* Input */
|
||||
int keyType = Id.type.public_key;
|
||||
if (data.containsKey(EXPORT_KEY_TYPE)) {
|
||||
keyType = data.getInt(EXPORT_KEY_TYPE);
|
||||
}
|
||||
|
||||
String outputFile = data.getString(EXPORT_FILENAME);
|
||||
|
||||
boolean exportAll = data.getBoolean(EXPORT_ALL);
|
||||
long keyRingMasterKeyId = -1;
|
||||
if (!exportAll) {
|
||||
keyRingMasterKeyId = data.getLong(EXPORT_KEY_RING_MASTER_KEY_ID);
|
||||
}
|
||||
|
||||
/* Operation */
|
||||
|
||||
// check if storage is ready
|
||||
if (!FileHelper.isStorageMounted(outputFile)) {
|
||||
throw new PgpGeneralException(getString(R.string.error_external_storage_not_ready));
|
||||
}
|
||||
|
||||
// OutputStream
|
||||
FileOutputStream outStream = new FileOutputStream(outputFile);
|
||||
|
||||
ArrayList<Long> keyRingMasterKeyIds = new ArrayList<Long>();
|
||||
if (exportAll) {
|
||||
// get all key ring row ids based on export type
|
||||
|
||||
if (keyType == Id.type.public_key) {
|
||||
keyRingMasterKeyIds = ProviderHelper.getPublicKeyRingsMasterKeyIds(this);
|
||||
} else {
|
||||
keyRingMasterKeyIds = ProviderHelper.getSecretKeyRingsMasterKeyIds(this);
|
||||
}
|
||||
} else {
|
||||
keyRingMasterKeyIds.add(keyRingMasterKeyId);
|
||||
}
|
||||
|
||||
Bundle resultData = new Bundle();
|
||||
|
||||
PgpImportExport pgpImportExport = new PgpImportExport(this, this);
|
||||
resultData = pgpImportExport
|
||||
.exportKeyRings(keyRingMasterKeyIds, keyType, outStream);
|
||||
|
||||
sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, resultData);
|
||||
} catch (Exception e) {
|
||||
sendErrorToHandler(e);
|
||||
}
|
||||
} else if (ACTION_UPLOAD_KEYRING.equals(action)) {
|
||||
try {
|
||||
|
||||
/* Input */
|
||||
int keyRingRowId = data.getInt(UPLOAD_KEY_KEYRING_ROW_ID);
|
||||
String keyServer = data.getString(UPLOAD_KEY_SERVER);
|
||||
|
||||
/* Operation */
|
||||
HkpKeyServer server = new HkpKeyServer(keyServer);
|
||||
|
||||
PGPPublicKeyRing keyring = ProviderHelper.getPGPPublicKeyRingByRowId(this,
|
||||
keyRingRowId);
|
||||
if (keyring != null) {
|
||||
PgpImportExport pgpImportExport = new PgpImportExport(this, null);
|
||||
|
||||
boolean uploaded = pgpImportExport.uploadKeyRingToServer(server,
|
||||
(PGPPublicKeyRing) keyring);
|
||||
if (!uploaded) {
|
||||
throw new PgpGeneralException("Unable to export key to selected server");
|
||||
}
|
||||
}
|
||||
|
||||
sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY);
|
||||
} catch (Exception e) {
|
||||
sendErrorToHandler(e);
|
||||
}
|
||||
} else if (ACTION_QUERY_KEYRING.equals(action)) {
|
||||
try {
|
||||
|
||||
/* Input */
|
||||
int queryType = data.getInt(QUERY_KEY_TYPE);
|
||||
String keyServer = data.getString(QUERY_KEY_SERVER);
|
||||
|
||||
String queryString = data.getString(QUERY_KEY_STRING);
|
||||
long keyId = data.getLong(QUERY_KEY_ID);
|
||||
|
||||
/* Operation */
|
||||
Bundle resultData = new Bundle();
|
||||
|
||||
HkpKeyServer server = new HkpKeyServer(keyServer);
|
||||
if (queryType == Id.keyserver.search) {
|
||||
ArrayList<KeyInfo> searchResult = server.search(queryString);
|
||||
|
||||
resultData.putParcelableArrayList(RESULT_QUERY_KEY_SEARCH_RESULT, searchResult);
|
||||
} else if (queryType == Id.keyserver.get) {
|
||||
String keyData = server.get(keyId);
|
||||
|
||||
resultData.putString(RESULT_QUERY_KEY_DATA, keyData);
|
||||
}
|
||||
|
||||
sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, resultData);
|
||||
} catch (Exception e) {
|
||||
sendErrorToHandler(e);
|
||||
}
|
||||
} else if (ACTION_SIGN_KEYRING.equals(action)) {
|
||||
try {
|
||||
|
||||
/* Input */
|
||||
long masterKeyId = data.getLong(SIGN_KEY_MASTER_KEY_ID);
|
||||
long pubKeyId = data.getLong(SIGN_KEY_PUB_KEY_ID);
|
||||
|
||||
/* Operation */
|
||||
String signaturePassPhrase = PassphraseCacheService.getCachedPassphrase(this,
|
||||
masterKeyId);
|
||||
|
||||
PgpKeyOperation keyOperation = new PgpKeyOperation(this, this);
|
||||
PGPPublicKeyRing signedPubKeyRing = keyOperation.signKey(masterKeyId, pubKeyId,
|
||||
signaturePassPhrase);
|
||||
|
||||
// store the signed key in our local cache
|
||||
PgpImportExport pgpImportExport = new PgpImportExport(this, null);
|
||||
int retval = pgpImportExport.storeKeyRingInCache(signedPubKeyRing);
|
||||
if (retval != Id.return_value.ok && retval != Id.return_value.updated) {
|
||||
throw new PgpGeneralException("Failed to store signed key in local cache");
|
||||
}
|
||||
|
||||
sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY);
|
||||
} catch (Exception e) {
|
||||
sendErrorToHandler(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void sendErrorToHandler(Exception e) {
|
||||
Log.e(Constants.TAG, "ApgService Exception: ", e);
|
||||
e.printStackTrace();
|
||||
|
||||
Bundle data = new Bundle();
|
||||
data.putString(KeychainIntentServiceHandler.DATA_ERROR, e.getMessage());
|
||||
sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_EXCEPTION, null, data);
|
||||
}
|
||||
|
||||
private void sendMessageToHandler(Integer arg1, Integer arg2, Bundle data) {
|
||||
Message msg = Message.obtain();
|
||||
msg.arg1 = arg1;
|
||||
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(Integer arg1, Bundle data) {
|
||||
sendMessageToHandler(arg1, null, data);
|
||||
}
|
||||
|
||||
private void sendMessageToHandler(Integer arg1) {
|
||||
sendMessageToHandler(arg1, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set progress of ProgressDialog by sending message to handler on UI thread
|
||||
*/
|
||||
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(KeychainIntentServiceHandler.DATA_MESSAGE, message);
|
||||
}
|
||||
data.putInt(KeychainIntentServiceHandler.DATA_PROGRESS, progress);
|
||||
data.putInt(KeychainIntentServiceHandler.DATA_PROGRESS_MAX, max);
|
||||
|
||||
sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_UPDATE_PROGRESS, null, data);
|
||||
}
|
||||
|
||||
public void setProgress(int resourceId, int progress, int max) {
|
||||
setProgress(getString(resourceId), progress, max);
|
||||
}
|
||||
|
||||
public void setProgress(int progress, int max) {
|
||||
setProgress(null, progress, max);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2013 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.service;
|
||||
|
||||
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
import android.widget.Toast;
|
||||
|
||||
public class KeychainIntentServiceHandler extends Handler {
|
||||
|
||||
// possible messages send from this service to handler on ui
|
||||
public static final int MESSAGE_OKAY = 1;
|
||||
public static final int MESSAGE_EXCEPTION = 2;
|
||||
public static final int MESSAGE_UPDATE_PROGRESS = 3;
|
||||
|
||||
// possible data keys for messages
|
||||
public static final String DATA_ERROR = "error";
|
||||
public static final String DATA_PROGRESS = "progress";
|
||||
public static final String DATA_PROGRESS_MAX = "max";
|
||||
public static final String DATA_MESSAGE = "message";
|
||||
public static final String DATA_MESSAGE_ID = "message_id";
|
||||
|
||||
Activity mActivity;
|
||||
ProgressDialogFragment mProgressDialogFragment;
|
||||
|
||||
public KeychainIntentServiceHandler(Activity activity) {
|
||||
this.mActivity = activity;
|
||||
}
|
||||
|
||||
public KeychainIntentServiceHandler(Activity activity, ProgressDialogFragment progressDialogFragment) {
|
||||
this.mActivity = activity;
|
||||
this.mProgressDialogFragment = progressDialogFragment;
|
||||
}
|
||||
|
||||
public KeychainIntentServiceHandler(Activity activity, int progressDialogMessageId, int progressDialogStyle) {
|
||||
this.mActivity = activity;
|
||||
this.mProgressDialogFragment = ProgressDialogFragment.newInstance(progressDialogMessageId,
|
||||
progressDialogStyle);
|
||||
}
|
||||
|
||||
public void showProgressDialog(FragmentActivity activity) {
|
||||
mProgressDialogFragment.show(activity.getSupportFragmentManager(), "progressDialog");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message message) {
|
||||
Bundle data = message.getData();
|
||||
|
||||
switch (message.arg1) {
|
||||
case MESSAGE_OKAY:
|
||||
mProgressDialogFragment.dismiss();
|
||||
|
||||
break;
|
||||
|
||||
case MESSAGE_EXCEPTION:
|
||||
mProgressDialogFragment.dismiss();
|
||||
|
||||
// show error from service
|
||||
if (data.containsKey(DATA_ERROR)) {
|
||||
Toast.makeText(mActivity,
|
||||
mActivity.getString(R.string.error_message, data.getString(DATA_ERROR)),
|
||||
Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case MESSAGE_UPDATE_PROGRESS:
|
||||
if (data.containsKey(DATA_PROGRESS) && data.containsKey(DATA_PROGRESS_MAX)) {
|
||||
|
||||
// update progress from service
|
||||
if (data.containsKey(DATA_MESSAGE)) {
|
||||
mProgressDialogFragment.setProgress(data.getString(DATA_MESSAGE),
|
||||
data.getInt(DATA_PROGRESS), data.getInt(DATA_PROGRESS_MAX));
|
||||
} else if (data.containsKey(DATA_MESSAGE_ID)) {
|
||||
mProgressDialogFragment.setProgress(data.getInt(DATA_MESSAGE_ID),
|
||||
data.getInt(DATA_PROGRESS), data.getInt(DATA_PROGRESS_MAX));
|
||||
} else {
|
||||
mProgressDialogFragment.setProgress(data.getInt(DATA_PROGRESS),
|
||||
data.getInt(DATA_PROGRESS_MAX));
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,370 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2013 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.service;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
|
||||
import org.spongycastle.openpgp.PGPException;
|
||||
import org.spongycastle.openpgp.PGPPrivateKey;
|
||||
import org.spongycastle.openpgp.PGPSecretKey;
|
||||
import org.spongycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor;
|
||||
import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.Id;
|
||||
import org.sufficientlysecure.keychain.helper.Preferences;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
|
||||
import android.app.AlarmManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.Service;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.os.Binder;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.os.IBinder;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* This service runs in its own process, but is available to all other processes as the main
|
||||
* passphrase cache. Use the static methods addCachedPassphrase and getCachedPassphrase for
|
||||
* convenience.
|
||||
*
|
||||
*/
|
||||
public class PassphraseCacheService extends Service {
|
||||
public static final String TAG = Constants.TAG + ": PassphraseCacheService";
|
||||
|
||||
public static final String ACTION_PASSPHRASE_CACHE_ADD = Constants.INTENT_PREFIX
|
||||
+ "PASSPHRASE_CACHE_ADD";
|
||||
public static final String ACTION_PASSPHRASE_CACHE_GET = Constants.INTENT_PREFIX
|
||||
+ "PASSPHRASE_CACHE_GET";
|
||||
|
||||
public static final String BROADCAST_ACTION_PASSPHRASE_CACHE_SERVICE = Constants.INTENT_PREFIX
|
||||
+ "PASSPHRASE_CACHE_BROADCAST";
|
||||
|
||||
public static final String EXTRA_TTL = "ttl";
|
||||
public static final String EXTRA_KEY_ID = "key_id";
|
||||
public static final String EXTRA_PASSPHRASE = "passphrase";
|
||||
public static final String EXTRA_MESSENGER = "messenger";
|
||||
|
||||
private static final int REQUEST_ID = 0;
|
||||
private static final long DEFAULT_TTL = 15;
|
||||
|
||||
private BroadcastReceiver mIntentReceiver;
|
||||
|
||||
private HashMap<Long, String> mPassphraseCache = new HashMap<Long, String>();
|
||||
|
||||
Context mContext;
|
||||
|
||||
/**
|
||||
* This caches a new passphrase in memory by sending a new command to the service. An android
|
||||
* service is only run once. Thus, when the service is already started, new commands just add
|
||||
* new events to the alarm manager for new passphrases to let them timeout in the future.
|
||||
*
|
||||
* @param context
|
||||
* @param keyId
|
||||
* @param passphrase
|
||||
*/
|
||||
public static void addCachedPassphrase(Context context, long keyId, String passphrase) {
|
||||
Log.d(TAG, "cacheNewPassphrase() for " + keyId);
|
||||
|
||||
Intent intent = new Intent(context, PassphraseCacheService.class);
|
||||
intent.setAction(ACTION_PASSPHRASE_CACHE_ADD);
|
||||
intent.putExtra(EXTRA_TTL, Preferences.getPreferences(context).getPassPhraseCacheTtl());
|
||||
intent.putExtra(EXTRA_PASSPHRASE, passphrase);
|
||||
intent.putExtra(EXTRA_KEY_ID, keyId);
|
||||
|
||||
context.startService(intent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a cached passphrase from memory by sending an intent to the service. This method is
|
||||
* designed to wait until the service returns the passphrase.
|
||||
*
|
||||
* @param context
|
||||
* @param keyId
|
||||
* @return passphrase or null (if no passphrase is cached for this keyId)
|
||||
*/
|
||||
public static String getCachedPassphrase(Context context, long keyId) {
|
||||
Log.d(TAG, "getCachedPassphrase() get masterKeyId for " + keyId);
|
||||
|
||||
Intent intent = new Intent(context, PassphraseCacheService.class);
|
||||
intent.setAction(ACTION_PASSPHRASE_CACHE_GET);
|
||||
|
||||
final Object mutex = new Object();
|
||||
final Bundle returnBundle = new Bundle();
|
||||
|
||||
HandlerThread handlerThread = new HandlerThread("getPassphraseThread");
|
||||
handlerThread.start();
|
||||
Handler returnHandler = new Handler(handlerThread.getLooper()) {
|
||||
@Override
|
||||
public void handleMessage(Message message) {
|
||||
if (message.obj != null) {
|
||||
String passphrase = ((Bundle) message.obj).getString(EXTRA_PASSPHRASE);
|
||||
returnBundle.putString(EXTRA_PASSPHRASE, passphrase);
|
||||
}
|
||||
synchronized (mutex) {
|
||||
mutex.notify();
|
||||
}
|
||||
// quit handlerThread
|
||||
getLooper().quit();
|
||||
}
|
||||
};
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(returnHandler);
|
||||
intent.putExtra(EXTRA_KEY_ID, keyId);
|
||||
intent.putExtra(EXTRA_MESSENGER, messenger);
|
||||
// send intent to this service
|
||||
context.startService(intent);
|
||||
|
||||
// Wait on mutex until passphrase is returned to handlerThread
|
||||
synchronized (mutex) {
|
||||
try {
|
||||
mutex.wait(3000);
|
||||
} catch (InterruptedException e) {
|
||||
}
|
||||
}
|
||||
|
||||
if (returnBundle.containsKey(EXTRA_PASSPHRASE)) {
|
||||
return returnBundle.getString(EXTRA_PASSPHRASE);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal implementation to get cached passphrase.
|
||||
*
|
||||
* @param keyId
|
||||
* @return
|
||||
*/
|
||||
private String getCachedPassphraseImpl(long keyId) {
|
||||
Log.d(TAG, "getCachedPassphraseImpl() get masterKeyId for " + keyId);
|
||||
|
||||
// try to get master key id which is used as an identifier for cached passphrases
|
||||
long masterKeyId = keyId;
|
||||
if (masterKeyId != Id.key.symmetric) {
|
||||
PGPSecretKeyRing keyRing = ProviderHelper.getPGPSecretKeyRingByKeyId(this, keyId);
|
||||
if (keyRing == null) {
|
||||
return null;
|
||||
}
|
||||
PGPSecretKey masterKey = PgpKeyHelper.getMasterKey(keyRing);
|
||||
if (masterKey == null) {
|
||||
return null;
|
||||
}
|
||||
masterKeyId = masterKey.getKeyID();
|
||||
}
|
||||
Log.d(TAG, "getCachedPassphraseImpl() for masterKeyId " + masterKeyId);
|
||||
|
||||
// get cached passphrase
|
||||
String cachedPassphrase = mPassphraseCache.get(masterKeyId);
|
||||
if (cachedPassphrase == null) {
|
||||
// if key has no passphrase -> cache and return empty passphrase
|
||||
if (!hasPassphrase(this, masterKeyId)) {
|
||||
Log.d(Constants.TAG, "Key has no passphrase! Caches and returns empty passphrase!");
|
||||
|
||||
addCachedPassphrase(this, masterKeyId, "");
|
||||
return "";
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
// set it again to reset the cache life cycle
|
||||
Log.d(TAG, "Cache passphrase again when getting it!");
|
||||
addCachedPassphrase(this, masterKeyId, cachedPassphrase);
|
||||
|
||||
return cachedPassphrase;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if key has a passphrase.
|
||||
*
|
||||
* @param secretKeyId
|
||||
* @return true if it has a passphrase
|
||||
*/
|
||||
public static boolean hasPassphrase(Context context, long secretKeyId) {
|
||||
// check if the key has no passphrase
|
||||
try {
|
||||
PGPSecretKey secretKey = PgpKeyHelper.getMasterKey(ProviderHelper
|
||||
.getPGPSecretKeyRingByKeyId(context, secretKeyId));
|
||||
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
|
||||
"SC").build("".toCharArray());
|
||||
PGPPrivateKey testKey = secretKey.extractPrivateKey(keyDecryptor);
|
||||
if (testKey != null) {
|
||||
return false;
|
||||
}
|
||||
} catch (PGPException e) {
|
||||
// silently catch
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register BroadcastReceiver that is unregistered when service is destroyed. This
|
||||
* BroadcastReceiver hears on intents with ACTION_PASSPHRASE_CACHE_SERVICE to then timeout
|
||||
* specific passphrases in memory.
|
||||
*/
|
||||
private void registerReceiver() {
|
||||
if (mIntentReceiver == null) {
|
||||
mIntentReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
String action = intent.getAction();
|
||||
|
||||
Log.d(TAG, "Received broadcast...");
|
||||
|
||||
if (action.equals(BROADCAST_ACTION_PASSPHRASE_CACHE_SERVICE)) {
|
||||
long keyId = intent.getLongExtra(EXTRA_KEY_ID, -1);
|
||||
timeout(context, keyId);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(BROADCAST_ACTION_PASSPHRASE_CACHE_SERVICE);
|
||||
registerReceiver(mIntentReceiver, filter);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build pending intent that is executed by alarm manager to time out a specific passphrase
|
||||
*
|
||||
* @param context
|
||||
* @param keyId
|
||||
* @return
|
||||
*/
|
||||
private static PendingIntent buildIntent(Context context, long keyId) {
|
||||
Intent intent = new Intent(BROADCAST_ACTION_PASSPHRASE_CACHE_SERVICE);
|
||||
intent.putExtra(EXTRA_KEY_ID, keyId);
|
||||
PendingIntent sender = PendingIntent.getBroadcast(context, REQUEST_ID, intent,
|
||||
PendingIntent.FLAG_CANCEL_CURRENT);
|
||||
|
||||
return sender;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executed when service is started by intent
|
||||
*/
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
Log.d(TAG, "onStartCommand()");
|
||||
|
||||
// register broadcastreceiver
|
||||
registerReceiver();
|
||||
|
||||
if (intent != null && intent.getAction() != null) {
|
||||
if (ACTION_PASSPHRASE_CACHE_ADD.equals(intent.getAction())) {
|
||||
long ttl = intent.getLongExtra(EXTRA_TTL, DEFAULT_TTL);
|
||||
long keyId = intent.getLongExtra(EXTRA_KEY_ID, -1);
|
||||
String passphrase = intent.getStringExtra(EXTRA_PASSPHRASE);
|
||||
|
||||
Log.d(TAG,
|
||||
"Received ACTION_PASSPHRASE_CACHE_ADD intent in onStartCommand() with keyId: "
|
||||
+ keyId + ", ttl: " + ttl);
|
||||
|
||||
// add keyId and passphrase to memory
|
||||
mPassphraseCache.put(keyId, passphrase);
|
||||
|
||||
if (ttl > 0) {
|
||||
// register new alarm with keyId for this passphrase
|
||||
long triggerTime = new Date().getTime() + (ttl * 1000);
|
||||
AlarmManager am = (AlarmManager) this.getSystemService(Context.ALARM_SERVICE);
|
||||
am.set(AlarmManager.RTC_WAKEUP, triggerTime, buildIntent(this, keyId));
|
||||
}
|
||||
} else if (ACTION_PASSPHRASE_CACHE_GET.equals(intent.getAction())) {
|
||||
long keyId = intent.getLongExtra(EXTRA_KEY_ID, -1);
|
||||
Messenger messenger = intent.getParcelableExtra(EXTRA_MESSENGER);
|
||||
|
||||
String passphrase = getCachedPassphraseImpl(keyId);
|
||||
|
||||
Message msg = Message.obtain();
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString(EXTRA_PASSPHRASE, passphrase);
|
||||
msg.obj = bundle;
|
||||
try {
|
||||
messenger.send(msg);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(Constants.TAG, "Sending message failed", e);
|
||||
}
|
||||
} else {
|
||||
Log.e(Constants.TAG, "Intent or Intent Action not supported!");
|
||||
}
|
||||
}
|
||||
|
||||
return START_STICKY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when one specific passphrase for keyId timed out
|
||||
*
|
||||
* @param context
|
||||
* @param keyId
|
||||
*/
|
||||
private void timeout(Context context, long keyId) {
|
||||
// remove passphrase corresponding to keyId from memory
|
||||
mPassphraseCache.remove(keyId);
|
||||
|
||||
Log.d(TAG, "Timeout of keyId " + keyId + ", removed from memory!");
|
||||
|
||||
// stop whole service if no cached passphrases remaining
|
||||
if (mPassphraseCache.isEmpty()) {
|
||||
Log.d(TAG, "No passphrases remaining in memory, stopping service!");
|
||||
stopSelf();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
mContext = this;
|
||||
Log.d(Constants.TAG, "PassphraseCacheService, onCreate()");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
Log.d(Constants.TAG, "PassphraseCacheService, onDestroy()");
|
||||
|
||||
unregisterReceiver(mIntentReceiver);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return mBinder;
|
||||
}
|
||||
|
||||
public class PassphraseCacheBinder extends Binder {
|
||||
public PassphraseCacheService getService() {
|
||||
return PassphraseCacheService.this;
|
||||
}
|
||||
}
|
||||
|
||||
private final IBinder mBinder = new PassphraseCacheBinder();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package org.sufficientlysecure.keychain.service.exception;
|
||||
|
||||
public class NoUserIdsException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = 7009311527126696207L;
|
||||
|
||||
public NoUserIdsException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package org.sufficientlysecure.keychain.service.exception;
|
||||
|
||||
public class UserInteractionRequiredException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = -60128148603511936L;
|
||||
|
||||
public UserInteractionRequiredException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package org.sufficientlysecure.keychain.service.exception;
|
||||
|
||||
public class WrongPackageSignatureException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = -8294642703122196028L;
|
||||
|
||||
public WrongPackageSignatureException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package org.sufficientlysecure.keychain.service.exception;
|
||||
|
||||
public class WrongPassphraseException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = -5309689232853485740L;
|
||||
|
||||
public WrongPassphraseException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.service.remote;
|
||||
|
||||
import org.spongycastle.bcpg.HashAlgorithmTags;
|
||||
import org.spongycastle.openpgp.PGPEncryptedData;
|
||||
import org.sufficientlysecure.keychain.Id;
|
||||
|
||||
public class AppSettings {
|
||||
private String packageName;
|
||||
private byte[] packageSignature;
|
||||
private long keyId = Id.key.none;
|
||||
private int encryptionAlgorithm;
|
||||
private int hashAlgorithm;
|
||||
private int compression;
|
||||
|
||||
public AppSettings() {
|
||||
|
||||
}
|
||||
|
||||
public AppSettings(String packageName, byte[] packageSignature) {
|
||||
super();
|
||||
this.packageName = packageName;
|
||||
this.packageSignature = packageSignature;
|
||||
// defaults:
|
||||
this.encryptionAlgorithm = PGPEncryptedData.AES_256;
|
||||
this.hashAlgorithm = HashAlgorithmTags.SHA512;
|
||||
this.compression = Id.choice.compression.zlib;
|
||||
}
|
||||
|
||||
public String getPackageName() {
|
||||
return packageName;
|
||||
}
|
||||
|
||||
public void setPackageName(String packageName) {
|
||||
this.packageName = packageName;
|
||||
}
|
||||
|
||||
public byte[] getPackageSignature() {
|
||||
return packageSignature;
|
||||
}
|
||||
|
||||
public void setPackageSignature(byte[] packageSignature) {
|
||||
this.packageSignature = packageSignature;
|
||||
}
|
||||
|
||||
public long getKeyId() {
|
||||
return keyId;
|
||||
}
|
||||
|
||||
public void setKeyId(long scretKeyId) {
|
||||
this.keyId = scretKeyId;
|
||||
}
|
||||
|
||||
public int getEncryptionAlgorithm() {
|
||||
return encryptionAlgorithm;
|
||||
}
|
||||
|
||||
public void setEncryptionAlgorithm(int encryptionAlgorithm) {
|
||||
this.encryptionAlgorithm = encryptionAlgorithm;
|
||||
}
|
||||
|
||||
public int getHashAlgorithm() {
|
||||
return hashAlgorithm;
|
||||
}
|
||||
|
||||
public void setHashAlgorithm(int hashAlgorithm) {
|
||||
this.hashAlgorithm = hashAlgorithm;
|
||||
}
|
||||
|
||||
public int getCompression() {
|
||||
return compression;
|
||||
}
|
||||
|
||||
public void setCompression(int compression) {
|
||||
this.compression = compression;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.service.remote;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.helper.ActionBarHelper;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
|
||||
import com.actionbarsherlock.app.SherlockFragmentActivity;
|
||||
import com.actionbarsherlock.view.Menu;
|
||||
import com.actionbarsherlock.view.MenuItem;
|
||||
|
||||
public class AppSettingsActivity extends SherlockFragmentActivity {
|
||||
private Uri mAppUri;
|
||||
|
||||
private AppSettingsFragment mSettingsFragment;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// Inflate a "Done" custom action bar
|
||||
ActionBarHelper.setDoneView(getSupportActionBar(), R.string.api_settings_save,
|
||||
new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
// "Done"
|
||||
save();
|
||||
}
|
||||
});
|
||||
|
||||
setContentView(R.layout.api_app_settings_activity);
|
||||
|
||||
mSettingsFragment = (AppSettingsFragment) getSupportFragmentManager().findFragmentById(
|
||||
R.id.api_app_settings_fragment);
|
||||
|
||||
Intent intent = getIntent();
|
||||
mAppUri = intent.getData();
|
||||
if (mAppUri == null) {
|
||||
Log.e(Constants.TAG, "Intent data missing. Should be Uri of app!");
|
||||
finish();
|
||||
return;
|
||||
} else {
|
||||
Log.d(Constants.TAG, "uri: " + mAppUri);
|
||||
loadData(mAppUri);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
super.onCreateOptionsMenu(menu);
|
||||
getSupportMenuInflater().inflate(R.menu.api_app_settings, menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.menu_api_settings_revoke:
|
||||
revokeAccess();
|
||||
return true;
|
||||
case R.id.menu_api_settings_cancel:
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
private void loadData(Uri appUri) {
|
||||
AppSettings settings = ProviderHelper.getApiAppSettings(this, appUri);
|
||||
mSettingsFragment.setAppSettings(settings);
|
||||
}
|
||||
|
||||
private void revokeAccess() {
|
||||
if (getContentResolver().delete(mAppUri, null, null) <= 0) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
finish();
|
||||
}
|
||||
|
||||
private void save() {
|
||||
ProviderHelper.updateApiApp(this, mSettingsFragment.getAppSettings(), mAppUri);
|
||||
|
||||
finish();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,234 @@
|
||||
/*
|
||||
* Copyright (C) 2013-2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.service.remote;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import org.spongycastle.util.encoders.Hex;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.ui.SelectSecretKeyLayoutFragment;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.KeyValueSpinnerAdapter;
|
||||
import org.sufficientlysecure.keychain.util.AlgorithmNames;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.animation.AlphaAnimation;
|
||||
import android.view.animation.Animation;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.AdapterView.OnItemSelectedListener;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.beardedhen.androidbootstrap.BootstrapButton;
|
||||
|
||||
public class AppSettingsFragment extends Fragment implements
|
||||
SelectSecretKeyLayoutFragment.SelectSecretKeyCallback {
|
||||
|
||||
// model
|
||||
private AppSettings appSettings;
|
||||
|
||||
// view
|
||||
private LinearLayout mAdvancedSettingsContainer;
|
||||
private BootstrapButton mAdvancedSettingsButton;
|
||||
private TextView mAppNameView;
|
||||
private ImageView mAppIconView;
|
||||
private Spinner mEncryptionAlgorithm;
|
||||
private Spinner mHashAlgorithm;
|
||||
private Spinner mCompression;
|
||||
private TextView mPackageName;
|
||||
private TextView mPackageSignature;
|
||||
|
||||
private SelectSecretKeyLayoutFragment mSelectKeyFragment;
|
||||
|
||||
KeyValueSpinnerAdapter encryptionAdapter;
|
||||
KeyValueSpinnerAdapter hashAdapter;
|
||||
KeyValueSpinnerAdapter compressionAdapter;
|
||||
|
||||
public AppSettings getAppSettings() {
|
||||
return appSettings;
|
||||
}
|
||||
|
||||
public void setAppSettings(AppSettings appSettings) {
|
||||
this.appSettings = appSettings;
|
||||
setPackage(appSettings.getPackageName());
|
||||
mPackageName.setText(appSettings.getPackageName());
|
||||
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
||||
md.update(appSettings.getPackageSignature());
|
||||
byte[] digest = md.digest();
|
||||
String signature = new String(Hex.encode(digest));
|
||||
|
||||
mPackageSignature.setText(signature);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
Log.e(Constants.TAG, "Should not happen!", e);
|
||||
}
|
||||
|
||||
mSelectKeyFragment.selectKey(appSettings.getKeyId());
|
||||
mEncryptionAlgorithm.setSelection(encryptionAdapter.getPosition(appSettings
|
||||
.getEncryptionAlgorithm()));
|
||||
mHashAlgorithm.setSelection(hashAdapter.getPosition(appSettings.getHashAlgorithm()));
|
||||
mCompression.setSelection(compressionAdapter.getPosition(appSettings.getCompression()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Inflate the layout for this fragment
|
||||
*/
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.api_app_settings_fragment, container, false);
|
||||
initView(view);
|
||||
return view;
|
||||
}
|
||||
|
||||
private void initView(View view) {
|
||||
mSelectKeyFragment = (SelectSecretKeyLayoutFragment) getFragmentManager().findFragmentById(
|
||||
R.id.api_app_settings_select_key_fragment);
|
||||
mSelectKeyFragment.setCallback(this);
|
||||
|
||||
mAdvancedSettingsButton = (BootstrapButton) view
|
||||
.findViewById(R.id.api_app_settings_advanced_button);
|
||||
mAdvancedSettingsContainer = (LinearLayout) view
|
||||
.findViewById(R.id.api_app_settings_advanced);
|
||||
|
||||
mAppNameView = (TextView) view.findViewById(R.id.api_app_settings_app_name);
|
||||
mAppIconView = (ImageView) view.findViewById(R.id.api_app_settings_app_icon);
|
||||
mEncryptionAlgorithm = (Spinner) view
|
||||
.findViewById(R.id.api_app_settings_encryption_algorithm);
|
||||
mHashAlgorithm = (Spinner) view.findViewById(R.id.api_app_settings_hash_algorithm);
|
||||
mCompression = (Spinner) view.findViewById(R.id.api_app_settings_compression);
|
||||
mPackageName = (TextView) view.findViewById(R.id.api_app_settings_package_name);
|
||||
mPackageSignature = (TextView) view.findViewById(R.id.api_app_settings_package_signature);
|
||||
|
||||
AlgorithmNames algorithmNames = new AlgorithmNames(getActivity());
|
||||
|
||||
encryptionAdapter = new KeyValueSpinnerAdapter(getActivity(),
|
||||
algorithmNames.getEncryptionNames());
|
||||
mEncryptionAlgorithm.setAdapter(encryptionAdapter);
|
||||
mEncryptionAlgorithm.setOnItemSelectedListener(new OnItemSelectedListener() {
|
||||
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||
appSettings.setEncryptionAlgorithm((int) id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> parent) {
|
||||
}
|
||||
});
|
||||
|
||||
hashAdapter = new KeyValueSpinnerAdapter(getActivity(), algorithmNames.getHashNames());
|
||||
mHashAlgorithm.setAdapter(hashAdapter);
|
||||
mHashAlgorithm.setOnItemSelectedListener(new OnItemSelectedListener() {
|
||||
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||
appSettings.setHashAlgorithm((int) id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> parent) {
|
||||
}
|
||||
});
|
||||
|
||||
compressionAdapter = new KeyValueSpinnerAdapter(getActivity(),
|
||||
algorithmNames.getCompressionNames());
|
||||
mCompression.setAdapter(compressionAdapter);
|
||||
mCompression.setOnItemSelectedListener(new OnItemSelectedListener() {
|
||||
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||
appSettings.setCompression((int) id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> parent) {
|
||||
}
|
||||
});
|
||||
|
||||
final Animation visibleAnimation = new AlphaAnimation(0.0f, 1.0f);
|
||||
visibleAnimation.setDuration(250);
|
||||
final Animation invisibleAnimation = new AlphaAnimation(1.0f, 0.0f);
|
||||
invisibleAnimation.setDuration(250);
|
||||
|
||||
// TODO: Better: collapse/expand animation
|
||||
// final Animation animation2 = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0.0f,
|
||||
// Animation.RELATIVE_TO_SELF, 0.0f, Animation.RELATIVE_TO_SELF, -1.0f,
|
||||
// Animation.RELATIVE_TO_SELF, 0.0f);
|
||||
// animation2.setDuration(150);
|
||||
|
||||
mAdvancedSettingsButton.setOnClickListener(new OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (mAdvancedSettingsContainer.getVisibility() == View.VISIBLE) {
|
||||
mAdvancedSettingsContainer.startAnimation(invisibleAnimation);
|
||||
mAdvancedSettingsContainer.setVisibility(View.GONE);
|
||||
mAdvancedSettingsButton.setText(getString(R.string.api_settings_show_advanced));
|
||||
mAdvancedSettingsButton.setLeftIcon("fa-caret-up");
|
||||
} else {
|
||||
mAdvancedSettingsContainer.startAnimation(visibleAnimation);
|
||||
mAdvancedSettingsContainer.setVisibility(View.VISIBLE);
|
||||
mAdvancedSettingsButton.setText(getString(R.string.api_settings_hide_advanced));
|
||||
mAdvancedSettingsButton.setLeftIcon("fa-caret-down");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setPackage(String packageName) {
|
||||
PackageManager pm = getActivity().getApplicationContext().getPackageManager();
|
||||
|
||||
// get application name and icon from package manager
|
||||
String appName = null;
|
||||
Drawable appIcon = null;
|
||||
try {
|
||||
ApplicationInfo ai = pm.getApplicationInfo(packageName, 0);
|
||||
|
||||
appName = (String) pm.getApplicationLabel(ai);
|
||||
appIcon = pm.getApplicationIcon(ai);
|
||||
} catch (final NameNotFoundException e) {
|
||||
// fallback
|
||||
appName = packageName;
|
||||
}
|
||||
mAppNameView.setText(appName);
|
||||
mAppIconView.setImageDrawable(appIcon);
|
||||
}
|
||||
|
||||
/**
|
||||
* callback from select secret key fragment
|
||||
*/
|
||||
@Override
|
||||
public void onKeySelected(long secretKeyId) {
|
||||
appSettings.setKeyId(secretKeyId);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.service.remote;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
import javax.security.auth.callback.Callback;
|
||||
import javax.security.auth.callback.CallbackHandler;
|
||||
import javax.security.auth.callback.PasswordCallback;
|
||||
|
||||
import org.spongycastle.openpgp.PGPPrivateKey;
|
||||
import org.spongycastle.openpgp.PGPSecretKey;
|
||||
import org.spongycastle.openssl.PEMWriter;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpToX509;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
|
||||
public class ExtendedApiService extends RemoteService {
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return mBinder;
|
||||
}
|
||||
|
||||
private void selfSignedX509CertSafe(String subjAltNameURI, IExtendedApiCallback callback,
|
||||
AppSettings appSettings) {
|
||||
|
||||
// TODO: for pgp keyrings with password
|
||||
CallbackHandler pgpPwdCallbackHandler = new PgpToX509.PredefinedPasswordCallbackHandler("");
|
||||
|
||||
try {
|
||||
long keyId = appSettings.getKeyId();
|
||||
PGPSecretKey pgpSecretKey = PgpKeyHelper.getSigningKey(this, keyId);
|
||||
|
||||
PasswordCallback pgpSecKeyPasswordCallBack = new PasswordCallback("pgp passphrase?",
|
||||
false);
|
||||
pgpPwdCallbackHandler.handle(new Callback[] { pgpSecKeyPasswordCallBack });
|
||||
PGPPrivateKey pgpPrivKey = pgpSecretKey.extractPrivateKey(
|
||||
pgpSecKeyPasswordCallBack.getPassword(), Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
||||
pgpSecKeyPasswordCallBack.clearPassword();
|
||||
|
||||
X509Certificate selfSignedCert = PgpToX509.createSelfSignedCert(pgpSecretKey,
|
||||
pgpPrivKey, subjAltNameURI);
|
||||
|
||||
// Write x509cert and privKey into files
|
||||
// FileOutputStream fosCert = context.openFileOutput(CERT_FILENAME,
|
||||
// Context.MODE_PRIVATE);
|
||||
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
|
||||
PEMWriter pemWriterCert = new PEMWriter(new PrintWriter(outStream));
|
||||
pemWriterCert.writeObject(selfSignedCert);
|
||||
pemWriterCert.close();
|
||||
|
||||
byte[] outputBytes = outStream.toByteArray();
|
||||
|
||||
callback.onSuccess(outputBytes);
|
||||
} catch (Exception e) {
|
||||
Log.e(Constants.TAG, "ExtendedApiService", e);
|
||||
try {
|
||||
callback.onError(e.getMessage());
|
||||
} catch (RemoteException e1) {
|
||||
Log.e(Constants.TAG, "ExtendedApiService", e);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: no private key at the moment! Don't give it to others
|
||||
// PrivateKey privKey = pgpPrivKey.getKey();
|
||||
// FileOutputStream fosKey = context.openFileOutput(PRIV_KEY_FILENAME,
|
||||
// Context.MODE_PRIVATE);
|
||||
// PEMWriter pemWriterKey = new PEMWriter(new PrintWriter(fosKey));
|
||||
// pemWriterKey.writeObject(privKey);
|
||||
// pemWriterKey.close();
|
||||
}
|
||||
|
||||
private final IExtendedApiService.Stub mBinder = new IExtendedApiService.Stub() {
|
||||
|
||||
@Override
|
||||
public void encrypt(byte[] inputBytes, String passphrase, IExtendedApiCallback callback)
|
||||
throws RemoteException {
|
||||
// TODO : implement
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void selfSignedX509Cert(final String subjAltNameURI,
|
||||
final IExtendedApiCallback callback) throws RemoteException {
|
||||
final AppSettings settings = getAppSettings();
|
||||
|
||||
Runnable r = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
selfSignedX509CertSafe(subjAltNameURI, callback, settings);
|
||||
}
|
||||
};
|
||||
|
||||
checkAndEnqueue(r);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,602 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.service.remote;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
import org.openintents.openpgp.IOpenPgpCallback;
|
||||
import org.openintents.openpgp.IOpenPgpKeyIdsCallback;
|
||||
import org.openintents.openpgp.IOpenPgpService;
|
||||
import org.openintents.openpgp.OpenPgpData;
|
||||
import org.openintents.openpgp.OpenPgpError;
|
||||
import org.openintents.openpgp.OpenPgpSignatureResult;
|
||||
import org.spongycastle.util.Arrays;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.Id;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.helper.Preferences;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpHelper;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpOperation;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.NoAsymmetricEncryptionException;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
|
||||
import org.sufficientlysecure.keychain.service.exception.NoUserIdsException;
|
||||
import org.sufficientlysecure.keychain.service.exception.UserInteractionRequiredException;
|
||||
import org.sufficientlysecure.keychain.service.exception.WrongPassphraseException;
|
||||
import org.sufficientlysecure.keychain.util.InputData;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.os.Message;
|
||||
import android.os.RemoteException;
|
||||
|
||||
public class OpenPgpService extends RemoteService {
|
||||
|
||||
private String getCachedPassphrase(long keyId, boolean allowUserInteraction)
|
||||
throws UserInteractionRequiredException {
|
||||
String passphrase = PassphraseCacheService.getCachedPassphrase(getContext(), keyId);
|
||||
|
||||
if (passphrase == null) {
|
||||
if (!allowUserInteraction) {
|
||||
throw new UserInteractionRequiredException(
|
||||
"Passphrase not found in cache, please enter your passphrase!");
|
||||
}
|
||||
|
||||
Log.d(Constants.TAG, "No passphrase! Activity required!");
|
||||
|
||||
// start passphrase dialog
|
||||
PassphraseActivityCallback callback = new PassphraseActivityCallback();
|
||||
Bundle extras = new Bundle();
|
||||
extras.putLong(RemoteServiceActivity.EXTRA_SECRET_KEY_ID, keyId);
|
||||
pauseAndStartUserInteraction(RemoteServiceActivity.ACTION_CACHE_PASSPHRASE, callback,
|
||||
extras);
|
||||
|
||||
if (callback.isSuccess()) {
|
||||
Log.d(Constants.TAG, "New passphrase entered!");
|
||||
|
||||
// get again after it was entered
|
||||
passphrase = PassphraseCacheService.getCachedPassphrase(getContext(), keyId);
|
||||
} else {
|
||||
Log.d(Constants.TAG, "Passphrase dialog canceled!");
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return passphrase;
|
||||
}
|
||||
|
||||
public class PassphraseActivityCallback extends UserInputCallback {
|
||||
|
||||
private boolean success = false;
|
||||
|
||||
public boolean isSuccess() {
|
||||
return success;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleUserInput(Message msg) {
|
||||
if (msg.arg1 == OKAY) {
|
||||
success = true;
|
||||
} else {
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Search database for key ids based on emails.
|
||||
*
|
||||
* @param encryptionUserIds
|
||||
* @return
|
||||
*/
|
||||
private long[] getKeyIdsFromEmails(String[] encryptionUserIds, boolean allowUserInteraction)
|
||||
throws UserInteractionRequiredException {
|
||||
// find key ids to given emails in database
|
||||
ArrayList<Long> keyIds = new ArrayList<Long>();
|
||||
|
||||
boolean missingUserIdsCheck = false;
|
||||
boolean dublicateUserIdsCheck = false;
|
||||
ArrayList<String> missingUserIds = new ArrayList<String>();
|
||||
ArrayList<String> dublicateUserIds = new ArrayList<String>();
|
||||
|
||||
for (String email : encryptionUserIds) {
|
||||
Uri uri = KeychainContract.KeyRings.buildPublicKeyRingsByEmailsUri(email);
|
||||
Cursor cur = getContentResolver().query(uri, null, null, null, null);
|
||||
if (cur.moveToFirst()) {
|
||||
long id = cur.getLong(cur.getColumnIndex(KeychainContract.KeyRings.MASTER_KEY_ID));
|
||||
keyIds.add(id);
|
||||
} else {
|
||||
missingUserIdsCheck = true;
|
||||
missingUserIds.add(email);
|
||||
Log.d(Constants.TAG, "user id missing");
|
||||
}
|
||||
if (cur.moveToNext()) {
|
||||
dublicateUserIdsCheck = true;
|
||||
dublicateUserIds.add(email);
|
||||
Log.d(Constants.TAG, "more than one user id with the same email");
|
||||
}
|
||||
}
|
||||
|
||||
// convert to long[]
|
||||
long[] keyIdsArray = new long[keyIds.size()];
|
||||
for (int i = 0; i < keyIdsArray.length; i++) {
|
||||
keyIdsArray[i] = keyIds.get(i);
|
||||
}
|
||||
|
||||
// allow the user to verify pub key selection
|
||||
if (allowUserInteraction && (missingUserIdsCheck || dublicateUserIdsCheck)) {
|
||||
SelectPubKeysActivityCallback callback = new SelectPubKeysActivityCallback();
|
||||
|
||||
Bundle extras = new Bundle();
|
||||
extras.putLongArray(RemoteServiceActivity.EXTRA_SELECTED_MASTER_KEY_IDS, keyIdsArray);
|
||||
extras.putStringArrayList(RemoteServiceActivity.EXTRA_MISSING_USER_IDS, missingUserIds);
|
||||
extras.putStringArrayList(RemoteServiceActivity.EXTRA_DUBLICATE_USER_IDS,
|
||||
dublicateUserIds);
|
||||
|
||||
pauseAndStartUserInteraction(RemoteServiceActivity.ACTION_SELECT_PUB_KEYS, callback,
|
||||
extras);
|
||||
|
||||
if (callback.isSuccess()) {
|
||||
Log.d(Constants.TAG, "New selection of pub keys!");
|
||||
keyIdsArray = callback.getPubKeyIds();
|
||||
} else {
|
||||
Log.d(Constants.TAG, "Pub key selection canceled!");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// if no user interaction is allow throw exceptions on duplicate or missing pub keys
|
||||
if (!allowUserInteraction) {
|
||||
if (missingUserIdsCheck)
|
||||
throw new UserInteractionRequiredException(
|
||||
"Pub keys for these user ids are missing:" + missingUserIds.toString());
|
||||
if (dublicateUserIdsCheck)
|
||||
throw new UserInteractionRequiredException(
|
||||
"More than one pub key with these user ids exist:"
|
||||
+ dublicateUserIds.toString());
|
||||
}
|
||||
|
||||
if (keyIdsArray.length == 0) {
|
||||
return null;
|
||||
}
|
||||
return keyIdsArray;
|
||||
}
|
||||
|
||||
public class SelectPubKeysActivityCallback extends UserInputCallback {
|
||||
public static final String PUB_KEY_IDS = "pub_key_ids";
|
||||
|
||||
private boolean success = false;
|
||||
private long[] pubKeyIds;
|
||||
|
||||
public boolean isSuccess() {
|
||||
return success;
|
||||
}
|
||||
|
||||
public long[] getPubKeyIds() {
|
||||
return pubKeyIds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleUserInput(Message msg) {
|
||||
if (msg.arg1 == OKAY) {
|
||||
success = true;
|
||||
pubKeyIds = msg.getData().getLongArray(PUB_KEY_IDS);
|
||||
} else {
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private synchronized void getKeyIdsSafe(String[] userIds, boolean allowUserInteraction,
|
||||
IOpenPgpKeyIdsCallback callback, AppSettings appSettings) {
|
||||
try {
|
||||
long[] keyIds = getKeyIdsFromEmails(userIds, allowUserInteraction);
|
||||
if (keyIds == null) {
|
||||
throw new NoUserIdsException("No user ids!");
|
||||
}
|
||||
|
||||
callback.onSuccess(keyIds);
|
||||
} catch (UserInteractionRequiredException e) {
|
||||
callbackOpenPgpError(callback, OpenPgpError.USER_INTERACTION_REQUIRED, e.getMessage());
|
||||
} catch (NoUserIdsException e) {
|
||||
callbackOpenPgpError(callback, OpenPgpError.NO_USER_IDS, e.getMessage());
|
||||
} catch (Exception e) {
|
||||
callbackOpenPgpError(callback, OpenPgpError.GENERIC_ERROR, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void encryptAndSignSafe(OpenPgpData inputData,
|
||||
final OpenPgpData outputData, long[] keyIds, boolean allowUserInteraction,
|
||||
IOpenPgpCallback callback, AppSettings appSettings, boolean sign) {
|
||||
try {
|
||||
// TODO: other options of OpenPgpData!
|
||||
byte[] inputBytes = getInput(inputData);
|
||||
boolean asciiArmor = false;
|
||||
if (outputData.getType() == OpenPgpData.TYPE_STRING) {
|
||||
asciiArmor = true;
|
||||
}
|
||||
|
||||
// add own key for encryption
|
||||
keyIds = Arrays.copyOf(keyIds, keyIds.length + 1);
|
||||
keyIds[keyIds.length - 1] = appSettings.getKeyId();
|
||||
|
||||
// build InputData and write into OutputStream
|
||||
InputStream inputStream = new ByteArrayInputStream(inputBytes);
|
||||
long inputLength = inputBytes.length;
|
||||
InputData inputDt = new InputData(inputStream, inputLength);
|
||||
|
||||
OutputStream outputStream = new ByteArrayOutputStream();
|
||||
|
||||
PgpOperation operation = new PgpOperation(getContext(), null, inputDt, outputStream);
|
||||
if (sign) {
|
||||
String passphrase = getCachedPassphrase(appSettings.getKeyId(),
|
||||
allowUserInteraction);
|
||||
if (passphrase == null) {
|
||||
throw new WrongPassphraseException("No or wrong passphrase!");
|
||||
}
|
||||
|
||||
operation.signAndEncrypt(asciiArmor, appSettings.getCompression(), keyIds, null,
|
||||
appSettings.getEncryptionAlgorithm(), appSettings.getKeyId(),
|
||||
appSettings.getHashAlgorithm(), true, passphrase);
|
||||
} else {
|
||||
operation.signAndEncrypt(asciiArmor, appSettings.getCompression(), keyIds, null,
|
||||
appSettings.getEncryptionAlgorithm(), Id.key.none,
|
||||
appSettings.getHashAlgorithm(), true, null);
|
||||
}
|
||||
|
||||
outputStream.close();
|
||||
|
||||
byte[] outputBytes = ((ByteArrayOutputStream) outputStream).toByteArray();
|
||||
|
||||
OpenPgpData output = null;
|
||||
if (asciiArmor) {
|
||||
output = new OpenPgpData(new String(outputBytes));
|
||||
} else {
|
||||
output = new OpenPgpData(outputBytes);
|
||||
}
|
||||
|
||||
// return over handler on client side
|
||||
callback.onSuccess(output, null);
|
||||
} catch (UserInteractionRequiredException e) {
|
||||
callbackOpenPgpError(callback, OpenPgpError.USER_INTERACTION_REQUIRED, e.getMessage());
|
||||
} catch (WrongPassphraseException e) {
|
||||
callbackOpenPgpError(callback, OpenPgpError.NO_OR_WRONG_PASSPHRASE, e.getMessage());
|
||||
} catch (Exception e) {
|
||||
callbackOpenPgpError(callback, OpenPgpError.GENERIC_ERROR, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: asciiArmor?!
|
||||
private void signSafe(byte[] inputBytes, boolean allowUserInteraction,
|
||||
IOpenPgpCallback callback, AppSettings appSettings) {
|
||||
try {
|
||||
// build InputData and write into OutputStream
|
||||
InputStream inputStream = new ByteArrayInputStream(inputBytes);
|
||||
long inputLength = inputBytes.length;
|
||||
InputData inputData = new InputData(inputStream, inputLength);
|
||||
|
||||
OutputStream outputStream = new ByteArrayOutputStream();
|
||||
|
||||
String passphrase = getCachedPassphrase(appSettings.getKeyId(), allowUserInteraction);
|
||||
if (passphrase == null) {
|
||||
throw new WrongPassphraseException("No or wrong passphrase!");
|
||||
}
|
||||
|
||||
PgpOperation operation = new PgpOperation(getContext(), null, inputData, outputStream);
|
||||
operation.signText(appSettings.getKeyId(), passphrase, appSettings.getHashAlgorithm(),
|
||||
Preferences.getPreferences(this).getForceV3Signatures());
|
||||
|
||||
outputStream.close();
|
||||
|
||||
byte[] outputBytes = ((ByteArrayOutputStream) outputStream).toByteArray();
|
||||
OpenPgpData output = new OpenPgpData(new String(outputBytes));
|
||||
|
||||
// return over handler on client side
|
||||
callback.onSuccess(output, null);
|
||||
} catch (UserInteractionRequiredException e) {
|
||||
callbackOpenPgpError(callback, OpenPgpError.USER_INTERACTION_REQUIRED, e.getMessage());
|
||||
} catch (WrongPassphraseException e) {
|
||||
callbackOpenPgpError(callback, OpenPgpError.NO_OR_WRONG_PASSPHRASE, e.getMessage());
|
||||
} catch (Exception e) {
|
||||
callbackOpenPgpError(callback, OpenPgpError.GENERIC_ERROR, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void decryptAndVerifySafe(byte[] inputBytes, boolean allowUserInteraction,
|
||||
IOpenPgpCallback callback, AppSettings appSettings) {
|
||||
try {
|
||||
// TODO: this is not really needed
|
||||
// checked if it is text with BEGIN and END tags
|
||||
String message = new String(inputBytes);
|
||||
Log.d(Constants.TAG, "in: " + message);
|
||||
boolean signedOnly = false;
|
||||
Matcher matcher = PgpHelper.PGP_MESSAGE.matcher(message);
|
||||
if (matcher.matches()) {
|
||||
Log.d(Constants.TAG, "PGP_MESSAGE matched");
|
||||
message = matcher.group(1);
|
||||
// replace non breakable spaces
|
||||
message = message.replaceAll("\\xa0", " ");
|
||||
|
||||
// overwrite inputBytes
|
||||
inputBytes = message.getBytes();
|
||||
} else {
|
||||
matcher = PgpHelper.PGP_SIGNED_MESSAGE.matcher(message);
|
||||
if (matcher.matches()) {
|
||||
signedOnly = true;
|
||||
Log.d(Constants.TAG, "PGP_SIGNED_MESSAGE matched");
|
||||
message = matcher.group(1);
|
||||
// replace non breakable spaces
|
||||
message = message.replaceAll("\\xa0", " ");
|
||||
|
||||
// overwrite inputBytes
|
||||
inputBytes = message.getBytes();
|
||||
} else {
|
||||
Log.d(Constants.TAG, "Nothing matched! Binary?");
|
||||
}
|
||||
}
|
||||
// END TODO
|
||||
|
||||
Log.d(Constants.TAG, "in: " + new String(inputBytes));
|
||||
|
||||
// TODO: This allows to decrypt messages with ALL secret keys, not only the one for the
|
||||
// app, Fix this?
|
||||
|
||||
String passphrase = null;
|
||||
if (!signedOnly) {
|
||||
// BEGIN Get key
|
||||
// TODO: this input stream is consumed after PgpMain.getDecryptionKeyId()... do it
|
||||
// better!
|
||||
InputStream inputStream2 = new ByteArrayInputStream(inputBytes);
|
||||
|
||||
// TODO: duplicates functions from DecryptActivity!
|
||||
long secretKeyId;
|
||||
try {
|
||||
if (inputStream2.markSupported()) {
|
||||
// should probably set this to the max size of two
|
||||
// pgpF objects, if it even needs to be anything other
|
||||
// than 0.
|
||||
inputStream2.mark(200);
|
||||
}
|
||||
secretKeyId = PgpHelper.getDecryptionKeyId(this, inputStream2);
|
||||
if (secretKeyId == Id.key.none) {
|
||||
throw new PgpGeneralException(getString(R.string.error_no_secret_key_found));
|
||||
}
|
||||
} catch (NoAsymmetricEncryptionException e) {
|
||||
if (inputStream2.markSupported()) {
|
||||
inputStream2.reset();
|
||||
}
|
||||
secretKeyId = Id.key.symmetric;
|
||||
if (!PgpOperation.hasSymmetricEncryption(this, inputStream2)) {
|
||||
throw new PgpGeneralException(
|
||||
getString(R.string.error_no_known_encryption_found));
|
||||
}
|
||||
// we do not support symmetric decryption from the API!
|
||||
throw new Exception("Symmetric decryption is not supported!");
|
||||
}
|
||||
|
||||
Log.d(Constants.TAG, "secretKeyId " + secretKeyId);
|
||||
|
||||
passphrase = getCachedPassphrase(secretKeyId, allowUserInteraction);
|
||||
if (passphrase == null) {
|
||||
throw new WrongPassphraseException("No or wrong passphrase!");
|
||||
}
|
||||
}
|
||||
|
||||
// build InputData and write into OutputStream
|
||||
InputStream inputStream = new ByteArrayInputStream(inputBytes);
|
||||
long inputLength = inputBytes.length;
|
||||
InputData inputData = new InputData(inputStream, inputLength);
|
||||
|
||||
OutputStream outputStream = new ByteArrayOutputStream();
|
||||
|
||||
Bundle outputBundle;
|
||||
PgpOperation operation = new PgpOperation(getContext(), null, inputData, outputStream);
|
||||
if (signedOnly) {
|
||||
// TODO: download missing keys from keyserver?
|
||||
outputBundle = operation.verifyText(false);
|
||||
} else {
|
||||
outputBundle = operation.decryptAndVerify(passphrase, false);
|
||||
}
|
||||
|
||||
outputStream.close();
|
||||
|
||||
byte[] outputBytes = ((ByteArrayOutputStream) outputStream).toByteArray();
|
||||
|
||||
// get signature informations from bundle
|
||||
boolean signature = outputBundle.getBoolean(KeychainIntentService.RESULT_SIGNATURE);
|
||||
|
||||
OpenPgpSignatureResult sigResult = null;
|
||||
if (signature) {
|
||||
long signatureKeyId = outputBundle
|
||||
.getLong(KeychainIntentService.RESULT_SIGNATURE_KEY_ID);
|
||||
String signatureUserId = outputBundle
|
||||
.getString(KeychainIntentService.RESULT_SIGNATURE_USER_ID);
|
||||
boolean signatureSuccess = outputBundle
|
||||
.getBoolean(KeychainIntentService.RESULT_SIGNATURE_SUCCESS);
|
||||
boolean signatureUnknown = outputBundle
|
||||
.getBoolean(KeychainIntentService.RESULT_SIGNATURE_UNKNOWN);
|
||||
|
||||
int signatureStatus = OpenPgpSignatureResult.SIGNATURE_ERROR;
|
||||
if (signatureSuccess) {
|
||||
signatureStatus = OpenPgpSignatureResult.SIGNATURE_SUCCESS_TRUSTED;
|
||||
} else if (signatureUnknown) {
|
||||
signatureStatus = OpenPgpSignatureResult.SIGNATURE_UNKNOWN_PUB_KEY;
|
||||
}
|
||||
|
||||
sigResult = new OpenPgpSignatureResult(signatureStatus, signatureUserId,
|
||||
signedOnly, signatureKeyId);
|
||||
}
|
||||
OpenPgpData output = new OpenPgpData(new String(outputBytes));
|
||||
|
||||
// return over handler on client side
|
||||
callback.onSuccess(output, sigResult);
|
||||
} catch (UserInteractionRequiredException e) {
|
||||
callbackOpenPgpError(callback, OpenPgpError.USER_INTERACTION_REQUIRED, e.getMessage());
|
||||
} catch (WrongPassphraseException e) {
|
||||
callbackOpenPgpError(callback, OpenPgpError.NO_OR_WRONG_PASSPHRASE, e.getMessage());
|
||||
} catch (Exception e) {
|
||||
callbackOpenPgpError(callback, OpenPgpError.GENERIC_ERROR, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns error to IOpenPgpCallback
|
||||
*
|
||||
* @param callback
|
||||
* @param errorId
|
||||
* @param message
|
||||
*/
|
||||
private void callbackOpenPgpError(IOpenPgpCallback callback, int errorId, String message) {
|
||||
try {
|
||||
callback.onError(new OpenPgpError(0, message));
|
||||
} catch (Exception t) {
|
||||
Log.e(Constants.TAG,
|
||||
"Exception while returning OpenPgpError to client via callback.onError()", t);
|
||||
}
|
||||
}
|
||||
|
||||
private void callbackOpenPgpError(IOpenPgpKeyIdsCallback callback, int errorId, String message) {
|
||||
try {
|
||||
callback.onError(new OpenPgpError(0, message));
|
||||
} catch (Exception t) {
|
||||
Log.e(Constants.TAG,
|
||||
"Exception while returning OpenPgpError to client via callback.onError()", t);
|
||||
}
|
||||
}
|
||||
|
||||
private final IOpenPgpService.Stub mBinder = new IOpenPgpService.Stub() {
|
||||
|
||||
@Override
|
||||
public void encrypt(final OpenPgpData input, final OpenPgpData output, final long[] keyIds,
|
||||
final IOpenPgpCallback callback) throws RemoteException {
|
||||
final AppSettings settings = getAppSettings();
|
||||
|
||||
Runnable r = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
encryptAndSignSafe(input, output, keyIds, true, callback, settings, false);
|
||||
}
|
||||
};
|
||||
|
||||
checkAndEnqueue(r);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void signAndEncrypt(final OpenPgpData input, final OpenPgpData output,
|
||||
final long[] keyIds, final IOpenPgpCallback callback) throws RemoteException {
|
||||
final AppSettings settings = getAppSettings();
|
||||
|
||||
Runnable r = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
encryptAndSignSafe(input, output, keyIds, true, callback, settings, true);
|
||||
}
|
||||
};
|
||||
|
||||
checkAndEnqueue(r);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sign(final OpenPgpData input, final OpenPgpData output,
|
||||
final IOpenPgpCallback callback) throws RemoteException {
|
||||
final AppSettings settings = getAppSettings();
|
||||
|
||||
Runnable r = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
signSafe(getInput(input), true, callback, settings);
|
||||
}
|
||||
};
|
||||
|
||||
checkAndEnqueue(r);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decryptAndVerify(final OpenPgpData input, final OpenPgpData output,
|
||||
final IOpenPgpCallback callback) throws RemoteException {
|
||||
|
||||
final AppSettings settings = getAppSettings();
|
||||
|
||||
Runnable r = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
decryptAndVerifySafe(getInput(input), true, callback, settings);
|
||||
}
|
||||
};
|
||||
|
||||
checkAndEnqueue(r);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getKeyIds(final String[] userIds, final boolean allowUserInteraction,
|
||||
final IOpenPgpKeyIdsCallback callback) throws RemoteException {
|
||||
|
||||
final AppSettings settings = getAppSettings();
|
||||
|
||||
Runnable r = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
getKeyIdsSafe(userIds, allowUserInteraction, callback, settings);
|
||||
}
|
||||
};
|
||||
|
||||
checkAndEnqueue(r);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
private static byte[] getInput(OpenPgpData data) {
|
||||
// TODO: support Uri and ParcelFileDescriptor
|
||||
|
||||
byte[] inBytes = null;
|
||||
switch (data.getType()) {
|
||||
case OpenPgpData.TYPE_STRING:
|
||||
inBytes = data.getString().getBytes();
|
||||
break;
|
||||
|
||||
case OpenPgpData.TYPE_BYTE_ARRAY:
|
||||
inBytes = data.getBytes();
|
||||
break;
|
||||
|
||||
default:
|
||||
Log.e(Constants.TAG, "Uri and ParcelFileDescriptor not supported right now!");
|
||||
break;
|
||||
}
|
||||
|
||||
return inBytes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return mBinder;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.service.remote;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.database.Cursor;
|
||||
import android.support.v4.widget.CursorAdapter;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
public class RegisteredAppsAdapter extends CursorAdapter {
|
||||
|
||||
private LayoutInflater mInflater;
|
||||
private PackageManager pm;
|
||||
|
||||
public RegisteredAppsAdapter(Context context, Cursor c, int flags) {
|
||||
super(context, c, flags);
|
||||
|
||||
mInflater = LayoutInflater.from(context);
|
||||
pm = context.getApplicationContext().getPackageManager();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindView(View view, Context context, Cursor cursor) {
|
||||
TextView text = (TextView) view.findViewById(R.id.api_apps_adapter_item_name);
|
||||
ImageView icon = (ImageView) view.findViewById(R.id.api_apps_adapter_item_icon);
|
||||
|
||||
String packageName = cursor.getString(cursor.getColumnIndex(ApiApps.PACKAGE_NAME));
|
||||
if (packageName != null) {
|
||||
// get application name
|
||||
try {
|
||||
ApplicationInfo ai = pm.getApplicationInfo(packageName, 0);
|
||||
|
||||
text.setText(pm.getApplicationLabel(ai));
|
||||
icon.setImageDrawable(pm.getApplicationIcon(ai));
|
||||
} catch (final NameNotFoundException e) {
|
||||
// fallback
|
||||
text.setText(packageName);
|
||||
}
|
||||
} else {
|
||||
// fallback
|
||||
text.setText(packageName);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public View newView(Context context, Cursor cursor, ViewGroup parent) {
|
||||
return mInflater.inflate(R.layout.api_apps_adapter_list_item, null);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.service.remote;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.ui.DrawerActivity;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
public class RegisteredAppsListActivity extends DrawerActivity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.api_apps_list_activity);
|
||||
|
||||
setupDrawerNavigation(savedInstanceState);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.service.remote;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps;
|
||||
|
||||
import android.content.ContentUris;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.content.CursorLoader;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.AdapterView.OnItemClickListener;
|
||||
|
||||
import com.actionbarsherlock.app.SherlockListFragment;
|
||||
|
||||
public class RegisteredAppsListFragment extends SherlockListFragment implements
|
||||
LoaderManager.LoaderCallbacks<Cursor> {
|
||||
|
||||
// This is the Adapter being used to display the list's data.
|
||||
RegisteredAppsAdapter mAdapter;
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
getListView().setOnItemClickListener(new OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
|
||||
// edit app settings
|
||||
Intent intent = new Intent(getActivity(), AppSettingsActivity.class);
|
||||
intent.setData(ContentUris.withAppendedId(KeychainContract.ApiApps.CONTENT_URI, id));
|
||||
startActivity(intent);
|
||||
}
|
||||
});
|
||||
|
||||
// Give some text to display if there is no data. In a real
|
||||
// application this would come from a resource.
|
||||
setEmptyText(getString(R.string.api_no_apps));
|
||||
|
||||
// We have a menu item to show in action bar.
|
||||
setHasOptionsMenu(true);
|
||||
|
||||
// Create an empty adapter we will use to display the loaded data.
|
||||
mAdapter = new RegisteredAppsAdapter(getActivity(), null, 0);
|
||||
setListAdapter(mAdapter);
|
||||
|
||||
// Prepare the loader. Either re-connect with an existing one,
|
||||
// or start a new one.
|
||||
getLoaderManager().initLoader(0, null, this);
|
||||
}
|
||||
|
||||
// These are the Contacts rows that we will retrieve.
|
||||
static final String[] PROJECTION = new String[] { ApiApps._ID, ApiApps.PACKAGE_NAME };
|
||||
|
||||
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||
// This is called when a new Loader needs to be created. This
|
||||
// sample only has one Loader, so we don't care about the ID.
|
||||
// First, pick the base URI to use depending on whether we are
|
||||
// currently filtering.
|
||||
Uri baseUri = ApiApps.CONTENT_URI;
|
||||
|
||||
// Now create and return a CursorLoader that will take care of
|
||||
// creating a Cursor for the data being displayed.
|
||||
return new CursorLoader(getActivity(), baseUri, PROJECTION, null, null,
|
||||
ApiApps.PACKAGE_NAME + " COLLATE LOCALIZED ASC");
|
||||
}
|
||||
|
||||
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
|
||||
// Swap the new cursor in. (The framework will take care of closing the
|
||||
// old cursor once we return.)
|
||||
mAdapter.swapCursor(data);
|
||||
}
|
||||
|
||||
public void onLoaderReset(Loader<Cursor> loader) {
|
||||
// This is called when the last Cursor provided to onLoadFinished()
|
||||
// above is about to be closed. We need to make sure we are no
|
||||
// longer using it.
|
||||
mAdapter.swapCursor(null);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,354 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.service.remote;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.service.exception.WrongPackageSignatureException;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.PausableThreadPoolExecutor;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.content.pm.Signature;
|
||||
import android.net.Uri;
|
||||
import android.os.Binder;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
|
||||
/**
|
||||
* Abstract service class for remote APIs that handle app registration and user input.
|
||||
*/
|
||||
public abstract class RemoteService extends Service {
|
||||
Context mContext;
|
||||
|
||||
private final ArrayBlockingQueue<Runnable> mPoolQueue = new ArrayBlockingQueue<Runnable>(100);
|
||||
// TODO: Are these parameters okay?
|
||||
private PausableThreadPoolExecutor mThreadPool = new PausableThreadPoolExecutor(2, 4, 10,
|
||||
TimeUnit.SECONDS, mPoolQueue);
|
||||
|
||||
private final Object userInputLock = new Object();
|
||||
|
||||
/**
|
||||
* Override handleUserInput() to handle OKAY (1) and CANCEL (0). After handling the waiting
|
||||
* threads will be notified and the queue resumed
|
||||
*/
|
||||
protected class UserInputCallback extends BaseCallback {
|
||||
|
||||
public void handleUserInput(Message msg) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handleMessage(Message msg) {
|
||||
handleUserInput(msg);
|
||||
|
||||
// resume
|
||||
synchronized (userInputLock) {
|
||||
userInputLock.notifyAll();
|
||||
}
|
||||
mThreadPool.resume();
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Extends Handler.Callback with OKAY (1), CANCEL (0) variables
|
||||
*/
|
||||
private class BaseCallback implements Handler.Callback {
|
||||
public static final int OKAY = 1;
|
||||
public static final int CANCEL = 0;
|
||||
|
||||
@Override
|
||||
public boolean handleMessage(Message msg) {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public Context getContext() {
|
||||
return mContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should be used from Stub implementations of AIDL interfaces to enqueue a runnable for
|
||||
* execution
|
||||
*
|
||||
* @param r
|
||||
*/
|
||||
protected void checkAndEnqueue(Runnable r) {
|
||||
try {
|
||||
if (isCallerAllowed(false)) {
|
||||
mThreadPool.execute(r);
|
||||
|
||||
Log.d(Constants.TAG, "Enqueued runnable…");
|
||||
} else {
|
||||
String[] callingPackages = getPackageManager().getPackagesForUid(
|
||||
Binder.getCallingUid());
|
||||
// TODO: currently simply uses first entry
|
||||
String packageName = callingPackages[0];
|
||||
|
||||
byte[] packageSignature;
|
||||
try {
|
||||
packageSignature = getPackageSignature(packageName);
|
||||
} catch (NameNotFoundException e) {
|
||||
Log.e(Constants.TAG, "Should not happen, returning!", e);
|
||||
return;
|
||||
}
|
||||
Log.e(Constants.TAG,
|
||||
"Not allowed to use service! Starting activity for registration!");
|
||||
Bundle extras = new Bundle();
|
||||
extras.putString(RemoteServiceActivity.EXTRA_PACKAGE_NAME, packageName);
|
||||
extras.putByteArray(RemoteServiceActivity.EXTRA_PACKAGE_SIGNATURE, packageSignature);
|
||||
RegisterActivityCallback callback = new RegisterActivityCallback();
|
||||
|
||||
pauseAndStartUserInteraction(RemoteServiceActivity.ACTION_REGISTER, callback,
|
||||
extras);
|
||||
|
||||
if (callback.isAllowed()) {
|
||||
mThreadPool.execute(r);
|
||||
Log.d(Constants.TAG, "Enqueued runnable…");
|
||||
} else {
|
||||
Log.d(Constants.TAG, "User disallowed app!");
|
||||
}
|
||||
}
|
||||
} catch (WrongPackageSignatureException e) {
|
||||
Log.e(Constants.TAG, e.getMessage());
|
||||
|
||||
Bundle extras = new Bundle();
|
||||
extras.putString(RemoteServiceActivity.EXTRA_ERROR_MESSAGE,
|
||||
getString(R.string.api_error_wrong_signature));
|
||||
pauseAndStartUserInteraction(RemoteServiceActivity.ACTION_ERROR_MESSAGE, null, extras);
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] getPackageSignature(String packageName) throws NameNotFoundException {
|
||||
PackageInfo pkgInfo = getPackageManager().getPackageInfo(packageName,
|
||||
PackageManager.GET_SIGNATURES);
|
||||
Signature[] signatures = pkgInfo.signatures;
|
||||
// TODO: Only first signature?!
|
||||
byte[] packageSignature = signatures[0].toByteArray();
|
||||
|
||||
return packageSignature;
|
||||
}
|
||||
|
||||
/**
|
||||
* Locks current thread and pauses execution of runnables and starts activity for user input
|
||||
*
|
||||
* @param action
|
||||
* @param messenger
|
||||
* @param extras
|
||||
*/
|
||||
protected void pauseAndStartUserInteraction(String action, BaseCallback callback, Bundle extras) {
|
||||
synchronized (userInputLock) {
|
||||
mThreadPool.pause();
|
||||
|
||||
Log.d(Constants.TAG, "starting activity...");
|
||||
Intent intent = new Intent(getBaseContext(), RemoteServiceActivity.class);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
intent.setAction(action);
|
||||
|
||||
Messenger messenger = new Messenger(new Handler(getMainLooper(), callback));
|
||||
|
||||
extras.putParcelable(RemoteServiceActivity.EXTRA_MESSENGER, messenger);
|
||||
intent.putExtras(extras);
|
||||
|
||||
startActivity(intent);
|
||||
|
||||
// lock current thread for user input
|
||||
try {
|
||||
userInputLock.wait();
|
||||
} catch (InterruptedException e) {
|
||||
Log.e(Constants.TAG, "CryptoService", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves AppSettings from database for the application calling this remote service
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
protected AppSettings getAppSettings() {
|
||||
String[] callingPackages = getPackageManager().getPackagesForUid(Binder.getCallingUid());
|
||||
|
||||
// get app settings for this package
|
||||
for (int i = 0; i < callingPackages.length; i++) {
|
||||
String currentPkg = callingPackages[i];
|
||||
|
||||
Uri uri = KeychainContract.ApiApps.buildByPackageNameUri(currentPkg);
|
||||
|
||||
AppSettings settings = ProviderHelper.getApiAppSettings(this, uri);
|
||||
|
||||
if (settings != null)
|
||||
return settings;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
class RegisterActivityCallback extends BaseCallback {
|
||||
public static final String PACKAGE_NAME = "package_name";
|
||||
|
||||
private boolean allowed = false;
|
||||
private String packageName;
|
||||
|
||||
public boolean isAllowed() {
|
||||
return allowed;
|
||||
}
|
||||
|
||||
public String getPackageName() {
|
||||
return packageName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handleMessage(Message msg) {
|
||||
if (msg.arg1 == OKAY) {
|
||||
allowed = true;
|
||||
packageName = msg.getData().getString(PACKAGE_NAME);
|
||||
|
||||
// resume threads
|
||||
try {
|
||||
if (isPackageAllowed(packageName)) {
|
||||
synchronized (userInputLock) {
|
||||
userInputLock.notifyAll();
|
||||
}
|
||||
mThreadPool.resume();
|
||||
} else {
|
||||
// Should not happen!
|
||||
Log.e(Constants.TAG, "Should not happen! Emergency shutdown!");
|
||||
mThreadPool.shutdownNow();
|
||||
}
|
||||
} catch (WrongPackageSignatureException e) {
|
||||
Log.e(Constants.TAG, e.getMessage());
|
||||
|
||||
Bundle extras = new Bundle();
|
||||
extras.putString(RemoteServiceActivity.EXTRA_ERROR_MESSAGE,
|
||||
getString(R.string.api_error_wrong_signature));
|
||||
pauseAndStartUserInteraction(RemoteServiceActivity.ACTION_ERROR_MESSAGE, null,
|
||||
extras);
|
||||
}
|
||||
} else {
|
||||
allowed = false;
|
||||
|
||||
synchronized (userInputLock) {
|
||||
userInputLock.notifyAll();
|
||||
}
|
||||
mThreadPool.resume();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if process that binds to this service (i.e. the package name corresponding to the
|
||||
* process) is in the list of allowed package names.
|
||||
*
|
||||
* @param allowOnlySelf
|
||||
* allow only Keychain app itself
|
||||
* @return true if process is allowed to use this service
|
||||
* @throws WrongPackageSignatureException
|
||||
*/
|
||||
private boolean isCallerAllowed(boolean allowOnlySelf) throws WrongPackageSignatureException {
|
||||
return isUidAllowed(Binder.getCallingUid(), allowOnlySelf);
|
||||
}
|
||||
|
||||
private boolean isUidAllowed(int uid, boolean allowOnlySelf)
|
||||
throws WrongPackageSignatureException {
|
||||
if (android.os.Process.myUid() == uid) {
|
||||
return true;
|
||||
}
|
||||
if (allowOnlySelf) { // barrier
|
||||
return false;
|
||||
}
|
||||
|
||||
String[] callingPackages = getPackageManager().getPackagesForUid(uid);
|
||||
|
||||
// is calling package allowed to use this service?
|
||||
for (int i = 0; i < callingPackages.length; i++) {
|
||||
String currentPkg = callingPackages[i];
|
||||
|
||||
if (isPackageAllowed(currentPkg)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
Log.d(Constants.TAG, "Caller is NOT allowed!");
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if packageName is a registered app for the API. Does not return true for own package!
|
||||
*
|
||||
* @param packageName
|
||||
* @return
|
||||
* @throws WrongPackageSignatureException
|
||||
*/
|
||||
private boolean isPackageAllowed(String packageName) throws WrongPackageSignatureException {
|
||||
Log.d(Constants.TAG, "packageName: " + packageName);
|
||||
|
||||
ArrayList<String> allowedPkgs = ProviderHelper.getRegisteredApiApps(this);
|
||||
Log.d(Constants.TAG, "allowed: " + allowedPkgs);
|
||||
|
||||
// check if package is allowed to use our service
|
||||
if (allowedPkgs.contains(packageName)) {
|
||||
Log.d(Constants.TAG, "Package is allowed! packageName: " + packageName);
|
||||
|
||||
// check package signature
|
||||
byte[] currentSig;
|
||||
try {
|
||||
currentSig = getPackageSignature(packageName);
|
||||
} catch (NameNotFoundException e) {
|
||||
throw new WrongPackageSignatureException(e.getMessage());
|
||||
}
|
||||
|
||||
byte[] storedSig = ProviderHelper.getApiAppSignature(this, packageName);
|
||||
if (Arrays.equals(currentSig, storedSig)) {
|
||||
Log.d(Constants.TAG,
|
||||
"Package signature is correct! (equals signature from database)");
|
||||
return true;
|
||||
} else {
|
||||
throw new WrongPackageSignatureException(
|
||||
"PACKAGE NOT ALLOWED! Signature wrong! (Signature not equals signature from database)");
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
mContext = this;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,352 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.service.remote;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import org.sufficientlysecure.htmltextview.HtmlTextView;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.Id;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.helper.ActionBarHelper;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.ui.SelectPublicKeyFragment;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.os.RemoteException;
|
||||
import android.view.View;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.actionbarsherlock.app.SherlockFragmentActivity;
|
||||
|
||||
public class RemoteServiceActivity extends SherlockFragmentActivity {
|
||||
|
||||
public static final String ACTION_REGISTER = Constants.INTENT_PREFIX + "API_ACTIVITY_REGISTER";
|
||||
public static final String ACTION_CACHE_PASSPHRASE = Constants.INTENT_PREFIX
|
||||
+ "API_ACTIVITY_CACHE_PASSPHRASE";
|
||||
public static final String ACTION_SELECT_PUB_KEYS = Constants.INTENT_PREFIX
|
||||
+ "API_ACTIVITY_SELECT_PUB_KEYS";
|
||||
public static final String ACTION_ERROR_MESSAGE = Constants.INTENT_PREFIX
|
||||
+ "API_ACTIVITY_ERROR_MESSAGE";
|
||||
|
||||
public static final String EXTRA_MESSENGER = "messenger";
|
||||
|
||||
// passphrase action
|
||||
public static final String EXTRA_SECRET_KEY_ID = "secret_key_id";
|
||||
// register action
|
||||
public static final String EXTRA_PACKAGE_NAME = "package_name";
|
||||
public static final String EXTRA_PACKAGE_SIGNATURE = "package_signature";
|
||||
// select pub keys action
|
||||
public static final String EXTRA_SELECTED_MASTER_KEY_IDS = "master_key_ids";
|
||||
public static final String EXTRA_MISSING_USER_IDS = "missing_user_ids";
|
||||
public static final String EXTRA_DUBLICATE_USER_IDS = "dublicate_user_ids";
|
||||
// error message
|
||||
public static final String EXTRA_ERROR_MESSAGE = "error_message";
|
||||
|
||||
private Messenger mMessenger;
|
||||
|
||||
// register view
|
||||
private AppSettingsFragment mSettingsFragment;
|
||||
// select pub keys view
|
||||
private SelectPublicKeyFragment mSelectFragment;
|
||||
|
||||
// has the user clicked one of the buttons
|
||||
// or do we need to handle the callback in onStop()
|
||||
private boolean finishHandled;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
handleActions(getIntent(), savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
|
||||
if (!finishHandled) {
|
||||
Message msg = Message.obtain();
|
||||
msg.arg1 = RemoteService.RegisterActivityCallback.CANCEL;
|
||||
try {
|
||||
mMessenger.send(msg);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(Constants.TAG, "CryptoServiceActivity", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void handleActions(Intent intent, Bundle savedInstanceState) {
|
||||
finishHandled = false;
|
||||
|
||||
String action = intent.getAction();
|
||||
Bundle extras = intent.getExtras();
|
||||
|
||||
if (extras == null) {
|
||||
extras = new Bundle();
|
||||
}
|
||||
|
||||
mMessenger = extras.getParcelable(EXTRA_MESSENGER);
|
||||
|
||||
/**
|
||||
* com.android.crypto actions
|
||||
*/
|
||||
if (ACTION_REGISTER.equals(action)) {
|
||||
final String packageName = extras.getString(EXTRA_PACKAGE_NAME);
|
||||
final byte[] packageSignature = extras.getByteArray(EXTRA_PACKAGE_SIGNATURE);
|
||||
|
||||
// Inflate a "Done"/"Cancel" custom action bar view
|
||||
ActionBarHelper.setDoneCancelView(getSupportActionBar(), R.string.api_register_allow,
|
||||
new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
// Allow
|
||||
|
||||
// user needs to select a key!
|
||||
if (mSettingsFragment.getAppSettings().getKeyId() == Id.key.none) {
|
||||
Toast.makeText(RemoteServiceActivity.this,
|
||||
R.string.api_register_error_select_key, Toast.LENGTH_LONG)
|
||||
.show();
|
||||
} else {
|
||||
ProviderHelper.insertApiApp(RemoteServiceActivity.this,
|
||||
mSettingsFragment.getAppSettings());
|
||||
|
||||
Message msg = Message.obtain();
|
||||
msg.arg1 = RemoteService.RegisterActivityCallback.OKAY;
|
||||
Bundle data = new Bundle();
|
||||
data.putString(RemoteService.RegisterActivityCallback.PACKAGE_NAME,
|
||||
packageName);
|
||||
msg.setData(data);
|
||||
try {
|
||||
mMessenger.send(msg);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(Constants.TAG, "CryptoServiceActivity", e);
|
||||
}
|
||||
|
||||
finishHandled = true;
|
||||
finish();
|
||||
}
|
||||
}
|
||||
}, R.string.api_register_disallow, new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
// Disallow
|
||||
|
||||
Message msg = Message.obtain();
|
||||
msg.arg1 = RemoteService.RegisterActivityCallback.CANCEL;
|
||||
try {
|
||||
mMessenger.send(msg);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(Constants.TAG, "CryptoServiceActivity", e);
|
||||
}
|
||||
|
||||
finishHandled = true;
|
||||
finish();
|
||||
}
|
||||
});
|
||||
|
||||
setContentView(R.layout.api_app_register_activity);
|
||||
|
||||
mSettingsFragment = (AppSettingsFragment) getSupportFragmentManager().findFragmentById(
|
||||
R.id.api_app_settings_fragment);
|
||||
|
||||
AppSettings settings = new AppSettings(packageName, packageSignature);
|
||||
mSettingsFragment.setAppSettings(settings);
|
||||
} else if (ACTION_CACHE_PASSPHRASE.equals(action)) {
|
||||
long secretKeyId = extras.getLong(EXTRA_SECRET_KEY_ID);
|
||||
|
||||
showPassphraseDialog(secretKeyId);
|
||||
} else if (ACTION_SELECT_PUB_KEYS.equals(action)) {
|
||||
long[] selectedMasterKeyIds = intent.getLongArrayExtra(EXTRA_SELECTED_MASTER_KEY_IDS);
|
||||
ArrayList<String> missingUserIds = intent
|
||||
.getStringArrayListExtra(EXTRA_MISSING_USER_IDS);
|
||||
ArrayList<String> dublicateUserIds = intent
|
||||
.getStringArrayListExtra(EXTRA_DUBLICATE_USER_IDS);
|
||||
|
||||
String text = new String();
|
||||
text += "<b>" + getString(R.string.api_select_pub_keys_text) + "</b>";
|
||||
text += "<br/><br/>";
|
||||
if (missingUserIds != null && missingUserIds.size() > 0) {
|
||||
text += getString(R.string.api_select_pub_keys_missing_text);
|
||||
text += "<br/>";
|
||||
text += "<ul>";
|
||||
for (String userId : missingUserIds) {
|
||||
text += "<li>" + userId + "</li>";
|
||||
}
|
||||
text += "</ul>";
|
||||
text += "<br/>";
|
||||
}
|
||||
if (dublicateUserIds != null && dublicateUserIds.size() > 0) {
|
||||
text += getString(R.string.api_select_pub_keys_dublicates_text);
|
||||
text += "<br/>";
|
||||
text += "<ul>";
|
||||
for (String userId : dublicateUserIds) {
|
||||
text += "<li>" + userId + "</li>";
|
||||
}
|
||||
text += "</ul>";
|
||||
}
|
||||
|
||||
// Inflate a "Done"/"Cancel" custom action bar view
|
||||
ActionBarHelper.setDoneCancelView(getSupportActionBar(), R.string.btn_okay,
|
||||
new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
// ok
|
||||
|
||||
Message msg = Message.obtain();
|
||||
msg.arg1 = OpenPgpService.SelectPubKeysActivityCallback.OKAY;
|
||||
Bundle data = new Bundle();
|
||||
data.putLongArray(
|
||||
OpenPgpService.SelectPubKeysActivityCallback.PUB_KEY_IDS,
|
||||
mSelectFragment.getSelectedMasterKeyIds());
|
||||
msg.setData(data);
|
||||
try {
|
||||
mMessenger.send(msg);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(Constants.TAG, "CryptoServiceActivity", e);
|
||||
}
|
||||
|
||||
finishHandled = true;
|
||||
finish();
|
||||
}
|
||||
}, R.string.btn_do_not_save, new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
// cancel
|
||||
|
||||
Message msg = Message.obtain();
|
||||
msg.arg1 = OpenPgpService.SelectPubKeysActivityCallback.CANCEL;
|
||||
;
|
||||
try {
|
||||
mMessenger.send(msg);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(Constants.TAG, "CryptoServiceActivity", e);
|
||||
}
|
||||
|
||||
finishHandled = true;
|
||||
finish();
|
||||
}
|
||||
});
|
||||
|
||||
setContentView(R.layout.api_app_select_pub_keys_activity);
|
||||
|
||||
// set text on view
|
||||
HtmlTextView textView = (HtmlTextView) findViewById(R.id.api_select_pub_keys_text);
|
||||
textView.setHtmlFromString(text);
|
||||
|
||||
/* Load select pub keys fragment */
|
||||
// Check that the activity is using the layout version with
|
||||
// the fragment_container FrameLayout
|
||||
if (findViewById(R.id.api_select_pub_keys_fragment_container) != null) {
|
||||
|
||||
// However, if we're being restored from a previous state,
|
||||
// then we don't need to do anything and should return or else
|
||||
// we could end up with overlapping fragments.
|
||||
if (savedInstanceState != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create an instance of the fragment
|
||||
mSelectFragment = SelectPublicKeyFragment.newInstance(selectedMasterKeyIds);
|
||||
|
||||
// Add the fragment to the 'fragment_container' FrameLayout
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.add(R.id.api_select_pub_keys_fragment_container, mSelectFragment).commit();
|
||||
}
|
||||
} else if (ACTION_ERROR_MESSAGE.equals(action)) {
|
||||
String errorMessage = intent.getStringExtra(EXTRA_ERROR_MESSAGE);
|
||||
|
||||
String text = new String();
|
||||
text += "<font color=\"red\">" + errorMessage + "</font>";
|
||||
|
||||
// Inflate a "Done" custom action bar view
|
||||
ActionBarHelper.setDoneView(getSupportActionBar(), R.string.btn_okay,
|
||||
new View.OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
finish();
|
||||
}
|
||||
});
|
||||
|
||||
setContentView(R.layout.api_app_error_message);
|
||||
|
||||
// set text on view
|
||||
HtmlTextView textView = (HtmlTextView) findViewById(R.id.api_app_error_message_text);
|
||||
textView.setHtmlFromString(text);
|
||||
} else {
|
||||
Log.e(Constants.TAG, "Wrong action!");
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows passphrase dialog to cache a new passphrase the user enters for using it later for
|
||||
* encryption. Based on mSecretKeyId it asks for a passphrase to open a private key or it asks
|
||||
* for a symmetric passphrase
|
||||
*/
|
||||
private void showPassphraseDialog(long secretKeyId) {
|
||||
// Message is received after passphrase is cached
|
||||
Handler returnHandler = new Handler() {
|
||||
@Override
|
||||
public void handleMessage(Message message) {
|
||||
if (message.what == PassphraseDialogFragment.MESSAGE_OKAY) {
|
||||
Message msg = Message.obtain();
|
||||
msg.arg1 = OpenPgpService.PassphraseActivityCallback.OKAY;
|
||||
try {
|
||||
mMessenger.send(msg);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(Constants.TAG, "CryptoServiceActivity", e);
|
||||
}
|
||||
} else {
|
||||
Message msg = Message.obtain();
|
||||
msg.arg1 = OpenPgpService.PassphraseActivityCallback.CANCEL;
|
||||
try {
|
||||
mMessenger.send(msg);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(Constants.TAG, "CryptoServiceActivity", e);
|
||||
}
|
||||
}
|
||||
|
||||
finishHandled = true;
|
||||
finish();
|
||||
}
|
||||
};
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(returnHandler);
|
||||
|
||||
try {
|
||||
PassphraseDialogFragment passphraseDialog = PassphraseDialogFragment.newInstance(this,
|
||||
messenger, secretKeyId);
|
||||
|
||||
passphraseDialog.show(getSupportFragmentManager(), "passphraseDialog");
|
||||
} catch (PgpGeneralException e) {
|
||||
Log.d(Constants.TAG, "No passphrase for this secret key, encrypt directly!");
|
||||
// send message to handler to start encryption directly
|
||||
returnHandler.sendEmptyMessage(PassphraseDialogFragment.MESSAGE_OKAY);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user