mirror of
https://github.com/moparisthebest/open-keychain
synced 2024-11-05 08:45:08 -05:00
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!
This commit is contained in:
parent
a7294d50b1
commit
ad16574657
@ -204,13 +204,18 @@
|
||||
<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>
|
||||
|
@ -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>
|
||||
|
@ -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,
|
||||
@ -45,8 +48,15 @@ public class ApgService extends Service {
|
||||
}
|
||||
}
|
||||
|
||||
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 */
|
||||
@ -137,6 +149,14 @@ public class ApgService extends Service {
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
qb.setTables(KeyRings.TABLE_NAME + " INNER JOIN " + Keys.TABLE_NAME + " ON " + "(" + KeyRings.TABLE_NAME + "." + KeyRings._ID + " = " + Keys.TABLE_NAME
|
||||
@ -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");
|
||||
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;
|
||||
}
|
||||
|
||||
@ -482,10 +534,23 @@ public class ApgService extends Service {
|
||||
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");
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -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";
|
||||
}
|
||||
|
@ -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!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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,6 +60,7 @@ interface IApgService {
|
||||
* String "PRIVATE_KEY_PASSPHRASE" = Passphrase for signing key
|
||||
*
|
||||
* Bundle returnVals (in addition to the ERRORS/WARNINGS above):
|
||||
* If "MESSAGE" was set:
|
||||
* String "RESULT" = Encrypted message
|
||||
*/
|
||||
|
||||
@ -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,6 +97,7 @@ interface IApgService {
|
||||
* String "PRIVATE_KEY_PASSPHRASE" = Private keys's passphrase on asymmetric encryption
|
||||
*
|
||||
* Bundle return_vals:
|
||||
* If "MESSAGE" was set:
|
||||
* String "RESULT" = Decrypted message
|
||||
*/
|
||||
boolean decrypt(in Bundle params, out Bundle returnVals);
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
@ -496,6 +501,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,17 +668,52 @@ 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
|
||||
*
|
||||
|
Loading…
Reference in New Issue
Block a user