aboutsummaryrefslogtreecommitdiffstats
path: root/src/org
diff options
context:
space:
mode:
Diffstat (limited to 'src/org')
-rw-r--r--src/org/thialfihar/android/apg/Apg.java146
-rw-r--r--src/org/thialfihar/android/apg/ApgService.java518
-rw-r--r--src/org/thialfihar/android/apg/IApgService.aidl113
-rw-r--r--src/org/thialfihar/android/apg/Preferences.java7
-rw-r--r--src/org/thialfihar/android/apg/utils/ApgCon.java761
-rw-r--r--src/org/thialfihar/android/apg/utils/ApgConInterface.java7
6 files changed, 1503 insertions, 49 deletions
diff --git a/src/org/thialfihar/android/apg/Apg.java b/src/org/thialfihar/android/apg/Apg.java
index 34c2acc6b..0d61d4f42 100644
--- a/src/org/thialfihar/android/apg/Apg.java
+++ b/src/org/thialfihar/android/apg/Apg.java
@@ -400,7 +400,8 @@ public class Apg {
throws Apg.GeneralException, NoSuchProviderException, PGPException,
NoSuchAlgorithmException, SignatureException, IOException, Database.GeneralException {
- progress.setProgress(R.string.progress_buildingKey, 0, 100);
+ if( progress != null )
+ progress.setProgress(R.string.progress_buildingKey, 0, 100);
Security.addProvider(new BouncyCastleProvider());
@@ -461,7 +462,8 @@ public class Apg {
keys.add(editor.getValue());
}
- progress.setProgress(R.string.progress_preparingMasterKey, 10, 100);
+ if( progress != null )
+ progress.setProgress(R.string.progress_preparingMasterKey, 10, 100);
KeyEditor keyEditor = (KeyEditor) keyEditors.getChildAt(0);
int usageId = keyEditor.getUsage();
boolean canSign = (usageId == Id.choice.usage.sign_only ||
@@ -481,7 +483,8 @@ public class Apg {
masterKey.extractPrivateKey(oldPassPhrase.toCharArray(),
new BouncyCastleProvider());
- progress.setProgress(R.string.progress_certifyingMasterKey, 20, 100);
+ if( progress != null )
+ progress.setProgress(R.string.progress_certifyingMasterKey, 20, 100);
for (int i = 0; i < userIds.size(); ++i) {
String userId = userIds.get(i);
@@ -525,7 +528,8 @@ public class Apg {
hashedPacketsGen.setKeyExpirationTime(true, numDays * 86400);
}
- progress.setProgress(R.string.progress_buildingMasterKeyRing, 30, 100);
+ if( progress != null )
+ progress.setProgress(R.string.progress_buildingMasterKeyRing, 30, 100);
PGPKeyRingGenerator keyGen =
new PGPKeyRingGenerator(PGPSignature.POSITIVE_CERTIFICATION,
masterKeyPair, mainUserId,
@@ -533,9 +537,11 @@ public class Apg {
hashedPacketsGen.generate(), unhashedPacketsGen.generate(),
new SecureRandom(), new BouncyCastleProvider().getName());
- progress.setProgress(R.string.progress_addingSubKeys, 40, 100);
+ if( progress != null )
+ progress.setProgress(R.string.progress_addingSubKeys, 40, 100);
for (int i = 1; i < keys.size(); ++i) {
- progress.setProgress(40 + 50 * (i - 1)/ (keys.size() - 1), 100);
+ if( progress != null )
+ progress.setProgress(40 + 50 * (i - 1)/ (keys.size() - 1), 100);
PGPSecretKey subKey = keys.get(i);
keyEditor = (KeyEditor) keyEditors.getChildAt(i);
PGPPublicKey subPublicKey = subKey.getPublicKey();
@@ -584,11 +590,13 @@ public class Apg {
PGPSecretKeyRing secretKeyRing = keyGen.generateSecretKeyRing();
PGPPublicKeyRing publicKeyRing = keyGen.generatePublicKeyRing();
- progress.setProgress(R.string.progress_savingKeyRing, 90, 100);
+ if( progress != null )
+ progress.setProgress(R.string.progress_savingKeyRing, 90, 100);
mDatabase.saveKeyRing(secretKeyRing);
mDatabase.saveKeyRing(publicKeyRing);
- progress.setProgress(R.string.progress_done, 100, 100);
+ if( progress != null )
+ progress.setProgress(R.string.progress_done, 100, 100);
}
public static Bundle importKeyRings(Activity context, int type,
@@ -598,9 +606,11 @@ public class Apg {
Bundle returnData = new Bundle();
if (type == Id.type.secret_key) {
- progress.setProgress(R.string.progress_importingSecretKeys, 0, 100);
+ if( progress != null )
+ progress.setProgress(R.string.progress_importingSecretKeys, 0, 100);
} else {
- progress.setProgress(R.string.progress_importingPublicKeys, 0, 100);
+ if( progress != null )
+ progress.setProgress(R.string.progress_importingPublicKeys, 0, 100);
}
if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
@@ -671,7 +681,8 @@ public class Apg {
} else if (retValue == Id.return_value.bad) {
++badKeys;
}
- progress.setProgress((int)(100 * progressIn.position() / data.getSize()), 100);
+ if( progress != null )
+ progress.setProgress((int)(100 * progressIn.position() / data.getSize()), 100);
obj = objectFactory.nextObject();
}
}
@@ -683,7 +694,8 @@ public class Apg {
returnData.putInt("updated", oldKeys);
returnData.putInt("bad", badKeys);
- progress.setProgress(R.string.progress_done, 100, 100);
+ if( progress != null )
+ progress.setProgress(R.string.progress_done, 100, 100);
return returnData;
}
@@ -695,9 +707,11 @@ public class Apg {
Bundle returnData = new Bundle();
if (keyRingIds.size() == 1) {
- progress.setProgress(R.string.progress_exportingKey, 0, 100);
+ if( progress != null )
+ progress.setProgress(R.string.progress_exportingKey, 0, 100);
} else {
- progress.setProgress(R.string.progress_exportingKeys, 0, 100);
+ if( progress != null )
+ progress.setProgress(R.string.progress_exportingKeys, 0, 100);
}
if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
@@ -707,7 +721,8 @@ public class Apg {
int numKeys = 0;
for (int i = 0; i < keyRingIds.size(); ++i) {
- progress.setProgress(i * 100 / keyRingIds.size(), 100);
+ if( progress != null )
+ progress.setProgress(i * 100 / keyRingIds.size(), 100);
Object obj = mDatabase.getKeyRing(keyRingIds.get(i));
PGPPublicKeyRing publicKeyRing;
PGPSecretKeyRing secretKeyRing;
@@ -726,7 +741,8 @@ public class Apg {
out.close();
returnData.putInt("exported", numKeys);
- progress.setProgress(R.string.progress_done, 100, 100);
+ if( progress != null )
+ progress.setProgress(R.string.progress_done, 100, 100);
return returnData;
}
@@ -1236,14 +1252,17 @@ public class Apg {
if (signaturePassPhrase == null) {
throw new GeneralException(context.getString(R.string.error_noSignaturePassPhrase));
}
- progress.setProgress(R.string.progress_extractingSignatureKey, 0, 100);
+ if( progress != null )
+ progress.setProgress(R.string.progress_extractingSignatureKey, 0, 100);
signaturePrivateKey = signingKey.extractPrivateKey(signaturePassPhrase.toCharArray(),
new BouncyCastleProvider());
if (signaturePrivateKey == null) {
throw new GeneralException(context.getString(R.string.error_couldNotExtractPrivateKey));
}
}
- progress.setProgress(R.string.progress_preparingStreams, 5, 100);
+ if( progress != null )
+ progress.setProgress(R.string.progress_preparingStreams, 5, 100);
+
// encrypt and compress input file content
PGPEncryptedDataGenerator cPk =
new PGPEncryptedDataGenerator(symmetricAlgorithm, true, new SecureRandom(),
@@ -1265,7 +1284,8 @@ public class Apg {
PGPV3SignatureGenerator signatureV3Generator = null;
if (signatureKeyId != 0) {
- progress.setProgress(R.string.progress_preparingSignature, 10, 100);
+ if( progress != null )
+ progress.setProgress(R.string.progress_preparingSignature, 10, 100);
if (forceV3Signature) {
signatureV3Generator =
new PGPV3SignatureGenerator(signingKey.getPublicKey().getAlgorithm(),
@@ -1306,7 +1326,9 @@ public class Apg {
// file name not needed, so empty string
OutputStream pOut = literalGen.open(bcpgOut, PGPLiteralData.BINARY, "",
new Date(), new byte[1 << 16]);
- progress.setProgress(R.string.progress_encrypting, 20, 100);
+ if( progress != null )
+ progress.setProgress(R.string.progress_encrypting, 20, 100);
+
long done = 0;
int n = 0;
byte[] buffer = new byte[1 << 16];
@@ -1322,14 +1344,16 @@ public class Apg {
}
done += n;
if (data.getSize() != 0) {
- progress.setProgress((int) (20 + (95 - 20) * done / data.getSize()), 100);
+ if( progress != null )
+ progress.setProgress((int) (20 + (95 - 20) * done / data.getSize()), 100);
}
}
literalGen.close();
if (signatureKeyId != 0) {
- progress.setProgress(R.string.progress_generatingSignature, 95, 100);
+ if( progress != null )
+ progress.setProgress(R.string.progress_generatingSignature, 95, 100);
if (forceV3Signature) {
signatureV3Generator.generate().encode(pOut);
} else {
@@ -1344,7 +1368,8 @@ public class Apg {
armorOut.close();
}
- progress.setProgress(R.string.progress_done, 100, 100);
+ if( progress != null )
+ progress.setProgress(R.string.progress_done, 100, 100);
}
public static void signText(Context context,
@@ -1383,9 +1408,11 @@ public class Apg {
if (signaturePrivateKey == null) {
throw new GeneralException(context.getString(R.string.error_couldNotExtractPrivateKey));
}
- progress.setProgress(R.string.progress_preparingStreams, 0, 100);
+ if( progress != null )
+ progress.setProgress(R.string.progress_preparingStreams, 0, 100);
- progress.setProgress(R.string.progress_preparingSignature, 30, 100);
+ if( progress != null )
+ progress.setProgress(R.string.progress_preparingSignature, 30, 100);
PGPSignatureGenerator signatureGenerator = null;
PGPV3SignatureGenerator signatureV3Generator = null;
@@ -1409,7 +1436,8 @@ public class Apg {
signatureGenerator.setHashedSubpackets(spGen.generate());
}
- progress.setProgress(R.string.progress_signing, 40, 100);
+ if( progress != null )
+ progress.setProgress(R.string.progress_signing, 40, 100);
armorOut.beginClearText(hashAlgorithm);
@@ -1452,7 +1480,8 @@ public class Apg {
}
armorOut.close();
- progress.setProgress(R.string.progress_done, 100, 100);
+ if( progress != null )
+ progress.setProgress(R.string.progress_done, 100, 100);
}
public static void generateSignature(Context context,
@@ -1499,9 +1528,11 @@ public class Apg {
if (signaturePrivateKey == null) {
throw new GeneralException(context.getString(R.string.error_couldNotExtractPrivateKey));
}
- progress.setProgress(R.string.progress_preparingStreams, 0, 100);
+ if( progress != null )
+ progress.setProgress(R.string.progress_preparingStreams, 0, 100);
- progress.setProgress(R.string.progress_preparingSignature, 30, 100);
+ if( progress != null )
+ progress.setProgress(R.string.progress_preparingSignature, 30, 100);
PGPSignatureGenerator signatureGenerator = null;
PGPV3SignatureGenerator signatureV3Generator = null;
@@ -1530,7 +1561,8 @@ public class Apg {
signatureGenerator.setHashedSubpackets(spGen.generate());
}
- progress.setProgress(R.string.progress_signing, 40, 100);
+ if( progress != null )
+ progress.setProgress(R.string.progress_signing, 40, 100);
InputStream inStream = data.getInputStream();
if (binary) {
@@ -1573,7 +1605,8 @@ public class Apg {
out.close();
outStream.close();
- progress.setProgress(R.string.progress_done, 100, 100);
+ if( progress != null )
+ progress.setProgress(R.string.progress_done, 100, 100);
}
public static long getDecryptionKeyId(Context context, InputData data)
@@ -1667,7 +1700,8 @@ public class Apg {
long signatureKeyId = 0;
int currentProgress = 0;
- progress.setProgress(R.string.progress_readingData, currentProgress, 100);
+ if( progress != null )
+ progress.setProgress(R.string.progress_readingData, currentProgress, 100);
if (o instanceof PGPEncryptedDataList) {
enc = (PGPEncryptedDataList) o;
@@ -1702,12 +1736,14 @@ public class Apg {
throw new GeneralException(context.getString(R.string.error_noSymmetricEncryptionPacket));
}
- progress.setProgress(R.string.progress_preparingStreams, currentProgress, 100);
+ if( progress != null )
+ progress.setProgress(R.string.progress_preparingStreams, currentProgress, 100);
clear = pbe.getDataStream(passPhrase.toCharArray(), new BouncyCastleProvider());
encryptedData = pbe;
currentProgress += 5;
} else {
- progress.setProgress(R.string.progress_findingKey, currentProgress, 100);
+ if( progress != null )
+ progress.setProgress(R.string.progress_findingKey, currentProgress, 100);
PGPPublicKeyEncryptedData pbe = null;
PGPSecretKey secretKey = null;
Iterator it = enc.getEncryptedDataObjects();
@@ -1729,7 +1765,8 @@ public class Apg {
}
currentProgress += 5;
- progress.setProgress(R.string.progress_extractingKey, currentProgress, 100);
+ if( progress != null )
+ progress.setProgress(R.string.progress_extractingKey, currentProgress, 100);
PGPPrivateKey privateKey = null;
try {
privateKey = secretKey.extractPrivateKey(passPhrase.toCharArray(),
@@ -1741,7 +1778,8 @@ public class Apg {
throw new GeneralException(context.getString(R.string.error_couldNotExtractPrivateKey));
}
currentProgress += 5;
- progress.setProgress(R.string.progress_preparingStreams, currentProgress, 100);
+ if( progress != null )
+ progress.setProgress(R.string.progress_preparingStreams, currentProgress, 100);
clear = pbe.getDataStream(privateKey, new BouncyCastleProvider());
encryptedData = pbe;
currentProgress += 5;
@@ -1754,7 +1792,8 @@ public class Apg {
int signatureIndex = -1;
if (dataChunk instanceof PGPCompressedData) {
- progress.setProgress(R.string.progress_decompressingData, currentProgress, 100);
+ if( progress != null )
+ progress.setProgress(R.string.progress_decompressingData, currentProgress, 100);
PGPObjectFactory fact =
new PGPObjectFactory(((PGPCompressedData) dataChunk).getDataStream());
dataChunk = fact.nextObject();
@@ -1763,7 +1802,8 @@ public class Apg {
}
if (dataChunk instanceof PGPOnePassSignatureList) {
- progress.setProgress(R.string.progress_processingSignature, currentProgress, 100);
+ if( progress != null )
+ progress.setProgress(R.string.progress_processingSignature, currentProgress, 100);
returnData.putBoolean(EXTRA_SIGNATURE, true);
PGPOnePassSignatureList sigList = (PGPOnePassSignatureList) dataChunk;
for (int i = 0; i < sigList.size(); ++i) {
@@ -1800,7 +1840,8 @@ public class Apg {
}
if (dataChunk instanceof PGPLiteralData) {
- progress.setProgress(R.string.progress_decrypting, currentProgress, 100);
+ if( progress != null )
+ progress.setProgress(R.string.progress_decrypting, currentProgress, 100);
PGPLiteralData literalData = (PGPLiteralData) dataChunk;
OutputStream out = outStream;
@@ -1836,11 +1877,13 @@ public class Apg {
currentProgress = (int)(startProgress + (endProgress - startProgress) *
(data.getStreamPosition() - startPos) / (data.getSize() - startPos));
}
- progress.setProgress(currentProgress, 100);
+ if( progress != null )
+ progress.setProgress(currentProgress, 100);
}
if (signature != null) {
- progress.setProgress(R.string.progress_verifyingSignature, 90, 100);
+ if( progress != null )
+ progress.setProgress(R.string.progress_verifyingSignature, 90, 100);
PGPSignatureList signatureList = (PGPSignatureList) plainFact.nextObject();
PGPSignature messageSignature = (PGPSignature) signatureList.get(signatureIndex);
if (signature.verify(messageSignature)) {
@@ -1853,7 +1896,8 @@ public class Apg {
// TODO: add integrity somewhere
if (encryptedData.isIntegrityProtected()) {
- progress.setProgress(R.string.progress_verifyingIntegrity, 95, 100);
+ if( progress != null )
+ progress.setProgress(R.string.progress_verifyingIntegrity, 95, 100);
if (encryptedData.verify()) {
// passed
} else {
@@ -1863,7 +1907,8 @@ public class Apg {
// no integrity check
}
- progress.setProgress(R.string.progress_done, 100, 100);
+ if( progress != null )
+ progress.setProgress(R.string.progress_done, 100, 100);
return returnData;
}
@@ -1876,7 +1921,8 @@ public class Apg {
ByteArrayOutputStream out = new ByteArrayOutputStream();
ArmoredInputStream aIn = new ArmoredInputStream(data.getInputStream());
- progress.setProgress(R.string.progress_done, 0, 100);
+ if( progress != null )
+ progress.setProgress(R.string.progress_done, 0, 100);
// mostly taken from ClearSignedFileProcessor
ByteArrayOutputStream lineOut = new ByteArrayOutputStream();
@@ -1901,7 +1947,8 @@ public class Apg {
returnData.putBoolean(EXTRA_SIGNATURE, true);
- progress.setProgress(R.string.progress_processingSignature, 60, 100);
+ if( progress != null )
+ progress.setProgress(R.string.progress_processingSignature, 60, 100);
PGPObjectFactory pgpFact = new PGPObjectFactory(aIn);
PGPSignatureList sigList = (PGPSignatureList) pgpFact.nextObject();
@@ -1948,7 +1995,8 @@ public class Apg {
if (signature == null) {
returnData.putBoolean(EXTRA_SIGNATURE_UNKNOWN, true);
- progress.setProgress(R.string.progress_done, 100, 100);
+ if( progress != null )
+ progress.setProgress(R.string.progress_done, 100, 100);
return returnData;
}
@@ -1974,7 +2022,8 @@ public class Apg {
returnData.putBoolean(EXTRA_SIGNATURE_SUCCESS, signature.verify());
- progress.setProgress(R.string.progress_done, 100, 100);
+ if( progress != null )
+ progress.setProgress(R.string.progress_done, 100, 100);
return returnData;
}
@@ -2232,7 +2281,8 @@ public class Apg {
int pos = 0;
String msg = context.getString(R.string.progress_deletingSecurely, file.getName());
while (pos < length) {
- progress.setProgress(msg, (int)(100 * pos / length), 100);
+ if( progress != null )
+ progress.setProgress(msg, (int)(100 * pos / length), 100);
random.nextBytes(data);
raf.write(data);
pos += data.length;
diff --git a/src/org/thialfihar/android/apg/ApgService.java b/src/org/thialfihar/android/apg/ApgService.java
new file mode 100644
index 000000000..0a25c6055
--- /dev/null
+++ b/src/org/thialfihar/android/apg/ApgService.java
@@ -0,0 +1,518 @@
+package org.thialfihar.android.apg;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+
+import org.thialfihar.android.apg.provider.KeyRings;
+import org.thialfihar.android.apg.provider.Keys;
+import org.thialfihar.android.apg.provider.UserIds;
+
+import android.content.Intent;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteQueryBuilder;
+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;
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ if( LOCAL_LOGD ) Log.d(TAG, "bound");
+ return mBinder;
+ }
+
+ /** error status */
+ private enum error {
+ ARGUMENTS_MISSING,
+ APG_FAILURE,
+ NO_MATCHING_SECRET_KEY,
+ PRIVATE_KEY_PASSPHRASE_WRONG,
+ PRIVATE_KEY_PASSPHRASE_MISSING;
+
+ public int shiftedOrdinal() {
+ return ordinal() + 100;
+ }
+ }
+
+ /** all arguments that can be passed by calling application */
+ private enum arg {
+ MESSAGE, // message to encrypt or to decrypt
+ SYMMETRIC_PASSPHRASE, // key for symmetric en/decryption
+ PUBLIC_KEYS, // public keys for encryption
+ ENCRYPTION_ALGORYTHM, // encryption algorithm
+ HASH_ALGORYTHM, // hash algorithm
+ ARMORED_OUTPUT, // whether to armor output
+ FORCE_V3_SIGNATURE, // whether to force v3 signature
+ COMPRESSION, // what compression to use for encrypted output
+ SIGNATURE_KEY, // key for signing
+ PRIVATE_KEY_PASSPHRASE, // passphrase for encrypted private key
+ KEY_TYPE, // type of key (private or public)
+ }
+
+ /** all things that might be returned */
+ private enum ret {
+ ERRORS, // string array list with errors
+ WARNINGS, // string array list with warnings
+ ERROR, // numeric error
+ RESULT, // en-/decrypted
+ FINGERPRINTS, // fingerprints of keys
+ USER_IDS, // user ids
+ }
+
+ /** required arguments for each AIDL function */
+ private static final HashMap<String, HashSet<arg>> FUNCTIONS_REQUIRED_ARGS = new HashMap<String, HashSet<arg>>();
+ static {
+ HashSet<arg> args = new HashSet<arg>();
+ args.add(arg.SYMMETRIC_PASSPHRASE);
+ args.add(arg.MESSAGE);
+ FUNCTIONS_REQUIRED_ARGS.put("encrypt_with_passphrase", args);
+
+ args = new HashSet<arg>();
+ args.add(arg.PUBLIC_KEYS);
+ args.add(arg.MESSAGE);
+ FUNCTIONS_REQUIRED_ARGS.put("encrypt_with_public_key", args);
+
+ args = new HashSet<arg>();
+ args.add(arg.MESSAGE);
+ FUNCTIONS_REQUIRED_ARGS.put("decrypt", args);
+
+ args = new HashSet<arg>();
+ args.add(arg.KEY_TYPE);
+ FUNCTIONS_REQUIRED_ARGS.put("get_keys", args);
+ }
+
+ /** optional arguments for each AIDL function */
+ private static final HashMap<String, HashSet<arg>> FUNCTIONS_OPTIONAL_ARGS = new HashMap<String, HashSet<arg>>();
+ static {
+ HashSet<arg> args = new HashSet<arg>();
+ args.add(arg.ENCRYPTION_ALGORYTHM);
+ args.add(arg.HASH_ALGORYTHM);
+ args.add(arg.ARMORED_OUTPUT);
+ args.add(arg.FORCE_V3_SIGNATURE);
+ 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 = 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);
+ }
+
+ /** a map from ApgService parameters to function calls to get the default */
+ private static final HashMap<arg, String> FUNCTIONS_DEFAULTS = new HashMap<arg, String>();
+ static {
+ FUNCTIONS_DEFAULTS.put(arg.ENCRYPTION_ALGORYTHM, "getDefaultEncryptionAlgorithm");
+ FUNCTIONS_DEFAULTS.put(arg.HASH_ALGORYTHM, "getDefaultHashAlgorithm");
+ FUNCTIONS_DEFAULTS.put(arg.ARMORED_OUTPUT, "getDefaultAsciiArmour");
+ FUNCTIONS_DEFAULTS.put(arg.FORCE_V3_SIGNATURE, "getForceV3Signatures");
+ FUNCTIONS_DEFAULTS.put(arg.COMPRESSION, "getDefaultMessageCompression");
+ }
+
+ /** a map of the default function names to their method */
+ private static final HashMap<String, Method> FUNCTIONS_DEFAULTS_METHODS = new HashMap<String, Method>();
+ static {
+ try {
+ FUNCTIONS_DEFAULTS_METHODS.put("getDefaultEncryptionAlgorithm", Preferences.class.getMethod("getDefaultEncryptionAlgorithm"));
+ FUNCTIONS_DEFAULTS_METHODS.put("getDefaultHashAlgorithm", Preferences.class.getMethod("getDefaultHashAlgorithm"));
+ FUNCTIONS_DEFAULTS_METHODS.put("getDefaultAsciiArmour", Preferences.class.getMethod("getDefaultAsciiArmour"));
+ FUNCTIONS_DEFAULTS_METHODS.put("getForceV3Signatures", Preferences.class.getMethod("getForceV3Signatures"));
+ FUNCTIONS_DEFAULTS_METHODS.put("getDefaultMessageCompression", Preferences.class.getMethod("getDefaultMessageCompression"));
+ } catch (Exception e) {
+ Log.e(TAG, "Function method exception: " + e.getMessage());
+ }
+ }
+
+ 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
+ + "." + Keys.KEY_RING_ID + " AND " + Keys.TABLE_NAME + "." + Keys.IS_MASTER_KEY + " = '1'" + ") " + " INNER JOIN " + UserIds.TABLE_NAME
+ + " ON " + "(" + Keys.TABLE_NAME + "." + Keys._ID + " = " + UserIds.TABLE_NAME + "." + UserIds.KEY_ID + " AND " + UserIds.TABLE_NAME + "."
+ + UserIds.RANK + " = '0') ");
+
+ String orderBy = pParams.containsKey("order_by") ? (String) pParams.get("order_by") : UserIds.TABLE_NAME + "." + UserIds.USER_ID + " ASC";
+
+ String typeVal[] = null;
+ String typeWhere = null;
+ if (pParams.containsKey("key_type")) {
+ typeWhere = KeyRings.TABLE_NAME + "." + KeyRings.TYPE + " = ?";
+ typeVal = new String[] {
+ "" + pParams.get("key_type")
+ };
+ }
+ return qb.query(Apg.getDatabase().db(), (String[]) pParams.get("columns"), typeWhere, typeVal, null, null, orderBy);
+ }
+
+ /**
+ * maps a fingerprint or user id of a key to a master key in database
+ *
+ * @param search_key
+ * fingerprint or user id to search for
+ * @return master key if found, or 0
+ */
+ private static long getMasterKey(String pSearchKey, Bundle pReturn) {
+ if (pSearchKey == null || pSearchKey.length() != 8) {
+ return 0;
+ }
+ ArrayList<String> keyList = new ArrayList<String>();
+ keyList.add(pSearchKey);
+ long[] keys = getMasterKey(keyList, pReturn);
+ if (keys.length > 0) {
+ return keys[0];
+ } else {
+ return 0;
+ }
+ }
+
+ /**
+ * maps fingerprints or user ids of keys to master keys in database
+ *
+ * @param search_keys
+ * a list of keys (fingerprints or user ids) to look for in
+ * database
+ * @return an array of master keys
+ */
+ private static long[] getMasterKey(ArrayList<String> pSearchKeys, Bundle pReturn) {
+
+ HashMap<String, Object> qParams = new HashMap<String, Object>();
+ qParams.put("columns", new String[] {
+ KeyRings.TABLE_NAME + "." + KeyRings.MASTER_KEY_ID, // 0
+ UserIds.TABLE_NAME + "." + UserIds.USER_ID, // 1
+ });
+ qParams.put("key_type", Id.database.type_public);
+
+ Cursor mCursor = getKeyEntries(qParams);
+
+ if( LOCAL_LOGV ) Log.v(TAG, "going through installed user keys");
+ ArrayList<Long> masterKeys = new ArrayList<Long>();
+ while (mCursor.moveToNext()) {
+ long curMkey = mCursor.getLong(0);
+ String curUser = mCursor.getString(1);
+
+ String curFprint = Apg.getSmallFingerPrint(curMkey);
+ if( LOCAL_LOGV ) Log.v(TAG, "current user: " + curUser + " (" + curFprint + ")");
+ if (pSearchKeys.contains(curFprint) || pSearchKeys.contains(curUser)) {
+ if( LOCAL_LOGV ) Log.v(TAG, "master key found for: " + curFprint);
+ masterKeys.add(curMkey);
+ pSearchKeys.remove(curFprint);
+ } else {
+ if( LOCAL_LOGV ) Log.v(TAG, "Installed key " + curFprint + " is not in the list of public keys to encrypt with");
+ }
+ }
+ mCursor.close();
+
+ long[] masterKeyLongs = new long[masterKeys.size()];
+ int i = 0;
+ for (Long key : masterKeys) {
+ masterKeyLongs[i++] = key;
+ }
+
+ if (i == 0) {
+ Log.w(TAG, "Found not one public key");
+ pReturn.getStringArrayList(ret.WARNINGS.name()).add("Searched for public key(s) but found not one");
+ }
+
+ for (String key : pSearchKeys) {
+ Log.w(TAG, "Searched for key " + key + " but cannot find it in APG");
+ pReturn.getStringArrayList(ret.WARNINGS.name()).add("Searched for key " + key + " but cannot find it in APG");
+ }
+
+ return masterKeyLongs;
+ }
+
+ /**
+ * Add default arguments if missing
+ *
+ * @param args
+ * the bundle to add default parameters to if missing
+ */
+ private void addDefaultArguments(String pCall, Bundle pArgs) {
+ // check whether there are optional elements defined for that call
+ if (FUNCTIONS_OPTIONAL_ARGS.containsKey(pCall)) {
+ Preferences preferences = Preferences.getPreferences(getBaseContext(), true);
+
+ Iterator<arg> iter = FUNCTIONS_DEFAULTS.keySet().iterator();
+ while (iter.hasNext()) {
+ arg currentArg = iter.next();
+ String currentKey = currentArg.name();
+ if (!pArgs.containsKey(currentKey) && FUNCTIONS_OPTIONAL_ARGS.get(pCall).contains(currentArg)) {
+ String currentFunctionName = FUNCTIONS_DEFAULTS.get(currentArg);
+ try {
+ Class<?> returnType = FUNCTIONS_DEFAULTS_METHODS.get(currentFunctionName).getReturnType();
+ if (returnType == String.class) {
+ pArgs.putString(currentKey, (String) FUNCTIONS_DEFAULTS_METHODS.get(currentFunctionName).invoke(preferences));
+ } else if (returnType == boolean.class) {
+ pArgs.putBoolean(currentKey, (Boolean) FUNCTIONS_DEFAULTS_METHODS.get(currentFunctionName).invoke(preferences));
+ } else if (returnType == int.class) {
+ pArgs.putInt(currentKey, (Integer) FUNCTIONS_DEFAULTS_METHODS.get(currentFunctionName).invoke(preferences));
+ } else {
+ Log.e(TAG, "Unknown return type " + returnType.toString() + " for default option");
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Exception in add_default_arguments " + e.getMessage());
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * updates a Bundle with default return values
+ *
+ * @param pReturn
+ * the Bundle to update
+ */
+ private void addDefaultReturns(Bundle pReturn) {
+ ArrayList<String> errors = new ArrayList<String>();
+ ArrayList<String> warnings = new ArrayList<String>();
+
+ pReturn.putStringArrayList(ret.ERRORS.name(), errors);
+ pReturn.putStringArrayList(ret.WARNINGS.name(), warnings);
+ }
+
+ /**
+ * checks for required arguments and adds them to the error if missing
+ *
+ * @param function
+ * the functions required arguments to check for
+ * @param pArgs
+ * the Bundle of arguments to check
+ * @param pReturn
+ * the bundle to write errors to
+ */
+ private void checkForRequiredArgs(String pFunction, Bundle pArgs, Bundle pReturn) {
+ if (FUNCTIONS_REQUIRED_ARGS.containsKey(pFunction)) {
+ Iterator<arg> iter = FUNCTIONS_REQUIRED_ARGS.get(pFunction).iterator();
+ while (iter.hasNext()) {
+ String curArg = iter.next().name();
+ if (!pArgs.containsKey(curArg)) {
+ pReturn.getStringArrayList(ret.ERRORS.name()).add("Argument missing: " + curArg);
+ }
+ }
+ }
+ }
+
+ /**
+ * checks for unknown arguments and add them to warning if found
+ *
+ * @param function
+ * the functions name to check against
+ * @param pArgs
+ * the Bundle of arguments to check
+ * @param pReturn
+ * the bundle to write warnings to
+ */
+ private void checkForUnknownArgs(String pFunction, Bundle pArgs, Bundle pReturn) {
+
+ HashSet<arg> allArgs = new HashSet<arg>();
+ if (FUNCTIONS_REQUIRED_ARGS.containsKey(pFunction)) {
+ allArgs.addAll(FUNCTIONS_REQUIRED_ARGS.get(pFunction));
+ }
+ if (FUNCTIONS_OPTIONAL_ARGS.containsKey(pFunction)) {
+ allArgs.addAll(FUNCTIONS_OPTIONAL_ARGS.get(pFunction));
+ }
+
+ ArrayList<String> unknownArgs = new ArrayList<String>();
+ Iterator<String> iter = pArgs.keySet().iterator();
+ while (iter.hasNext()) {
+ String curKey = iter.next();
+ try {
+ arg curArg = arg.valueOf(curKey);
+ if (!allArgs.contains(curArg)) {
+ pReturn.getStringArrayList(ret.WARNINGS.name()).add("Unknown argument: " + curKey);
+ unknownArgs.add(curKey);
+ }
+ } catch (Exception e) {
+ pReturn.getStringArrayList(ret.WARNINGS.name()).add("Unknown argument: " + curKey);
+ unknownArgs.add(curKey);
+ }
+ }
+
+ // remove unknown arguments so our bundle has just what we need
+ for (String arg : unknownArgs) {
+ pArgs.remove(arg);
+ }
+ }
+
+ private boolean prepareArgs(String pCall, Bundle pArgs, Bundle pReturn) {
+ Apg.initialize(getBaseContext());
+
+ /* add default return values for all functions */
+ addDefaultReturns(pReturn);
+
+ /* add default arguments if missing */
+ addDefaultArguments(pCall, pArgs);
+ if( LOCAL_LOGV ) Log.v(TAG, "add_default_arguments");
+
+ /* check for required arguments */
+ checkForRequiredArgs(pCall, pArgs, pReturn);
+ if( LOCAL_LOGV ) Log.v(TAG, "check_required_args");
+
+ /* check for unknown arguments and add to warning if found */
+ checkForUnknownArgs(pCall, pArgs, pReturn);
+ if( LOCAL_LOGV ) Log.v(TAG, "check_unknown_args");
+
+ /* return if errors happened */
+ if (pReturn.getStringArrayList(ret.ERRORS.name()).size() != 0) {
+ if( LOCAL_LOGV ) Log.v(TAG, "Errors after preparing, not executing "+pCall);
+ pReturn.putInt(ret.ERROR.name(), error.ARGUMENTS_MISSING.shiftedOrdinal());
+ return false;
+ }
+ if( LOCAL_LOGV ) Log.v(TAG, "error return");
+
+ return true;
+ }
+
+ private boolean encrypt(Bundle pArgs, Bundle pReturn) {
+
+ long pubMasterKeys[] = {};
+ if (pArgs.containsKey(arg.PUBLIC_KEYS.name())) {
+ ArrayList<String> list = pArgs.getStringArrayList(arg.PUBLIC_KEYS.name());
+ ArrayList<String> pubKeys = new ArrayList<String>();
+ if( LOCAL_LOGV ) Log.v(TAG, "Long size: " + list.size());
+ Iterator<String> iter = list.iterator();
+ while (iter.hasNext()) {
+ pubKeys.add(iter.next());
+ }
+ pubMasterKeys = getMasterKey(pubKeys, pReturn);
+ }
+
+ InputStream inStream = new ByteArrayInputStream(pArgs.getString(arg.MESSAGE.name()).getBytes());
+ InputData in = new InputData(inStream, 0); // XXX Size second param?
+
+ OutputStream out = new ByteArrayOutputStream();
+ if( LOCAL_LOGV ) Log.v(TAG, "About to encrypt");
+ try {
+ Apg.encrypt(getBaseContext(), // context
+ in, // input stream
+ out, // output stream
+ pArgs.getBoolean(arg.ARMORED_OUTPUT.name()), // ARMORED_OUTPUT
+ pubMasterKeys, // encryption keys
+ getMasterKey(pArgs.getString(arg.SIGNATURE_KEY.name()), pReturn), // signature key
+ pArgs.getString(arg.PRIVATE_KEY_PASSPHRASE.name()), // signature passphrase
+ null, // progress
+ pArgs.getInt(arg.ENCRYPTION_ALGORYTHM.name()), // encryption
+ pArgs.getInt(arg.HASH_ALGORYTHM.name()), // hash
+ pArgs.getInt(arg.COMPRESSION.name()), // compression
+ pArgs.getBoolean(arg.FORCE_V3_SIGNATURE.name()), // mPreferences.getForceV3Signatures(),
+ pArgs.getString(arg.SYMMETRIC_PASSPHRASE.name()) // passPhrase
+ );
+ } catch (Exception e) {
+ Log.e(TAG, "Exception in encrypt");
+ String msg = e.getMessage();
+ if (msg.equals(getBaseContext().getString(R.string.error_noSignaturePassPhrase))) {
+ pReturn.getStringArrayList(ret.ERRORS.name()).add("Cannot encrypt (" + arg.PRIVATE_KEY_PASSPHRASE.name() + " missing): " + msg);
+ pReturn.putInt(ret.ERROR.name(), error.PRIVATE_KEY_PASSPHRASE_MISSING.shiftedOrdinal());
+ } else if (msg.equals(getBaseContext().getString(R.string.error_couldNotExtractPrivateKey))) {
+ pReturn.getStringArrayList(ret.ERRORS.name()).add("Cannot encrypt (" + arg.PRIVATE_KEY_PASSPHRASE.name() + " probably wrong): " + msg);
+ pReturn.putInt(ret.ERROR.name(), error.PRIVATE_KEY_PASSPHRASE_WRONG.shiftedOrdinal());
+ } else {
+ pReturn.getStringArrayList(ret.ERRORS.name()).add("Internal failure (" + e.getClass() + ") in APG when encrypting: " + e.getMessage());
+ pReturn.putInt(ret.ERROR.name(), error.APG_FAILURE.shiftedOrdinal());
+ }
+ return false;
+ }
+ if( LOCAL_LOGV ) Log.v(TAG, "Encrypted");
+ pReturn.putString(ret.RESULT.name(), out.toString());
+ return true;
+ }
+
+ private final IApgService.Stub mBinder = new IApgService.Stub() {
+
+ public boolean getKeys(Bundle pArgs, Bundle pReturn) {
+
+ prepareArgs("get_keys", pArgs, pReturn);
+
+ HashMap<String, Object> qParams = new HashMap<String, Object>();
+ qParams.put("columns", new String[] {
+ KeyRings.TABLE_NAME + "." + KeyRings.MASTER_KEY_ID, // 0
+ UserIds.TABLE_NAME + "." + UserIds.USER_ID, // 1
+ });
+
+ qParams.put("key_type", pArgs.getInt(arg.KEY_TYPE.name()));
+
+ Cursor cursor = getKeyEntries(qParams);
+ ArrayList<String> fPrints = new ArrayList<String>();
+ ArrayList<String> ids = new ArrayList<String>();
+ while (cursor.moveToNext()) {
+ if( LOCAL_LOGV ) Log.v(TAG, "adding key "+Apg.getSmallFingerPrint(cursor.getLong(0)));
+ fPrints.add(Apg.getSmallFingerPrint(cursor.getLong(0)));
+ ids.add(cursor.getString(1));
+ }
+ cursor.close();
+
+ pReturn.putStringArrayList(ret.FINGERPRINTS.name(), fPrints);
+ pReturn.putStringArrayList(ret.USER_IDS.name(), ids);
+ return true;
+ }
+
+ public boolean encryptWithPublicKey(Bundle pArgs, Bundle pReturn) {
+ if (!prepareArgs("encrypt_with_public_key", pArgs, pReturn)) {
+ return false;
+ }
+
+ return encrypt(pArgs, pReturn);
+ }
+
+ public boolean encryptWithPassphrase(Bundle pArgs, Bundle pReturn) {
+ if (!prepareArgs("encrypt_with_passphrase", pArgs, pReturn)) {
+ return false;
+ }
+
+ return encrypt(pArgs, pReturn);
+
+ }
+
+ public boolean decrypt(Bundle pArgs, Bundle pReturn) {
+ if (!prepareArgs("decrypt", pArgs, pReturn)) {
+ return false;
+ }
+
+ 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());
+ 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");
+ try {
+ Apg.decrypt(getBaseContext(), in, out, passphrase, null, // progress
+ pArgs.getString(arg.SYMMETRIC_PASSPHRASE.name()) != null // symmetric
+ );
+ } catch (Exception e) {
+ Log.e(TAG, "Exception in decrypt");
+ String msg = e.getMessage();
+ if (msg.equals(getBaseContext().getString(R.string.error_noSecretKeyFound))) {
+ pReturn.getStringArrayList(ret.ERRORS.name()).add("Cannot decrypt: " + msg);
+ pReturn.putInt(ret.ERROR.name(), error.NO_MATCHING_SECRET_KEY.shiftedOrdinal());
+ } else if (msg.equals(getBaseContext().getString(R.string.error_wrongPassPhrase))) {
+ pReturn.getStringArrayList(ret.ERRORS.name()).add("Cannot decrypt (" + arg.PRIVATE_KEY_PASSPHRASE.name() + " wrong/missing): " + msg);
+ pReturn.putInt(ret.ERROR.name(), error.PRIVATE_KEY_PASSPHRASE_WRONG.shiftedOrdinal());
+ } else {
+ pReturn.getStringArrayList(ret.ERRORS.name()).add("Internal failure (" + e.getClass() + ") in APG when decrypting: " + msg);
+ pReturn.putInt(ret.ERROR.name(), error.APG_FAILURE.shiftedOrdinal());
+ }
+ return false;
+ }
+ if( LOCAL_LOGV ) Log.v(TAG, "Decrypted");
+
+ pReturn.putString(ret.RESULT.name(), out.toString());
+ return true;
+ }
+
+ };
+}
diff --git a/src/org/thialfihar/android/apg/IApgService.aidl b/src/org/thialfihar/android/apg/IApgService.aidl
new file mode 100644
index 000000000..df46805ac
--- /dev/null
+++ b/src/org/thialfihar/android/apg/IApgService.aidl
@@ -0,0 +1,113 @@
+package org.thialfihar.android.apg;
+
+interface IApgService {
+
+ /* All functions fill the returnVals Bundle with the following keys:
+ *
+ * ArrayList<String> "WARNINGS" = Warnings, if any
+ * ArrayList<String> "ERRORS" = Human readable error descriptions, if any
+ * int "ERROR" = Numeric representation of error, if any
+ * starting with 100:
+ * 100: Required argument missing
+ * 101: Generic failure of APG
+ * 102: No matching private key found
+ * 103: Private key's passphrase wrong
+ * 104: Private key's passphrase missing
+ */
+
+ /* ********************************************************
+ * Encryption
+ * ********************************************************/
+
+ /* All encryption function's arguments
+ *
+ * Bundle params' keys:
+ * (optional/required)
+ * TYPE "STRING KEY" = EXPLANATION / VALUES
+ *
+ * (required)
+ * String "MESSAGE" = Message to encrypt
+ *
+ * (optional)
+ * int "ENCRYPTION_ALGORYTHM" = Encryption Algorithm
+ * 7: AES-128, 8: AES-192, 9: AES-256,
+ * 4: Blowfish, 10: Twofish, 3: CAST5,
+ * 6: DES, 2: Triple DES, 1: IDEA
+ * (optional)
+ * int "HASH_ALGORYTHM" = Hash Algorithm
+ * 1: MD5, 3: RIPEMD-160, 2: SHA-1,
+ * 11: SHA-224, 8: SHA-256, 9: SHA-384,
+ * 10: SHA-512
+ * (optional)
+ * Boolean "ARMORED_OUTPUT" = Armor output
+ *
+ * (optional)
+ * Boolean "FORCE_V3_SIGNATURE" = Force V3 Signatures
+ *
+ * (optional)
+ * int "COMPRESSION" = Compression to use
+ * 0x21070001: none, 1: Zip, 2: Zlib,
+ * 3: BZip2
+ * (optional)
+ * String "SIGNATURE_KEY" = Key to sign with
+ *
+ * (optional)
+ * String "PRIVATE_KEY_PASSPHRASE" = Passphrase for signing key
+ *
+ * Bundle returnVals (in addition to the ERRORS/WARNINGS above):
+ * String "RESULT" = Encrypted message
+ */
+
+ /* Additional argument for function below:
+ * (required)
+ * String "SYMMETRIC_PASSPHRASE" = Symmetric passphrase to use
+ */
+ boolean encryptWithPassphrase(in Bundle params, out Bundle returnVals);
+
+ /* Additional argument:
+ * (required)
+ * ArrayList<String> "PUBLIC_KEYS" = Public keys (8char fingerprint "123ABC12" OR
+ * complete id "Alice Meyer <ab@email.com>")
+ */
+ boolean encryptWithPublicKey(in Bundle params, out Bundle returnVals);
+
+ /* ********************************************************
+ * Decryption
+ * ********************************************************/
+
+ /* Bundle params:
+ * (required)
+ * String "MESSAGE" = Message to decrypt
+ *
+ * (optional)
+ * String "SYMMETRIC_PASSPHRASE" = Symmetric passphrase for decryption
+ *
+ * (optional)
+ * String "PRIVATE_KEY_PASSPHRASE" = Private keys's passphrase on asymmetric encryption
+ *
+ * Bundle return_vals:
+ * String "RESULT" = Decrypted message
+ */
+ boolean decrypt(in Bundle params, out Bundle returnVals);
+
+ /* ********************************************************
+ * Get key information
+ * ********************************************************/
+
+ /* Get info about all available keys
+ *
+ * Bundle params:
+ * (required)
+ * int "KEY_TYPE" = info about what type of keys to return
+ * 0: public keys
+ * 1: private keys
+ *
+ * Returns:
+ * StringArrayList "FINGERPRINTS" = Short fingerprints of keys
+ *
+ * StringArrayList "USER_IDS" = User ids of corresponding fingerprints
+ * (order is the same as in FINGERPRINTS)
+ */
+ boolean getKeys(in Bundle params, out Bundle returnVals);
+
+} \ No newline at end of file
diff --git a/src/org/thialfihar/android/apg/Preferences.java b/src/org/thialfihar/android/apg/Preferences.java
index 36325a126..e704d79f3 100644
--- a/src/org/thialfihar/android/apg/Preferences.java
+++ b/src/org/thialfihar/android/apg/Preferences.java
@@ -13,7 +13,12 @@ public class Preferences {
private SharedPreferences mSharedPreferences;
public static synchronized Preferences getPreferences(Context context) {
- if (mPreferences == null) {
+ return getPreferences(context, false);
+ }
+
+ public static synchronized Preferences getPreferences(Context context, boolean force_new)
+ {
+ if (mPreferences == null || force_new) {
mPreferences = new Preferences(context);
}
return mPreferences;
diff --git a/src/org/thialfihar/android/apg/utils/ApgCon.java b/src/org/thialfihar/android/apg/utils/ApgCon.java
new file mode 100644
index 000000000..475f7e9a9
--- /dev/null
+++ b/src/org/thialfihar/android/apg/utils/ApgCon.java
@@ -0,0 +1,761 @@
+/*
+ * Copyright (C) 2011 Markus Doits <markus.doits@googlemail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.thialfihar.android.apg.utils;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+
+import android.content.Context;
+import android.content.ComponentName;
+import android.content.ServiceConnection;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
+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;
+
+/**
+ * A APG-AIDL-Wrapper
+ *
+ * <p>
+ * This class can be used by other projects to simplify connecting to the
+ * APG-AIDL-Service. Kind of wrapper of for AIDL.
+ * </p>
+ *
+ * <p>
+ * It is not used in this project.
+ * </p>
+ *
+ * @author Markus Doits <markus.doits@googlemail.com>
+ * @version 1.0rc1
+ *
+ */
+public class ApgCon {
+ private static final boolean LOCAL_LOGV = true;
+ 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
+
+ /**
+ * How many seconds to wait for a connection to AGP when connecting.
+ * Being unsuccessful for this number of seconds, a connection
+ * is assumed to be failed.
+ */
+ public int secondsToWaitForConnection = 15;
+
+ private class CallAsync extends AsyncTask<String, Void, Void> {
+
+ @Override
+ protected Void doInBackground(String... arg) {
+ if( LOCAL_LOGD ) Log.d(TAG, "Async execution starting");
+ call(arg[0]);
+ return null;
+ }
+
+ protected void onPostExecute(Void res) {
+ if( LOCAL_LOGD ) Log.d(TAG, "Async execution finished");
+ mAsyncRunning = false;
+
+ }
+
+ }
+
+
+ private final Context mContext;
+ private final error mConnectionStatus;
+ private boolean mAsyncRunning = false;
+ private OnCallFinishListener mOnCallFinishListener;
+
+ private final Bundle mResult = new Bundle();
+ private final Bundle mArgs = new Bundle();
+ private final ArrayList<String> mErrorList = new ArrayList<String>();
+ private final ArrayList<String> mWarningList = new ArrayList<String>();
+
+ /** Remote service for decrypting and encrypting data */
+ private IApgService mApgService = null;
+
+ /** Set apgService accordingly to connection status */
+ private ServiceConnection mApgConnection = new ServiceConnection() {
+ public void onServiceConnected(ComponentName className, IBinder service) {
+ if( LOCAL_LOGD ) Log.d(TAG, "IApgService bound to apgService");
+ mApgService = IApgService.Stub.asInterface(service);
+ }
+
+ public void onServiceDisconnected(ComponentName className) {
+ if( LOCAL_LOGD ) Log.d(TAG, "IApgService disconnected");
+ mApgService = null;
+ }
+ };
+
+ /**
+ * Different types of local errors
+ */
+ public static enum error {
+ /**
+ * no error
+ */
+ NO_ERROR,
+ /**
+ * generic error
+ */
+ GENERIC,
+ /**
+ * connection to apg service not possible
+ */
+ CANNOT_BIND_TO_APG,
+ /**
+ * function to call not provided
+ */
+ CALL_MISSING,
+ /**
+ * apg service does not know what to do
+ */
+ CALL_NOT_KNOWN,
+ /**
+ * could not find APG being installed
+ */
+ APG_NOT_FOUND,
+ /**
+ * found APG but without AIDL interface
+ */
+ APG_AIDL_MISSING,
+ /**
+ * found APG but with wrong API
+ */
+ APG_API_MISSMATCH
+ }
+
+ private static enum ret {
+ ERROR, // returned from AIDL
+ RESULT, // returned from AIDL
+ WARNINGS, // mixed AIDL and LOCAL
+ ERRORS, // mixed AIDL and LOCAL
+ }
+
+ /**
+ * Constructor
+ *
+ * <p>
+ * Creates a new ApgCon object and searches for the right APG version on
+ * initialization. If not found, errors are printed to the error log.
+ * </p>
+ *
+ * @param ctx
+ * the running context
+ */
+ public ApgCon(Context ctx) {
+ if( LOCAL_LOGV ) Log.v(TAG, "EncryptionService created");
+ mContext = ctx;
+
+ error tmpError = null;
+ try {
+ if( LOCAL_LOGV ) Log.v(TAG, "Searching for the right APG version");
+ ServiceInfo apgServices[] = ctx.getPackageManager().getPackageInfo("org.thialfihar.android.apg",
+ PackageManager.GET_SERVICES | PackageManager.GET_META_DATA).services;
+ if (apgServices == null) {
+ Log.e(TAG, "Could not fetch services");
+ tmpError = error.GENERIC;
+ } else {
+ boolean apgServiceFound = false;
+ for (ServiceInfo inf : apgServices) {
+ if( LOCAL_LOGV ) Log.v(TAG, "Found service of APG: " + inf.name);
+ if (inf.name.equals("org.thialfihar.android.apg.ApgService")) {
+ apgServiceFound = true;
+ if (inf.metaData == null) {
+ Log.w(TAG, "Could not determine ApgService API");
+ Log.w(TAG, "This probably won't work!");
+ 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, "This probably won't work!");
+ 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");
+ tmpError = error.NO_ERROR;
+ }
+ }
+ }
+
+ if (!apgServiceFound) {
+ Log.e(TAG, "Could not find APG with AIDL interface, this probably won't work");
+ mErrorList.add("(LOCAL) Could not find APG with AIDL interface, this probably won't work");
+ mResult.putInt(ret.ERROR.name(), error.APG_AIDL_MISSING.ordinal());
+ tmpError = error.APG_NOT_FOUND;
+ }
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, "Could not find APG, is it installed?", e);
+ mErrorList.add("(LOCAL) Could not find APG, is it installed?");
+ mResult.putInt(ret.ERROR.name(), error.APG_NOT_FOUND.ordinal());
+ tmpError = error.APG_NOT_FOUND;
+ }
+
+ mConnectionStatus = tmpError;
+
+ }
+
+ /** try to connect to the apg service */
+ private boolean connect() {
+ if( LOCAL_LOGV ) Log.v(TAG, "trying to bind the apgService to context");
+
+ if (mApgService != null) {
+ if( LOCAL_LOGV ) Log.v(TAG, "allready connected");
+ return true;
+ }
+
+ try {
+ mContext.bindService(new Intent(IApgService.class.getName()), mApgConnection, Context.BIND_AUTO_CREATE);
+ } catch (Exception e) {
+ Log.e(TAG, "could not bind APG service", e);
+ return false;
+ }
+
+ int waitCount = 0;
+ while (mApgService == null && waitCount++ < secondsToWaitForConnection) {
+ if( LOCAL_LOGV ) Log.v(TAG, "sleeping 1 second to wait for apg");
+ android.os.SystemClock.sleep(1000);
+ }
+
+ if (waitCount >= secondsToWaitForConnection) {
+ if( LOCAL_LOGV ) Log.v(TAG, "slept waiting for nothing!");
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Disconnects ApgCon from Apg
+ *
+ * <p>
+ * This should be called whenever all work with APG is done (e.g. everything
+ * you wanted to encrypt is encrypted), since connections with AIDL should
+ * not be upheld indefinitely.
+ * <p>
+ *
+ * <p>
+ * Also, if you destroy you end using your ApgCon-instance, this must be
+ * called or else the connection to APG is leaked
+ * </p>
+ */
+ public void disconnect() {
+ if( LOCAL_LOGV ) Log.v(TAG, "disconnecting apgService");
+ if (mApgService != null) {
+ mContext.unbindService(mApgConnection);
+ mApgService = null;
+ }
+ }
+
+ private boolean initialize() {
+ if (mApgService == null) {
+ if (!connect()) {
+ if( LOCAL_LOGV ) Log.v(TAG, "connection to apg service failed");
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Calls a function from APG's AIDL-interface
+ *
+ * <p>
+ * After you have set up everything with {@link #setArg(String, String)}
+ * (and variants), you can call a function of the AIDL-interface. This
+ * will:
+ * <ul>
+ * <li>start connection to the remote interface (if not already connected)</li>
+ * <li>call the function passed with all parameters synchronously</li>
+ * <li>set up everything to retrieve the result and/or warnings/errors</li>
+ * <li>call the callback if provided
+ * </ul>
+ * </p>
+ *
+ * <p>
+ * Note your thread will be blocked during execution - if you want to call
+ * the function asynchronously, see {@link #callAsync(String)}.
+ * </p>
+ *
+ * @param function
+ * a remote function to call
+ * @return true, if call successful (= no errors), else false
+ *
+ * @see #callAsync(String)
+ * @see #setArg(String, String)
+ * @see #setOnCallFinishListener(OnCallFinishListener)
+ */
+ public boolean call(String function) {
+ boolean success = this.call(function, mArgs, mResult);
+ if (mOnCallFinishListener != null) {
+ try {
+ if( LOCAL_LOGD ) Log.d(TAG, "About to execute callback");
+ mOnCallFinishListener.onCallFinish(mResult);
+ if( LOCAL_LOGD ) Log.d(TAG, "Callback executed");
+ } catch (Exception e) {
+ Log.w(TAG, "Exception on callback: (" + e.getClass() + ") " + e.getMessage(), e);
+ mWarningList.add("(LOCAL) Could not execute callback (" + e.getClass() + "): " + e.getMessage());
+ }
+ }
+ return success;
+ }
+
+ /**
+ * Calls a function of remote interface asynchronously
+ *
+ * <p>
+ * This does exactly the same as {@link #call(String)}, but asynchronously.
+ * While connection to APG and work are done in background, your thread can
+ * go on executing.
+ * <p>
+ *
+ * <p>
+ * To see whether the task is finished, you have two possibilities:
+ * <ul>
+ * <li>In your thread, poll {@link #isRunning()}</li>
+ * <li>Supply a callback with {@link #setOnCallFinishListener(OnCallFinishListener)}</li>
+ * </ul>
+ * </p>
+ *
+ * @param function
+ * a remote function to call
+ *
+ * @see #call(String)
+ * @see #isRunning()
+ * @see #setOnCallFinishListener(OnCallFinishListener)
+ */
+ public void callAsync(String function) {
+ mAsyncRunning = true;
+ new CallAsync().execute(function);
+ }
+
+ private boolean call(String function, Bundle pArgs, Bundle pReturn) {
+
+ if (!initialize()) {
+ mErrorList.add("(LOCAL) Cannot bind to ApgService");
+ mResult.putInt(ret.ERROR.name(), error.CANNOT_BIND_TO_APG.ordinal());
+ return false;
+ }
+
+ if (function == null || function.length() == 0) {
+ mErrorList.add("(LOCAL) Function to call missing");
+ mResult.putInt(ret.ERROR.name(), error.CALL_MISSING.ordinal());
+ return false;
+ }
+
+ try {
+ Boolean success = (Boolean) IApgService.class.getMethod(function, Bundle.class, Bundle.class).invoke(mApgService, pArgs, pReturn);
+ mErrorList.addAll(pReturn.getStringArrayList(ret.ERRORS.name()));
+ mWarningList.addAll(pReturn.getStringArrayList(ret.WARNINGS.name()));
+ return success;
+ } catch (NoSuchMethodException e) {
+ Log.e(TAG, "Remote call not known (" + function + "): " + e.getMessage(), e);
+ mErrorList.add("(LOCAL) Remote call not known (" + function + "): " + e.getMessage());
+ mResult.putInt(ret.ERROR.name(), error.CALL_NOT_KNOWN.ordinal());
+ return false;
+ } catch (InvocationTargetException e) {
+ Throwable orig = e.getTargetException();
+ Log.w(TAG, "Exception of type '" + orig.getClass() + "' on AIDL call '" + function + "': " + orig.getMessage(), orig);
+ mErrorList.add("(LOCAL) Exception of type '" + orig.getClass() + "' on AIDL call '" + function + "': " + orig.getMessage());
+ return false;
+ } catch (Exception e) {
+ Log.e(TAG, "Generic error (" + e.getClass() + "): " + e.getMessage(), e);
+ mErrorList.add("(LOCAL) Generic error (" + e.getClass() + "): " + e.getMessage());
+ mResult.putInt(ret.ERROR.name(), error.GENERIC.ordinal());
+ return false;
+ }
+
+ }
+
+ /**
+ * Set a string argument for APG
+ *
+ * <p>
+ * This defines a string argument for APG's AIDL-interface.
+ * </p>
+ *
+ * <p>
+ * To know what key-value-pairs are possible (or required), take a look into
+ * the IApgService.aidl
+ * </p>
+ *
+ * <p>
+ * Note that parameters are not reseted after a call, so you have to
+ * reset ({@link #clearArgs()}) them manually if you want to.
+ * </p>
+ *
+ *
+ * @param key
+ * the key
+ * @param val
+ * the value
+ *
+ * @see #clearArgs()
+ */
+ public void setArg(String key, String val) {
+ mArgs.putString(key, val);
+ }
+
+ /**
+ * Set a string-array argument for APG
+ *
+ * <p>
+ * If the AIDL-parameter is an {@literal ArrayList<String>}, you have to use
+ * this function.
+ * </p>
+ *
+ * <code>
+ * <pre>
+ * setArg("a key", new String[]{ "entry 1", "entry 2" });
+ * </pre>
+ * </code>
+ *
+ * @param key
+ * the key
+ * @param vals
+ * the value
+ *
+ * @see #setArg(String, String)
+ */
+ public void setArg(String key, String vals[]) {
+ ArrayList<String> list = new ArrayList<String>();
+ for (String val : vals) {
+ list.add(val);
+ }
+ mArgs.putStringArrayList(key, list);
+ }
+
+ /**
+ * Set up a boolean argument for APG
+ *
+ * @param key
+ * the key
+ * @param vals
+ * the value
+ *
+ * @see #setArg(String, String)
+ */
+ public void setArg(String key, boolean val) {
+ mArgs.putBoolean(key, val);
+ }
+
+ /**
+ * Set up a int argument for APG
+ *
+ * @param key
+ * the key
+ * @param vals
+ * the value
+ *
+ * @see #setArg(String, String)
+ */
+ public void setArg(String key, int val) {
+ mArgs.putInt(key, val);
+ }
+
+ /**
+ * Set up a int-array argument for APG
+ * <p>
+ * If the AIDL-parameter is an {@literal ArrayList<Integer>}, you have to
+ * use this function.
+ * </p>
+ *
+ * @param key
+ * the key
+ * @param vals
+ * the value
+ *
+ * @see #setArg(String, String)
+ */
+ public void setArg(String key, int vals[]) {
+ ArrayList<Integer> list = new ArrayList<Integer>();
+ for (int val : vals) {
+ list.add(val);
+ }
+ mArgs.putIntegerArrayList(key, list);
+ }
+
+ /**
+ * Clears all arguments
+ *
+ * <p>
+ * Anything the has been set up with the various
+ * {@link #setArg(String, String)} functions is cleared.
+ * </p>
+ *
+ * <p>
+ * Note that any warning, error, callback, result, etc. is NOT cleared with
+ * this.
+ * </p>
+ *
+ * @see #reset()
+ */
+ public void clearArgs() {
+ mArgs.clear();
+ }
+
+ /**
+ * Return the object associated with the key
+ *
+ * @param key
+ * the object's key you want to return
+ * @return an object at position key, or null if not set
+ */
+ public Object getArg(String key) {
+ return mArgs.get(key);
+ }
+
+ /**
+ * Iterates through the errors
+ *
+ * <p>
+ * With this method you can iterate through all errors. The errors are only
+ * returned once and deleted immediately afterwards, so you can only return
+ * each error once.
+ * </p>
+ *
+ * @return a human readable description of a error that happened, or null if
+ * no more errors
+ *
+ * @see #hasNextError()
+ * @see #clearErrors()
+ */
+ public String getNextError() {
+ if (mErrorList.size() != 0)
+ return mErrorList.remove(0);
+ else
+ return null;
+ }
+
+ /**
+ * Check if there are any new errors
+ *
+ * @return true, if there are unreturned errors, false otherwise
+ *
+ * @see #getNextError()
+ */
+ public boolean hasNextError() {
+ return mErrorList.size() != 0;
+ }
+
+ /**
+ * Get the numeric representation of the last error
+ *
+ * <p>
+ * Values <100 mean the error happened locally, values >=100 mean the error
+ * happened at the remote side (APG). See the IApgService.aidl (or get the
+ * human readable description with {@link #getNextError()}) for what
+ * errors >=100 mean.
+ * </p>
+ *
+ * @return the id of the error that happened
+ */
+ public int getError() {
+ if (mResult.containsKey(ret.ERROR.name()))
+ return mResult.getInt(ret.ERROR.name());
+ else
+ return -1;
+ }
+
+ /**
+ * Iterates through the warnings
+ *
+ * <p>
+ * With this method you can iterate through all warnings. Warnings are
+ * only returned once and deleted immediately afterwards, so you can only
+ * return each warning once.
+ * </p>
+ *
+ * @return a human readable description of a warning that happened, or null
+ * if no more warnings
+ *
+ * @see #hasNextWarning()
+ * @see #clearWarnings()
+ */
+ public String getNextWarning() {
+ if (mWarningList.size() != 0)
+ return mWarningList.remove(0);
+ else
+ return null;
+ }
+
+ /**
+ * Check if there are any new warnings
+ *
+ * @return true, if there are unreturned warnings, false otherwise
+ *
+ * @see #getNextWarning()
+ */
+ public boolean hasNextWarning() {
+ return mWarningList.size() != 0;
+ }
+
+ /**
+ * Get the result
+ *
+ * <p>
+ * This gets your result. After doing an encryption or decryption with APG,
+ * you get the output with this function.
+ * </p>
+ *
+ * <p>
+ * Note when your last remote call is unsuccessful, the result will
+ * still have the same value like the last successful call (or null, if no
+ * call was successful). To ensure you do not work with old call's results,
+ * either be sure to {@link #reset()} (or at least {@link #clearResult()})
+ * your instance before each new call or always check that
+ * {@link #hasNextError()} is false.
+ * </p>
+ *
+ * @return the mResult of the last {@link #call(String)} or
+ * {@link #callAsync(String)}.
+ *
+ * @see #reset()
+ * @see #clearResult()
+ * @see #getResultBundle()
+ */
+ public String getResult() {
+ return mResult.getString(ret.RESULT.name());
+ }
+
+ /**
+ * Get the result bundle
+ *
+ * <p>
+ * Unlike {@link #getResult()}, which only returns any en-/decrypted
+ * message, this function returns the complete information that was returned
+ * by Apg. This also includes the "RESULT", but additionally the warnings,
+ * errors and any other information.
+ * </p>
+ * <p>
+ * For warnings and errors it is suggested to use the functions that are
+ * provided here, namely {@link #getError()}, {@link #getNextError()},
+ * {@link #get_next_Warning()} etc.), but if any call returns something non
+ * standard, you have access to the complete result bundle to extract the
+ * information.
+ * </p>
+ *
+ * @return the complete result bundle of the last call to apg
+ */
+ public Bundle getResultBundle() {
+ return mResult;
+ }
+
+ public error getConnectionStatus() {
+ return mConnectionStatus;
+ }
+
+ /**
+ * Clears all unfetched errors
+ *
+ * @see #getNextError()
+ * @see #hasNextError()
+ */
+ public void clearErrors() {
+ mErrorList.clear();
+ mResult.remove(ret.ERROR.name());
+ }
+
+ /**
+ * Clears all unfetched warnings
+ *
+ * @see #getNextWarning()
+ * @see #hasNextWarning()
+ */
+ public void clearWarnings() {
+ mWarningList.clear();
+ }
+
+ /**
+ * Clears the last mResult
+ *
+ * @see #getResult()
+ */
+ public void clearResult() {
+ mResult.remove(ret.RESULT.name());
+ }
+
+ /**
+ * Set a callback listener when call to AIDL finishes
+ *
+ * @param obj
+ * a object to call back after async execution
+ * @see ApgConInterface
+ */
+ public void setOnCallFinishListener(OnCallFinishListener lis) {
+ mOnCallFinishListener = lis;
+ }
+
+ /**
+ * Clears any callback object
+ *
+ * @see #setOnCallFinishListener(OnCallFinishListener)
+ */
+ public void clearOnCallFinishListener() {
+ mOnCallFinishListener = null;
+ }
+
+ /**
+ * Checks if an async execution is running
+ *
+ * <p>
+ * If you started something with {@link #callAsync(String)}, this will
+ * return true if the task is still running
+ * </p>
+ *
+ * @return true, if an async task is still running, false otherwise
+ *
+ * @see #callAsync(String)
+ *
+ */
+ public boolean isRunning() {
+ return mAsyncRunning;
+ }
+
+ /**
+ * Completely resets your instance
+ *
+ * <p>
+ * This currently resets everything in this instance. Errors, warnings,
+ * results, callbacks, ... are removed. Any connection to the remote
+ * interface is upheld, though.
+ * </p>
+ *
+ * <p>
+ * Note when an async execution ({@link #callAsync(String)}) is
+ * running, it's result, warnings etc. will still be evaluated (which might
+ * be not what you want). Also mind that any callback you set is also
+ * reseted, so when finishing the execution any before defined callback will
+ * NOT BE TRIGGERED.
+ * </p>
+ */
+ public void reset() {
+ clearErrors();
+ clearWarnings();
+ clearArgs();
+ clearOnCallFinishListener();
+ mResult.clear();
+ }
+
+}
diff --git a/src/org/thialfihar/android/apg/utils/ApgConInterface.java b/src/org/thialfihar/android/apg/utils/ApgConInterface.java
new file mode 100644
index 000000000..27254fe95
--- /dev/null
+++ b/src/org/thialfihar/android/apg/utils/ApgConInterface.java
@@ -0,0 +1,7 @@
+package org.thialfihar.android.apg.utils;
+
+public interface ApgConInterface {
+ public static interface OnCallFinishListener {
+ public abstract void onCallFinish(android.os.Bundle result);
+ }
+}