Restructure

This commit is contained in:
Dominik Schürmann
2013-07-01 23:23:53 +02:00
parent 202ccc2c36
commit 14350679d1
8 changed files with 18 additions and 14 deletions

View File

@@ -0,0 +1,44 @@
package org.sufficientlysecure.keychain.remote_api;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.ui.MainActivity;
import com.actionbarsherlock.app.ActionBar;
import com.actionbarsherlock.app.SherlockFragmentActivity;
import com.actionbarsherlock.view.MenuItem;
import android.content.Intent;
import android.os.Bundle;
public class CryptoConsumersActivity extends SherlockFragmentActivity {
private ActionBar mActionBar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mActionBar = getSupportActionBar();
setContentView(R.layout.crypto_consumers_activity);
mActionBar.setDisplayShowTitleEnabled(true);
mActionBar.setDisplayHomeAsUpEnabled(true);
}
/**
* Menu Options
*/
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
// app icon in Action Bar clicked; go home
Intent intent = new Intent(this, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
return true;
default:
return super.onOptionsItemSelected(item);
}
}
}

View File

@@ -0,0 +1,85 @@
package org.sufficientlysecure.keychain.remote_api;
import org.sufficientlysecure.keychain.provider.KeychainContract.CryptoConsumers;
import org.sufficientlysecure.keychain.util.Log;
import com.actionbarsherlock.app.SherlockListFragment;
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.support.v4.widget.SimpleCursorAdapter;
import android.view.View;
import android.widget.ListView;
public class CryptoConsumersFragment extends SherlockListFragment implements
LoaderManager.LoaderCallbacks<Cursor> {
// This is the Adapter being used to display the list's data.
SimpleCursorAdapter mAdapter;
// If non-null, this is the current filter the user has provided.
String mCurFilter;
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// Give some text to display if there is no data. In a real
// application this would come from a resource.
setEmptyText("TODO no crypto consumers");
// 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 SimpleCursorAdapter(getActivity(), android.R.layout.simple_list_item_2,
null, new String[] { CryptoConsumers.PACKAGE_NAME, CryptoConsumers.PACKAGE_NAME },
new int[] { android.R.id.text1, android.R.id.text2 }, 0);
setListAdapter(mAdapter);
// Prepare the loader. Either re-connect with an existing one,
// or start a new one.
getLoaderManager().initLoader(0, null, this);
}
@Override
public void onListItemClick(ListView l, View v, int position, long id) {
// Insert desired behavior here.
Log.i("FragmentComplexList", "Item clicked: " + id);
}
// These are the Contacts rows that we will retrieve.
static final String[] CONSUMERS_SUMMARY_PROJECTION = new String[] { CryptoConsumers._ID,
CryptoConsumers.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 = CryptoConsumers.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, CONSUMERS_SUMMARY_PROJECTION, null, null,
CryptoConsumers.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);
}
}

View File

