add tests for MessageCryptoHelper

This commit is contained in:
Vincent Breitmoser 2016-06-21 15:32:43 +02:00 committed by Philip
parent 8b1cfb0730
commit 4d580d539d
6 changed files with 290 additions and 15 deletions

View file

@ -57,7 +57,7 @@ public abstract class Message implements Part, Body {
final int MULTIPLIER = 31; final int MULTIPLIER = 31;
int result = 1; int result = 1;
result = MULTIPLIER * result + mFolder.getName().hashCode(); result = MULTIPLIER * result + (mFolder != null ? mFolder.getName().hashCode() : 0);
result = MULTIPLIER * result + mUid.hashCode(); result = MULTIPLIER * result + mUid.hashCode();
return result; return result;
} }

View file

@ -13,6 +13,8 @@ import android.os.Parcelable;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.annotation.UiThread; import android.support.annotation.UiThread;
import com.fsck.k9.ui.crypto.OpenPgpApiFactory;
import timber.log.Timber; import timber.log.Timber;
import com.fsck.k9.Account; import com.fsck.k9.Account;
@ -268,7 +270,7 @@ public class MessageLoaderHelper {
messageCryptoHelper = retainCryptoHelperFragment.getData(); messageCryptoHelper = retainCryptoHelperFragment.getData();
} }
if (messageCryptoHelper == null || messageCryptoHelper.isConfiguredForOutdatedCryptoProvider()) { if (messageCryptoHelper == null || messageCryptoHelper.isConfiguredForOutdatedCryptoProvider()) {
messageCryptoHelper = new MessageCryptoHelper(context); messageCryptoHelper = new MessageCryptoHelper(context, new OpenPgpApiFactory());
retainCryptoHelperFragment.setData(messageCryptoHelper); retainCryptoHelperFragment.setData(messageCryptoHelper);
} }
messageCryptoHelper.asyncStartOrResumeProcessingMessage( messageCryptoHelper.asyncStartOrResumeProcessingMessage(

View file

@ -200,13 +200,11 @@ public class MessageDecryptVerifier {
if (body instanceof Multipart) { if (body instanceof Multipart) {
Multipart multi = (Multipart) body; Multipart multi = (Multipart) body;
BodyPart signatureBody = multi.getBodyPart(1); BodyPart signatureBody = multi.getBodyPart(1);
if (isSameMimeType(signatureBody.getMimeType(), APPLICATION_PGP_SIGNATURE)) {
ByteArrayOutputStream bos = new ByteArrayOutputStream(); ByteArrayOutputStream bos = new ByteArrayOutputStream();
signatureBody.getBody().writeTo(bos); signatureBody.getBody().writeTo(bos);
return bos.toByteArray(); return bos.toByteArray();
} }
} }
}
return null; return null;
} }

View file

