Extended api

This commit is contained in:
Dominik Schürmann
2013-09-15 15:20:15 +02:00
parent 1e188ee2fa
commit 312b735fbd
12 changed files with 553 additions and 248 deletions

View File

@@ -1,32 +0,0 @@
/*
* 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.sufficientlysecure.keychain.service;
import org.sufficientlysecure.keychain.service.handler.IKeychainGetKeyringsHandler;
/**
* All methods are oneway, which means they are asynchronous and non-blocking.
* Results are returned into given Handler, which has to be implemented on client side.
*/
interface IKeychainKeyService {
oneway void getPublicKeyRings(in long[] masterKeyIds, in boolean asAsciiArmoredStringArray,
in IKeychainGetKeyringsHandler handler);
oneway void getSecretKeyRings(in long[] masterKeyIds, in boolean asAsciiArmoredStringArray,
in IKeychainGetKeyringsHandler handler);
}

View File

@@ -1,139 +0,0 @@
/*
* 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.ArrayList;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.service.IKeychainKeyService;
import org.sufficientlysecure.keychain.service.handler.IKeychainGetKeyringsHandler;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
public class KeychainKeyService extends Service {
Context mContext;
@Override
public void onCreate() {
super.onCreate();
mContext = this;
Log.d(Constants.TAG, "ApgKeyService, onCreate()");
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d(Constants.TAG, "ApgKeyService, onDestroy()");
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
/**
* Synchronized implementation of getPublicKeyRings
*/
private synchronized void getPublicKeyRingsSafe(long[] masterKeyIds,
boolean asAsciiArmoredStringArray, IKeychainGetKeyringsHandler handler)
throws RemoteException {
if (asAsciiArmoredStringArray) {
ArrayList<String> output = ProviderHelper.getPublicKeyRingsAsArmoredString(mContext,
masterKeyIds);
handler.onSuccess(null, output);
} else {
byte[] outputBytes = ProviderHelper
.getPublicKeyRingsAsByteArray(mContext, masterKeyIds);
handler.onSuccess(outputBytes, null);
}
}
/**
* Synchronized implementation of getSecretKeyRings
*/
private synchronized void getSecretKeyRingsSafe(long[] masterKeyIds,
boolean asAsciiArmoredStringArray, IKeychainGetKeyringsHandler handler)
throws RemoteException {
if (asAsciiArmoredStringArray) {
ArrayList<String> output = ProviderHelper.getSecretKeyRingsAsArmoredString(mContext,
masterKeyIds);
handler.onSuccess(null, output);
} else {
byte[] outputBytes = ProviderHelper
.getSecretKeyRingsAsByteArray(mContext, masterKeyIds);
handler.onSuccess(outputBytes, null);
}
}
/**
* This is the implementation of the interface IApgKeyService. All methods are oneway, meaning
* asynchronous and return to the client using handlers.
*
* The real PGP code is located in PGPMain.
*/
private final IKeychainKeyService.Stub mBinder = new IKeychainKeyService.Stub() {
@Override
public void getPublicKeyRings(long[] masterKeyIds, boolean asAsciiArmoredStringArray,
IKeychainGetKeyringsHandler handler) throws RemoteException {
getPublicKeyRingsSafe(masterKeyIds, asAsciiArmoredStringArray, handler);
}
@Override
public void getSecretKeyRings(long[] masterKeyIds, boolean asAsciiArmoredStringArray,
IKeychainGetKeyringsHandler handler) throws RemoteException {
getSecretKeyRingsSafe(masterKeyIds, asAsciiArmoredStringArray, handler);
}
};
/**
* As we can not throw an exception through Android RPC, we assign identifiers to the exception
* types.
*
* @param e
* @return
*/
// private int getExceptionId(Exception e) {
// if (e instanceof NoSuchProviderException) {
// return 0;
// } else if (e instanceof NoSuchAlgorithmException) {
// return 1;
// } else if (e instanceof SignatureException) {
// return 2;
// } else if (e instanceof IOException) {
// return 3;
// } else if (e instanceof ApgGeneralException) {
// return 4;
// } else if (e instanceof PGPException) {
// return 5;
// } else {
// return -1;
// }
// }
}

View File