@@ -0,0 +1,367 @@
/*
* Copyright (C) 2013 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.sufficientlysecure.keychain.remote_api;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;
import org.openintents.crypto.CryptoError;
import org.openintents.crypto.CryptoSignatureResult;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.Id;
import org.sufficientlysecure.keychain.helper.PgpMain;
import org.sufficientlysecure.keychain.util.InputData;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.remote_api.IServiceActivityCallback;
import org.sufficientlysecure.keychain.service.KeychainIntentService;
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
import org.sufficientlysecure.keychain.util.PausableThreadPoolExecutor;
import org.openintents.crypto.ICryptoCallback;
import org.openintents.crypto.ICryptoService;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
public class CryptoService extends Service {
Context mContext;
// just one pool of 4 threads, pause on every user action needed
final ArrayBlockingQueue<Runnable> mPoolQueue = new ArrayBlockingQueue<Runnable>(20);
PausableThreadPoolExecutor mThreadPool = new PausableThreadPoolExecutor(2, 4, 10,
TimeUnit.SECONDS, mPoolQueue);
public static final String ACTION_SERVICE_ACTIVITY = "org.sufficientlysecure.keychain.crypto_provider.IServiceActivityCallback";
@Override
public void onCreate() {
super.onCreate();
mContext = this;
Log.d(Constants.TAG, "CryptoService, onCreate()");
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d(Constants.TAG, "CryptoService, onDestroy()");
}
@Override
public IBinder onBind(Intent intent) {
// return different binder for connections from internal service activity
if (ACTION_SERVICE_ACTIVITY.equals(intent.getAction())) {
String callingPackageName = intent.getPackage();
// this binder can only be used from OpenPGP Keychain
if (callingPackageName.equals(Constants.PACKAGE_NAME)) {
return mBinderServiceActivity;
} else {
Log.e(Constants.TAG, "This binder can only be used from " + Constants.PACKAGE_NAME);
return null;
}
} else {
return mBinder;
}
}
private synchronized void encryptSafe(byte[] inputBytes, String[] encryptionUserIds,
ICryptoCallback callback) throws RemoteException {
try {
// build InputData and write into OutputStream
InputStream inputStream = new ByteArrayInputStream(inputBytes);
long inputLength = inputBytes.length;
InputData inputData = new InputData(inputStream, inputLength);
OutputStream outStream = new ByteArrayOutputStream();
// TODO: hardcoded...
boolean useAsciiArmor = true;
int compressionId = 2; // zlib
// PgpMain.encryptAndSign(this, this, inputData, outStream, useAsciiArmor,
// compressionId, encryptionKeyIds, encryptionPassphrase, Preferences
// .getPreferences(this).getDefaultEncryptionAlgorithm(),
// secretKeyId,
// Preferences.getPreferences(this).getDefaultHashAlgorithm(), Preferences
// .getPreferences(this).getForceV3Signatures(),
// PassphraseCacheService.getCachedPassphrase(this, secretKeyId));
outStream.close();
} catch (Exception e) {
Log.e(Constants.TAG, "KeychainService, Exception!", e);
try {
callback.onError(new CryptoError(0, e.getMessage()));
} catch (Exception t) {
Log.e(Constants.TAG, "Error returning exception to client", t);
}
}
}
private synchronized void decryptAndVerifySafe(byte[] inputBytes, ICryptoCallback callback)
throws RemoteException {
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();
long secretKeyId = PgpMain.getDecryptionKeyId(mContext, inputStream);
if (secretKeyId == Id.key.none) {
throw new PgpMain.PgpGeneralException(getString(R.string.error_noSecretKeyFound));
}
Log.d(Constants.TAG, "Got input:\n" + new String(inputBytes));
Log.d(Constants.TAG, "secretKeyId " + secretKeyId);
String passphrase = PassphraseCacheService.getCachedPassphrase(mContext, secretKeyId);
if (passphrase == null) {
Log.d(Constants.TAG, "No passphrase! Activity required!");
// start passphrase dialog
Bundle extras = new Bundle();
extras.putLong(ServiceActivity.EXTRA_SECRET_KEY_ID, secretKeyId);
pauseQueueAndStartServiceActivity(ServiceActivity.ACTION_CACHE_PASSPHRASE, extras);
}
// if (signedOnly) {
// resultData = PgpMain.verifyText(this, this, inputData, outStream,
// lookupUnknownKey);
// } else {
// resultData = PgpMain.decryptAndVerify(this, this, inputData, outStream,
// PassphraseCacheService.getCachedPassphrase(this, secretKeyId),
// assumeSymmetricEncryption);
// }
Bundle outputBundle = PgpMain.decryptAndVerify(mContext, null, inputData, outputStream,
passphrase, false);
outputStream.close();
byte[] outputBytes = ((ByteArrayOutputStream) outputStream).toByteArray();
// get signature informations from bundle
boolean signature = outputBundle.getBoolean(KeychainIntentService.RESULT_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);
CryptoSignatureResult sigResult = new CryptoSignatureResult(signatureUserId, signature,
signatureSuccess, signatureUnknown);
// return over handler on client side
callback.onSuccess(outputBytes, sigResult);
} catch (Exception e) {
Log.e(Constants.TAG, "KeychainService, Exception!", e);
try {
callback.onError(new CryptoError(0, e.getMessage()));
} catch (Exception t) {
Log.e(Constants.TAG, "Error returning exception to client", t);
}
}
}
private final ICryptoService.Stub mBinder = new ICryptoService.Stub() {
@Override
public void encrypt(final byte[] inputBytes, final String[] encryptionUserIds,
final ICryptoCallback callback) throws RemoteException {
Runnable r = new Runnable() {
@Override
public void run() {
try {
encryptSafe(inputBytes, encryptionUserIds, callback);
} catch (RemoteException e) {
Log.e(Constants.TAG, "CryptoService", e);
}
}
};
checkAndEnqueue(r);
}
@Override
public void encryptAndSign(byte[] inputBytes, String[] encryptionUserIds,
String signatureUserId, ICryptoCallback callback) throws RemoteException {
// TODO Auto-generated method stub
}
@Override
public void sign(byte[] inputBytes, String signatureUserId, ICryptoCallback callback)
throws RemoteException {
// TODO Auto-generated method stub
}
@Override
public void decryptAndVerify(final byte[] inputBytes, final ICryptoCallback callback)
throws RemoteException {
Runnable r = new Runnable() {
@Override
public void run() {
try {
decryptAndVerifySafe(inputBytes, callback);
} catch (RemoteException e) {
Log.e(Constants.TAG, "CryptoService", e);
}
}
};
checkAndEnqueue(r);
}
@Override
public void setup(boolean asciiArmor, boolean newKeyring, String newKeyringUserId)
throws RemoteException {
// TODO Auto-generated method stub
}
};
private final IServiceActivityCallback.Stub mBinderServiceActivity = new IServiceActivityCallback.Stub() {
@Override
public void register(boolean success, String packageName) throws RemoteException {
if (success) {
// resume threads
if (isPackageAllowed(packageName)) {
mThreadPool.resume();
} else {
// TODO: should not happen?
}
} else {
// TODO
mPoolQueue.clear();
}
}
@Override
public void cachePassphrase(boolean success, String passphrase) throws RemoteException {
}
};
private void checkAndEnqueue(Runnable r) {
if (isCallerAllowed()) {
mThreadPool.execute(r);
Log.d(Constants.TAG, "Enqueued runnable…");
} else {
String[] callingPackages = getPackageManager()
.getPackagesForUid(Binder.getCallingUid());
Log.e(Constants.TAG, "Not allowed to use service! Starting activity for registration!");
Bundle extras = new Bundle();
// TODO: currently simply uses first entry
extras.putString(ServiceActivity.EXTRA_PACKAGE_NAME, callingPackages[0]);
pauseQueueAndStartServiceActivity(ServiceActivity.ACTION_REGISTER, extras);
mThreadPool.execute(r);
Log.d(Constants.TAG, "Enqueued runnable…");
}
}
/**
* 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.
*
* @return true if process is allowed to use this service
*/
private boolean isCallerAllowed() {
String[] callingPackages = getPackageManager().getPackagesForUid(Binder.getCallingUid());
// 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;
}
private boolean isPackageAllowed(String packageName) {
Log.d(Constants.TAG, "packageName: " + packageName);
ArrayList<String> allowedPkgs = ProviderHelper.getCryptoConsumers(mContext);
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);
return true;
} else if (Constants.PACKAGE_NAME.equals(packageName)) {
Log.d(Constants.TAG, "Package is OpenPGP Keychain! -> allowed!");
return true;
}
return false;
}
private void pauseQueueAndStartServiceActivity(String action, Bundle extras) {
mThreadPool.pause();
Log.d(Constants.TAG, "starting activity...");
Intent intent = new Intent(getBaseContext(), ServiceActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
// intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
// intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
intent.setAction(action);
if (extras != null) {
intent.putExtras(extras);
}
getApplication().startActivity(intent);
}
}

