diff options
author | Dominik <dominik@dominikschuermann.de> | 2012-03-09 10:51:38 +0100 |
---|---|---|
committer | Dominik <dominik@dominikschuermann.de> | 2012-03-09 10:51:38 +0100 |
commit | a942c4a507a0df871c43d69076689ad1dd659e10 (patch) | |
tree | a78a06e665b5ecb415c635fc9d776f7a882a8d14 /src | |
parent | 3ec27e07aead6f548d62bde55220d20337f1c570 (diff) | |
parent | ad1657465778c05cef7a7ba9d25f97943cf68144 (diff) | |
download | open-keychain-a942c4a507a0df871c43d69076689ad1dd659e10.tar.gz open-keychain-a942c4a507a0df871c43d69076689ad1dd659e10.tar.bz2 open-keychain-a942c4a507a0df871c43d69076689ad1dd659e10.zip |
merged apg_service from googlecode
Diffstat (limited to 'src')
20 files changed, 2029 insertions, 203 deletions
diff --git a/src/org/thialfihar/android/apg/Apg.java b/src/org/thialfihar/android/apg/Apg.java index 860893075..f45973af1 100644 --- a/src/org/thialfihar/android/apg/Apg.java +++ b/src/org/thialfihar/android/apg/Apg.java @@ -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; diff --git a/src/org/thialfihar/android/apg/ApgService.java b/src/org/thialfihar/android/apg/ApgService.java new file mode 100644 index 000000000..46d8b4765 --- /dev/null +++ b/src/org/thialfihar/android/apg/ApgService.java @@ -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; + } + + }; +} diff --git a/src/org/thialfihar/android/apg/AskForSecretKeyPassPhrase.java b/src/org/thialfihar/android/apg/AskForSecretKeyPassPhrase.java index bc9ddfdaf..f473b157b 100644 --- a/src/org/thialfihar/android/apg/AskForSecretKeyPassPhrase.java +++ b/src/org/thialfihar/android/apg/AskForSecretKeyPassPhrase.java @@ -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; diff --git a/src/org/thialfihar/android/apg/Constants.java b/src/org/thialfihar/android/apg/Constants.java index 89751e268..bd0746085 100644 --- a/src/org/thialfihar/android/apg/Constants.java +++ b/src/org/thialfihar/android/apg/Constants.java @@ -19,6 +19,9 @@ package org.thialfihar.android.apg; import android.os.Environment; public final class Constants { + + public static final String tag = "APG"; + public static final class path { public static final String app_dir = Environment.getExternalStorageDirectory() + "/APG"; } diff --git a/src/org/thialfihar/android/apg/DecryptActivity.java b/src/org/thialfihar/android/apg/DecryptActivity.java index 5f0a26451..ca7562801 100644 --- a/src/org/thialfihar/android/apg/DecryptActivity.java +++ b/src/org/thialfihar/android/apg/DecryptActivity.java @@ -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!"); } } } diff --git a/src/org/thialfihar/android/apg/EditKeyActivity.java b/src/org/thialfihar/android/apg/EditKeyActivity.java index 0d8e874b1..6f81ecf2e 100644 --- a/src/org/thialfihar/android/apg/EditKeyActivity.java +++ b/src/org/thialfihar/android/apg/EditKeyActivity.java @@ -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; diff --git a/src/org/thialfihar/android/apg/EncryptActivity.java b/src/org/thialfihar/android/apg/EncryptActivity.java index 197745a4d..62f7ff09c 100644 --- a/src/org/thialfihar/android/apg/EncryptActivity.java +++ b/src/org/thialfihar/android/apg/EncryptActivity.java @@ -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; diff --git a/src/org/thialfihar/android/apg/IApgService.aidl b/src/org/thialfihar/android/apg/IApgService.aidl new file mode 100644 index 000000000..25780f366 --- /dev/null +++ b/src/org/thialfihar/android/apg/IApgService.aidl @@ -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); + +}
\ No newline at end of file diff --git a/src/org/thialfihar/android/apg/Id.java b/src/org/thialfihar/android/apg/Id.java index ebc1ecf87..2b486e84a 100644 --- a/src/org/thialfihar/android/apg/Id.java +++ b/src/org/thialfihar/android/apg/Id.java @@ -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; diff --git a/src/org/thialfihar/android/apg/KeyListActivity.java b/src/org/thialfihar/android/apg/KeyListActivity.java index b9e0087d7..8a1788eab 100644 --- a/src/org/thialfihar/android/apg/KeyListActivity.java +++ b/src/org/thialfihar/android/apg/KeyListActivity.java @@ -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; diff --git a/src/org/thialfihar/android/apg/Preferences.java b/src/org/thialfihar/android/apg/Preferences.java index 36325a126..198294377 100644 --- a/src/org/thialfihar/android/apg/Preferences.java +++ b/src/org/thialfihar/android/apg/Preferences.java @@ -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; diff --git a/src/org/thialfihar/android/apg/PreferencesActivity.java b/src/org/thialfihar/android/apg/PreferencesActivity.java index 0d0d76318..49ea1a983 100644 --- a/src/org/thialfihar/android/apg/PreferencesActivity.java +++ b/src/org/thialfihar/android/apg/PreferencesActivity.java @@ -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; diff --git a/src/org/thialfihar/android/apg/PublicKeyListActivity.java b/src/org/thialfihar/android/apg/PublicKeyListActivity.java index 4cc3b4df9..509a81374 100644 --- a/src/org/thialfihar/android/apg/PublicKeyListActivity.java +++ b/src/org/thialfihar/android/apg/PublicKeyListActivity.java @@ -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; diff --git a/src/org/thialfihar/android/apg/provider/ApgServiceBlobDatabase.java b/src/org/thialfihar/android/apg/provider/ApgServiceBlobDatabase.java new file mode 100644 index 000000000..9a891ddfa --- /dev/null +++ b/src/org/thialfihar/android/apg/provider/ApgServiceBlobDatabase.java @@ -0,0 +1,54 @@ +package org.thialfihar.android.apg.provider; + +import org.thialfihar.android.apg.ApgService; + +import android.content.ContentUris; +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.net.Uri; +import android.util.Log; + +public class ApgServiceBlobDatabase extends SQLiteOpenHelper { + + private static final String TAG = "ApgServiceBlobDatabase"; + + private static final int VERSION = 1; + private static final String NAME = "apg_service_blob_data"; + private static final String TABLE = "data"; + + public ApgServiceBlobDatabase(Context context) { + super(context, NAME, null, VERSION); + if(ApgService.LOCAL_LOGD) Log.d(TAG, "constructor called"); + } + + @Override + public void onCreate(SQLiteDatabase db) { + if(ApgService.LOCAL_LOGD) Log.d(TAG, "onCreate() called"); + db.execSQL("create table " + TABLE + " ( _id integer primary key autoincrement," + + "key text not null)"); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + if(ApgService.LOCAL_LOGD) Log.d(TAG, "onUpgrade() called"); + // no upgrade necessary yet + } + + public Uri insert(ContentValues vals) { + if(ApgService.LOCAL_LOGD) Log.d(TAG, "insert() called"); + SQLiteDatabase db = this.getWritableDatabase(); + long newId = db.insert(TABLE, null, vals); + return ContentUris.withAppendedId(ApgServiceBlobProvider.CONTENT_URI, newId); + } + + public Cursor query(String id, String key) { + if(ApgService.LOCAL_LOGD) Log.d(TAG, "query() called"); + SQLiteDatabase db = this.getReadableDatabase(); + return db.query(TABLE, new String[] {"_id"}, + "_id = ? and key = ?", new String[] {id, key}, + null, null, null); + } +} diff --git a/src/org/thialfihar/android/apg/provider/ApgServiceBlobProvider.java b/src/org/thialfihar/android/apg/provider/ApgServiceBlobProvider.java new file mode 100644 index 000000000..fd4145f4c --- /dev/null +++ b/src/org/thialfihar/android/apg/provider/ApgServiceBlobProvider.java @@ -0,0 +1,138 @@ +package org.thialfihar.android.apg.provider; + +import org.thialfihar.android.apg.ApgService; +import org.thialfihar.android.apg.Constants; + +import android.content.ContentProvider; +import android.content.ContentValues; +import android.database.Cursor; +import android.net.Uri; +import android.os.ParcelFileDescriptor; +import android.util.Log; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.List; +import java.util.UUID; + +public class ApgServiceBlobProvider extends ContentProvider { + + private static final String TAG = "ApgServiceBlobProvider"; + + public static final Uri CONTENT_URI = Uri.parse("content://org.thialfihar.android.apg.provider.apgserviceblobprovider"); + + private static final String COLUMN_KEY = "key"; + + private static final String STORE_PATH = Constants.path.app_dir+"/ApgServiceBlobs"; + + private ApgServiceBlobDatabase mDb = null; + + public ApgServiceBlobProvider() { + if(ApgService.LOCAL_LOGD) Log.d(TAG, "Constructor called"); + File dir = new File(STORE_PATH); + dir.mkdirs(); + if(ApgService.LOCAL_LOGD) Log.d(TAG, "Constructor finished"); + } + + @Override + public int delete(Uri arg0, String arg1, String[] arg2) { + if(ApgService.LOCAL_LOGD) Log.d(TAG, "delete() called"); + // TODO Auto-generated method stub + return 0; + } + + @Override + public String getType(Uri arg0) { + if(ApgService.LOCAL_LOGD) Log.d(TAG, "getType() called"); + // not needed for now + return null; + } + + @Override + public Uri insert(Uri uri, ContentValues ignored) { + if(ApgService.LOCAL_LOGD) Log.d(TAG, "insert() called"); + // ContentValues are actually ignored, because we want to store a blob with no more information + // but have to create an record with the password generated here first + + ContentValues vals = new ContentValues(); + + // Insert a random key in the database. This has to provided by the caller when updating or + // getting the blob + String password = UUID.randomUUID().toString(); + vals.put(COLUMN_KEY, password); + + Uri insertedUri = mDb.insert(vals); + return Uri.withAppendedPath(insertedUri, password); + } + + @Override + public boolean onCreate() { + if(ApgService.LOCAL_LOGD) Log.d(TAG, "onCreate() called"); + mDb = new ApgServiceBlobDatabase(getContext()); + // TODO Auto-generated method stub + return true; + } + + @Override + public Cursor query(Uri arg0, String[] arg1, String arg2, String[] arg3, String arg4) { + if(ApgService.LOCAL_LOGD) Log.d(TAG, "query() called"); + // TODO Auto-generated method stub + return null; + } + + @Override + public int update(Uri arg0, ContentValues arg1, String arg2, String[] arg3) { + if(ApgService.LOCAL_LOGD) Log.d(TAG, "update() called"); + // TODO Auto-generated method stub + return 0; + } + + @Override + public ParcelFileDescriptor openFile(Uri uri, String mode) throws SecurityException, FileNotFoundException { + if(ApgService.LOCAL_LOGD) Log.d(TAG, "openFile() called"); + if(ApgService.LOCAL_LOGD) Log.d(TAG, "... with uri: "+uri.toString()); + if(ApgService.LOCAL_LOGD) Log.d(TAG, "... with mode: "+mode); + + List<String> segments = uri.getPathSegments(); + if(segments.size() < 2) { + throw new SecurityException("Password not found in URI"); + } + String id = segments.get(0); + String key = segments.get(1); + + if(ApgService.LOCAL_LOGD) Log.d(TAG, "... got id: "+id); + if(ApgService.LOCAL_LOGD) Log.d(TAG, "... and key: "+key); + + // get the data + Cursor result = mDb.query(id, key); + + if(result.getCount() == 0) { + // either the key is wrong or no id exists + throw new FileNotFoundException("No file found with that ID and/or password"); + } + + File targetFile = new File(STORE_PATH, id); + if(mode.equals("w")) { + if(ApgService.LOCAL_LOGD) Log.d(TAG, "... will try to open file w"); + if( !targetFile.exists() ) { + try { + targetFile.createNewFile(); + } catch (IOException e) { + Log.e(TAG, "... got IEOException on creating new file", e); + throw new FileNotFoundException("Could not create file to write to"); + } + } + return ParcelFileDescriptor.open(targetFile, ParcelFileDescriptor.MODE_WRITE_ONLY | ParcelFileDescriptor.MODE_TRUNCATE ); + } else if(mode.equals("r")) { + if(ApgService.LOCAL_LOGD) Log.d(TAG, "... will try to open file r"); + if( !targetFile.exists() ) { + throw new FileNotFoundException("Error: Could not find the file requested"); + } + return ParcelFileDescriptor.open(targetFile, ParcelFileDescriptor.MODE_READ_ONLY); + } + + return null; + } + +} diff --git a/src/org/thialfihar/android/apg/provider/Database.java b/src/org/thialfihar/android/apg/provider/Database.java index 05beb980d..92d8ae9fb 100644 --- a/src/org/thialfihar/android/apg/provider/Database.java +++ b/src/org/thialfihar/android/apg/provider/Database.java @@ -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; diff --git a/src/org/thialfihar/android/apg/ui/widget/KeyEditor.java b/src/org/thialfihar/android/apg/ui/widget/KeyEditor.java index 9de10acfa..6bed42b88 100644 --- a/src/org/thialfihar/android/apg/ui/widget/KeyEditor.java +++ b/src/org/thialfihar/android/apg/ui/widget/KeyEditor.java @@ -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; diff --git a/src/org/thialfihar/android/apg/ui/widget/SectionView.java b/src/org/thialfihar/android/apg/ui/widget/SectionView.java index 196e4ec2f..13b8e01ac 100644 --- a/src/org/thialfihar/android/apg/ui/widget/SectionView.java +++ b/src/org/thialfihar/android/apg/ui/widget/SectionView.java @@ -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; diff --git a/src/org/thialfihar/android/apg/utils/ApgCon.java b/src/org/thialfihar/android/apg/utils/ApgCon.java new file mode 100644 index 000000000..c46ccf6b1 --- /dev/null +++ b/src/org/thialfihar/android/apg/utils/ApgCon.java @@ -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(); + } + +} diff --git a/src/org/thialfihar/android/apg/utils/ApgConInterface.java b/src/org/thialfihar/android/apg/utils/ApgConInterface.java new file mode 100644 index 000000000..27254fe95 --- /dev/null +++ b/src/org/thialfihar/android/apg/utils/ApgConInterface.java @@ -0,0 +1,7 @@ +package org.thialfihar.android.apg.utils; + +public interface ApgConInterface { + public static interface OnCallFinishListener { + public abstract void onCallFinish(android.os.Bundle result); + } +} |