open-keychain/org_apg/src/org/thialfihar/android/apg/service/ApgService.java

547 lines
23 KiB
Java

/*
* Copyright (C) 2012 Dominik Schürmann <dominik@dominikschuermann.de>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thialfihar.android.apg.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.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import org.spongycastle.openpgp.PGPSecretKey;
import org.spongycastle.openpgp.PGPSecretKeyRing;
import org.thialfihar.android.apg.Apg;
import org.thialfihar.android.apg.Constants;
import org.thialfihar.android.apg.DataDestination;
import org.thialfihar.android.apg.DataSource;
import org.thialfihar.android.apg.Id;
import org.thialfihar.android.apg.InputData;
import org.thialfihar.android.apg.Preferences;
import org.thialfihar.android.apg.ProgressDialogUpdater;
import org.thialfihar.android.apg.R;
import org.thialfihar.android.apg.Apg.GeneralException;
import org.thialfihar.android.apg.provider.DataProvider;
import org.thialfihar.android.apg.util.Utils;
import android.app.IntentService;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.util.Log;
/**
* 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.
*/
// TODO: ProgressDialogUpdater rework???
public class ApgService extends IntentService implements ProgressDialogUpdater {
// extras that can be given by intent
public static final String EXTRA_MESSENGER = "messenger";
public static final String EXTRA_ACTION = "action";
public static final String EXTRA_DATA = "data";
// keys for data bundle
// edit keys
public static final String NEW_PASSPHRASE = "new_passphrase";
public static final String CURRENT_PASSPHRASE = "current_passphrase";
public static final String USER_IDS = "user_ids";
public static final String KEYS = "keys";
public static final String KEYS_USAGES = "keys_usages";
public static final String MASTER_KEY_ID = "master_key_id";
// generate key
public static final String ALGORITHM = "algorithm";
public static final String KEY_SIZE = "key_size";
public static final String PASSPHRASE = "passphrase";
public static final String MASTER_KEY = "master_key";
// encrypt
public static final String SECRET_KEY_ID = "secret_key_id";
// public static final String DATA_SOURCE = "data_source";
// public static final String DATA_DESTINATION = "data_destination";
public static final String USE_ASCII_AMOR = "use_ascii_amor";
// public static final String ENCRYPTION_TARGET = "encryption_target";
public static final String ENCRYPTION_KEYS_IDS = "encryption_keys_ids";
public static final String SIGNATURE_KEY_ID = "signature_key_id";
public static final String COMPRESSION_ID = "compression_id";
public static final String GENERATE_SIGNATURE = "generate_signature";
public static final String SIGN_ONLY = "sign_only";
public static final String BYTES = "bytes";
public static final String FILE_URI = "file_uri";
public static final String OUTPUT_FILENAME = "output_filename";
public static final String PROVIDER_URI = "provider_uri";
// possible ints for EXTRA_ACTION
public static final int ACTION_SAVE_KEYRING = 1;
public static final int ACTION_GENERATE_KEY = 2;
public static final int ACTION_GENERATE_DEFAULT_RSA_KEYS = 3;
public static final int ACTION_ENCRYPT_SIGN_BYTES = 4;
public static final int ACTION_ENCRYPT_SIGN_FILE = 5;
public static final int ACTION_ENCRYPT_SIGN_STREAM = 6;
// possible data keys as result
public static final String RESULT_NEW_KEY = "new_key";
public static final String RESULT_NEW_KEY2 = "new_key2";
public static final String RESULT_SIGNATURE_DATA = "signatureData";
public static final String RESULT_SIGNATURE_TEXT = "signatureText";
public static final String RESULT_ENCRYPTED_MESSAGE = "encryptedMessage";
public static final String RESULT_ENCRYPTED_DATA = "encryptedData";
public static final String RESULT_URI = "resultUri";
Messenger mMessenger;
public ApgService() {
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) || extras
.containsKey(EXTRA_ACTION))) {
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);
int action = extras.getInt(EXTRA_ACTION);
// execute action from extra bundle
switch (action) {
case ACTION_SAVE_KEYRING:
try {
// Input
String oldPassPhrase = data.getString(CURRENT_PASSPHRASE);
String newPassPhrase = data.getString(NEW_PASSPHRASE);
if (newPassPhrase == null) {
newPassPhrase = oldPassPhrase;
}
@SuppressWarnings("unchecked")
ArrayList<String> userIds = (ArrayList<String>) data.getSerializable(USER_IDS);
ArrayList<PGPSecretKey> keys = Utils.BytesToPGPSecretKeyList(data
.getByteArray(KEYS));
@SuppressWarnings("unchecked")
ArrayList<Integer> keysUsages = (ArrayList<Integer>) data
.getSerializable(KEYS_USAGES);
long masterKeyId = data.getLong(MASTER_KEY_ID);
// Operation
Apg.buildSecretKey(this, userIds, keys, keysUsages, masterKeyId, oldPassPhrase,
newPassPhrase, this);
Apg.setCachedPassPhrase(masterKeyId, newPassPhrase);
// Output
sendMessageToHandler(ApgHandler.MESSAGE_OKAY);
} catch (Exception e) {
sendErrorToHandler(e);
}
break;
case ACTION_GENERATE_KEY:
try {
// Input
int algorithm = data.getInt(ALGORITHM);
String passphrase = data.getString(PASSPHRASE);
int keysize = data.getInt(KEY_SIZE);
PGPSecretKey masterKey = null;
if (data.containsKey(MASTER_KEY)) {
masterKey = Utils.BytesToPGPSecretKey(data.getByteArray(MASTER_KEY));
}
// Operation
PGPSecretKeyRing newKeyRing = Apg.createKey(this, algorithm, keysize, passphrase,
masterKey);
// Output
Bundle resultData = new Bundle();
resultData.putByteArray(RESULT_NEW_KEY, Utils.PGPSecretKeyRingToBytes(newKeyRing));
sendMessageToHandler(ApgHandler.MESSAGE_OKAY, resultData);
} catch (Exception e) {
sendErrorToHandler(e);
}
break;
case ACTION_GENERATE_DEFAULT_RSA_KEYS:
// generate one RSA 2048 key for signing and one subkey for encrypting!
try {
String passphrase = data.getString(PASSPHRASE);
// Operation
PGPSecretKeyRing masterKeyRing = Apg.createKey(this, Id.choice.algorithm.rsa, 2048,
passphrase, null);
PGPSecretKeyRing subKeyRing = Apg.createKey(this, Id.choice.algorithm.rsa, 2048,
passphrase, masterKeyRing.getSecretKey());
// Output
Bundle resultData = new Bundle();
resultData.putByteArray(RESULT_NEW_KEY,
Utils.PGPSecretKeyRingToBytes(masterKeyRing));
resultData.putByteArray(RESULT_NEW_KEY2, Utils.PGPSecretKeyRingToBytes(subKeyRing));
sendMessageToHandler(ApgHandler.MESSAGE_OKAY, resultData);
} catch (Exception e) {
sendErrorToHandler(e);
}
break;
case ACTION_ENCRYPT_SIGN_BYTES:
try {
// Input
long secretKeyId = data.getLong(SECRET_KEY_ID);
String passphrase = data.getString(PASSPHRASE);
byte[] bytes = data.getByteArray(BYTES);
boolean useAsciiArmour = data.getBoolean(USE_ASCII_AMOR);
long encryptionKeyIds[] = data.getLongArray(ENCRYPTION_KEYS_IDS);
long signatureKeyId = data.getLong(SIGNATURE_KEY_ID);
int compressionId = data.getInt(COMPRESSION_ID);
boolean generateSignature = data.getBoolean(GENERATE_SIGNATURE);
boolean signOnly = data.getBoolean(SIGN_ONLY);
// Operation
ByteArrayInputStream inStream = new ByteArrayInputStream(bytes);
int inLength = bytes.length;
InputData inputData = new InputData(inStream, inLength);
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
if (generateSignature) {
Apg.generateSignature(this, inputData, outStream, useAsciiArmour, false,
secretKeyId, Apg.getCachedPassPhrase(secretKeyId), Preferences
.getPreferences(this).getDefaultHashAlgorithm(), Preferences
.getPreferences(this).getForceV3Signatures(), this);
} else if (signOnly) {
Apg.signText(this, inputData, outStream, secretKeyId, Apg
.getCachedPassPhrase(secretKeyId), Preferences.getPreferences(this)
.getDefaultHashAlgorithm(), Preferences.getPreferences(this)
.getForceV3Signatures(), this);
} else {
Apg.encrypt(this, inputData, outStream, useAsciiArmour, encryptionKeyIds,
signatureKeyId, Apg.getCachedPassPhrase(signatureKeyId), this,
Preferences.getPreferences(this).getDefaultEncryptionAlgorithm(),
Preferences.getPreferences(this).getDefaultHashAlgorithm(),
compressionId, Preferences.getPreferences(this).getForceV3Signatures(),
passphrase);
}
outStream.close();
// Output
Bundle resultData = new Bundle();
// if (encryptionTarget != Id.target.file) {
// if (out instanceof ByteArrayOutputStream) {
if (useAsciiArmour) {
String output = new String(outStream.toByteArray());
if (generateSignature) {
resultData.putString(RESULT_SIGNATURE_TEXT, output);
} else {
resultData.putString(RESULT_ENCRYPTED_MESSAGE, output);
}
} else {
byte output[] = outStream.toByteArray();
if (generateSignature) {
resultData.putByteArray(RESULT_SIGNATURE_DATA, output);
} else {
resultData.putByteArray(RESULT_ENCRYPTED_DATA, output);
}
}
// } else if (out instanceof FileOutputStream) {
// String fileName = dataDestination.getStreamFilename();
// String uri = "content://" + DataProvider.AUTHORITY + "/data/" + fileName;
// resultData.putString(RESULT_URI, uri);
// } else {
// sendErrorToHandler(new Apg.GeneralException("No output-data found."));
// }
// }
sendMessageToHandler(ApgHandler.MESSAGE_OKAY, resultData);
} catch (Exception e) {
sendErrorToHandler(e);
}
break;
case ACTION_ENCRYPT_SIGN_FILE:
try {
// Input
long secretKeyId = data.getLong(SECRET_KEY_ID);
String passphrase = data.getString(PASSPHRASE);
Uri fileUri = Uri.parse(data.getString(FILE_URI));
String outputFilename = data.getString(OUTPUT_FILENAME);
boolean useAsciiArmour = data.getBoolean(USE_ASCII_AMOR);
long encryptionKeyIds[] = data.getLongArray(ENCRYPTION_KEYS_IDS);
long signatureKeyId = data.getLong(SIGNATURE_KEY_ID);
int compressionId = data.getInt(COMPRESSION_ID);
boolean generateSignature = data.getBoolean(GENERATE_SIGNATURE);
boolean signOnly = data.getBoolean(SIGN_ONLY);
// InputStream
long inLength = -1;
FileInputStream inStream = null;
if (fileUri.getScheme().equals("file")) {
// get the rest after "file://"
String path = Uri.decode(fileUri.toString().substring(7));
if (path.startsWith(Environment.getExternalStorageDirectory().getAbsolutePath())) {
if (!Environment.getExternalStorageState()
.equals(Environment.MEDIA_MOUNTED)) {
sendErrorToHandler(new GeneralException(
getString(R.string.error_externalStorageNotReady)));
return;
}
}
inStream = new FileInputStream(path);
File file = new File(path);
inLength = file.length();
}
InputData inputData = new InputData(inStream, inLength);
// OutputStream
if (outputFilename.startsWith(Environment.getExternalStorageDirectory()
.getAbsolutePath())) {
if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
sendErrorToHandler(new GeneralException(
getString(R.string.error_externalStorageNotReady)));
return;
}
}
FileOutputStream outStream = new FileOutputStream(outputFilename);
// Operation
if (generateSignature) {
Apg.generateSignature(this, inputData, outStream, useAsciiArmour, true,
secretKeyId, Apg.getCachedPassPhrase(secretKeyId), Preferences
.getPreferences(this).getDefaultHashAlgorithm(), Preferences
.getPreferences(this).getForceV3Signatures(), this);
} else if (signOnly) {
Apg.signText(this, inputData, outStream, secretKeyId, Apg
.getCachedPassPhrase(secretKeyId), Preferences.getPreferences(this)
.getDefaultHashAlgorithm(), Preferences.getPreferences(this)
.getForceV3Signatures(), this);
} else {
Apg.encrypt(this, inputData, outStream, useAsciiArmour, encryptionKeyIds,
signatureKeyId, Apg.getCachedPassPhrase(signatureKeyId), this,
Preferences.getPreferences(this).getDefaultEncryptionAlgorithm(),
Preferences.getPreferences(this).getDefaultHashAlgorithm(),
compressionId, Preferences.getPreferences(this).getForceV3Signatures(),
passphrase);
}
outStream.close();
sendMessageToHandler(ApgHandler.MESSAGE_OKAY);
} catch (Exception e) {
sendErrorToHandler(e);
}
break;
case ACTION_ENCRYPT_SIGN_STREAM:
try {
// Input
long secretKeyId = data.getLong(SECRET_KEY_ID);
String passphrase = data.getString(PASSPHRASE);
Uri providerUri = Uri.parse(data.getString(PROVIDER_URI));
boolean useAsciiArmour = data.getBoolean(USE_ASCII_AMOR);
long encryptionKeyIds[] = data.getLongArray(ENCRYPTION_KEYS_IDS);
long signatureKeyId = data.getLong(SIGNATURE_KEY_ID);
int compressionId = data.getInt(COMPRESSION_ID);
boolean generateSignature = data.getBoolean(GENERATE_SIGNATURE);
boolean signOnly = data.getBoolean(SIGN_ONLY);
// InputStream
InputStream in = getContentResolver().openInputStream(providerUri);
long inLength = Apg.getLengthOfStream(in);
InputData inputData = new InputData(in, inLength);
// OutputStream
String streamFilename = null;
try {
while (true) {
streamFilename = Apg.generateRandomString(32);
if (streamFilename == null) {
throw new Apg.GeneralException("couldn't generate random file name");
}
openFileInput(streamFilename).close();
}
} catch (FileNotFoundException e) {
// found a name that isn't used yet
}
FileOutputStream outStream = openFileOutput(streamFilename, Context.MODE_PRIVATE);
// Operation
if (generateSignature) {
Apg.generateSignature(this, inputData, outStream, useAsciiArmour, true,
secretKeyId, Apg.getCachedPassPhrase(secretKeyId), Preferences
.getPreferences(this).getDefaultHashAlgorithm(), Preferences
.getPreferences(this).getForceV3Signatures(), this);
} else if (signOnly) {
Apg.signText(this, inputData, outStream, secretKeyId, Apg
.getCachedPassPhrase(secretKeyId), Preferences.getPreferences(this)
.getDefaultHashAlgorithm(), Preferences.getPreferences(this)
.getForceV3Signatures(), this);
} else {
Apg.encrypt(this, inputData, outStream, useAsciiArmour, encryptionKeyIds,
signatureKeyId, Apg.getCachedPassPhrase(signatureKeyId), this,
Preferences.getPreferences(this).getDefaultEncryptionAlgorithm(),
Preferences.getPreferences(this).getDefaultHashAlgorithm(),
compressionId, Preferences.getPreferences(this).getForceV3Signatures(),
passphrase);
}
outStream.close();
// Output
Bundle resultData = new Bundle();
// if (encryptionTarget != Id.target.file) {
// if (out instanceof ByteArrayOutputStream) {
// if (useAsciiArmour) {
// String output = new String(outStream.toByteArray());
// if (generateSignature) {
// resultData.putString(RESULT_SIGNATURE_TEXT, output);
// } else {
// resultData.putString(RESULT_ENCRYPTED_MESSAGE, output);
// }
// } else {
// byte output[] = outStream.toByteArray();
// if (generateSignature) {
// resultData.putByteArray(RESULT_SIGNATURE_DATA, output);
// } else {
// resultData.putByteArray(RESULT_ENCRYPTED_DATA, output);
// }
// }
// } else if (out instanceof FileOutputStream) {
// String fileName = dataDestination.getStreamFilename();
String uri = "content://" + DataProvider.AUTHORITY + "/data/" + streamFilename;
resultData.putString(RESULT_URI, uri);
// } else {
// sendErrorToHandler(new Apg.GeneralException("No output-data found."));
// }
// }
sendMessageToHandler(ApgHandler.MESSAGE_OKAY, resultData);
} catch (Exception e) {
sendErrorToHandler(e);
}
break;
default:
break;
}
}
private void sendErrorToHandler(Exception e) {
Log.e(Constants.TAG, "ApgService Exception: ", e);
e.printStackTrace();
Bundle data = new Bundle();
data.putString(ApgHandler.DATA_ERROR, e.getMessage());
sendMessageToHandler(ApgHandler.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");
Bundle data = new Bundle();
if (message != null) {
data.putString(ApgHandler.DATA_MESSAGE, message);
}
data.putInt(ApgHandler.DATA_PROGRESS, progress);
data.putInt(ApgHandler.DATA_PROGRESS_MAX, max);
sendMessageToHandler(ApgHandler.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);
}
}