View File

@@ -0,0 +1,28 @@
/*
* Copyright (C) 2013 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.sufficientlysecure.keychain.remote_api;
interface IServiceActivityCallback {
oneway void register(in boolean success, in String packageName);
oneway void cachePassphrase(in boolean success, in String passphrase);
}

View File

@@ -0,0 +1,199 @@
/*
* Copyright (C) 2013 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.sufficientlysecure.keychain.remote_api;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.remote_api.IServiceActivityCallback;
import org.sufficientlysecure.keychain.helper.PgpMain;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment;
import org.sufficientlysecure.keychain.util.Log;
import com.actionbarsherlock.app.SherlockFragmentActivity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
public class ServiceActivity extends SherlockFragmentActivity {
public static final String ACTION_REGISTER = "org.sufficientlysecure.keychain.REGISTER";
public static final String ACTION_CACHE_PASSPHRASE = "org.sufficientlysecure.keychain.CRYPTO_CACHE_PASSPHRASE";
public static final String EXTRA_SECRET_KEY_ID = "secretKeyId";
public static final String EXTRA_PACKAGE_NAME = "packageName";
private IServiceActivityCallback mService;
private boolean mServiceBound;
private ServiceConnection mServiceActivityConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName name, IBinder service) {
mService = IServiceActivityCallback.Stub.asInterface(service);
Log.d(Constants.TAG, "connected to ICryptoServiceActivity");
mServiceBound = true;
}
public void onServiceDisconnected(ComponentName name) {
mService = null;
Log.d(Constants.TAG, "disconnected from ICryptoServiceActivity");
mServiceBound = false;
}
};
/**
* If not already bound, bind!
*
* @return
*/
public boolean bindToService() {
if (mService == null && !mServiceBound) { // if not already connected
try {
Log.d(Constants.TAG, "not bound yet");
Intent serviceIntent = new Intent();
serviceIntent.setAction("org.openintents.crypto.ICryptoService");
bindService(serviceIntent, mServiceActivityConnection, Context.BIND_AUTO_CREATE);
return true;
} catch (Exception e) {
Log.d(Constants.TAG, "Exception", e);
return false;
}
} else { // already connected
Log.d(Constants.TAG, "already bound... ");
return true;
}
}
public void unbindFromService() {
unbindService(mServiceActivityConnection);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(Constants.TAG, "onCreate…");
// bind to our own crypto service
bindToService();
handleActions(getIntent());
}
@Override
protected void onDestroy() {
super.onDestroy();
// unbind from our crypto service
if (mServiceActivityConnection != null) {
unbindFromService();
}
}
protected void handleActions(Intent intent) {
String action = intent.getAction();
Bundle extras = intent.getExtras();
if (extras == null) {
extras = new Bundle();
}
/**
* com.android.crypto actions
*/
if (ACTION_REGISTER.equals(action)) {
final String packageName = extras.getString(EXTRA_PACKAGE_NAME);
setContentView(R.layout.register_crypto_consumer_activity);
Button allowButton = (Button) findViewById(R.id.register_crypto_consumer_allow);
Button disallowButton = (Button) findViewById(R.id.register_crypto_consumer_disallow);
allowButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
ProviderHelper.addCryptoConsumer(ServiceActivity.this, packageName);
// Intent data = new Intent();
setResult(RESULT_OK);
finish();
}
});
disallowButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
setResult(RESULT_CANCELED);
finish();
}
});
} else if (ACTION_CACHE_PASSPHRASE.equals(action)) {
long secretKeyId = extras.getLong(EXTRA_SECRET_KEY_ID);
showPassphraseDialog(secretKeyId);
} else {
Log.e(Constants.TAG, "Wrong action!");
setResult(RESULT_CANCELED);
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) {
setResult(RESULT_OK);
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 (PgpMain.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);
}
}
}