@ -16,7 +16,6 @@ import android.content.Intent;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.annotation.WorkerThread; import android.support.annotation.WorkerThread;
import timber.log.Timber;
import com.fsck.k9.K9; import com.fsck.k9.K9;
import com.fsck.k9.crypto.MessageDecryptVerifier; import com.fsck.k9.crypto.MessageDecryptVerifier;
@ -24,6 +23,7 @@ import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.Body; import com.fsck.k9.mail.Body;
import com.fsck.k9.mail.BodyPart; import com.fsck.k9.mail.BodyPart;
import com.fsck.k9.mail.Flag; import com.fsck.k9.mail.Flag;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessagingException; import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Multipart; import com.fsck.k9.mail.Multipart;
import com.fsck.k9.mail.Part; import com.fsck.k9.mail.Part;
@ -34,7 +34,6 @@ import com.fsck.k9.mail.internet.SizeAware;
import com.fsck.k9.mail.internet.TextBody; import com.fsck.k9.mail.internet.TextBody;
import com.fsck.k9.mailstore.CryptoResultAnnotation; import com.fsck.k9.mailstore.CryptoResultAnnotation;
import com.fsck.k9.mailstore.CryptoResultAnnotation.CryptoError; import com.fsck.k9.mailstore.CryptoResultAnnotation.CryptoError;
import com.fsck.k9.mailstore.LocalMessage;
import com.fsck.k9.mailstore.MessageHelper; import com.fsck.k9.mailstore.MessageHelper;
import com.fsck.k9.mailstore.MimePartStreamParser; import com.fsck.k9.mailstore.MimePartStreamParser;
import com.fsck.k9.mailstore.util.FileFactory; import com.fsck.k9.mailstore.util.FileFactory;
@ -52,6 +51,7 @@ import org.openintents.openpgp.util.OpenPgpApi.OpenPgpDataSource;
import org.openintents.openpgp.util.OpenPgpServiceConnection; import org.openintents.openpgp.util.OpenPgpServiceConnection;
import org.openintents.openpgp.util.OpenPgpServiceConnection.OnBound; import org.openintents.openpgp.util.OpenPgpServiceConnection.OnBound;
import org.openintents.openpgp.util.OpenPgpUtils; import org.openintents.openpgp.util.OpenPgpUtils;
import timber.log.Timber;
public class MessageCryptoHelper { public class MessageCryptoHelper {
@ -68,7 +68,7 @@ public class MessageCryptoHelper {
@Nullable @Nullable
private MessageCryptoCallback callback; private MessageCryptoCallback callback;
private LocalMessage currentMessage; private Message currentMessage;
private OpenPgpDecryptionResult cachedDecryptionResult; private OpenPgpDecryptionResult cachedDecryptionResult;
private MessageCryptoAnnotations queuedResult; private MessageCryptoAnnotations queuedResult;
private PendingIntent queuedPendingIntent; private PendingIntent queuedPendingIntent;
@ -84,15 +84,17 @@ public class MessageCryptoHelper {
private OpenPgpApi openPgpApi; private OpenPgpApi openPgpApi;
private OpenPgpServiceConnection openPgpServiceConnection; private OpenPgpServiceConnection openPgpServiceConnection;
private OpenPgpApiFactory openPgpApiFactory;
public MessageCryptoHelper(Context context) { public MessageCryptoHelper(Context context, OpenPgpApiFactory openPgpApiFactory) {
this.context = context.getApplicationContext(); this.context = context.getApplicationContext();
if (!K9.isOpenPgpProviderConfigured()) { if (!K9.isOpenPgpProviderConfigured()) {
throw new IllegalStateException("MessageCryptoHelper must only be called with a openpgp provider!"); throw new IllegalStateException("MessageCryptoHelper must only be called with a openpgp provider!");
} }
this.openPgpApiFactory = openPgpApiFactory;
openPgpProviderPackage = K9.getOpenPgpProvider(); openPgpProviderPackage = K9.getOpenPgpProvider();
} }
@ -100,7 +102,7 @@ public class MessageCryptoHelper {
return !openPgpProviderPackage.equals(K9.getOpenPgpProvider()); return !openPgpProviderPackage.equals(K9.getOpenPgpProvider());
} }
public void asyncStartOrResumeProcessingMessage(LocalMessage message, MessageCryptoCallback callback, public void asyncStartOrResumeProcessingMessage(Message message, MessageCryptoCallback callback,
OpenPgpDecryptionResult cachedDecryptionResult) { OpenPgpDecryptionResult cachedDecryptionResult) {
if (this.currentMessage != null) { if (this.currentMessage != null) {
reattachCallback(message, callback); reattachCallback(message, callback);
@ -215,9 +217,10 @@ public class MessageCryptoHelper {
private void connectToCryptoProviderService() { private void connectToCryptoProviderService() {
openPgpServiceConnection = new OpenPgpServiceConnection(context, openPgpProviderPackage, openPgpServiceConnection = new OpenPgpServiceConnection(context, openPgpProviderPackage,
new OnBound() { new OnBound() {
@Override @Override
public void onBound(IOpenPgpService2 service) { public void onBound(IOpenPgpService2 service) {
openPgpApi = new OpenPgpApi(context, service); openPgpApi = openPgpApiFactory.createOpenPgpApi(context, service);
decryptOrVerifyNextPart(); decryptOrVerifyNextPart();
} }
@ -621,7 +624,7 @@ public class MessageCryptoHelper {
} }
} }
private void reattachCallback(LocalMessage message, MessageCryptoCallback callback) { private void reattachCallback(Message message, MessageCryptoCallback callback) {
if (!message.equals(currentMessage)) { if (!message.equals(currentMessage)) {
throw new AssertionError("Callback may only be reattached for the same message!"); throw new AssertionError("Callback may only be reattached for the same message!");
} }
@ -726,5 +729,4 @@ public class MessageCryptoHelper {
return NO_REPLACEMENT_PART; return NO_REPLACEMENT_PART;
} }
} }
} }

View file

@ -0,0 +1,14 @@
package com.fsck.k9.ui.crypto;
import android.content.Context;
import org.openintents.openpgp.util.OpenPgpApi;
import org.openintents.openpgp.IOpenPgpService2;
public class OpenPgpApiFactory {
OpenPgpApi createOpenPgpApi(Context context, IOpenPgpService2 service) {
return new OpenPgpApi(context, service);
}
}

View file

@ -0,0 +1,259 @@
package com.fsck.k9.ui.crypto;
import java.io.OutputStream;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import com.fsck.k9.K9;
import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.Body;
import com.fsck.k9.mail.BodyPart;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.Multipart;
import com.fsck.k9.mail.internet.MimeBodyPart;
import com.fsck.k9.mail.internet.MimeMessage;
import com.fsck.k9.mail.internet.MimeMultipart;
import com.fsck.k9.mail.internet.TextBody;
import com.fsck.k9.mailstore.CryptoResultAnnotation;
import com.fsck.k9.mailstore.CryptoResultAnnotation.CryptoError;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.openintents.openpgp.IOpenPgpService2;
import org.openintents.openpgp.OpenPgpDecryptionResult;
import org.openintents.openpgp.OpenPgpSignatureResult;
import org.openintents.openpgp.util.OpenPgpApi;
import org.openintents.openpgp.util.OpenPgpApi.IOpenPgpSinkResultCallback;
import org.openintents.openpgp.util.OpenPgpApi.OpenPgpDataSink;
import org.openintents.openpgp.util.OpenPgpApi.OpenPgpDataSource;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertSame;
import static junit.framework.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
@SuppressWarnings("unchecked")
@RunWith(RobolectricTestRunner.class)
@Config(manifest = Config.NONE, sdk = 21)
public class MessageCryptoHelperTest {
private MessageCryptoHelper messageCryptoHelper;
private OpenPgpApi openPgpApi;
private Intent capturedApiIntent;
private IOpenPgpSinkResultCallback capturedCallback;
private MessageCryptoCallback messageCryptoCallback;
@Before
public void setUp() throws Exception {
openPgpApi = mock(OpenPgpApi.class);
K9.setOpenPgpProvider("org.example.dummy");
OpenPgpApiFactory openPgpApiFactory = mock(OpenPgpApiFactory.class);
when(openPgpApiFactory.createOpenPgpApi(any(Context.class), any(IOpenPgpService2.class))).thenReturn(openPgpApi);
messageCryptoHelper = new MessageCryptoHelper(RuntimeEnvironment.application, openPgpApiFactory);
messageCryptoCallback = mock(MessageCryptoCallback.class);
}
@Test
public void textPlain() throws Exception {
MimeMessage message = new MimeMessage();
message.setUid("msguid");
message.setHeader("Content-Type", "text/plain");
MessageCryptoCallback messageCryptoCallback = mock(MessageCryptoCallback.class);
messageCryptoHelper.asyncStartOrResumeProcessingMessage(message, messageCryptoCallback, null);
ArgumentCaptor<MessageCryptoAnnotations> captor = ArgumentCaptor.forClass(MessageCryptoAnnotations.class);
verify(messageCryptoCallback).onCryptoOperationsFinished(captor.capture());
MessageCryptoAnnotations annotations = captor.getValue();
assertTrue(annotations.isEmpty());
verifyNoMoreInteractions(messageCryptoCallback);
}
@Test
public void multipartSigned__withNullBody__shouldReturnSignedIncomplete() throws Exception {
MimeMessage message = new MimeMessage();
message.setUid("msguid");
message.setHeader("Content-Type", "multipart/signed");
MessageCryptoCallback messageCryptoCallback = mock(MessageCryptoCallback.class);
messageCryptoHelper.asyncStartOrResumeProcessingMessage(message, messageCryptoCallback, null);
assertPartAnnotationHasState(message, messageCryptoCallback, CryptoError.OPENPGP_SIGNED_BUT_INCOMPLETE, null,
null, null, null);
}
@Test
public void multipartEncrypted__withNullBody__shouldReturnEncryptedIncomplete() throws Exception {
MimeMessage message = new MimeMessage();
message.setUid("msguid");
message.setHeader("Content-Type", "multipart/encrypted");
MessageCryptoCallback messageCryptoCallback = mock(MessageCryptoCallback.class);
messageCryptoHelper.asyncStartOrResumeProcessingMessage(message, messageCryptoCallback, null);
assertPartAnnotationHasState(
message, messageCryptoCallback, CryptoError.OPENPGP_ENCRYPTED_BUT_INCOMPLETE, null, null, null, null);
}
@Test
public void multipartEncrypted__withUnknownProtocol__shouldReturnEncryptedUnsupported() throws Exception {
MimeMessage message = new MimeMessage();
message.setUid("msguid");
message.setHeader("Content-Type", "multipart/encrypted; protocol=\"unknown protocol\"");
message.setBody(new MimeMultipart("multipart/encrypted", "--------"));
MessageCryptoCallback messageCryptoCallback = mock(MessageCryptoCallback.class);
messageCryptoHelper.asyncStartOrResumeProcessingMessage(message, messageCryptoCallback, null);
assertPartAnnotationHasState(message, messageCryptoCallback, CryptoError.ENCRYPTED_BUT_UNSUPPORTED, null, null,
null, null);
}
@Test
public void multipartSigned__withUnknownProtocol__shouldReturnSignedUnsupported() throws Exception {
MimeMessage message = new MimeMessage();
message.setUid("msguid");
message.setHeader("Content-Type", "multipart/signed; protocol=\"unknown protocol\"");
message.setBody(new MimeMultipart("multipart/encrypted", "--------"));
MessageCryptoCallback messageCryptoCallback = mock(MessageCryptoCallback.class);
messageCryptoHelper.asyncStartOrResumeProcessingMessage(message, messageCryptoCallback, null);
assertPartAnnotationHasState(message, messageCryptoCallback, CryptoError.SIGNED_BUT_UNSUPPORTED, null, null,
null, null);
}
@Test
public void multipartSigned__shouldCallOpenPgpApiAsync() throws Exception {
BodyPart signedBodyPart = spy(new MimeBodyPart(new TextBody("text")));
BodyPart signatureBodyPart = new MimeBodyPart(new TextBody("text"));
Multipart messageBody = new MimeMultipart("boundary1");
messageBody.addBodyPart(signedBodyPart);
messageBody.addBodyPart(signatureBodyPart);
MimeMessage message = new MimeMessage();
message.setUid("msguid");
message.setHeader("Content-Type", "multipart/signed; protocol=\"application/pgp-signature\"");
message.setFrom(Address.parse("Test <test@example.org>")[0]);
message.setBody(messageBody);
OutputStream outputStream = mock(OutputStream.class);
processSignedMessageAndCaptureMocks(message, signedBodyPart, outputStream);
assertEquals(OpenPgpApi.ACTION_DECRYPT_VERIFY, capturedApiIntent.getAction());
assertEquals("test@example.org", capturedApiIntent.getStringExtra(OpenPgpApi.EXTRA_SENDER_ADDRESS));
}
@Test
public void multipartEncrypted__shouldCallOpenPgpApiAsync() throws Exception {
BodyPart dummyBodyPart = new MimeBodyPart(new TextBody("text"));
Body encryptedBody = spy(new TextBody("encrypted data"));
BodyPart encryptedBodyPart = spy(new MimeBodyPart(encryptedBody));
Multipart messageBody = new MimeMultipart("boundary1");
messageBody.addBodyPart(dummyBodyPart);
messageBody.addBodyPart(encryptedBodyPart);
MimeMessage message = new MimeMessage();
message.setUid("msguid");
message.setHeader("Content-Type", "multipart/encrypted; protocol=\"application/pgp-encrypted\"");
message.setFrom(Address.parse("Test <test@example.org>")[0]);
message.setBody(messageBody);
OutputStream outputStream = mock(OutputStream.class);
Intent resultIntent = new Intent();
resultIntent.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
OpenPgpDecryptionResult decryptionResult = mock(OpenPgpDecryptionResult.class);
resultIntent.putExtra(OpenPgpApi.RESULT_DECRYPTION, decryptionResult);
OpenPgpSignatureResult signatureResult = mock(OpenPgpSignatureResult.class);
resultIntent.putExtra(OpenPgpApi.RESULT_SIGNATURE, signatureResult);
PendingIntent pendingIntent = mock(PendingIntent.class);
resultIntent.putExtra(OpenPgpApi.RESULT_INTENT, pendingIntent);
processEncryptedMessageAndCaptureMocks(message, encryptedBody, outputStream);
MimeBodyPart decryptedPart = new MimeBodyPart(new TextBody("text"));
capturedCallback.onReturn(resultIntent, decryptedPart);
assertEquals(OpenPgpApi.ACTION_DECRYPT_VERIFY, capturedApiIntent.getAction());
assertEquals("test@example.org", capturedApiIntent.getStringExtra(OpenPgpApi.EXTRA_SENDER_ADDRESS));
assertPartAnnotationHasState(message, messageCryptoCallback, CryptoError.OPENPGP_OK, decryptedPart,
decryptionResult, signatureResult, pendingIntent);
}
private void processEncryptedMessageAndCaptureMocks(Message message, Body encryptedBody, OutputStream outputStream)
throws Exception {
messageCryptoHelper.asyncStartOrResumeProcessingMessage(message, messageCryptoCallback, null);
ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
ArgumentCaptor<OpenPgpDataSource> dataSourceCaptor = ArgumentCaptor.forClass(OpenPgpDataSource.class);
ArgumentCaptor<IOpenPgpSinkResultCallback> callbackCaptor = ArgumentCaptor.forClass(IOpenPgpSinkResultCallback.class);
verify(openPgpApi).executeApiAsync(intentCaptor.capture(), dataSourceCaptor.capture(),
any(OpenPgpDataSink.class), callbackCaptor.capture());
capturedApiIntent = intentCaptor.getValue();
capturedCallback = callbackCaptor.getValue();
OpenPgpDataSource dataSource = dataSourceCaptor.getValue();
dataSource.writeTo(outputStream);
verify(encryptedBody).writeTo(outputStream);
}
private void processSignedMessageAndCaptureMocks(Message message, BodyPart signedBodyPart,
OutputStream outputStream) throws Exception {
messageCryptoHelper.asyncStartOrResumeProcessingMessage(message, messageCryptoCallback, null);
ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
ArgumentCaptor<OpenPgpDataSource> dataSourceCaptor = ArgumentCaptor.forClass(OpenPgpDataSource.class);
ArgumentCaptor<IOpenPgpSinkResultCallback> callbackCaptor = ArgumentCaptor.forClass(IOpenPgpSinkResultCallback.class);
verify(openPgpApi).executeApiAsync(intentCaptor.capture(), dataSourceCaptor.capture(),
callbackCaptor.capture());
capturedApiIntent = intentCaptor.getValue();
capturedCallback = callbackCaptor.getValue();
OpenPgpDataSource dataSource = dataSourceCaptor.getValue();
dataSource.writeTo(outputStream);
verify(signedBodyPart).writeTo(outputStream);
}
private void assertPartAnnotationHasState(Message message, MessageCryptoCallback messageCryptoCallback,
CryptoError cryptoErrorState, MimeBodyPart replacementPart, OpenPgpDecryptionResult openPgpDecryptionResult,
OpenPgpSignatureResult openPgpSignatureResult, PendingIntent openPgpPendingIntent) {
ArgumentCaptor<MessageCryptoAnnotations> captor = ArgumentCaptor.forClass(MessageCryptoAnnotations.class);
verify(messageCryptoCallback).onCryptoOperationsFinished(captor.capture());
MessageCryptoAnnotations annotations = captor.getValue();
CryptoResultAnnotation cryptoResultAnnotation = annotations.get(message);
assertEquals(cryptoErrorState, cryptoResultAnnotation.getErrorType());
if (replacementPart != null) {
assertSame(replacementPart, cryptoResultAnnotation.getReplacementData());
}
assertSame(openPgpDecryptionResult, cryptoResultAnnotation.getOpenPgpDecryptionResult());
assertSame(openPgpSignatureResult, cryptoResultAnnotation.getOpenPgpSignatureResult());
assertSame(openPgpPendingIntent, cryptoResultAnnotation.getOpenPgpPendingIntent());
verifyNoMoreInteractions(messageCryptoCallback);
}
}