From 6f36b72eee8ed2a0baa768e696bbb6e9632ed795 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Sat, 22 Mar 2014 13:04:31 +0100 Subject: [PATCH 1/2] PgpKeyOperation: factor out all android dependencies Conflicts: OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java --- .../keychain/pgp/PgpKeyOperation.java | 218 +++++++++--------- .../pgp/exception/PgpGeneralException.java | 3 + .../exception/PgpGeneralMsgIdException.java | 35 +++ .../service/KeychainIntentService.java | 47 +++- .../keychain/util/ProgressScaler.java | 50 ++++ 5 files changed, 236 insertions(+), 117 deletions(-) create mode 100644 OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/exception/PgpGeneralMsgIdException.java create mode 100644 OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/ProgressScaler.java diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java index 592bdec73..520189448 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java @@ -17,20 +17,6 @@ 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.HashAlgorithmTags; import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags; @@ -46,18 +32,11 @@ import org.spongycastle.openpgp.operator.jcajce.*; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Id; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; -import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralMsgIdException; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.util.Primes; 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.Log; -import org.sufficientlysecure.keychain.util.Primes; -import org.sufficientlysecure.keychain.util.ProgressDialogUpdater; import java.io.IOException; import java.math.BigInteger; @@ -69,9 +48,17 @@ import java.util.Iterator; import java.util.List; 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 { - private final Context mContext; - private final ProgressDialogUpdater mProgress; + private ProgressDialogUpdater mProgress; private static final int[] PREFERRED_SYMMETRIC_ALGORITHMS = new int[]{ SymmetricKeyAlgorithmTags.AES_256, SymmetricKeyAlgorithmTags.AES_192, @@ -83,9 +70,8 @@ public class PgpKeyOperation { CompressionAlgorithmTags.ZLIB, CompressionAlgorithmTags.BZIP2, CompressionAlgorithmTags.ZIP}; - public PgpKeyOperation(Context context, ProgressDialogUpdater progress) { + public PgpKeyOperation(ProgressDialogUpdater progress) { super(); - this.mContext = context; 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, boolean isMasterKey) throws NoSuchAlgorithmException, PGPException, NoSuchProviderException, - PgpGeneralException, InvalidAlgorithmParameterException { + PgpGeneralMsgIdException, InvalidAlgorithmParameterException { 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) { @@ -127,8 +129,7 @@ public class PgpKeyOperation { case Id.choice.algorithm.elgamal: { if (isMasterKey) { - throw new PgpGeneralException( - mContext.getString(R.string.error_master_key_must_not_be_el_gamal)); + throw new PgpGeneralMsgIdException(R.string.error_master_key_must_not_be_el_gamal); } keyGen = KeyPairGenerator.getInstance("ElGamal", Constants.BOUNCY_CASTLE_PROVIDER_NAME); BigInteger p = Primes.getBestPrime(keySize); @@ -150,8 +151,7 @@ public class PgpKeyOperation { } default: { - throw new PgpGeneralException( - mContext.getString(R.string.error_unknown_algorithm_choice)); + throw new PgpGeneralMsgIdException(R.string.error_unknown_algorithm_choice); } } @@ -171,8 +171,9 @@ public class PgpKeyOperation { sha1Calc, isMasterKey, keyEncryptor); } - public void changeSecretKeyPassphrase(PGPSecretKeyRing keyRing, String oldPassPhrase, - String newPassPhrase) throws IOException, PGPException { + public PGPSecretKeyRing changeSecretKeyPassphrase(PGPSecretKeyRing keyRing, String oldPassPhrase, + String newPassPhrase) throws IOException, PGPException, + NoSuchProviderException { updateProgress(R.string.progress_building_key, 0, 100); if (oldPassPhrase == null) { @@ -190,16 +191,16 @@ public class PgpKeyOperation { new JcePBESecretKeyEncryptorBuilder(keyRing.getSecretKey() .getKeyEncryptionAlgorithm()).build(newPassPhrase.toCharArray())); - updateProgress(R.string.progress_saving_key_ring, 50, 100); - - ProviderHelper.saveKeyRing(mContext, newKeyRing); - - updateProgress(R.string.progress_done, 100, 100); + return newKeyRing; } - private void buildNewSecretKey(ArrayList userIds, ArrayList keys, ArrayList keysExpiryDates, ArrayList keysUsages, String newPassPhrase, String oldPassPhrase) throws PgpGeneralException, - PGPException, SignatureException, IOException { + private Pair buildNewSecretKey( + ArrayList userIds, ArrayList keys, + ArrayList keysExpiryDates, + ArrayList keysUsages, + String newPassPhrase, String oldPassPhrase) + throws PgpGeneralMsgIdException, PGPException, SignatureException, IOException { int usageId = keysUsages.get(0); boolean canSign; @@ -210,8 +211,6 @@ public class PgpKeyOperation { // this removes all userIds and certifications previously attached to the masterPublicKey PGPPublicKey masterPublicKey = masterKey.getPublicKey(); - PGPSecretKeyRing mKR = ProviderHelper.getPGPSecretKeyRingByKeyId(mContext, masterKey.getKeyID()); - PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider( Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(oldPassPhrase.toCharArray()); 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! long numDays = (expiryDate.getTimeInMillis() / 86400000) - (creationDate.getTimeInMillis() / 86400000); 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); } else { hashedPacketsGen.setKeyExpirationTime(false, 0); //do this explicitly, although since we're rebuilding, @@ -319,9 +318,11 @@ public class PgpKeyOperation { GregorianCalendar expiryDate = keysExpiryDates.get(i); //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! - long numDays = (expiryDate.getTimeInMillis() / 86400000) - (creationDate.getTimeInMillis() / 86400000); - if (numDays <= 0) - throw new PgpGeneralException(mContext.getString(R.string.error_expiry_must_come_after_creation)); + long numDays = + (expiryDate.getTimeInMillis() / 86400000) - (creationDate.getTimeInMillis() / 86400000); + if (numDays <= 0) { + throw new PgpGeneralMsgIdException(R.string.error_expiry_must_come_after_creation); + } hashedPacketsGen.setKeyExpirationTime(false, numDays * 86400); } else { hashedPacketsGen.setKeyExpirationTime(false, 0); //do this explicitly, although since we're rebuilding, @@ -334,23 +335,18 @@ public class PgpKeyOperation { PGPSecretKeyRing secretKeyRing = keyGen.generateSecretKeyRing(); PGPPublicKeyRing publicKeyRing = keyGen.generatePublicKeyRing(); - updateProgress(R.string.progress_saving_key_ring, 90, 100); + return new Pair(secretKeyRing, publicKeyRing); - ProviderHelper.saveKeyRing(mContext, secretKeyRing); - ProviderHelper.saveKeyRing(mContext, publicKeyRing); - - updateProgress(R.string.progress_done, 100, 100); } - public void buildSecretKey (SaveKeyringParcel saveParcel)throws PgpGeneralException, - PGPException, SignatureException, IOException { + public Pair buildSecretKey (PGPSecretKeyRing mKR, + PGPPublicKeyRing pKR, + SaveKeyringParcel saveParcel) + throws PgpGeneralMsgIdException, PGPException, SignatureException, IOException { updateProgress(R.string.progress_building_key, 0, 100); PGPSecretKey masterKey = saveParcel.keys.get(0); - PGPSecretKeyRing mKR = ProviderHelper.getPGPSecretKeyRingByKeyId(mContext, masterKey.getKeyID()); - PGPPublicKeyRing pKR = ProviderHelper.getPGPPublicKeyRingByKeyId(mContext, masterKey.getKeyID()); - if (saveParcel.oldPassPhrase == null) { saveParcel.oldPassPhrase = ""; } @@ -359,9 +355,8 @@ public class PgpKeyOperation { } 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 - return; } /* @@ -430,7 +425,7 @@ public class PgpKeyOperation { //here we purposefully ignore partial days in each date - long type has no fractional part! long numDays = (expiryDate.getTimeInMillis() / 86400000) - (creationDate.getTimeInMillis() / 86400000); 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); } else { 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! long numDays = (expiryDate.getTimeInMillis() / 86400000) - (creationDate.getTimeInMillis() / 86400000); 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); } else { hashedPacketsGen.setKeyExpirationTime(false, 0); //do this explicitly, although since we're rebuilding, @@ -637,7 +632,6 @@ public class PgpKeyOperation { //update the passphrase mKR = PGPSecretKeyRing.copyWithNewPassword(mKR, keyDecryptor, keyEncryptorNew); - updateProgress(R.string.progress_saving_key_ring, 90, 100); /* additional handy debug info @@ -660,62 +654,72 @@ public class PgpKeyOperation { */ + return new Pair(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 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 userIds, String passphrase) + throws PgpGeneralMsgIdException, NoSuchAlgorithmException, NoSuchProviderException, PGPException, SignatureException { - if (passphrase == null) { - throw new PgpGeneralException("Unable to obtain passphrase"); - } else { - // create a signatureGenerator from the supplied masterKeyId and passphrase - PGPSignatureGenerator signatureGenerator; { + // create a signatureGenerator from the supplied masterKeyId and passphrase + PGPSignatureGenerator signatureGenerator; { - PGPSecretKey certificationKey = PgpKeyHelper.getCertificationKey(mContext, masterKeyId); - if (certificationKey == null) { - 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); + if (certificationKey == null) { + throw new PgpGeneralMsgIdException(R.string.error_signature_failed); } - { // supply signatureGenerator with a SubpacketVector - PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator(); - PGPSignatureSubpacketVector packetVector = spGen.generate(); - signatureGenerator.setHashedSubpackets(packetVector); + PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider( + Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.toCharArray()); + PGPPrivateKey signaturePrivateKey = certificationKey.extractPrivateKey(keyDecryptor); + if (signaturePrivateKey == null) { + throw new PgpGeneralMsgIdException(R.string.error_could_not_extract_private_key); } - // fetch public key ring, add the certification and return it - PGPPublicKeyRing pubring = ProviderHelper - .getPGPPublicKeyRingByKeyId(mContext, pubKeyId); - PGPPublicKey signedKey = pubring.getPublicKey(pubKeyId); - for(String userId : new IterableIterator(userIds.iterator())) { - PGPSignature sig = signatureGenerator.generateCertification(userId, signedKey); - signedKey = PGPPublicKey.addCertification(signedKey, userId, sig); - } - pubring = PGPPublicKeyRing.insertPublicKey(pubring, signedKey); + // TODO: SHA256 fixed? + JcaPGPContentSignerBuilder contentSignerBuilder = new JcaPGPContentSignerBuilder( + certificationKey.getPublicKey().getAlgorithm(), PGPUtil.SHA256) + .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); - 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(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 { + public final K first; + public final V second; + public Pair(K first, V second) { + this.first = first; + this.second = second; } } } diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/exception/PgpGeneralException.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/exception/PgpGeneralException.java index bb80d27ee..418445367 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/exception/PgpGeneralException.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/exception/PgpGeneralException.java @@ -23,4 +23,7 @@ public class PgpGeneralException extends Exception { public PgpGeneralException(String message) { super(message); } + public PgpGeneralException(String message, Throwable cause) { + super(message, cause); + } } diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/exception/PgpGeneralMsgIdException.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/exception/PgpGeneralMsgIdException.java new file mode 100644 index 000000000..3df85cfc8 --- /dev/null +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/exception/PgpGeneralMsgIdException.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2012-2013 Dominik Schürmann + * Copyright (C) 2010 Thialfihar + * + * 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); + } +} diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java index 2060c6f61..f82e5e619 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java @@ -35,6 +35,8 @@ import org.sufficientlysecure.keychain.helper.OtherHelper; import org.sufficientlysecure.keychain.helper.Preferences; import org.sufficientlysecure.keychain.pgp.*; 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.ui.adapter.ImportKeysListEntry; import org.sufficientlysecure.keychain.util.*; @@ -473,14 +475,26 @@ public class KeychainIntentService extends IntentService long masterKeyId = saveParams.keys.get(0).getKeyID(); - PgpKeyOperation keyOperations = new PgpKeyOperation(this, this); /* Operation */ if (!canSign) { - keyOperations.changeSecretKeyPassphrase( - ProviderHelper.getPGPSecretKeyRingByKeyId(this, masterKeyId), + PgpKeyOperation keyOperations = new PgpKeyOperation(new ProgressScaler(this, 0, 50, 100)); + PGPSecretKeyRing keyRing = ProviderHelper + .getPGPSecretKeyRingByKeyId(this, masterKeyId); + keyRing = keyOperations.changeSecretKeyPassphrase(keyRing, oldPassPhrase, newPassPhrase); + setProgress(R.string.progress_saving_key_ring, 50, 100); + ProviderHelper.saveKeyRing(this, keyRing); + setProgress(R.string.progress_done, 100, 100); } 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 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); @@ -498,7 +512,7 @@ public class KeychainIntentService extends IntentService boolean masterKey = data.getBoolean(GENERATE_KEY_MASTER_KEY); /* Operation */ - PgpKeyOperation keyOperations = new PgpKeyOperation(this, this); + PgpKeyOperation keyOperations = new PgpKeyOperation(new ProgressScaler(this, 0, 100, 100)); PGPSecretKey newKey = keyOperations.createKey(algorithm, keysize, passphrase, masterKey); @@ -529,7 +543,7 @@ public class KeychainIntentService extends IntentService getQuantityString(R.plurals.progress_generating, keysTotal), keysCreated, 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, 4096, passphrase, true); @@ -771,14 +785,23 @@ public class KeychainIntentService extends IntentService /* Operation */ String signaturePassPhrase = PassphraseCacheService.getCachedPassphrase(this, masterKeyId); + if (signaturePassPhrase == null) { + throw new PgpGeneralException("Unable to obtain passphrase"); + } - PgpKeyOperation keyOperation = new PgpKeyOperation(this, this); - PGPPublicKeyRing signedPubKeyRing = keyOperation.certifyKey(masterKeyId, pubKeyId, + PgpKeyOperation keyOperation = new PgpKeyOperation(new ProgressScaler(this, 0, 100, 100)); + PGPPublicKeyRing publicRing = ProviderHelper + .getPGPPublicKeyRingByKeyId(this, pubKeyId); + PGPPublicKey publicKey = publicRing.getPublicKey(pubKeyId); + PGPSecretKey certificationKey = PgpKeyHelper.getCertificationKey(this, + masterKeyId); + publicKey = keyOperation.certifyKey(certificationKey, publicKey, userIds, signaturePassPhrase); + publicRing = PGPPublicKeyRing.insertPublicKey(publicRing, publicKey); // store the signed key in our local cache 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) { throw new PgpGeneralException("Failed to store signed key in local cache"); } @@ -795,7 +818,11 @@ public class KeychainIntentService extends IntentService if (this.mIsCanceled) { 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(); Bundle data = new Bundle(); diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/ProgressScaler.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/ProgressScaler.java new file mode 100644 index 000000000..15b70e22e --- /dev/null +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/ProgressScaler.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2012-2014 Dominik Schürmann + * + * 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 . + */ + +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); + } + +} From 62388e3fdbe4ade7628ed938c4f91a4f39654c4e Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Sat, 22 Mar 2014 14:57:08 +0100 Subject: [PATCH 2/2] test: add junit+robolectric to buildfiles and stub test file --- OpenPGP-Keychain/build.gradle | 42 +++++++++++++++++ .../main/res/layout/key_server_preference.xml | 2 +- .../keychain/PgpKeyOperationTest.java | 46 +++++++++++++++++++ .../keychain/RobolectricGradleTestRunner.java | 23 ++++++++++ 4 files changed, 112 insertions(+), 1 deletion(-) create mode 100644 OpenPGP-Keychain/src/test/java/org/sufficientlysecure/keychain/PgpKeyOperationTest.java create mode 100644 OpenPGP-Keychain/src/test/java/org/sufficientlysecure/keychain/RobolectricGradleTestRunner.java diff --git a/OpenPGP-Keychain/build.gradle b/OpenPGP-Keychain/build.gradle index 04ee46715..7fd1fecce 100644 --- a/OpenPGP-Keychain/build.gradle +++ b/OpenPGP-Keychain/build.gradle @@ -1,5 +1,12 @@ apply plugin: 'android' +sourceSets { + testLocal { + java.srcDir file('src/test/java') + resources.srcDir file('src/test/resources') + } +} + dependencies { compile 'com.android.support:support-v4: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:prov') 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 { @@ -61,3 +87,19 @@ android { 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 diff --git a/OpenPGP-Keychain/src/main/res/layout/key_server_preference.xml b/OpenPGP-Keychain/src/main/res/layout/key_server_preference.xml index eddbe3cbf..b8897a7b3 100644 --- a/OpenPGP-Keychain/src/main/res/layout/key_server_preference.xml +++ b/OpenPGP-Keychain/src/main/res/layout/key_server_preference.xml @@ -6,7 +6,7 @@ android:orientation="vertical" > 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)); + } +} +