@@ -0,0 +1,125 @@
/*
* 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.helper.PgpHelper;
import org.sufficientlysecure.keychain.helper.PgpMain;
import org.sufficientlysecure.keychain.helper.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) throws RemoteException {
// TODO: for pgp keyrings with password
CallbackHandler pgpPwdCallbackHandler = new PgpToX509.PredefinedPasswordCallbackHandler("");
try {
long keyId = appSettings.getKeyId();
PGPSecretKey pgpSecretKey = PgpHelper.getSigningKey(this, keyId);
PasswordCallback pgpSecKeyPasswordCallBack = new PasswordCallback("pgp passphrase?",
false);
pgpPwdCallbackHandler.handle(new Callback[] { pgpSecKeyPasswordCallBack });
PGPPrivateKey pgpPrivKey = pgpSecretKey.extractPrivateKey(
pgpSecKeyPasswordCallBack.getPassword(), PgpMain.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);
callback.onError(e.getMessage());
}
// 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() {
try {
selfSignedX509CertSafe(subjAltNameURI, callback, settings);
} catch (RemoteException e) {
Log.e(Constants.TAG, "OpenPgpService", e);
}
}
};
checkAndEnqueue(r);
}
};
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2012 Dominik Schürmann <dominik@dominikschuermann.de>
* 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.
@@ -13,16 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sufficientlysecure.keychain.service.handler;
interface IKeychainGetKeyringsHandler {
/**
* Either outputBytes or outputString is given. One of them is null
*
*/
oneway void onSuccess(in byte[] outputBytes, in List<String> outputString);
package org.sufficientlysecure.keychain.service.remote;
interface IExtendedApiCallback {
oneway void onSuccess(in byte[] outputBytes);
oneway void onException(in int exceptionNumber, in String message);
oneway void onError(in String error);
}

View File

@@ -0,0 +1,48 @@
/*
* 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.service.remote;
import org.sufficientlysecure.keychain.service.remote.IExtendedApiCallback;
/**
* All methods are oneway, which means they are asynchronous and non-blocking.
* Results are returned to the callback, which has to be implemented on client side.
*/
interface IExtendedApiService {
/**
* Symmetric Encrypt
*
* @param inputBytes
* Byte array you want to encrypt
* @param passphrase
* symmetric passhprase
* @param callback
* Callback where to return results
*/
oneway void encrypt(in byte[] inputBytes, in String passphrase, in IExtendedApiCallback callback);
/**
* Generates self signed X509 certificate signed by OpenPGP private key (from app settings)
*
* @param subjAltNameURI
* @param callback
* Callback where to return results
*/
oneway void selfSignedX509Cert(in String subjAltNameURI, in IExtendedApiCallback callback);
}

View File

