fix encrypt/decrypt classes

This commit is contained in:
Vincent Breitmoser 2014-09-17 14:32:14 +02:00
parent 27cc92cbef
commit a3e7406b3d
2 changed files with 190 additions and 18 deletions

View File

@ -18,20 +18,24 @@
package org.sufficientlysecure.keychain.pgp; package org.sufficientlysecure.keychain.pgp;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass; import org.junit.BeforeClass;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.robolectric.*; import org.robolectric.*;
import org.robolectric.shadows.ShadowLog; import org.robolectric.shadows.ShadowLog;
import org.spongycastle.bcpg.sig.KeyFlags;
import org.spongycastle.jce.provider.BouncyCastleProvider; import org.spongycastle.jce.provider.BouncyCastleProvider;
import org.spongycastle.openpgp.PGPEncryptedData; import org.spongycastle.openpgp.PGPEncryptedData;
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify.NoSecretKeyException;
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify.PassphraseCache;
import org.sufficientlysecure.keychain.pgp.PgpSignEncrypt.Builder; import org.sufficientlysecure.keychain.pgp.PgpSignEncrypt.Builder;
import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm;
import org.sufficientlysecure.keychain.service.results.DecryptVerifyResult; import org.sufficientlysecure.keychain.service.results.DecryptVerifyResult;
import org.sufficientlysecure.keychain.service.results.EditKeyResult;
import org.sufficientlysecure.keychain.service.results.SignEncryptResult; import org.sufficientlysecure.keychain.service.results.SignEncryptResult;
import org.sufficientlysecure.keychain.util.InputData; import org.sufficientlysecure.keychain.util.InputData;
import org.sufficientlysecure.keychain.util.ProgressScaler;
import org.sufficientlysecure.keychain.util.TestingUtils; import org.sufficientlysecure.keychain.util.TestingUtils;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
@ -42,58 +46,229 @@ import java.security.Security;
@org.robolectric.annotation.Config(emulateSdk = 18) // Robolectric doesn't yet support 19 @org.robolectric.annotation.Config(emulateSdk = 18) // Robolectric doesn't yet support 19
public class PgpEncryptDecryptTest { public class PgpEncryptDecryptTest {
String mPassphrase = TestingUtils.genPassphrase(true); static String mPassphrase = TestingUtils.genPassphrase(true);
static UncachedKeyRing mStaticRing1, mStaticRing2;
static String mKeyPhrase1 = TestingUtils.genPassphrase(true);
static String mKeyPhrase2 = TestingUtils.genPassphrase(true);
@BeforeClass @BeforeClass
public static void setUpOnce() throws Exception { public static void setUpOnce() throws Exception {
Security.insertProviderAt(new BouncyCastleProvider(), 1); Security.insertProviderAt(new BouncyCastleProvider(), 1);
ShadowLog.stream = System.out; ShadowLog.stream = System.out;
PgpKeyOperation op = new PgpKeyOperation(null);
{
SaveKeyringParcel parcel = new SaveKeyringParcel();
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
Algorithm.RSA, 1024, null, KeyFlags.CERTIFY_OTHER, 0L));
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
Algorithm.DSA, 1024, null, KeyFlags.SIGN_DATA, 0L));
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
Algorithm.ELGAMAL, 1024, null, KeyFlags.ENCRYPT_COMMS, 0L));
parcel.mAddUserIds.add("bloom");
parcel.mNewPassphrase = mKeyPhrase1;
EditKeyResult result = op.createSecretKeyRing(parcel);
Assert.assertTrue("initial test key creation must succeed", result.success());
Assert.assertNotNull("initial test key creation must succeed", result.getRing());
mStaticRing1 = result.getRing();
}
{
SaveKeyringParcel parcel = new SaveKeyringParcel();
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
Algorithm.RSA, 1024, null, KeyFlags.CERTIFY_OTHER, 0L));
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
Algorithm.DSA, 1024, null, KeyFlags.SIGN_DATA, 0L));
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
Algorithm.ELGAMAL, 1024, null, KeyFlags.ENCRYPT_COMMS, 0L));
parcel.mAddUserIds.add("belle");
parcel.mNewPassphrase = mKeyPhrase2;
EditKeyResult result = op.createSecretKeyRing(parcel);
Assert.assertTrue("initial test key creation must succeed", result.success());
Assert.assertNotNull("initial test key creation must succeed", result.getRing());
mStaticRing2 = result.getRing();
}
}
@Before
public void setUp() {
ProviderHelper providerHelper = new ProviderHelper(Robolectric.application);
providerHelper.saveSecretKeyRing(mStaticRing1, new ProgressScaler());
providerHelper.saveSecretKeyRing(mStaticRing1, new ProgressScaler());
} }
@Test @Test
public void testSymmetricEncryptDecrypt() { public void testSymmetricEncryptDecrypt() {
ByteArrayOutputStream out = new ByteArrayOutputStream(); String plaintext = "dies ist ein plaintext ☭" + TestingUtils.genPassphrase(true);
byte[] ciphertext;
{ { // encrypt data with a given passphrase
ByteArrayInputStream in = new ByteArrayInputStream("dies ist ein plaintext ☭".getBytes()); ByteArrayOutputStream out = new ByteArrayOutputStream();
ByteArrayInputStream in = new ByteArrayInputStream(plaintext.getBytes());
InputData data = new InputData(in, in.available()); InputData data = new InputData(in, in.available());
Builder b = new PgpSignEncrypt.Builder(new ProviderHelper(Robolectric.application), data, out); Builder b = new PgpSignEncrypt.Builder(
new ProviderHelper(Robolectric.application),
new DummyPassphraseCache(mPassphrase, 0L),
data, out);
b.setSymmetricPassphrase(mPassphrase); b.setSymmetricPassphrase(mPassphrase);
b.setSymmetricEncryptionAlgorithm(PGPEncryptedData.AES_128); b.setSymmetricEncryptionAlgorithm(PGPEncryptedData.AES_128);
SignEncryptResult result = b.build().execute(); SignEncryptResult result = b.build().execute();
Assert.assertTrue("encryption must succeed", result.success()); Assert.assertTrue("encryption must succeed", result.success());
ciphertext = out.toByteArray();
} }
{ { // decryption with same passphrase should yield the same result
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
InputData data = new InputData(in, in.available());
out.reset(); ByteArrayOutputStream out = new ByteArrayOutputStream();
ByteArrayInputStream in = new ByteArrayInputStream(ciphertext);
InputData data = new InputData(in, in.available());
PgpDecryptVerify.Builder b = new PgpDecryptVerify.Builder( PgpDecryptVerify.Builder b = new PgpDecryptVerify.Builder(
new ProviderHelper(Robolectric.application), new ProviderHelper(Robolectric.application),
new DummyPassphraseCache(mPassphrase), data, out); new DummyPassphraseCache(mPassphrase, 0L), data, out);
b.setPassphrase(mPassphrase); b.setPassphrase(mPassphrase);
DecryptVerifyResult result = b.build().execute(); DecryptVerifyResult result = b.build().execute();
Assert.assertTrue("decryption must succeed", result.success()); Assert.assertTrue("decryption must succeed", result.success());
Assert.assertArrayEquals("decrypted ciphertext should equal plaintext",
out.toByteArray(), plaintext.getBytes());
Assert.assertNull("signature should be an error", result.getSignatureResult());
}
{ // decryption with a bad passphrase should fail
ByteArrayOutputStream out = new ByteArrayOutputStream();
ByteArrayInputStream in = new ByteArrayInputStream(ciphertext);
InputData data = new InputData(in, in.available());
PgpDecryptVerify.Builder b = new PgpDecryptVerify.Builder(
new ProviderHelper(Robolectric.application),
new DummyPassphraseCache(mPassphrase, 0L), data, out);
b.setPassphrase(mPassphrase + "x");
DecryptVerifyResult result = b.build().execute();
Assert.assertFalse("decryption must succeed", result.success());
Assert.assertEquals("decrypted plaintext should be empty", 0, out.size());
Assert.assertNull("signature should be an error", result.getSignatureResult());
}
{ // decryption with an unset passphrase should fail
ByteArrayOutputStream out = new ByteArrayOutputStream();
ByteArrayInputStream in = new ByteArrayInputStream(ciphertext);
InputData data = new InputData(in, in.available());
PgpDecryptVerify.Builder b = new PgpDecryptVerify.Builder(
new ProviderHelper(Robolectric.application),
new DummyPassphraseCache(mPassphrase, 0L), data, out);
DecryptVerifyResult result = b.build().execute();
Assert.assertFalse("decryption must succeed", result.success());
Assert.assertEquals("decrypted plaintext should be empty", 0, out.size());
Assert.assertNull("signature should be an error", result.getSignatureResult());
} }
} }
static class DummyPassphraseCache implements PassphraseCache { @Test
public void testAsymmetricEncryptDecrypt() {
String plaintext = "dies ist ein plaintext ☭" + TestingUtils.genPassphrase(true);
byte[] ciphertext;
{ // encrypt data with a given passphrase
ByteArrayOutputStream out = new ByteArrayOutputStream();
ByteArrayInputStream in = new ByteArrayInputStream(plaintext.getBytes());
InputData data = new InputData(in, in.available());
Builder b = new PgpSignEncrypt.Builder(
new ProviderHelper(Robolectric.application),
new DummyPassphraseCache(mPassphrase, 0L),
data, out);
b.setEncryptionMasterKeyIds(new long[]{ mStaticRing1.getMasterKeyId() });
b.setSymmetricEncryptionAlgorithm(PGPEncryptedData.AES_128);
SignEncryptResult result = b.build().execute();
Assert.assertTrue("encryption must succeed", result.success());
ciphertext = out.toByteArray();
}
{ // decryption with provided passphrase should yield the same result
ByteArrayOutputStream out = new ByteArrayOutputStream();
ByteArrayInputStream in = new ByteArrayInputStream(ciphertext);
InputData data = new InputData(in, in.available());
PgpDecryptVerify.Builder b = new PgpDecryptVerify.Builder(
new ProviderHelper(Robolectric.application),
new DummyPassphraseCache(null, null), data, out);
b.setPassphrase(mKeyPhrase1);
DecryptVerifyResult result = b.build().execute();
Assert.assertTrue("decryption with provided passphrase must succeed", result.success());
Assert.assertArrayEquals("decrypted ciphertext with provided passphrase should equal plaintext",
out.toByteArray(), plaintext.getBytes());
Assert.assertNull("signature be empty", result.getSignatureResult());
}
{ // decryption with passphrase cached should succeed
ByteArrayOutputStream out = new ByteArrayOutputStream();
ByteArrayInputStream in = new ByteArrayInputStream(ciphertext);
InputData data = new InputData(in, in.available());
PgpDecryptVerify.Builder b = new PgpDecryptVerify.Builder(
new ProviderHelper(Robolectric.application),
new DummyPassphraseCache(mKeyPhrase1, null), data, out);
DecryptVerifyResult result = b.build().execute();
Assert.assertTrue("decryption with cached passphrase must succeed", result.success());
Assert.assertArrayEquals("decrypted ciphertext with cached passphrase should equal plaintext",
out.toByteArray(), plaintext.getBytes());
Assert.assertNull("signature should be empty", result.getSignatureResult());
}
{ // decryption with no passphrase provided should return status pending
ByteArrayOutputStream out = new ByteArrayOutputStream();
ByteArrayInputStream in = new ByteArrayInputStream(ciphertext);
InputData data = new InputData(in, in.available());
PgpDecryptVerify.Builder b = new PgpDecryptVerify.Builder(
new ProviderHelper(Robolectric.application),
new DummyPassphraseCache(null, null), data, out);
DecryptVerifyResult result = b.build().execute();
Assert.assertFalse("decryption with no passphrase must return pending", result.success());
Assert.assertTrue("decryption with no passphrase should return pending", result.isPending());
Assert.assertEquals("decryption with no passphrase should return pending passphrase",
DecryptVerifyResult.RESULT_PENDING_ASYM_PASSPHRASE, result.getResult());
}
}
static class DummyPassphraseCache implements PassphraseCacheInterface {
String mPassphrase; String mPassphrase;
public DummyPassphraseCache(String passphrase) { Long mExpectedId;
public DummyPassphraseCache(String passphrase, Long expectedId) {
mPassphrase = passphrase; mPassphrase = passphrase;
mExpectedId = expectedId;
} }
@Override @Override
public String getCachedPassphrase(long masterKeyId) throws NoSecretKeyException { public String getCachedPassphrase(long masterKeyId) throws NoSecretKeyException {
Assert.assertEquals("requested passphrase must be for symmetric id", 0L, masterKeyId); if (mExpectedId != null){
Assert.assertEquals("requested passphrase must be for expected id",
(long) mExpectedId, masterKeyId);
}
return mPassphrase; return mPassphrase;
} }
} }

View File

@ -643,9 +643,6 @@ public class PgpDecryptVerify {
return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log); return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
} }
} else { } else {
// no integrity check
Log.d(Constants.TAG, "Encrypted data was not integrity protected! MDC packet is missing!");
// If no valid signature is present: // If no valid signature is present:
// Handle missing integrity protection like failed integrity protection! // Handle missing integrity protection like failed integrity protection!
// The MDC packet can be stripped by an attacker! // The MDC packet can be stripped by an attacker!