mirror of
https://github.com/moparisthebest/open-keychain
synced 2024-11-17 22:35:05 -05:00
Merge pull request #501 from Valodim/master
factor out android dependencies from PgpKeyOperation, and add JUnit/Robolectric testing stubs
This commit is contained in:
commit
247ad6207a
@ -1,5 +1,12 @@
|
|||||||
apply plugin: 'android'
|
apply plugin: 'android'
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
testLocal {
|
||||||
|
java.srcDir file('src/test/java')
|
||||||
|
resources.srcDir file('src/test/resources')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compile 'com.android.support:support-v4:19.0.1'
|
compile 'com.android.support:support-v4:19.0.1'
|
||||||
compile 'com.android.support:appcompat-v7:19.0.1'
|
compile 'com.android.support:appcompat-v7:19.0.1'
|
||||||
@ -15,6 +22,25 @@ dependencies {
|
|||||||
compile project(':libraries:spongycastle:pkix')
|
compile project(':libraries:spongycastle:pkix')
|
||||||
compile project(':libraries:spongycastle:prov')
|
compile project(':libraries:spongycastle:prov')
|
||||||
compile project(':libraries:Android-AppMsg:library')
|
compile project(':libraries:Android-AppMsg:library')
|
||||||
|
|
||||||
|
// Dependencies for the `testLocal` task, make sure to list all your global dependencies here as well
|
||||||
|
testLocalCompile 'junit:junit:4.11'
|
||||||
|
testLocalCompile 'org.robolectric:robolectric:2.1.+'
|
||||||
|
testLocalCompile 'com.google.android:android:4.1.1.4'
|
||||||
|
testLocalCompile 'com.android.support:support-v4:19.0.1'
|
||||||
|
testLocalCompile 'com.android.support:appcompat-v7:19.0.1'
|
||||||
|
testLocalCompile project(':OpenPGP-Keychain-API:libraries:openpgp-api-library')
|
||||||
|
testLocalCompile project(':OpenPGP-Keychain-API:libraries:openkeychain-api-library')
|
||||||
|
testLocalCompile project(':libraries:HtmlTextView')
|
||||||
|
testLocalCompile project(':libraries:StickyListHeaders:library')
|
||||||
|
testLocalCompile project(':libraries:AndroidBootstrap')
|
||||||
|
testLocalCompile project(':libraries:zxing')
|
||||||
|
testLocalCompile project(':libraries:zxing-android-integration')
|
||||||
|
testLocalCompile project(':libraries:spongycastle:core')
|
||||||
|
testLocalCompile project(':libraries:spongycastle:pg')
|
||||||
|
testLocalCompile project(':libraries:spongycastle:pkix')
|
||||||
|
testLocalCompile project(':libraries:spongycastle:prov')
|
||||||
|
testLocalCompile project(':libraries:Android-AppMsg:library')
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
@ -61,3 +87,19 @@ android {
|
|||||||
htmlOutput file("lint-report.html")
|
htmlOutput file("lint-report.html")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
task localTest(type: Test, dependsOn: assemble) {
|
||||||
|
testClassesDir = sourceSets.testLocal.output.classesDir
|
||||||
|
|
||||||
|
android.sourceSets.main.java.srcDirs.each { dir ->
|
||||||
|
def buildDir = dir.getAbsolutePath().split('/')
|
||||||
|
buildDir = (buildDir[0..(buildDir.length - 4)] + ['build', 'classes', 'debug']).join('/')
|
||||||
|
|
||||||
|
sourceSets.testLocal.compileClasspath += files(buildDir)
|
||||||
|
sourceSets.testLocal.runtimeClasspath += files(buildDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
classpath = sourceSets.testLocal.runtimeClasspath
|
||||||
|
}
|
||||||
|
|
||||||
|
check.dependsOn localTest
|
||||||
|
@ -17,20 +17,6 @@
|
|||||||
|
|
||||||
package org.sufficientlysecure.keychain.pgp;
|
package org.sufficientlysecure.keychain.pgp;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.math.BigInteger;
|
|
||||||
import java.security.InvalidAlgorithmParameterException;
|
|
||||||
import java.security.KeyPairGenerator;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.security.NoSuchProviderException;
|
|
||||||
import java.security.SecureRandom;
|
|
||||||
import java.security.SignatureException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.GregorianCalendar;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.TimeZone;
|
|
||||||
import android.content.Context;
|
|
||||||
import org.spongycastle.bcpg.CompressionAlgorithmTags;
|
import org.spongycastle.bcpg.CompressionAlgorithmTags;
|
||||||
import org.spongycastle.bcpg.HashAlgorithmTags;
|
import org.spongycastle.bcpg.HashAlgorithmTags;
|
||||||
import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags;
|
import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags;
|
||||||
@ -46,18 +32,11 @@ import org.spongycastle.openpgp.operator.jcajce.*;
|
|||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
import org.sufficientlysecure.keychain.Id;
|
import org.sufficientlysecure.keychain.Id;
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralMsgIdException;
|
||||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
|
||||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
|
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
|
||||||
import org.sufficientlysecure.keychain.util.Primes;
|
import org.sufficientlysecure.keychain.util.Primes;
|
||||||
import org.sufficientlysecure.keychain.util.ProgressDialogUpdater;
|
import org.sufficientlysecure.keychain.util.ProgressDialogUpdater;
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.util.Pair;
|
|
||||||
import org.sufficientlysecure.keychain.util.IterableIterator;
|
import org.sufficientlysecure.keychain.util.IterableIterator;
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
|
||||||
import org.sufficientlysecure.keychain.util.Primes;
|
|
||||||
import org.sufficientlysecure.keychain.util.ProgressDialogUpdater;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
@ -69,9 +48,17 @@ import java.util.Iterator;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.TimeZone;
|
import java.util.TimeZone;
|
||||||
|
|
||||||
|
/** This class is the single place where ALL operations that actually modify a PGP public or secret
|
||||||
|
* key take place.
|
||||||
|
*
|
||||||
|
* Note that no android specific stuff should be done here, ie no imports from com.android.
|
||||||
|
*
|
||||||
|
* All operations support progress reporting to a ProgressDialogUpdater passed on initialization.
|
||||||
|
* This indicator may be null.
|
||||||
|
*
|
||||||
|
*/
|
||||||
public class PgpKeyOperation {
|
public class PgpKeyOperation {
|
||||||
private final Context mContext;
|
private ProgressDialogUpdater mProgress;
|
||||||
private final ProgressDialogUpdater mProgress;
|
|
||||||
|
|
||||||
private static final int[] PREFERRED_SYMMETRIC_ALGORITHMS = new int[]{
|
private static final int[] PREFERRED_SYMMETRIC_ALGORITHMS = new int[]{
|
||||||
SymmetricKeyAlgorithmTags.AES_256, SymmetricKeyAlgorithmTags.AES_192,
|
SymmetricKeyAlgorithmTags.AES_256, SymmetricKeyAlgorithmTags.AES_192,
|
||||||
@ -83,9 +70,8 @@ public class PgpKeyOperation {
|
|||||||
CompressionAlgorithmTags.ZLIB, CompressionAlgorithmTags.BZIP2,
|
CompressionAlgorithmTags.ZLIB, CompressionAlgorithmTags.BZIP2,
|
||||||
CompressionAlgorithmTags.ZIP};
|
CompressionAlgorithmTags.ZIP};
|
||||||
|
|
||||||
public PgpKeyOperation(Context context, ProgressDialogUpdater progress) {
|
public PgpKeyOperation(ProgressDialogUpdater progress) {
|
||||||
super();
|
super();
|
||||||
this.mContext = context;
|
|
||||||
this.mProgress = progress;
|
this.mProgress = progress;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -101,13 +87,29 @@ public class PgpKeyOperation {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates new secret key.
|
||||||
|
*
|
||||||
|
* @param algorithmChoice
|
||||||
|
* @param keySize
|
||||||
|
* @param passphrase
|
||||||
|
* @param isMasterKey
|
||||||
|
* @return A newly created PGPSecretKey
|
||||||
|
* @throws NoSuchAlgorithmException
|
||||||
|
* @throws PGPException
|
||||||
|
* @throws NoSuchProviderException
|
||||||
|
* @throws PgpGeneralMsgIdException
|
||||||
|
* @throws InvalidAlgorithmParameterException
|
||||||
|
*/
|
||||||
|
|
||||||
|
// TODO: key flags?
|
||||||
public PGPSecretKey createKey(int algorithmChoice, int keySize, String passphrase,
|
public PGPSecretKey createKey(int algorithmChoice, int keySize, String passphrase,
|
||||||
boolean isMasterKey)
|
boolean isMasterKey)
|
||||||
throws NoSuchAlgorithmException, PGPException, NoSuchProviderException,
|
throws NoSuchAlgorithmException, PGPException, NoSuchProviderException,
|
||||||
PgpGeneralException, InvalidAlgorithmParameterException {
|
PgpGeneralMsgIdException, InvalidAlgorithmParameterException {
|
||||||
|
|
||||||
if (keySize < 512) {
|
if (keySize < 512) {
|
||||||
throw new PgpGeneralException(mContext.getString(R.string.error_key_size_minimum512bit));
|
throw new PgpGeneralMsgIdException(R.string.error_key_size_minimum512bit);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (passphrase == null) {
|
if (passphrase == null) {
|
||||||
@ -127,8 +129,7 @@ public class PgpKeyOperation {
|
|||||||
|
|
||||||
case Id.choice.algorithm.elgamal: {
|
case Id.choice.algorithm.elgamal: {
|
||||||
if (isMasterKey) {
|
if (isMasterKey) {
|
||||||
throw new PgpGeneralException(
|
throw new PgpGeneralMsgIdException(R.string.error_master_key_must_not_be_el_gamal);
|
||||||
mContext.getString(R.string.error_master_key_must_not_be_el_gamal));
|
|
||||||
}
|
}
|
||||||
keyGen = KeyPairGenerator.getInstance("ElGamal", Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
keyGen = KeyPairGenerator.getInstance("ElGamal", Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
||||||
BigInteger p = Primes.getBestPrime(keySize);
|
BigInteger p = Primes.getBestPrime(keySize);
|
||||||
@ -150,8 +151,7 @@ public class PgpKeyOperation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
default: {
|
default: {
|
||||||
throw new PgpGeneralException(
|
throw new PgpGeneralMsgIdException(R.string.error_unknown_algorithm_choice);
|
||||||
mContext.getString(R.string.error_unknown_algorithm_choice));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -171,8 +171,9 @@ public class PgpKeyOperation {
|
|||||||
sha1Calc, isMasterKey, keyEncryptor);
|
sha1Calc, isMasterKey, keyEncryptor);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void changeSecretKeyPassphrase(PGPSecretKeyRing keyRing, String oldPassPhrase,
|
public PGPSecretKeyRing changeSecretKeyPassphrase(PGPSecretKeyRing keyRing, String oldPassPhrase,
|
||||||
String newPassPhrase) throws IOException, PGPException {
|
String newPassPhrase) throws IOException, PGPException,
|
||||||
|
NoSuchProviderException {
|
||||||
|
|
||||||
updateProgress(R.string.progress_building_key, 0, 100);
|
updateProgress(R.string.progress_building_key, 0, 100);
|
||||||
if (oldPassPhrase == null) {
|
if (oldPassPhrase == null) {
|
||||||
@ -190,16 +191,16 @@ public class PgpKeyOperation {
|
|||||||
new JcePBESecretKeyEncryptorBuilder(keyRing.getSecretKey()
|
new JcePBESecretKeyEncryptorBuilder(keyRing.getSecretKey()
|
||||||
.getKeyEncryptionAlgorithm()).build(newPassPhrase.toCharArray()));
|
.getKeyEncryptionAlgorithm()).build(newPassPhrase.toCharArray()));
|
||||||
|
|
||||||
updateProgress(R.string.progress_saving_key_ring, 50, 100);
|
return newKeyRing;
|
||||||
|
|
||||||
ProviderHelper.saveKeyRing(mContext, newKeyRing);
|
|
||||||
|
|
||||||
updateProgress(R.string.progress_done, 100, 100);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void buildNewSecretKey(ArrayList<String> userIds, ArrayList<PGPSecretKey> keys, ArrayList<GregorianCalendar> keysExpiryDates, ArrayList<Integer> keysUsages, String newPassPhrase, String oldPassPhrase) throws PgpGeneralException,
|
private Pair<PGPSecretKeyRing,PGPPublicKeyRing> buildNewSecretKey(
|
||||||
PGPException, SignatureException, IOException {
|
ArrayList<String> userIds, ArrayList<PGPSecretKey> keys,
|
||||||
|
ArrayList<GregorianCalendar> keysExpiryDates,
|
||||||
|
ArrayList<Integer> keysUsages,
|
||||||
|
String newPassPhrase, String oldPassPhrase)
|
||||||
|
throws PgpGeneralMsgIdException, PGPException, SignatureException, IOException {
|
||||||
|
|
||||||
int usageId = keysUsages.get(0);
|
int usageId = keysUsages.get(0);
|
||||||
boolean canSign;
|
boolean canSign;
|
||||||
@ -210,8 +211,6 @@ public class PgpKeyOperation {
|
|||||||
// this removes all userIds and certifications previously attached to the masterPublicKey
|
// this removes all userIds and certifications previously attached to the masterPublicKey
|
||||||
PGPPublicKey masterPublicKey = masterKey.getPublicKey();
|
PGPPublicKey masterPublicKey = masterKey.getPublicKey();
|
||||||
|
|
||||||
PGPSecretKeyRing mKR = ProviderHelper.getPGPSecretKeyRingByKeyId(mContext, masterKey.getKeyID());
|
|
||||||
|
|
||||||
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
|
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
|
||||||
Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(oldPassPhrase.toCharArray());
|
Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(oldPassPhrase.toCharArray());
|
||||||
PGPPrivateKey masterPrivateKey = masterKey.extractPrivateKey(keyDecryptor);
|
PGPPrivateKey masterPrivateKey = masterKey.extractPrivateKey(keyDecryptor);
|
||||||
@ -249,7 +248,7 @@ public class PgpKeyOperation {
|
|||||||
//here we purposefully ignore partial days in each date - long type has no fractional part!
|
//here we purposefully ignore partial days in each date - long type has no fractional part!
|
||||||
long numDays = (expiryDate.getTimeInMillis() / 86400000) - (creationDate.getTimeInMillis() / 86400000);
|
long numDays = (expiryDate.getTimeInMillis() / 86400000) - (creationDate.getTimeInMillis() / 86400000);
|
||||||
if (numDays <= 0)
|
if (numDays <= 0)
|
||||||
throw new PgpGeneralException(mContext.getString(R.string.error_expiry_must_come_after_creation));
|
throw new PgpGeneralMsgIdException(R.string.error_expiry_must_come_after_creation);
|
||||||
hashedPacketsGen.setKeyExpirationTime(false, numDays * 86400);
|
hashedPacketsGen.setKeyExpirationTime(false, numDays * 86400);
|
||||||
} else {
|
} else {
|
||||||
hashedPacketsGen.setKeyExpirationTime(false, 0); //do this explicitly, although since we're rebuilding,
|
hashedPacketsGen.setKeyExpirationTime(false, 0); //do this explicitly, although since we're rebuilding,
|
||||||
@ -319,9 +318,11 @@ public class PgpKeyOperation {
|
|||||||
GregorianCalendar expiryDate = keysExpiryDates.get(i);
|
GregorianCalendar expiryDate = keysExpiryDates.get(i);
|
||||||
//note that the below, (a/c) - (b/c) is *not* the same as (a - b) /c
|
//note that the below, (a/c) - (b/c) is *not* the same as (a - b) /c
|
||||||
//here we purposefully ignore partial days in each date - long type has no fractional part!
|
//here we purposefully ignore partial days in each date - long type has no fractional part!
|
||||||
long numDays = (expiryDate.getTimeInMillis() / 86400000) - (creationDate.getTimeInMillis() / 86400000);
|
long numDays =
|
||||||
if (numDays <= 0)
|
(expiryDate.getTimeInMillis() / 86400000) - (creationDate.getTimeInMillis() / 86400000);
|
||||||
throw new PgpGeneralException(mContext.getString(R.string.error_expiry_must_come_after_creation));
|
if (numDays <= 0) {
|
||||||
|
throw new PgpGeneralMsgIdException(R.string.error_expiry_must_come_after_creation);
|
||||||
|
}
|
||||||
hashedPacketsGen.setKeyExpirationTime(false, numDays * 86400);
|
hashedPacketsGen.setKeyExpirationTime(false, numDays * 86400);
|
||||||
} else {
|
} else {
|
||||||
hashedPacketsGen.setKeyExpirationTime(false, 0); //do this explicitly, although since we're rebuilding,
|
hashedPacketsGen.setKeyExpirationTime(false, 0); //do this explicitly, although since we're rebuilding,
|
||||||
@ -334,23 +335,18 @@ public class PgpKeyOperation {
|
|||||||
PGPSecretKeyRing secretKeyRing = keyGen.generateSecretKeyRing();
|
PGPSecretKeyRing secretKeyRing = keyGen.generateSecretKeyRing();
|
||||||
PGPPublicKeyRing publicKeyRing = keyGen.generatePublicKeyRing();
|
PGPPublicKeyRing publicKeyRing = keyGen.generatePublicKeyRing();
|
||||||
|
|
||||||
updateProgress(R.string.progress_saving_key_ring, 90, 100);
|
return new Pair<PGPSecretKeyRing,PGPPublicKeyRing>(secretKeyRing, publicKeyRing);
|
||||||
|
|
||||||
ProviderHelper.saveKeyRing(mContext, secretKeyRing);
|
|
||||||
ProviderHelper.saveKeyRing(mContext, publicKeyRing);
|
|
||||||
|
|
||||||
updateProgress(R.string.progress_done, 100, 100);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void buildSecretKey (SaveKeyringParcel saveParcel)throws PgpGeneralException,
|
public Pair<PGPSecretKeyRing,PGPPublicKeyRing> buildSecretKey (PGPSecretKeyRing mKR,
|
||||||
PGPException, SignatureException, IOException {
|
PGPPublicKeyRing pKR,
|
||||||
|
SaveKeyringParcel saveParcel)
|
||||||
|
throws PgpGeneralMsgIdException, PGPException, SignatureException, IOException {
|
||||||
|
|
||||||
updateProgress(R.string.progress_building_key, 0, 100);
|
updateProgress(R.string.progress_building_key, 0, 100);
|
||||||
PGPSecretKey masterKey = saveParcel.keys.get(0);
|
PGPSecretKey masterKey = saveParcel.keys.get(0);
|
||||||
|
|
||||||
PGPSecretKeyRing mKR = ProviderHelper.getPGPSecretKeyRingByKeyId(mContext, masterKey.getKeyID());
|
|
||||||
PGPPublicKeyRing pKR = ProviderHelper.getPGPPublicKeyRingByKeyId(mContext, masterKey.getKeyID());
|
|
||||||
|
|
||||||
if (saveParcel.oldPassPhrase == null) {
|
if (saveParcel.oldPassPhrase == null) {
|
||||||
saveParcel.oldPassPhrase = "";
|
saveParcel.oldPassPhrase = "";
|
||||||
}
|
}
|
||||||
@ -359,9 +355,8 @@ public class PgpKeyOperation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (mKR == null) {
|
if (mKR == null) {
|
||||||
buildNewSecretKey(saveParcel.userIDs, saveParcel.keys, saveParcel.keysExpiryDates,
|
return buildNewSecretKey(saveParcel.userIDs, saveParcel.keys, saveParcel.keysExpiryDates,
|
||||||
saveParcel.keysUsages, saveParcel.newPassPhrase, saveParcel.oldPassPhrase); //new Keyring
|
saveParcel.keysUsages, saveParcel.newPassPhrase, saveParcel.oldPassPhrase); //new Keyring
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -430,7 +425,7 @@ public class PgpKeyOperation {
|
|||||||
//here we purposefully ignore partial days in each date - long type has no fractional part!
|
//here we purposefully ignore partial days in each date - long type has no fractional part!
|
||||||
long numDays = (expiryDate.getTimeInMillis() / 86400000) - (creationDate.getTimeInMillis() / 86400000);
|
long numDays = (expiryDate.getTimeInMillis() / 86400000) - (creationDate.getTimeInMillis() / 86400000);
|
||||||
if (numDays <= 0)
|
if (numDays <= 0)
|
||||||
throw new PgpGeneralException(mContext.getString(R.string.error_expiry_must_come_after_creation));
|
throw new PgpGeneralMsgIdException(R.string.error_expiry_must_come_after_creation);
|
||||||
hashedPacketsGen.setKeyExpirationTime(false, numDays * 86400);
|
hashedPacketsGen.setKeyExpirationTime(false, numDays * 86400);
|
||||||
} else {
|
} else {
|
||||||
hashedPacketsGen.setKeyExpirationTime(false, 0); //do this explicitly, although since we're rebuilding,
|
hashedPacketsGen.setKeyExpirationTime(false, 0); //do this explicitly, although since we're rebuilding,
|
||||||
@ -592,7 +587,7 @@ public class PgpKeyOperation {
|
|||||||
//here we purposefully ignore partial days in each date - long type has no fractional part!
|
//here we purposefully ignore partial days in each date - long type has no fractional part!
|
||||||
long numDays = (expiryDate.getTimeInMillis() / 86400000) - (creationDate.getTimeInMillis() / 86400000);
|
long numDays = (expiryDate.getTimeInMillis() / 86400000) - (creationDate.getTimeInMillis() / 86400000);
|
||||||
if (numDays <= 0)
|
if (numDays <= 0)
|
||||||
throw new PgpGeneralException(mContext.getString(R.string.error_expiry_must_come_after_creation));
|
throw new PgpGeneralMsgIdException(R.string.error_expiry_must_come_after_creation);
|
||||||
hashedPacketsGen.setKeyExpirationTime(false, numDays * 86400);
|
hashedPacketsGen.setKeyExpirationTime(false, numDays * 86400);
|
||||||
} else {
|
} else {
|
||||||
hashedPacketsGen.setKeyExpirationTime(false, 0); //do this explicitly, although since we're rebuilding,
|
hashedPacketsGen.setKeyExpirationTime(false, 0); //do this explicitly, although since we're rebuilding,
|
||||||
@ -637,7 +632,6 @@ public class PgpKeyOperation {
|
|||||||
|
|
||||||
//update the passphrase
|
//update the passphrase
|
||||||
mKR = PGPSecretKeyRing.copyWithNewPassword(mKR, keyDecryptor, keyEncryptorNew);
|
mKR = PGPSecretKeyRing.copyWithNewPassword(mKR, keyDecryptor, keyEncryptorNew);
|
||||||
updateProgress(R.string.progress_saving_key_ring, 90, 100);
|
|
||||||
|
|
||||||
/* additional handy debug info
|
/* additional handy debug info
|
||||||
|
|
||||||
@ -660,62 +654,72 @@ public class PgpKeyOperation {
|
|||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
return new Pair<PGPSecretKeyRing,PGPPublicKeyRing>(mKR, pKR);
|
||||||
|
|
||||||
ProviderHelper.saveKeyRing(mContext, mKR);
|
|
||||||
ProviderHelper.saveKeyRing(mContext, pKR);
|
|
||||||
|
|
||||||
updateProgress(R.string.progress_done, 100, 100);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public PGPPublicKeyRing certifyKey(long masterKeyId, long pubKeyId, List<String> userIds, String passphrase)
|
/**
|
||||||
throws PgpGeneralException, NoSuchAlgorithmException, NoSuchProviderException,
|
* Certify the given pubkeyid with the given masterkeyid.
|
||||||
|
*
|
||||||
|
* @param certificationKey Certifying key
|
||||||
|
* @param publicKey public key to certify
|
||||||
|
* @param userIds User IDs to certify, must not be null or empty
|
||||||
|
* @param passphrase Passphrase of the secret key
|
||||||
|
* @return A keyring with added certifications
|
||||||
|
*/
|
||||||
|
public PGPPublicKey certifyKey(PGPSecretKey certificationKey, PGPPublicKey publicKey, List<String> userIds, String passphrase)
|
||||||
|
throws PgpGeneralMsgIdException, NoSuchAlgorithmException, NoSuchProviderException,
|
||||||
PGPException, SignatureException {
|
PGPException, SignatureException {
|
||||||
if (passphrase == null) {
|
|
||||||
throw new PgpGeneralException("Unable to obtain passphrase");
|
|
||||||
} else {
|
|
||||||
|
|
||||||
// create a signatureGenerator from the supplied masterKeyId and passphrase
|
// create a signatureGenerator from the supplied masterKeyId and passphrase
|
||||||
PGPSignatureGenerator signatureGenerator; {
|
PGPSignatureGenerator signatureGenerator; {
|
||||||
|
|
||||||
PGPSecretKey certificationKey = PgpKeyHelper.getCertificationKey(mContext, masterKeyId);
|
if (certificationKey == null) {
|
||||||
if (certificationKey == null) {
|
throw new PgpGeneralMsgIdException(R.string.error_signature_failed);
|
||||||
throw new PgpGeneralException(mContext.getString(R.string.error_signature_failed));
|
|
||||||
}
|
|
||||||
|
|
||||||
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
|
|
||||||
Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.toCharArray());
|
|
||||||
PGPPrivateKey signaturePrivateKey = certificationKey.extractPrivateKey(keyDecryptor);
|
|
||||||
if (signaturePrivateKey == null) {
|
|
||||||
throw new PgpGeneralException(
|
|
||||||
mContext.getString(R.string.error_could_not_extract_private_key));
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: SHA256 fixed?
|
|
||||||
JcaPGPContentSignerBuilder contentSignerBuilder = new JcaPGPContentSignerBuilder(
|
|
||||||
certificationKey.getPublicKey().getAlgorithm(), PGPUtil.SHA256)
|
|
||||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
|
||||||
|
|
||||||
signatureGenerator = new PGPSignatureGenerator(contentSignerBuilder);
|
|
||||||
signatureGenerator.init(PGPSignature.DEFAULT_CERTIFICATION, signaturePrivateKey);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
{ // supply signatureGenerator with a SubpacketVector
|
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
|
||||||
PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
|
Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.toCharArray());
|
||||||
PGPSignatureSubpacketVector packetVector = spGen.generate();
|
PGPPrivateKey signaturePrivateKey = certificationKey.extractPrivateKey(keyDecryptor);
|
||||||
signatureGenerator.setHashedSubpackets(packetVector);
|
if (signaturePrivateKey == null) {
|
||||||
|
throw new PgpGeneralMsgIdException(R.string.error_could_not_extract_private_key);
|
||||||
}
|
}
|
||||||
|
|
||||||
// fetch public key ring, add the certification and return it
|
// TODO: SHA256 fixed?
|
||||||
PGPPublicKeyRing pubring = ProviderHelper
|
JcaPGPContentSignerBuilder contentSignerBuilder = new JcaPGPContentSignerBuilder(
|
||||||
.getPGPPublicKeyRingByKeyId(mContext, pubKeyId);
|
certificationKey.getPublicKey().getAlgorithm(), PGPUtil.SHA256)
|
||||||
PGPPublicKey signedKey = pubring.getPublicKey(pubKeyId);
|
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
||||||
for(String userId : new IterableIterator<String>(userIds.iterator())) {
|
|
||||||
PGPSignature sig = signatureGenerator.generateCertification(userId, signedKey);
|
|
||||||
signedKey = PGPPublicKey.addCertification(signedKey, userId, sig);
|
|
||||||
}
|
|
||||||
pubring = PGPPublicKeyRing.insertPublicKey(pubring, signedKey);
|
|
||||||
|
|
||||||
return pubring;
|
signatureGenerator = new PGPSignatureGenerator(contentSignerBuilder);
|
||||||
|
signatureGenerator.init(PGPSignature.DEFAULT_CERTIFICATION, signaturePrivateKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
{ // supply signatureGenerator with a SubpacketVector
|
||||||
|
PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
|
||||||
|
PGPSignatureSubpacketVector packetVector = spGen.generate();
|
||||||
|
signatureGenerator.setHashedSubpackets(packetVector);
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetch public key ring, add the certification and return it
|
||||||
|
for(String userId : new IterableIterator<String>(userIds.iterator())) {
|
||||||
|
PGPSignature sig = signatureGenerator.generateCertification(userId, publicKey);
|
||||||
|
publicKey = PGPPublicKey.addCertification(publicKey, userId, sig);
|
||||||
|
}
|
||||||
|
|
||||||
|
return publicKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Simple static subclass that stores two values.
|
||||||
|
*
|
||||||
|
* This is only used to return a pair of values in one function above. We specifically don't use
|
||||||
|
* com.android.Pair to keep this class free from android dependencies.
|
||||||
|
*/
|
||||||
|
public static class Pair<K, V> {
|
||||||
|
public final K first;
|
||||||
|
public final V second;
|
||||||
|
public Pair(K first, V second) {
|
||||||
|
this.first = first;
|
||||||
|
this.second = second;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,4 +23,7 @@ public class PgpGeneralException extends Exception {
|
|||||||
public PgpGeneralException(String message) {
|
public PgpGeneralException(String message) {
|
||||||
super(message);
|
super(message);
|
||||||
}
|
}
|
||||||
|
public PgpGeneralException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2012-2013 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||||
|
* Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||||
|
*
|
||||||
|
* 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.sufficientlysecure.keychain.pgp.exception;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
public class PgpGeneralMsgIdException extends Exception {
|
||||||
|
static final long serialVersionUID = 0xf812773343L;
|
||||||
|
|
||||||
|
private final int msgId;
|
||||||
|
|
||||||
|
public PgpGeneralMsgIdException(int msgId) {
|
||||||
|
super("msg[" + msgId + "]");
|
||||||
|
this.msgId = msgId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PgpGeneralException getContextualized(Context context) {
|
||||||
|
return new PgpGeneralException(context.getString(msgId), this);
|
||||||
|
}
|
||||||
|
}
|
@ -35,6 +35,8 @@ import org.sufficientlysecure.keychain.helper.OtherHelper;
|
|||||||
import org.sufficientlysecure.keychain.helper.Preferences;
|
import org.sufficientlysecure.keychain.helper.Preferences;
|
||||||
import org.sufficientlysecure.keychain.pgp.*;
|
import org.sufficientlysecure.keychain.pgp.*;
|
||||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||||
|
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralMsgIdException;
|
||||||
|
import org.sufficientlysecure.keychain.provider.KeychainContract.DataStream;
|
||||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||||
import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListEntry;
|
import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListEntry;
|
||||||
import org.sufficientlysecure.keychain.util.*;
|
import org.sufficientlysecure.keychain.util.*;
|
||||||
@ -473,14 +475,26 @@ public class KeychainIntentService extends IntentService
|
|||||||
|
|
||||||
long masterKeyId = saveParams.keys.get(0).getKeyID();
|
long masterKeyId = saveParams.keys.get(0).getKeyID();
|
||||||
|
|
||||||
PgpKeyOperation keyOperations = new PgpKeyOperation(this, this);
|
|
||||||
/* Operation */
|
/* Operation */
|
||||||
if (!canSign) {
|
if (!canSign) {
|
||||||
keyOperations.changeSecretKeyPassphrase(
|
PgpKeyOperation keyOperations = new PgpKeyOperation(new ProgressScaler(this, 0, 50, 100));
|
||||||
ProviderHelper.getPGPSecretKeyRingByKeyId(this, masterKeyId),
|
PGPSecretKeyRing keyRing = ProviderHelper
|
||||||
|
.getPGPSecretKeyRingByKeyId(this, masterKeyId);
|
||||||
|
keyRing = keyOperations.changeSecretKeyPassphrase(keyRing,
|
||||||
oldPassPhrase, newPassPhrase);
|
oldPassPhrase, newPassPhrase);
|
||||||
|
setProgress(R.string.progress_saving_key_ring, 50, 100);
|
||||||
|
ProviderHelper.saveKeyRing(this, keyRing);
|
||||||
|
setProgress(R.string.progress_done, 100, 100);
|
||||||
} else {
|
} else {
|
||||||
keyOperations.buildSecretKey(saveParams);
|
PgpKeyOperation keyOperations = new PgpKeyOperation(new ProgressScaler(this, 0, 90, 100));
|
||||||
|
PGPSecretKeyRing privkey = ProviderHelper.getPGPSecretKeyRingByMasterKeyId(this, masterKeyId);
|
||||||
|
PGPPublicKeyRing pubkey = ProviderHelper.getPGPPublicKeyRingByMasterKeyId(this, masterKeyId);
|
||||||
|
PgpKeyOperation.Pair<PGPSecretKeyRing,PGPPublicKeyRing> pair =
|
||||||
|
keyOperations.buildSecretKey(privkey, pubkey, saveParams);
|
||||||
|
setProgress(R.string.progress_saving_key_ring, 90, 100);
|
||||||
|
ProviderHelper.saveKeyRing(this, pair.first);
|
||||||
|
ProviderHelper.saveKeyRing(this, pair.second);
|
||||||
|
setProgress(R.string.progress_done, 100, 100);
|
||||||
}
|
}
|
||||||
PassphraseCacheService.addCachedPassphrase(this, masterKeyId, newPassPhrase);
|
PassphraseCacheService.addCachedPassphrase(this, masterKeyId, newPassPhrase);
|
||||||
|
|
||||||
@ -498,7 +512,7 @@ public class KeychainIntentService extends IntentService
|
|||||||
boolean masterKey = data.getBoolean(GENERATE_KEY_MASTER_KEY);
|
boolean masterKey = data.getBoolean(GENERATE_KEY_MASTER_KEY);
|
||||||
|
|
||||||
/* Operation */
|
/* Operation */
|
||||||
PgpKeyOperation keyOperations = new PgpKeyOperation(this, this);
|
PgpKeyOperation keyOperations = new PgpKeyOperation(new ProgressScaler(this, 0, 100, 100));
|
||||||
PGPSecretKey newKey = keyOperations.createKey(algorithm, keysize,
|
PGPSecretKey newKey = keyOperations.createKey(algorithm, keysize,
|
||||||
passphrase, masterKey);
|
passphrase, masterKey);
|
||||||
|
|
||||||
@ -529,7 +543,7 @@ public class KeychainIntentService extends IntentService
|
|||||||
getQuantityString(R.plurals.progress_generating, keysTotal),
|
getQuantityString(R.plurals.progress_generating, keysTotal),
|
||||||
keysCreated,
|
keysCreated,
|
||||||
keysTotal);
|
keysTotal);
|
||||||
PgpKeyOperation keyOperations = new PgpKeyOperation(this, this);
|
PgpKeyOperation keyOperations = new PgpKeyOperation(new ProgressScaler(this, 0, 100, 100));
|
||||||
|
|
||||||
PGPSecretKey masterKey = keyOperations.createKey(Id.choice.algorithm.rsa,
|
PGPSecretKey masterKey = keyOperations.createKey(Id.choice.algorithm.rsa,
|
||||||
4096, passphrase, true);
|
4096, passphrase, true);
|
||||||
@ -771,14 +785,23 @@ public class KeychainIntentService extends IntentService
|
|||||||
/* Operation */
|
/* Operation */
|
||||||
String signaturePassPhrase = PassphraseCacheService.getCachedPassphrase(this,
|
String signaturePassPhrase = PassphraseCacheService.getCachedPassphrase(this,
|
||||||
masterKeyId);
|
masterKeyId);
|
||||||
|
if (signaturePassPhrase == null) {
|
||||||
|
throw new PgpGeneralException("Unable to obtain passphrase");
|
||||||
|
}
|
||||||
|
|
||||||
PgpKeyOperation keyOperation = new PgpKeyOperation(this, this);
|
PgpKeyOperation keyOperation = new PgpKeyOperation(new ProgressScaler(this, 0, 100, 100));
|
||||||
PGPPublicKeyRing signedPubKeyRing = keyOperation.certifyKey(masterKeyId, pubKeyId,
|
PGPPublicKeyRing publicRing = ProviderHelper
|
||||||
|
.getPGPPublicKeyRingByKeyId(this, pubKeyId);
|
||||||
|
PGPPublicKey publicKey = publicRing.getPublicKey(pubKeyId);
|
||||||
|
PGPSecretKey certificationKey = PgpKeyHelper.getCertificationKey(this,
|
||||||
|
masterKeyId);
|
||||||
|
publicKey = keyOperation.certifyKey(certificationKey, publicKey,
|
||||||
userIds, signaturePassPhrase);
|
userIds, signaturePassPhrase);
|
||||||
|
publicRing = PGPPublicKeyRing.insertPublicKey(publicRing, publicKey);
|
||||||
|
|
||||||
// store the signed key in our local cache
|
// store the signed key in our local cache
|
||||||
PgpImportExport pgpImportExport = new PgpImportExport(this, null);
|
PgpImportExport pgpImportExport = new PgpImportExport(this, null);
|
||||||
int retval = pgpImportExport.storeKeyRingInCache(signedPubKeyRing);
|
int retval = pgpImportExport.storeKeyRingInCache(publicRing);
|
||||||
if (retval != Id.return_value.ok && retval != Id.return_value.updated) {
|
if (retval != Id.return_value.ok && retval != Id.return_value.updated) {
|
||||||
throw new PgpGeneralException("Failed to store signed key in local cache");
|
throw new PgpGeneralException("Failed to store signed key in local cache");
|
||||||
}
|
}
|
||||||
@ -795,7 +818,11 @@ public class KeychainIntentService extends IntentService
|
|||||||
if (this.mIsCanceled) {
|
if (this.mIsCanceled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Log.e(Constants.TAG, "KeychainIntentService Exception: ", e);
|
// contextualize the exception, if necessary
|
||||||
|
if(e instanceof PgpGeneralMsgIdException) {
|
||||||
|
e = ((PgpGeneralMsgIdException) e).getContextualized(this);
|
||||||
|
}
|
||||||
|
Log.e(Constants.TAG, "ApgService Exception: ", e);
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
|
|
||||||
Bundle data = new Bundle();
|
Bundle data = new Bundle();
|
||||||
|
@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.sufficientlysecure.keychain.util;
|
||||||
|
|
||||||
|
/** This is a simple class that wraps a ProgressDialogUpdater, scaling the progress
|
||||||
|
* values into a specified range.
|
||||||
|
*/
|
||||||
|
public class ProgressScaler implements ProgressDialogUpdater {
|
||||||
|
|
||||||
|
final ProgressDialogUpdater mWrapped;
|
||||||
|
final int mFrom, mTo, mMax;
|
||||||
|
|
||||||
|
public ProgressScaler(ProgressDialogUpdater wrapped, int from, int to, int max) {
|
||||||
|
this.mWrapped = wrapped;
|
||||||
|
this.mFrom = from;
|
||||||
|
this.mTo = to;
|
||||||
|
this.mMax = max;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set progressDialogUpdater of ProgressDialog by sending message to handler on UI thread
|
||||||
|
*/
|
||||||
|
public void setProgress(String message, int progress, int max) {
|
||||||
|
mWrapped.setProgress(message, mFrom+ progress*(mTo-mFrom)/max, mMax);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProgress(int resourceId, int progress, int max) {
|
||||||
|
mWrapped.setProgress(resourceId, progress, mMax);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProgress(int progress, int max) {
|
||||||
|
mWrapped.setProgress(progress, max);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -6,7 +6,7 @@
|
|||||||
android:orientation="vertical" >
|
android:orientation="vertical" >
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+android:id/text_layout"
|
android:id="@+id/text_layout"
|
||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:gravity="center_vertical"
|
android:gravity="center_vertical"
|
||||||
|
@ -0,0 +1,46 @@
|
|||||||
|
package org.sufficientlysecure.keychain;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.robolectric.Robolectric;
|
||||||
|
|
||||||
|
import org.sufficientlysecure.keychain.pgp.*;
|
||||||
|
import org.spongycastle.openpgp.*;
|
||||||
|
|
||||||
|
@RunWith(RobolectricGradleTestRunner.class)
|
||||||
|
public class PgpKeyOperationTest {
|
||||||
|
|
||||||
|
PGPSecretKey key;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() throws Exception {
|
||||||
|
|
||||||
|
/* Input */
|
||||||
|
int algorithm = Id.choice.algorithm.dsa;
|
||||||
|
String passphrase = "swag";
|
||||||
|
int keysize = 2048;
|
||||||
|
boolean masterKey = true;
|
||||||
|
|
||||||
|
/* Operation */
|
||||||
|
PgpKeyOperation keyOperations = new PgpKeyOperation(null);
|
||||||
|
key = keyOperations.createKey(algorithm, keysize, passphrase, masterKey);
|
||||||
|
|
||||||
|
System.err.println("initialized, test key: " + PgpKeyHelper.convertKeyIdToHex(key.getKeyID()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void tearDown() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void createTest() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void certifyKey() {
|
||||||
|
System.err.println("swag");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
package org.sufficientlysecure.keychain;
|
||||||
|
|
||||||
|
import org.junit.runners.model.InitializationError;
|
||||||
|
import org.robolectric.AndroidManifest;
|
||||||
|
import org.robolectric.RobolectricTestRunner;
|
||||||
|
import org.robolectric.annotation.Config;
|
||||||
|
import org.robolectric.res.Fs;
|
||||||
|
import org.robolectric.res.FsFile;
|
||||||
|
|
||||||
|
import org.sufficientlysecure.keychain.KeychainApplication;
|
||||||
|
|
||||||
|
public class RobolectricGradleTestRunner extends RobolectricTestRunner {
|
||||||
|
public RobolectricGradleTestRunner(Class<?> testClass) throws InitializationError {
|
||||||
|
super(testClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override protected AndroidManifest getAppManifest(Config config) {
|
||||||
|
String myAppPath = KeychainApplication.class.getProtectionDomain().getCodeSource().getLocation().getPath();
|
||||||
|
String manifestPath = myAppPath + "../../../src/main/AndroidManifest.xml";
|
||||||
|
return createAppManifest(Fs.fileFromPath(manifestPath));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user