Merge pull request #501 from Valodim/master

factor out android dependencies from PgpKeyOperation, and add JUnit/Robolectric testing stubs
This commit is contained in:
Dominik Schürmann 2014-04-01 16:13:41 +02:00
commit 247ad6207a
9 changed files with 348 additions and 118 deletions

View File

@ -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

View File

@ -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;
} }
} }
} }

View File

@ -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);
}
} }

View File

@ -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);
}
}

View File

@ -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();

View File

@ -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);
}
}

View File

@ -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"

View File

@ -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");
}
}

View File

@ -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));
}
}