@@ -49,18 +49,18 @@ import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
public class OpenPgpService extends RemoteApiService {
public class OpenPgpService extends RemoteService {
@Override
public void onCreate() {
super.onCreate();
Log.d(Constants.TAG, "CryptoService, onCreate()");
Log.d(Constants.TAG, "OpenPgpService, onCreate()");
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d(Constants.TAG, "CryptoService, onDestroy()");
Log.d(Constants.TAG, "OpenPgpService, onDestroy()");
}
@Override
@@ -69,26 +69,26 @@ public class OpenPgpService extends RemoteApiService {
}
private String getCachedPassphrase(long keyId) {
String passphrase = PassphraseCacheService.getCachedPassphrase(mContext, keyId);
String passphrase = PassphraseCacheService.getCachedPassphrase(getContext(), keyId);
if (passphrase == null) {
Log.d(Constants.TAG, "No passphrase! Activity required!");
// start passphrase dialog
Bundle extras = new Bundle();
extras.putLong(OpenPgpServiceActivity.EXTRA_SECRET_KEY_ID, keyId);
extras.putLong(RemoteServiceActivity.EXTRA_SECRET_KEY_ID, keyId);
PassphraseActivityCallback callback = new PassphraseActivityCallback();
Messenger messenger = new Messenger(new Handler(getMainLooper(), callback));
pauseQueueAndStartServiceActivity(OpenPgpServiceActivity.ACTION_CACHE_PASSPHRASE,
pauseQueueAndStartServiceActivity(RemoteServiceActivity.ACTION_CACHE_PASSPHRASE,
messenger, extras);
if (callback.isSuccess()) {
Log.d(Constants.TAG, "New passphrase entered!");
// get again after it was entered
passphrase = PassphraseCacheService.getCachedPassphrase(mContext, keyId);
passphrase = PassphraseCacheService.getCachedPassphrase(getContext(), keyId);
} else {
Log.d(Constants.TAG, "Passphrase dialog canceled!");
@@ -165,12 +165,12 @@ public class OpenPgpService extends RemoteApiService {
Messenger messenger = new Messenger(new Handler(getMainLooper(), callback));
Bundle extras = new Bundle();
extras.putLongArray(OpenPgpServiceActivity.EXTRA_SELECTED_MASTER_KEY_IDS, keyIdsArray);
extras.putStringArrayList(OpenPgpServiceActivity.EXTRA_MISSING_USER_IDS, missingUserIds);
extras.putStringArrayList(OpenPgpServiceActivity.EXTRA_DUBLICATE_USER_IDS,
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);
pauseQueueAndStartServiceActivity(OpenPgpServiceActivity.ACTION_SELECT_PUB_KEYS,
pauseQueueAndStartServiceActivity(RemoteServiceActivity.ACTION_SELECT_PUB_KEYS,
messenger, extras);
if (callback.isSuccess()) {
@@ -238,12 +238,12 @@ public class OpenPgpService extends RemoteApiService {
return;
}
PgpMain.encryptAndSign(mContext, null, inputData, outputStream, asciiArmor,
PgpMain.encryptAndSign(getContext(), null, inputData, outputStream, asciiArmor,
appSettings.getCompression(), keyIds, null,
appSettings.getEncryptionAlgorithm(), appSettings.getKeyId(),
appSettings.getHashAlgorithm(), true, passphrase);
} else {
PgpMain.encryptAndSign(mContext, null, inputData, outputStream, asciiArmor,
PgpMain.encryptAndSign(getContext(), null, inputData, outputStream, asciiArmor,
appSettings.getCompression(), keyIds, null,
appSettings.getEncryptionAlgorithm(), Id.key.none,
appSettings.getHashAlgorithm(), true, null);
@@ -345,7 +345,7 @@ public class OpenPgpService extends RemoteApiService {
// TODO: This allows to decrypt messages with ALL secret keys, not only the one for the
// app, Fix this?
// long secretKeyId = PgpMain.getDecryptionKeyId(mContext, inputStream);
// long secretKeyId = PgpMain.getDecryptionKeyId(getContext(), inputStream);
// if (secretKeyId == Id.key.none) {
// throw new PgpMain.PgpGeneralException(getString(R.string.error_noSecretKeyFound));
// }
@@ -363,11 +363,10 @@ public class OpenPgpService extends RemoteApiService {
long secretKeyId;
try {
if (inputStream2.markSupported()) {
inputStream2.mark(200); // should probably set this to the max size of two
// pgpF
// objects, if it even needs to be anything other
// than
// 0.
// 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 = PgpMain.getDecryptionKeyId(this, inputStream2);
if (secretKeyId == Id.key.none) {
@@ -471,7 +470,7 @@ public class OpenPgpService extends RemoteApiService {
encryptAndSignSafe(inputBytes, encryptionUserIds, asciiArmor, callback,
settings, false);
} catch (RemoteException e) {
Log.e(Constants.TAG, "CryptoService", e);
Log.e(Constants.TAG, "OpenPgpService", e);
}
}
};
@@ -493,7 +492,7 @@ public class OpenPgpService extends RemoteApiService {
encryptAndSignSafe(inputBytes, encryptionUserIds, asciiArmor, callback,
settings, true);
} catch (RemoteException e) {
Log.e(Constants.TAG, "CryptoService", e);
Log.e(Constants.TAG, "OpenPgpService", e);
}
}
};
@@ -513,7 +512,7 @@ public class OpenPgpService extends RemoteApiService {
try {
signSafe(inputBytes, callback, settings);
} catch (RemoteException e) {
Log.e(Constants.TAG, "CryptoService", e);
Log.e(Constants.TAG, "OpenPgpService", e);
}
}
};
@@ -535,7 +534,7 @@ public class OpenPgpService extends RemoteApiService {
try {
decryptAndVerifySafe(inputBytes, callback, settings);
} catch (RemoteException e) {
Log.e(Constants.TAG, "CryptoService", e);
Log.e(Constants.TAG, "OpenPgpService", e);
}
}
};

View File

@@ -40,12 +40,12 @@ import android.os.Messenger;
/**
* Abstract service for remote APIs that handle app registration and user input.
*/
public abstract class RemoteApiService extends Service {
public abstract class RemoteService extends Service {
Context mContext;
final ArrayBlockingQueue<Runnable> mPoolQueue = new ArrayBlockingQueue<Runnable>(100);
private final ArrayBlockingQueue<Runnable> mPoolQueue = new ArrayBlockingQueue<Runnable>(100);
// TODO: Are these parameters okay?
PausableThreadPoolExecutor mThreadPool = new PausableThreadPoolExecutor(2, 4, 10,
private PausableThreadPoolExecutor mThreadPool = new PausableThreadPoolExecutor(2, 4, 10,
TimeUnit.SECONDS, mPoolQueue);
private final Object userInputLock = new Object();
@@ -87,6 +87,10 @@ public abstract class RemoteApiService extends Service {
}
public Context getContext() {
return mContext;
}
/**
* Should be used from Stub implementations of AIDL interfaces to enqueue a runnable for
* execution
@@ -105,12 +109,12 @@ public abstract class RemoteApiService extends Service {
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(OpenPgpServiceActivity.EXTRA_PACKAGE_NAME, callingPackages[0]);
extras.putString(RemoteServiceActivity.EXTRA_PACKAGE_NAME, callingPackages[0]);
RegisterActivityCallback callback = new RegisterActivityCallback();
Messenger messenger = new Messenger(new Handler(getMainLooper(), callback));
pauseQueueAndStartServiceActivity(OpenPgpServiceActivity.ACTION_REGISTER, messenger,
pauseQueueAndStartServiceActivity(RemoteServiceActivity.ACTION_REGISTER, messenger,
extras);
if (callback.isAllowed()) {
@@ -135,11 +139,11 @@ public abstract class RemoteApiService extends Service {
mThreadPool.pause();
Log.d(Constants.TAG, "starting activity...");
Intent intent = new Intent(getBaseContext(), OpenPgpServiceActivity.class);
Intent intent = new Intent(getBaseContext(), RemoteServiceActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setAction(action);
extras.putParcelable(OpenPgpServiceActivity.EXTRA_MESSENGER, messenger);
extras.putParcelable(RemoteServiceActivity.EXTRA_MESSENGER, messenger);
intent.putExtras(extras);
startActivity(intent);

View File

@@ -41,7 +41,7 @@ import android.widget.Toast;
import com.actionbarsherlock.app.SherlockFragmentActivity;
public class OpenPgpServiceActivity extends 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
@@ -84,7 +84,7 @@ public class OpenPgpServiceActivity extends SherlockFragmentActivity {
if (!finishHandled) {
Message msg = Message.obtain();
msg.arg1 = OpenPgpService.RegisterActivityCallback.CANCEL;
msg.arg1 = RemoteService.RegisterActivityCallback.CANCEL;
try {
mMessenger.send(msg);
} catch (RemoteException e) {
@@ -120,17 +120,17 @@ public class OpenPgpServiceActivity extends SherlockFragmentActivity {
// user needs to select a key!
if (mSettingsFragment.getAppSettings().getKeyId() == Id.key.none) {
Toast.makeText(OpenPgpServiceActivity.this,
Toast.makeText(RemoteServiceActivity.this,
R.string.api_register_error_select_key, Toast.LENGTH_LONG)
.show();
} else {
ProviderHelper.insertApiApp(OpenPgpServiceActivity.this,
ProviderHelper.insertApiApp(RemoteServiceActivity.this,
mSettingsFragment.getAppSettings());
Message msg = Message.obtain();
msg.arg1 = OpenPgpService.RegisterActivityCallback.OKAY;
msg.arg1 = RemoteService.RegisterActivityCallback.OKAY;
Bundle data = new Bundle();
data.putString(OpenPgpService.RegisterActivityCallback.PACKAGE_NAME,
data.putString(RemoteService.RegisterActivityCallback.PACKAGE_NAME,
packageName);
msg.setData(data);
try {
@@ -149,7 +149,7 @@ public class OpenPgpServiceActivity extends SherlockFragmentActivity {
// Disallow
Message msg = Message.obtain();
msg.arg1 = OpenPgpService.RegisterActivityCallback.CANCEL;
msg.arg1 = RemoteService.RegisterActivityCallback.CANCEL;
try {
mMessenger.send(msg);
} catch (RemoteException e) {