mirror of
https://github.com/moparisthebest/open-keychain
synced 2024-12-24 07:58:50 -05:00
merged apg_service from googlecode
This commit is contained in:
commit
a942c4a507
6
.gitignore
vendored
6
.gitignore
vendored
@ -1,6 +1,6 @@
|
||||
/bin
|
||||
/gen
|
||||
bin/
|
||||
gen/
|
||||
.classpath
|
||||
.project
|
||||
.DS_Store
|
||||
.settings
|
||||
|
||||
|
@ -17,7 +17,7 @@
|
||||
<manifest
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.thialfihar.android.apg"
|
||||
android:versionCode="10900" android:versionName="1.0.9 beta 00"
|
||||
android:versionCode="10900" android:versionName="1.0.9/apg_service beta 00"
|
||||
android:installLocation="auto">
|
||||
|
||||
<application
|
||||
@ -195,11 +195,26 @@
|
||||
android:configChanges="keyboardHidden|orientation|keyboard"/>
|
||||
|
||||
<service android:name=".Service" />
|
||||
|
||||
<service
|
||||
android:name=".ApgService"
|
||||
android:enabled="true" android:exported="true"
|
||||
android:process=":remote"
|
||||
android:permission="org.thialfihar.android.apg.permission.READ_KEY_DETAILS"
|
||||
>
|
||||
<intent-filter>
|
||||
<action android:name="org.thialfihar.android.apg.IApgService"/>
|
||||
</intent-filter>
|
||||
<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" />
|
||||
@ -209,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>
|
||||
|
BIN
lib/bcprov-jdk16-146.jar
Normal file
BIN
lib/bcprov-jdk16-146.jar
Normal file
Binary file not shown.
11
project.properties
Normal file
11
project.properties
Normal file
@ -0,0 +1,11 @@
|
||||
# This file is automatically generated by Android Tools.
|
||||
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
|
||||
#
|
||||
# This file must be checked in Version Control Systems.
|
||||
#
|
||||
# To customize properties used by the Ant build system use,
|
||||
# "ant.properties", and override values to adapt the script to your
|
||||
# project structure.
|
||||
|
||||
# Project target.
|
||||
target=android-10
|
@ -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>
|
||||
|
@ -16,6 +16,64 @@
|
||||
|
||||
package org.thialfihar.android.apg;
|
||||
|
||||
import org.spongycastle.bcpg.ArmoredInputStream;
|
||||
import org.spongycastle.bcpg.ArmoredOutputStream;
|
||||
import org.spongycastle.bcpg.BCPGOutputStream;
|
||||
import org.spongycastle.bcpg.CompressionAlgorithmTags;
|
||||
import org.spongycastle.bcpg.HashAlgorithmTags;
|
||||
import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags;
|
||||
import org.spongycastle.bcpg.sig.KeyFlags;
|
||||
import org.spongycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.spongycastle.jce.spec.ElGamalParameterSpec;
|
||||
import org.spongycastle.openpgp.PGPCompressedData;
|
||||
import org.spongycastle.openpgp.PGPCompressedDataGenerator;
|
||||
import org.spongycastle.openpgp.PGPEncryptedData;
|
||||
import org.spongycastle.openpgp.PGPEncryptedDataGenerator;
|
||||
import org.spongycastle.openpgp.PGPEncryptedDataList;
|
||||
import org.spongycastle.openpgp.PGPException;
|
||||
import org.spongycastle.openpgp.PGPKeyPair;
|
||||
import org.spongycastle.openpgp.PGPKeyRingGenerator;
|
||||
import org.spongycastle.openpgp.PGPLiteralData;
|
||||
import org.spongycastle.openpgp.PGPLiteralDataGenerator;
|
||||
import org.spongycastle.openpgp.PGPObjectFactory;
|
||||
import org.spongycastle.openpgp.PGPOnePassSignature;
|
||||
import org.spongycastle.openpgp.PGPOnePassSignatureList;
|
||||
import org.spongycastle.openpgp.PGPPBEEncryptedData;
|
||||
import org.spongycastle.openpgp.PGPPrivateKey;
|
||||
import org.spongycastle.openpgp.PGPPublicKey;
|
||||
import org.spongycastle.openpgp.PGPPublicKeyEncryptedData;
|
||||
import org.spongycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.spongycastle.openpgp.PGPSecretKey;
|
||||
import org.spongycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.spongycastle.openpgp.PGPSignature;
|
||||
import org.spongycastle.openpgp.PGPSignatureGenerator;
|
||||
import org.spongycastle.openpgp.PGPSignatureList;
|
||||
import org.spongycastle.openpgp.PGPSignatureSubpacketGenerator;
|
||||
import org.spongycastle.openpgp.PGPSignatureSubpacketVector;
|
||||
import org.spongycastle.openpgp.PGPUtil;
|
||||
import org.spongycastle.openpgp.PGPV3SignatureGenerator;
|
||||
import org.thialfihar.android.apg.provider.DataProvider;
|
||||
import org.thialfihar.android.apg.provider.Database;
|
||||
import org.thialfihar.android.apg.provider.KeyRings;
|
||||
import org.thialfihar.android.apg.provider.Keys;
|
||||
import org.thialfihar.android.apg.provider.UserIds;
|
||||
import org.thialfihar.android.apg.ui.widget.KeyEditor;
|
||||
import org.thialfihar.android.apg.ui.widget.SectionView;
|
||||
import org.thialfihar.android.apg.ui.widget.UserIdEditor;
|
||||
import org.thialfihar.android.apg.utils.IterableIterator;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.os.Message;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayInputStream;
|
||||
@ -45,64 +103,6 @@ import java.util.Map;
|
||||
import java.util.Vector;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.bouncycastle2.bcpg.ArmoredInputStream;
|
||||
import org.bouncycastle2.bcpg.ArmoredOutputStream;
|
||||
import org.bouncycastle2.bcpg.BCPGOutputStream;
|
||||
import org.bouncycastle2.bcpg.CompressionAlgorithmTags;
|
||||
import org.bouncycastle2.bcpg.HashAlgorithmTags;
|
||||
import org.bouncycastle2.bcpg.SymmetricKeyAlgorithmTags;
|
||||
import org.bouncycastle2.bcpg.sig.KeyFlags;
|
||||
import org.bouncycastle2.jce.provider.BouncyCastleProvider;
|
||||
import org.bouncycastle2.jce.spec.ElGamalParameterSpec;
|
||||
import org.bouncycastle2.openpgp.PGPCompressedData;
|
||||
import org.bouncycastle2.openpgp.PGPCompressedDataGenerator;
|
||||
import org.bouncycastle2.openpgp.PGPEncryptedData;
|
||||
import org.bouncycastle2.openpgp.PGPEncryptedDataGenerator;
|
||||
import org.bouncycastle2.openpgp.PGPEncryptedDataList;
|
||||
import org.bouncycastle2.openpgp.PGPException;
|
||||
import org.bouncycastle2.openpgp.PGPKeyPair;
|
||||
import org.bouncycastle2.openpgp.PGPKeyRingGenerator;
|
||||
import org.bouncycastle2.openpgp.PGPLiteralData;
|
||||
import org.bouncycastle2.openpgp.PGPLiteralDataGenerator;
|
||||
import org.bouncycastle2.openpgp.PGPObjectFactory;
|
||||
import org.bouncycastle2.openpgp.PGPOnePassSignature;
|
||||
import org.bouncycastle2.openpgp.PGPOnePassSignatureList;
|
||||
import org.bouncycastle2.openpgp.PGPPBEEncryptedData;
|
||||
import org.bouncycastle2.openpgp.PGPPrivateKey;
|
||||
import org.bouncycastle2.openpgp.PGPPublicKey;
|
||||
import org.bouncycastle2.openpgp.PGPPublicKeyEncryptedData;
|
||||
import org.bouncycastle2.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle2.openpgp.PGPSecretKey;
|
||||
import org.bouncycastle2.openpgp.PGPSecretKeyRing;
|
||||
import org.bouncycastle2.openpgp.PGPSignature;
|
||||
import org.bouncycastle2.openpgp.PGPSignatureGenerator;
|
||||
import org.bouncycastle2.openpgp.PGPSignatureList;
|
||||
import org.bouncycastle2.openpgp.PGPSignatureSubpacketGenerator;
|
||||
import org.bouncycastle2.openpgp.PGPSignatureSubpacketVector;
|
||||
import org.bouncycastle2.openpgp.PGPUtil;
|
||||
import org.bouncycastle2.openpgp.PGPV3SignatureGenerator;
|
||||
import org.thialfihar.android.apg.provider.DataProvider;
|
||||
import org.thialfihar.android.apg.provider.Database;
|
||||
import org.thialfihar.android.apg.provider.KeyRings;
|
||||
import org.thialfihar.android.apg.provider.Keys;
|
||||
import org.thialfihar.android.apg.provider.UserIds;
|
||||
import org.thialfihar.android.apg.ui.widget.KeyEditor;
|
||||
import org.thialfihar.android.apg.ui.widget.SectionView;
|
||||
import org.thialfihar.android.apg.ui.widget.UserIdEditor;
|
||||
import org.thialfihar.android.apg.utils.IterableIterator;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.os.Message;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
public class Apg {
|
||||
private static final String mApgPackageName = "org.thialfihar.android.apg";
|
||||
|
||||
@ -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;
|
||||
}
|
||||
@ -1129,11 +1145,8 @@ public class Apg {
|
||||
if (keyRing == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return keyRing.getPublicKey(keyId);
|
||||
} catch (PGPException e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static Vector<Integer> getKeyRingIds(int type) {
|
||||
@ -1236,14 +1249,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 +1281,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 +1323,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 +1341,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 +1365,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 +1405,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 +1433,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 +1477,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 +1525,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 +1558,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 +1602,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 +1697,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 +1733,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 +1762,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 +1775,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 +1789,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 +1799,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 +1837,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 +1874,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 = signatureList.get(signatureIndex);
|
||||
if (signature.verify(messageSignature)) {
|
||||
@ -1853,7 +1893,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 +1904,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 +1918,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 +1944,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 +1992,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 +2019,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 +2278,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;
|
||||
|
594
src/org/thialfihar/android/apg/ApgService.java
Normal file
594
src/org/thialfihar/android/apg/ApgService.java
Normal file
@ -0,0 +1,594 @@
|
||||
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;
|
||||
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.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";
|
||||
public static final boolean LOCAL_LOGV = true;
|
||||
public static final boolean LOCAL_LOGD = true;
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
if( LOCAL_LOGD ) Log.d(TAG, "bound");
|
||||
return mBinder;
|
||||
}
|
||||
|
||||
/** error status */
|
||||
private static enum error {
|
||||
ARGUMENTS_MISSING,
|
||||
APG_FAILURE,
|
||||
NO_MATCHING_SECRET_KEY,
|
||||
PRIVATE_KEY_PASSPHRASE_WRONG,
|
||||
PRIVATE_KEY_PASSPHRASE_MISSING;
|
||||
|
||||
public int shiftedOrdinal() {
|
||||
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 */
|
||||
public static 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)
|
||||
BLOB, // blob passed
|
||||
}
|
||||
|
||||
/** all things that might be returned */
|
||||
private static 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);
|
||||
FUNCTIONS_REQUIRED_ARGS.put(call.encrypt_with_passphrase.name(), args);
|
||||
|
||||
args = new HashSet<arg>();
|
||||
args.add(arg.PUBLIC_KEYS);
|
||||
FUNCTIONS_REQUIRED_ARGS.put(call.encrypt_with_public_key.name(), args);
|
||||
|
||||
args = new HashSet<arg>();
|
||||
FUNCTIONS_REQUIRED_ARGS.put(call.decrypt.name(), args);
|
||||
|
||||
args = new HashSet<arg>();
|
||||
args.add(arg.KEY_TYPE);
|
||||
FUNCTIONS_REQUIRED_ARGS.put(call.get_keys.name(), 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);
|
||||
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);
|
||||
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 */
|
||||
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 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
|
||||
+ "." + 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
boolean isBlob = pArgs.containsKey(arg.BLOB.name());
|
||||
|
||||
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 = 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();
|
||||
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");
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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 = 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");
|
||||
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");
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
};
|
||||
}
|
@ -16,10 +16,11 @@
|
||||
|
||||
package org.thialfihar.android.apg;
|
||||
|
||||
import org.bouncycastle2.jce.provider.BouncyCastleProvider;
|
||||
import org.bouncycastle2.openpgp.PGPException;
|
||||
import org.bouncycastle2.openpgp.PGPPrivateKey;
|
||||
import org.bouncycastle2.openpgp.PGPSecretKey;
|
||||
import org.spongycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.spongycastle.openpgp.PGPException;
|
||||
import org.spongycastle.openpgp.PGPPrivateKey;
|
||||
import org.spongycastle.openpgp.PGPSecretKey;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
|
@ -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";
|
||||
}
|
||||
|
@ -16,19 +16,9 @@
|
||||
|
||||
package org.thialfihar.android.apg;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.security.Security;
|
||||
import java.security.SignatureException;
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
import org.bouncycastle2.jce.provider.BouncyCastleProvider;
|
||||
import org.bouncycastle2.openpgp.PGPException;
|
||||
import org.bouncycastle2.openpgp.PGPPublicKeyRing;
|
||||
import org.spongycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.spongycastle.openpgp.PGPException;
|
||||
import org.spongycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.thialfihar.android.apg.provider.DataProvider;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
@ -40,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;
|
||||
@ -53,6 +44,16 @@ import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import android.widget.ViewFlipper;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.security.Security;
|
||||
import java.security.SignatureException;
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
public class DecryptActivity extends BaseActivity {
|
||||
private long mSignatureKeyId = 0;
|
||||
|
||||
@ -186,19 +187,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", " ");
|
||||
@ -206,11 +216,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!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,15 +16,9 @@
|
||||
|
||||
package org.thialfihar.android.apg;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.security.SignatureException;
|
||||
import java.util.Vector;
|
||||
|
||||
import org.bouncycastle2.openpgp.PGPException;
|
||||
import org.bouncycastle2.openpgp.PGPSecretKey;
|
||||
import org.bouncycastle2.openpgp.PGPSecretKeyRing;
|
||||
import org.spongycastle.openpgp.PGPException;
|
||||
import org.spongycastle.openpgp.PGPSecretKey;
|
||||
import org.spongycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.thialfihar.android.apg.provider.Database;
|
||||
import org.thialfihar.android.apg.ui.widget.KeyEditor;
|
||||
import org.thialfihar.android.apg.ui.widget.SectionView;
|
||||
@ -46,6 +40,12 @@ import android.widget.EditText;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.security.SignatureException;
|
||||
import java.util.Vector;
|
||||
|
||||
public class EditKeyActivity extends BaseActivity implements OnClickListener {
|
||||
|
||||
private PGPSecretKeyRing mKeyRing = null;
|
||||
|
@ -16,21 +16,11 @@
|
||||
|
||||
package org.thialfihar.android.apg;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.security.SignatureException;
|
||||
import java.util.Vector;
|
||||
|
||||
import org.bouncycastle2.openpgp.PGPException;
|
||||
import org.bouncycastle2.openpgp.PGPPublicKey;
|
||||
import org.bouncycastle2.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle2.openpgp.PGPSecretKey;
|
||||
import org.bouncycastle2.openpgp.PGPSecretKeyRing;
|
||||
import org.spongycastle.openpgp.PGPException;
|
||||
import org.spongycastle.openpgp.PGPPublicKey;
|
||||
import org.spongycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.spongycastle.openpgp.PGPSecretKey;
|
||||
import org.spongycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.thialfihar.android.apg.provider.DataProvider;
|
||||
import org.thialfihar.android.apg.utils.Choice;
|
||||
|
||||
@ -55,6 +45,16 @@ import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import android.widget.ViewFlipper;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.security.SignatureException;
|
||||
import java.util.Vector;
|
||||
|
||||
public class EncryptActivity extends BaseActivity {
|
||||
private Intent mIntent = null;
|
||||
private String mSubject = null;
|
||||
|
125
src/org/thialfihar/android/apg/IApgService.aidl
Normal file
125
src/org/thialfihar/android/apg/IApgService.aidl
Normal file
@ -0,0 +1,125 @@
|
||||
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
|
||||
* 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
|
||||
* 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):
|
||||
* If "MESSAGE" was set:
|
||||
* 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 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
|
||||
*
|
||||
* (optional)
|
||||
* 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);
|
||||
|
||||
/* ********************************************************
|
||||
* 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);
|
||||
|
||||
}
|
@ -16,9 +16,12 @@
|
||||
|
||||
package org.thialfihar.android.apg;
|
||||
|
||||
import org.bouncycastle2.bcpg.CompressionAlgorithmTags;
|
||||
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;
|
||||
|
@ -16,19 +16,9 @@
|
||||
|
||||
package org.thialfihar.android.apg;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Vector;
|
||||
|
||||
import org.bouncycastle2.openpgp.PGPException;
|
||||
import org.bouncycastle2.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle2.openpgp.PGPSecretKeyRing;
|
||||
import org.spongycastle.openpgp.PGPException;
|
||||
import org.spongycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.spongycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.thialfihar.android.apg.provider.KeyRings;
|
||||
import org.thialfihar.android.apg.provider.Keys;
|
||||
import org.thialfihar.android.apg.provider.UserIds;
|
||||
@ -58,6 +48,16 @@ import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Vector;
|
||||
|
||||
public class KeyListActivity extends BaseActivity {
|
||||
protected ExpandableListView mList;
|
||||
protected KeyListAdapter mListAdapter;
|
||||
|
@ -1,19 +1,24 @@
|
||||
package org.thialfihar.android.apg;
|
||||
|
||||
import java.util.Vector;
|
||||
|
||||
import org.bouncycastle2.bcpg.HashAlgorithmTags;
|
||||
import org.bouncycastle2.openpgp.PGPEncryptedData;
|
||||
import org.spongycastle.bcpg.HashAlgorithmTags;
|
||||
import org.spongycastle.openpgp.PGPEncryptedData;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import java.util.Vector;
|
||||
|
||||
public class Preferences {
|
||||
private static Preferences mPreferences;
|
||||
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;
|
||||
|
@ -16,12 +16,8 @@
|
||||
|
||||
package org.thialfihar.android.apg;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Vector;
|
||||
|
||||
import org.bouncycastle2.bcpg.HashAlgorithmTags;
|
||||
import org.bouncycastle2.openpgp.PGPEncryptedData;
|
||||
import org.spongycastle.bcpg.HashAlgorithmTags;
|
||||
import org.spongycastle.openpgp.PGPEncryptedData;
|
||||
import org.thialfihar.android.apg.ui.widget.IntegerListPreference;
|
||||
|
||||
import android.content.Intent;
|
||||
@ -32,6 +28,10 @@ import android.preference.Preference;
|
||||
import android.preference.PreferenceActivity;
|
||||
import android.preference.PreferenceScreen;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Vector;
|
||||
|
||||
public class PreferencesActivity extends PreferenceActivity {
|
||||
private ListPreference mLanguage = null;
|
||||
private IntegerListPreference mPassPhraseCacheTtl = null;
|
||||
|
@ -16,7 +16,7 @@
|
||||
|
||||
package org.thialfihar.android.apg;
|
||||
|
||||
import org.bouncycastle2.openpgp.PGPPublicKeyRing;
|
||||
import org.spongycastle.openpgp.PGPPublicKeyRing;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -1,15 +1,10 @@
|
||||
package org.thialfihar.android.apg.provider;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Vector;
|
||||
|
||||
import org.bouncycastle2.openpgp.PGPException;
|
||||
import org.bouncycastle2.openpgp.PGPPublicKey;
|
||||
import org.bouncycastle2.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle2.openpgp.PGPSecretKey;
|
||||
import org.bouncycastle2.openpgp.PGPSecretKeyRing;
|
||||
import org.spongycastle.openpgp.PGPException;
|
||||
import org.spongycastle.openpgp.PGPPublicKey;
|
||||
import org.spongycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.spongycastle.openpgp.PGPSecretKey;
|
||||
import org.spongycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.thialfihar.android.apg.Apg;
|
||||
import org.thialfihar.android.apg.Id;
|
||||
import org.thialfihar.android.apg.utils.IterableIterator;
|
||||
@ -21,6 +16,11 @@ import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Vector;
|
||||
|
||||
public class Database extends SQLiteOpenHelper {
|
||||
public static class GeneralException extends Exception {
|
||||
static final long serialVersionUID = 0xf812773343L;
|
||||
|
@ -16,14 +16,8 @@
|
||||
|
||||
package org.thialfihar.android.apg.ui.widget;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.Vector;
|
||||
|
||||
import org.bouncycastle2.openpgp.PGPPublicKey;
|
||||
import org.bouncycastle2.openpgp.PGPSecretKey;
|
||||
import org.spongycastle.openpgp.PGPPublicKey;
|
||||
import org.spongycastle.openpgp.PGPSecretKey;
|
||||
import org.thialfihar.android.apg.Apg;
|
||||
import org.thialfihar.android.apg.Id;
|
||||
import org.thialfihar.android.apg.R;
|
||||
@ -45,6 +39,12 @@ import android.widget.LinearLayout;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.Vector;
|
||||
|
||||
public class KeyEditor extends LinearLayout implements Editor, OnClickListener {
|
||||
private PGPSecretKey mKey;
|
||||
|
||||
|
@ -16,14 +16,8 @@
|
||||
|
||||
package org.thialfihar.android.apg.ui.widget;
|
||||
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidParameterException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.util.Vector;
|
||||
|
||||
import org.bouncycastle2.openpgp.PGPException;
|
||||
import org.bouncycastle2.openpgp.PGPSecretKey;
|
||||
import org.spongycastle.openpgp.PGPException;
|
||||
import org.spongycastle.openpgp.PGPSecretKey;
|
||||
import org.thialfihar.android.apg.Apg;
|
||||
import org.thialfihar.android.apg.Id;
|
||||
import org.thialfihar.android.apg.R;
|
||||
@ -49,6 +43,12 @@ import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidParameterException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.util.Vector;
|
||||
|
||||
public class SectionView extends LinearLayout implements OnClickListener, EditorListener, Runnable {
|
||||
private LayoutInflater mInflater;
|
||||
private View mAdd;
|
||||
|
836
src/org/thialfihar/android/apg/utils/ApgCon.java
Normal file
836
src/org/thialfihar/android/apg/utils/ApgCon.java
Normal file
@ -0,0 +1,836 @@
|
||||
/*
|
||||
* 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 org.thialfihar.android.apg.IApgService;
|
||||
import org.thialfihar.android.apg.utils.ApgConInterface.OnCallFinishListener;
|
||||
|
||||
import android.content.ComponentName;
|
||||
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 java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* 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.1rc1
|
||||
*
|
||||
*/
|
||||
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 = 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.
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
* <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>
|
||||
*
|
||||
* <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>
|
||||
* 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();
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user