aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMarkus Doits <markus.doits@googlemail.com>2011-11-04 21:22:49 +0100
committerMarkus Doits <markus.doits@googlemail.com>2011-11-04 21:22:49 +0100
commitad1657465778c05cef7a7ba9d25f97943cf68144 (patch)
tree3fadab61a099b49f8a9ac49f3d3abe5240f8996e
parenta7294d50b1f6a7f0ab30118602a1fcbdad0b8169 (diff)
downloadopen-keychain-ad1657465778c05cef7a7ba9d25f97943cf68144.tar.gz
open-keychain-ad1657465778c05cef7a7ba9d25f97943cf68144.tar.bz2
open-keychain-ad1657465778c05cef7a7ba9d25f97943cf68144.zip
Allow to pass large blobs and a new content provider to simplify this
Since AIDL is not for passing large data, a blob can be passed to APG by a Uri. This Uri is opened as a file by APG and read/written to. Note the file is overwritten by APG, so make sure it is a copy if you want to keep the original. With the ApgServiceBlobProvider, Apg has an own ContentProvider that can be used like mentioned above. For now the data is stored in the dir where APG stores other files and NOT DELETED after en/decryption. This is tbd. It can only be accessed by an application with the permission "org.thialfihar.android.apg.permission.STORE_BLOBS". ApgCon has been updated accordingly and can handle blobs with `setBlob` and `getBlobResult`. That is a really easy way to en/decrypt large data. Note that encrypting by blob should only be used for large files (1MB+). On all other cases, the data should be passed as as String through the AIDl-Interface, so no temporary file must be created. See ApgCon for a complete example of how to connect to the AIDL and use it. Or use it in your own project!
-rw-r--r--AndroidManifest.xml13
-rw-r--r--res/values/strings.xml2
-rw-r--r--src/org/thialfihar/android/apg/ApgService.java118
-rw-r--r--src/org/thialfihar/android/apg/Constants.java3
-rw-r--r--src/org/thialfihar/android/apg/DecryptActivity.java13
-rw-r--r--src/org/thialfihar/android/apg/IApgService.aidl18
-rw-r--r--src/org/thialfihar/android/apg/Id.java3
-rw-r--r--src/org/thialfihar/android/apg/provider/ApgServiceBlobDatabase.java54
-rw-r--r--src/org/thialfihar/android/apg/provider/ApgServiceBlobProvider.java138
-rw-r--r--src/org/thialfihar/android/apg/utils/ApgCon.java97
10 files changed, 423 insertions, 36 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 937eb488a..e72197138 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -204,12 +204,17 @@
<intent-filter>
<action android:name="org.thialfihar.android.apg.IApgService"/>
</intent-filter>
- <meta-data android:name="api_version" android:value="1" />
+ <meta-data android:name="api_version" android:value="2" />
</service>
<provider
android:readPermission="org.thialfihar.android.apg.permission.READ_KEY_DETAILS"
android:name="org.thialfihar.android.apg.provider.DataProvider"
android:authorities="org.thialfihar.android.apg.provider"/>
+
+ <provider
+ android:permission="org.thialfihar.android.apg.permission.STORE_BLOBS"
+ android:name="org.thialfihar.android.apg.provider.ApgServiceBlobProvider"
+ android:authorities="org.thialfihar.android.apg.provider.apgserviceblobprovider"/>
</application>
<uses-sdk android:minSdkVersion="3" android:targetSdkVersion="5" />
@@ -219,10 +224,16 @@
android:label="@string/permission_read_key_details_label"
android:description="@string/permission_read_key_details_description"/>
+ <permission android:name="org.thialfihar.android.apg.permission.STORE_BLOBS"
+ android:protectionLevel="dangerous"
+ android:label="@string/permission_store_blobs_label"
+ android:description="@string/permission_store_blobs_description"/>
+
<uses-permission android:name="com.google.android.providers.gmail.permission.READ_GMAIL" />
<uses-permission android:name="com.google.android.gm.permission.READ_GMAIL" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="com.fsck.k9.permission.READ_ATTACHMENT" />
<uses-permission android:name="android.permission.INTERNET"/>
+ <uses-permission android:name="org.thialfihar.android.apg.permission.STORE_BLOBS"/>
</manifest>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 5b29e4f12..62e0935a9 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -288,6 +288,8 @@
<!-- permission strings -->
<string name="permission_read_key_details_label">Read key details from APG.</string>
<string name="permission_read_key_details_description">Read key details of public and secret keys stored in APG, such as key ID and user IDs. The keys themselves can NOT be read.</string>
+ <string name="permission_store_blobs_label">Store blobs to en/decrypt with APG.</string>
+ <string name="permission_store_blobs_description">Store and read files on the android file system through APG. It cannot read files of other applications.</string>
<!-- action strings -->
<string name="action_encrypt">Encrypt</string>
diff --git a/src/org/thialfihar/android/apg/ApgService.java b/src/org/thialfihar/android/apg/ApgService.java
index 0a25c6055..46d8b4765 100644
--- a/src/org/thialfihar/android/apg/ApgService.java
+++ b/src/org/thialfihar/android/apg/ApgService.java
@@ -2,6 +2,7 @@ package org.thialfihar.android.apg;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
+import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Method;
@@ -14,17 +15,19 @@ import org.thialfihar.android.apg.provider.KeyRings;
import org.thialfihar.android.apg.provider.Keys;
import org.thialfihar.android.apg.provider.UserIds;
+import android.content.ContentResolver;
import android.content.Intent;
import android.database.Cursor;
import android.database.sqlite.SQLiteQueryBuilder;
+import android.net.Uri;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;
public class ApgService extends Service {
private final static String TAG = "ApgService";
- private static final boolean LOCAL_LOGV = true;
- private static final boolean LOCAL_LOGD = true;
+ public static final boolean LOCAL_LOGV = true;
+ public static final boolean LOCAL_LOGD = true;
@Override
public IBinder onBind(Intent intent) {
@@ -33,7 +36,7 @@ public class ApgService extends Service {
}
/** error status */
- private enum error {
+ private static enum error {
ARGUMENTS_MISSING,
APG_FAILURE,
NO_MATCHING_SECRET_KEY,
@@ -44,9 +47,16 @@ public class ApgService extends Service {
return ordinal() + 100;
}
}
+
+ private static enum call {
+ encrypt_with_passphrase,
+ encrypt_with_public_key,
+ decrypt,
+ get_keys
+ }
/** all arguments that can be passed by calling application */
- private enum arg {
+ public static enum arg {
MESSAGE, // message to encrypt or to decrypt
SYMMETRIC_PASSPHRASE, // key for symmetric en/decryption
PUBLIC_KEYS, // public keys for encryption
@@ -58,10 +68,11 @@ public class ApgService extends Service {
SIGNATURE_KEY, // key for signing
PRIVATE_KEY_PASSPHRASE, // passphrase for encrypted private key
KEY_TYPE, // type of key (private or public)
+ BLOB, // blob passed
}
/** all things that might be returned */
- private enum ret {
+ private static enum ret {
ERRORS, // string array list with errors
WARNINGS, // string array list with warnings
ERROR, // numeric error
@@ -75,21 +86,18 @@ public class ApgService extends Service {
static {
HashSet<arg> args = new HashSet<arg>();
args.add(arg.SYMMETRIC_PASSPHRASE);
- args.add(arg.MESSAGE);
- FUNCTIONS_REQUIRED_ARGS.put("encrypt_with_passphrase", args);
+ FUNCTIONS_REQUIRED_ARGS.put(call.encrypt_with_passphrase.name(), args);
args = new HashSet<arg>();
args.add(arg.PUBLIC_KEYS);
- args.add(arg.MESSAGE);
- FUNCTIONS_REQUIRED_ARGS.put("encrypt_with_public_key", args);
+ FUNCTIONS_REQUIRED_ARGS.put(call.encrypt_with_public_key.name(), args);
args = new HashSet<arg>();
- args.add(arg.MESSAGE);
- FUNCTIONS_REQUIRED_ARGS.put("decrypt", args);
+ FUNCTIONS_REQUIRED_ARGS.put(call.decrypt.name(), args);
args = new HashSet<arg>();
args.add(arg.KEY_TYPE);
- FUNCTIONS_REQUIRED_ARGS.put("get_keys", args);
+ FUNCTIONS_REQUIRED_ARGS.put(call.get_keys.name(), args);
}
/** optional arguments for each AIDL function */
@@ -103,14 +111,18 @@ public class ApgService extends Service {
args.add(arg.COMPRESSION);
args.add(arg.PRIVATE_KEY_PASSPHRASE);
args.add(arg.SIGNATURE_KEY);
- FUNCTIONS_OPTIONAL_ARGS.put("encrypt_with_passphrase", args);
- FUNCTIONS_OPTIONAL_ARGS.put("encrypt_with_public_key", args);
+ args.add(arg.BLOB);
+ args.add(arg.MESSAGE);
+ FUNCTIONS_OPTIONAL_ARGS.put(call.encrypt_with_passphrase.name(), args);
+ FUNCTIONS_OPTIONAL_ARGS.put(call.encrypt_with_public_key.name(), args);
args = new HashSet<arg>();
args.add(arg.SYMMETRIC_PASSPHRASE);
args.add(arg.PUBLIC_KEYS);
args.add(arg.PRIVATE_KEY_PASSPHRASE);
- FUNCTIONS_OPTIONAL_ARGS.put("decrypt", args);
+ args.add(arg.MESSAGE);
+ args.add(arg.BLOB);
+ FUNCTIONS_OPTIONAL_ARGS.put(call.decrypt.name(), args);
}
/** a map from ApgService parameters to function calls to get the default */
@@ -136,6 +148,14 @@ public class ApgService extends Service {
Log.e(TAG, "Function method exception: " + e.getMessage());
}
}
+
+ private static void writeToOutputStream(InputStream is, OutputStream os) throws IOException {
+ byte[] buffer = new byte[8];
+ int len = 0;
+ while( (len = is.read(buffer)) != -1) {
+ os.write(buffer, 0, len);
+ }
+ }
private static Cursor getKeyEntries(HashMap<String, Object> pParams) {
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
@@ -304,6 +324,16 @@ public class ApgService extends Service {
}
}
}
+
+ if(pFunction.equals(call.encrypt_with_passphrase.name()) ||
+ pFunction.equals(call.encrypt_with_public_key.name()) ||
+ pFunction.equals(call.decrypt.name())) {
+ // check that either MESSAGE or BLOB are there
+ if( !pArgs.containsKey(arg.MESSAGE.name()) && !pArgs.containsKey(arg.BLOB.name())) {
+ pReturn.getStringArrayList(ret.ERRORS.name()).add("Arguments missing: Neither MESSAGE nor BLOG found");
+ }
+
+ }
}
/**
@@ -378,6 +408,7 @@ public class ApgService extends Service {
}
private boolean encrypt(Bundle pArgs, Bundle pReturn) {
+ boolean isBlob = pArgs.containsKey(arg.BLOB.name());
long pubMasterKeys[] = {};
if (pArgs.containsKey(arg.PUBLIC_KEYS.name())) {
@@ -391,7 +422,17 @@ public class ApgService extends Service {
pubMasterKeys = getMasterKey(pubKeys, pReturn);
}
- InputStream inStream = new ByteArrayInputStream(pArgs.getString(arg.MESSAGE.name()).getBytes());
+ InputStream inStream = null;
+ if(isBlob) {
+ ContentResolver cr = getContentResolver();
+ try {
+ inStream = cr.openInputStream(Uri.parse(pArgs.getString(arg.BLOB.name())));
+ } catch (Exception e) {
+ Log.e(TAG, "... exception on opening blob", e);
+ }
+ } else {
+ inStream = new ByteArrayInputStream(pArgs.getString(arg.MESSAGE.name()).getBytes());
+ }
InputData in = new InputData(inStream, 0); // XXX Size second param?
OutputStream out = new ByteArrayOutputStream();
@@ -427,7 +468,18 @@ public class ApgService extends Service {
return false;
}
if( LOCAL_LOGV ) Log.v(TAG, "Encrypted");
- pReturn.putString(ret.RESULT.name(), out.toString());
+ if(isBlob) {
+ ContentResolver cr = getContentResolver();
+ try {
+ OutputStream outStream = cr.openOutputStream(Uri.parse(pArgs.getString(arg.BLOB.name())));
+ writeToOutputStream(new ByteArrayInputStream(out.toString().getBytes()), outStream);
+ outStream.close();
+ } catch (Exception e) {
+ Log.e(TAG, "... exception on writing blob", e);
+ }
+ } else {
+ pReturn.putString(ret.RESULT.name(), out.toString());
+ }
return true;
}
@@ -481,11 +533,24 @@ public class ApgService extends Service {
if (!prepareArgs("decrypt", pArgs, pReturn)) {
return false;
}
+
+ boolean isBlob = pArgs.containsKey(arg.BLOB.name());
String passphrase = pArgs.getString(arg.SYMMETRIC_PASSPHRASE.name()) != null ? pArgs.getString(arg.SYMMETRIC_PASSPHRASE.name()) : pArgs
.getString(arg.PRIVATE_KEY_PASSPHRASE.name());
- InputStream inStream = new ByteArrayInputStream(pArgs.getString(arg.MESSAGE.name()).getBytes());
+ InputStream inStream = null;
+ if(isBlob) {
+ ContentResolver cr = getContentResolver();
+ try {
+ inStream = cr.openInputStream(Uri.parse(pArgs.getString(arg.BLOB.name())));
+ } catch (Exception e) {
+ Log.e(TAG, "... exception on opening blob", e);
+ }
+ } else {
+ inStream = new ByteArrayInputStream(pArgs.getString(arg.MESSAGE.name()).getBytes());
+ }
+
InputData in = new InputData(inStream, 0); // XXX what size in second parameter?
OutputStream out = new ByteArrayOutputStream();
if( LOCAL_LOGV ) Log.v(TAG, "About to decrypt");
@@ -508,9 +573,20 @@ public class ApgService extends Service {
}
return false;
}
- if( LOCAL_LOGV ) Log.v(TAG, "Decrypted");
-
- pReturn.putString(ret.RESULT.name(), out.toString());
+ if( LOCAL_LOGV ) Log.v(TAG, "... decrypted");
+
+ if(isBlob) {
+ ContentResolver cr = getContentResolver();
+ try {
+ OutputStream outStream = cr.openOutputStream(Uri.parse(pArgs.getString(arg.BLOB.name())));
+ writeToOutputStream(new ByteArrayInputStream(out.toString().getBytes()), outStream);
+ outStream.close();
+ } catch (Exception e) {
+ Log.e(TAG, "... exception on writing blob", e);
+ }
+ } else {
+ pReturn.putString(ret.RESULT.name(), out.toString());
+ }
return true;
}
diff --git a/src/org/thialfihar/android/apg/Constants.java b/src/org/thialfihar/android/apg/Constants.java
index 89751e268..bd0746085 100644
--- a/src/org/thialfihar/android/apg/Constants.java
+++ b/src/org/thialfihar/android/apg/Constants.java
@@ -19,6 +19,9 @@ package org.thialfihar.android.apg;
import android.os.Environment;
public final class Constants {
+
+ public static final String tag = "APG";
+
public static final class path {
public static final String app_dir = Environment.getExternalStorageDirectory() + "/APG";
}
diff --git a/src/org/thialfihar/android/apg/DecryptActivity.java b/src/org/thialfihar/android/apg/DecryptActivity.java
index dfa4cb69b..a10a168de 100644
--- a/src/org/thialfihar/android/apg/DecryptActivity.java
+++ b/src/org/thialfihar/android/apg/DecryptActivity.java
@@ -30,6 +30,7 @@ import android.net.Uri;
import android.os.Bundle;
import android.os.Message;
import android.text.ClipboardManager;
+import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.animation.AnimationUtils;
@@ -189,19 +190,28 @@ public class DecryptActivity extends BaseActivity {
// ignore, then
}
} else if (Apg.Intent.DECRYPT.equals(mIntent.getAction())) {
+ Log.d(Constants.tag, "Apg Intent DECRYPT startet");
Bundle extras = mIntent.getExtras();
if (extras == null) {
+ Log.d(Constants.tag, "extra bundle was null");
extras = new Bundle();
+ } else {
+ Log.d(Constants.tag, "got extras");
}
mData = extras.getByteArray(Apg.EXTRA_DATA);
String textData = null;
if (mData == null) {
+ Log.d(Constants.tag, "EXTRA_DATA was null");
textData = extras.getString(Apg.EXTRA_TEXT);
+ } else {
+ Log.d(Constants.tag, "Got data from EXTRA_DATA");
}
if (textData != null) {
+ Log.d(Constants.tag, "textData null, matching text ...");
Matcher matcher = Apg.PGP_MESSAGE.matcher(textData);
if (matcher.matches()) {
+ Log.d(Constants.tag, "PGP_MESSAGE matched");
textData = matcher.group(1);
// replace non breakable spaces
textData = textData.replaceAll("\\xa0", " ");
@@ -209,11 +219,14 @@ public class DecryptActivity extends BaseActivity {
} else {
matcher = Apg.PGP_SIGNED_MESSAGE.matcher(textData);
if (matcher.matches()) {
+ Log.d(Constants.tag, "PGP_SIGNED_MESSAGE matched");
textData = matcher.group(1);
// replace non breakable spaces
textData = textData.replaceAll("\\xa0", " ");
mMessage.setText(textData);
mDecryptButton.setText(R.string.btn_verify);
+ } else {
+ Log.d(Constants.tag, "Nothing matched!");
}
}
}
diff --git a/src/org/thialfihar/android/apg/IApgService.aidl b/src/org/thialfihar/android/apg/IApgService.aidl
index df46805ac..25780f366 100644
--- a/src/org/thialfihar/android/apg/IApgService.aidl
+++ b/src/org/thialfihar/android/apg/IApgService.aidl
@@ -27,6 +27,11 @@ interface IApgService {
*
* (required)
* String "MESSAGE" = Message to encrypt
+ * OR
+ * String "BLOB" = ContentUri to a file handle
+ * with binary data to encrypt
+ * (Attention: file will be overwritten
+ * with encrypted content!)
*
* (optional)
* int "ENCRYPTION_ALGORYTHM" = Encryption Algorithm
@@ -55,7 +60,8 @@ interface IApgService {
* String "PRIVATE_KEY_PASSPHRASE" = Passphrase for signing key
*
* Bundle returnVals (in addition to the ERRORS/WARNINGS above):
- * String "RESULT" = Encrypted message
+ * If "MESSAGE" was set:
+ * String "RESULT" = Encrypted message
*/
/* Additional argument for function below:
@@ -77,7 +83,12 @@ interface IApgService {
/* Bundle params:
* (required)
- * String "MESSAGE" = Message to decrypt
+ * String "MESSAGE" = Message to dencrypt
+ * OR
+ * String "BLOB" = ContentUri to a file handle
+ * with binary data to dencrypt
+ * (Attention: file will be overwritten
+ * with dencrypted content!)
*
* (optional)
* String "SYMMETRIC_PASSPHRASE" = Symmetric passphrase for decryption
@@ -86,7 +97,8 @@ interface IApgService {
* String "PRIVATE_KEY_PASSPHRASE" = Private keys's passphrase on asymmetric encryption
*
* Bundle return_vals:
- * String "RESULT" = Decrypted message
+ * If "MESSAGE" was set:
+ * String "RESULT" = Decrypted message
*/
boolean decrypt(in Bundle params, out Bundle returnVals);
diff --git a/src/org/thialfihar/android/apg/Id.java b/src/org/thialfihar/android/apg/Id.java
index 1ce53eabd..2b486e84a 100644
--- a/src/org/thialfihar/android/apg/Id.java
+++ b/src/org/thialfihar/android/apg/Id.java
@@ -19,6 +19,9 @@ package org.thialfihar.android.apg;
import org.spongycastle.bcpg.CompressionAlgorithmTags;
public final class Id {
+
+ public static final String TAG = "APG";
+
public static final class menu {
public static final int export = 0x21070001;
public static final int delete = 0x21070002;
diff --git a/src/org/thialfihar/android/apg/provider/ApgServiceBlobDatabase.java b/src/org/thialfihar/android/apg/provider/ApgServiceBlobDatabase.java
new file mode 100644
index 000000000..9a891ddfa
--- /dev/null
+++ b/src/org/thialfihar/android/apg/provider/ApgServiceBlobDatabase.java
@@ -0,0 +1,54 @@
+package org.thialfihar.android.apg.provider;
+
+import org.thialfihar.android.apg.ApgService;
+
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.net.Uri;
+import android.util.Log;
+
+public class ApgServiceBlobDatabase extends SQLiteOpenHelper {
+
+ private static final String TAG = "ApgServiceBlobDatabase";
+
+ private static final int VERSION = 1;
+ private static final String NAME = "apg_service_blob_data";
+ private static final String TABLE = "data";
+
+ public ApgServiceBlobDatabase(Context context) {
+ super(context, NAME, null, VERSION);
+ if(ApgService.LOCAL_LOGD) Log.d(TAG, "constructor called");
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ if(ApgService.LOCAL_LOGD) Log.d(TAG, "onCreate() called");
+ db.execSQL("create table " + TABLE + " ( _id integer primary key autoincrement," +
+ "key text not null)");
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ if(ApgService.LOCAL_LOGD) Log.d(TAG, "onUpgrade() called");
+ // no upgrade necessary yet
+ }
+
+ public Uri insert(ContentValues vals) {
+ if(ApgService.LOCAL_LOGD) Log.d(TAG, "insert() called");
+ SQLiteDatabase db = this.getWritableDatabase();
+ long newId = db.insert(TABLE, null, vals);
+ return ContentUris.withAppendedId(ApgServiceBlobProvider.CONTENT_URI, newId);
+ }
+
+ public Cursor query(String id, String key) {
+ if(ApgService.LOCAL_LOGD) Log.d(TAG, "query() called");
+ SQLiteDatabase db = this.getReadableDatabase();
+ return db.query(TABLE, new String[] {"_id"},
+ "_id = ? and key = ?", new String[] {id, key},
+ null, null, null);
+ }
+}
diff --git a/src/org/thialfihar/android/apg/provider/ApgServiceBlobProvider.java b/src/org/thialfihar/android/apg/provider/ApgServiceBlobProvider.java
new file mode 100644
index 000000000..fd4145f4c
--- /dev/null
+++ b/src/org/thialfihar/android/apg/provider/ApgServiceBlobProvider.java
@@ -0,0 +1,138 @@
+package org.thialfihar.android.apg.provider;
+
+import org.thialfihar.android.apg.ApgService;
+import org.thialfihar.android.apg.Constants;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.List;
+import java.util.UUID;
+
+public class ApgServiceBlobProvider extends ContentProvider {
+
+ private static final String TAG = "ApgServiceBlobProvider";
+
+ public static final Uri CONTENT_URI = Uri.parse("content://org.thialfihar.android.apg.provider.apgserviceblobprovider");
+
+ private static final String COLUMN_KEY = "key";
+
+ private static final String STORE_PATH = Constants.path.app_dir+"/ApgServiceBlobs";
+
+ private ApgServiceBlobDatabase mDb = null;
+
+ public ApgServiceBlobProvider() {
+ if(ApgService.LOCAL_LOGD) Log.d(TAG, "Constructor called");
+ File dir = new File(STORE_PATH);
+ dir.mkdirs();
+ if(ApgService.LOCAL_LOGD) Log.d(TAG, "Constructor finished");
+ }
+
+ @Override
+ public int delete(Uri arg0, String arg1, String[] arg2) {
+ if(ApgService.LOCAL_LOGD) Log.d(TAG, "delete() called");
+ // TODO Auto-generated method stub
+ return 0;
+ }
+
+ @Override
+ public String getType(Uri arg0) {
+ if(ApgService.LOCAL_LOGD) Log.d(TAG, "getType() called");
+ // not needed for now
+ return null;
+ }
+
+ @Override
+ public Uri insert(Uri uri, ContentValues ignored) {
+ if(ApgService.LOCAL_LOGD) Log.d(TAG, "insert() called");
+ // ContentValues are actually ignored, because we want to store a blob with no more information
+ // but have to create an record with the password generated here first
+
+ ContentValues vals = new ContentValues();
+
+ // Insert a random key in the database. This has to provided by the caller when updating or
+ // getting the blob
+ String password = UUID.randomUUID().toString();
+ vals.put(COLUMN_KEY, password);
+
+ Uri insertedUri = mDb.insert(vals);
+ return Uri.withAppendedPath(insertedUri, password);
+ }
+
+ @Override
+ public boolean onCreate() {
+ if(ApgService.LOCAL_LOGD) Log.d(TAG, "onCreate() called");
+ mDb = new ApgServiceBlobDatabase(getContext());
+ // TODO Auto-generated method stub
+ return true;
+ }
+
+ @Override
+ public Cursor query(Uri arg0, String[] arg1, String arg2, String[] arg3, String arg4) {
+ if(ApgService.LOCAL_LOGD) Log.d(TAG, "query() called");
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public int update(Uri arg0, ContentValues arg1, String arg2, String[] arg3) {
+ if(ApgService.LOCAL_LOGD) Log.d(TAG, "update() called");
+ // TODO Auto-generated method stub
+ return 0;
+ }
+
+ @Override
+ public ParcelFileDescriptor openFile(Uri uri, String mode) throws SecurityException, FileNotFoundException {
+ if(ApgService.LOCAL_LOGD) Log.d(TAG, "openFile() called");
+ if(ApgService.LOCAL_LOGD) Log.d(TAG, "... with uri: "+uri.toString());
+ if(ApgService.LOCAL_LOGD) Log.d(TAG, "... with mode: "+mode);
+
+ List<String> segments = uri.getPathSegments();
+ if(segments.size() < 2) {
+ throw new SecurityException("Password not found in URI");
+ }
+ String id = segments.get(0);
+ String key = segments.get(1);
+
+ if(ApgService.LOCAL_LOGD) Log.d(TAG, "... got id: "+id);
+ if(ApgService.LOCAL_LOGD) Log.d(TAG, "... and key: "+key);
+
+ // get the data
+ Cursor result = mDb.query(id, key);
+
+ if(result.getCount() == 0) {
+ // either the key is wrong or no id exists
+ throw new FileNotFoundException("No file found with that ID and/or password");
+ }
+
+ File targetFile = new File(STORE_PATH, id);
+ if(mode.equals("w")) {
+ if(ApgService.LOCAL_LOGD) Log.d(TAG, "... will try to open file w");
+ if( !targetFile.exists() ) {
+ try {
+ targetFile.createNewFile();
+ } catch (IOException e) {
+ Log.e(TAG, "... got IEOException on creating new file", e);
+ throw new FileNotFoundException("Could not create file to write to");
+ }
+ }
+ return ParcelFileDescriptor.open(targetFile, ParcelFileDescriptor.MODE_WRITE_ONLY | ParcelFileDescriptor.MODE_TRUNCATE );
+ } else if(mode.equals("r")) {
+ if(ApgService.LOCAL_LOGD) Log.d(TAG, "... will try to open file r");
+ if( !targetFile.exists() ) {
+ throw new FileNotFoundException("Error: Could not find the file requested");
+ }
+ return ParcelFileDescriptor.open(targetFile, ParcelFileDescriptor.MODE_READ_ONLY);
+ }
+
+ return null;
+ }
+
+}
diff --git a/src/org/thialfihar/android/apg/utils/ApgCon.java b/src/org/thialfihar/android/apg/utils/ApgCon.java
index 475f7e9a9..c46ccf6b1 100644
--- a/src/org/thialfihar/android/apg/utils/ApgCon.java
+++ b/src/org/thialfihar/android/apg/utils/ApgCon.java
@@ -16,22 +16,27 @@
package org.thialfihar.android.apg.utils;
-import java.lang.reflect.InvocationTargetException;
-import java.util.ArrayList;
+import org.thialfihar.android.apg.IApgService;
+import org.thialfihar.android.apg.utils.ApgConInterface.OnCallFinishListener;
-import android.content.Context;
import android.content.ComponentName;
-import android.content.ServiceConnection;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
import android.content.Intent;
+import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
+import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;
-import org.thialfihar.android.apg.IApgService;
-import org.thialfihar.android.apg.utils.ApgConInterface.OnCallFinishListener;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
/**
* A APG-AIDL-Wrapper
@@ -46,7 +51,7 @@ import org.thialfihar.android.apg.utils.ApgConInterface.OnCallFinishListener;
* </p>
*
* @author Markus Doits <markus.doits@googlemail.com>
- * @version 1.0rc1
+ * @version 1.1rc1
*
*/
public class ApgCon {
@@ -54,7 +59,8 @@ public class ApgCon {
private static final boolean LOCAL_LOGD = true;
private final static String TAG = "ApgCon";
- private final static int API_VERSION = 1; // aidl api-version it expects
+ private final static int API_VERSION = 2; // aidl api-version it expects
+ private final static String BLOB_URI = "content://org.thialfihar.android.apg.provider.apgserviceblobprovider";
/**
* How many seconds to wait for a connection to AGP when connecting.
@@ -80,7 +86,6 @@ public class ApgCon {
}
-
private final Context mContext;
private final error mConnectionStatus;
private boolean mAsyncRunning = false;
@@ -187,9 +192,9 @@ public class ApgCon {
mWarningList.add("(LOCAL) Could not determine ApgService API");
tmpError = error.APG_API_MISSMATCH;
} else if (inf.metaData.getInt("api_version") != API_VERSION) {
- Log.w(TAG, "Found ApgService API version" + inf.metaData.getInt("api_version") + " but exspected " + API_VERSION);
+ Log.w(TAG, "Found ApgService API version " + inf.metaData.getInt("api_version") + " but exspected " + API_VERSION);
Log.w(TAG, "This probably won't work!");
- mWarningList.add("(LOCAL) Found ApgService API version" + inf.metaData.getInt("api_version") + " but exspected " + API_VERSION);
+ mWarningList.add("(LOCAL) Found ApgService API version " + inf.metaData.getInt("api_version") + " but exspected " + API_VERSION);
tmpError = error.APG_API_MISSMATCH;
} else {
if( LOCAL_LOGV ) Log.v(TAG, "Found api_version " + API_VERSION + ", everything should work");
@@ -495,6 +500,41 @@ public class ApgCon {
}
mArgs.putIntegerArrayList(key, list);
}
+
+ /**
+ * Set up binary data to en/decrypt
+ *
+ * @param is
+ * InputStream to get the data from
+ */
+ public void setBlob(InputStream is) {
+ if( LOCAL_LOGD ) Log.d(TAG, "setBlob() called");
+ // 1. get the new contentUri
+ ContentResolver cr = mContext.getContentResolver();
+ Uri contentUri = cr.insert(Uri.parse(BLOB_URI), new ContentValues());
+
+ // 2. insert binary data
+ OutputStream os = null;
+ try {
+ os = cr.openOutputStream(contentUri, "w");
+ } catch( Exception e ) {
+ Log.e(TAG, "... exception on setBlob", e);
+ }
+
+ byte[] buffer = new byte[8];
+ int len = 0;
+ try {
+ while( (len = is.read(buffer)) != -1) {
+ os.write(buffer, 0, len);
+ }
+ if(LOCAL_LOGD) Log.d(TAG, "... write finished, now closing");
+ os.close();
+ } catch (Exception e) {
+ Log.e(TAG, "... error on writing buffer", e);
+ }
+
+ mArgs.putString("BLOB", contentUri.toString() );
+ }
/**
* Clears all arguments
@@ -628,18 +668,53 @@ public class ApgCon {
* {@link #hasNextError()} is false.
* </p>
*
+ * <p>
+ * Note: When handling binary data with {@link #setBlob(InputStream)}, you
+ * get your result with {@link #getBlobResult()}.
+ * </p>
+ *
* @return the mResult of the last {@link #call(String)} or
* {@link #callAsync(String)}.
*
* @see #reset()
* @see #clearResult()
* @see #getResultBundle()
+ * @see #getBlobResult()
*/
public String getResult() {
return mResult.getString(ret.RESULT.name());
}
/**
+ * Get the binary result
+ *
+ * <p>
+ * This gets your binary result. It only works if you called {@link #setBlob(InputStream)} before.
+ *
+ * If you did not call encrypt nor decrypt, this will be the same data as you inputed.
+ * </p>
+ *
+ * @return InputStream of the binary data which was en/decrypted
+ *
+ * @see #setBlob(InputStream)
+ * @see #getResult()
+ */
+ public InputStream getBlobResult() {
+ if(mArgs.containsKey("BLOB")) {
+ ContentResolver cr = mContext.getContentResolver();
+ InputStream in = null;
+ try {
+ in = cr.openInputStream(Uri.parse(mArgs.getString("BLOB")));
+ } catch( Exception e ) {
+ Log.e(TAG, "Could not return blob in result", e);
+ }
+ return in;
+ } else {
+ return null;
+ }
+ }
+
+ /**
* Get the result bundle
*
* <p>