Merge pull request #2763 from k9mail/improve-crypto-structure-detection

Improve crypto structure detection
This commit is contained in:
cketti 2017-09-16 23:36:48 +02:00 committed by GitHub
commit e266547bfc
14 changed files with 469 additions and 269 deletions

View file

@ -162,6 +162,11 @@ public class MessageExtractor {
Alternative alternative = new Alternative(text, html);
outputViewableParts.add(alternative);
}
} else if (isSameMimeType(part.getMimeType(), "multipart/signed")) {
if (multipart.getCount() > 0) {
BodyPart bodyPart = multipart.getBodyPart(0);
findViewablesAndAttachments(bodyPart, outputViewableParts, outputNonViewableParts);
}
} else {
// For all other multipart parts we recurse to grab all viewable children.
for (Part bodyPart : multipart.getBodyParts()) {

View file

@ -30,10 +30,10 @@ public class MimeBodyPart extends BodyPart {
this(body, null);
}
public MimeBodyPart(Body body, String mimeType) throws MessagingException {
public MimeBodyPart(Body body, String contentType) throws MessagingException {
mHeader = new MimeHeader();
if (mimeType != null) {
addHeader(MimeHeader.HEADER_CONTENT_TYPE, mimeType);
if (contentType != null) {
addHeader(MimeHeader.HEADER_CONTENT_TYPE, contentType);
}
MimeMessageHelper.setBody(this, body);
}

View file

@ -81,6 +81,7 @@ public class MessageLoaderHelper {
private LoaderManager loaderManager;
@Nullable // make this explicitly nullable, make sure to cancel/ignore any operation if this is null
private MessageLoaderCallbacks callback;
private final boolean processSignedOnly;
// transient state
@ -100,6 +101,8 @@ public class MessageLoaderHelper {
this.loaderManager = loaderManager;
this.fragmentManager = fragmentManager;
this.callback = callback;
processSignedOnly = K9.getOpenPgpSupportSignOnly();
}
@ -276,7 +279,7 @@ public class MessageLoaderHelper {
retainCryptoHelperFragment.setData(messageCryptoHelper);
}
messageCryptoHelper.asyncStartOrResumeProcessingMessage(
localMessage, messageCryptoCallback, cachedDecryptionResult);
localMessage, messageCryptoCallback, cachedDecryptionResult, processSignedOnly);
}
private void cancelAndClearCryptoOperation() {

View file

@ -18,6 +18,7 @@ import com.fsck.k9.mail.Multipart;
import com.fsck.k9.mail.Part;
import com.fsck.k9.mail.internet.MessageExtractor;
import com.fsck.k9.mail.internet.MimeBodyPart;
import com.fsck.k9.mail.internet.MimeMultipart;
import com.fsck.k9.mail.internet.MimeUtility;
import com.fsck.k9.mailstore.CryptoResultAnnotation;
import com.fsck.k9.ui.crypto.MessageCryptoAnnotations;
@ -25,7 +26,7 @@ import com.fsck.k9.ui.crypto.MessageCryptoAnnotations;
import static com.fsck.k9.mail.internet.MimeUtility.isSameMimeType;
public class MessageDecryptVerifier {
public class MessageCryptoStructureDetector {
private static final String MULTIPART_ENCRYPTED = "multipart/encrypted";
private static final String MULTIPART_SIGNED = "multipart/signed";
private static final String PROTOCOL_PARAMETER = "protocol";
@ -109,7 +110,7 @@ public class MessageDecryptVerifier {
return null;
}
public static List<Part> findEncryptedParts(Part startPart) {
public static List<Part> findMultipartEncryptedParts(Part startPart) {
List<Part> encryptedParts = new ArrayList<>();
Stack<Part> partsToCheck = new Stack<>();
partsToCheck.push(startPart);
@ -135,7 +136,7 @@ public class MessageDecryptVerifier {
return encryptedParts;
}
public static List<Part> findSignedParts(Part startPart, MessageCryptoAnnotations messageCryptoAnnotations) {
public static List<Part> findMultipartSignedParts(Part startPart, MessageCryptoAnnotations messageCryptoAnnotations) {
List<Part> signedParts = new ArrayList<>();
Stack<Part> partsToCheck = new Stack<>();
partsToCheck.push(startPart);
@ -214,24 +215,59 @@ public class MessageDecryptVerifier {
}
private static boolean isPartMultipartSigned(Part part) {
return isSameMimeType(part.getMimeType(), MULTIPART_SIGNED);
if (!isSameMimeType(part.getMimeType(), MULTIPART_SIGNED)) {
return false;
}
if (! (part.getBody() instanceof MimeMultipart)) {
return false;
}
MimeMultipart mimeMultipart = (MimeMultipart) part.getBody();
if (mimeMultipart.getCount() != 2) {
return false;
}
String protocolParameter = MimeUtility.getHeaderParameter(part.getContentType(), PROTOCOL_PARAMETER);
boolean dataUnavailable = protocolParameter == null && mimeMultipart.getBodyPart(0).getBody() == null;
boolean protocolMatches = isSameMimeType(protocolParameter, mimeMultipart.getBodyPart(1).getMimeType());
return dataUnavailable || protocolMatches;
}
private static boolean isPartMultipartEncrypted(Part part) {
return isSameMimeType(part.getMimeType(), MULTIPART_ENCRYPTED);
if (!isSameMimeType(part.getMimeType(), MULTIPART_ENCRYPTED)) {
return false;
}
if (! (part.getBody() instanceof MimeMultipart)) {
return false;
}
MimeMultipart mimeMultipart = (MimeMultipart) part.getBody();
if (mimeMultipart.getCount() != 2) {
return false;
}
// TODO also guess by mime-type of contained part?
public static boolean isPgpMimeEncryptedOrSignedPart(Part part) {
String contentType = part.getContentType();
String protocolParameter = MimeUtility.getHeaderParameter(contentType, PROTOCOL_PARAMETER);
String protocolParameter = MimeUtility.getHeaderParameter(part.getContentType(), PROTOCOL_PARAMETER);
boolean isPgpEncrypted = isSameMimeType(part.getMimeType(), MULTIPART_ENCRYPTED) &&
APPLICATION_PGP_ENCRYPTED.equalsIgnoreCase(protocolParameter);
boolean isPgpSigned = isSameMimeType(part.getMimeType(), MULTIPART_SIGNED) &&
APPLICATION_PGP_SIGNATURE.equalsIgnoreCase(protocolParameter);
boolean dataUnavailable = protocolParameter == null && mimeMultipart.getBodyPart(1).getBody() == null;
boolean protocolMatches = isSameMimeType(protocolParameter, mimeMultipart.getBodyPart(0).getMimeType());
return dataUnavailable || protocolMatches;
}
return isPgpEncrypted || isPgpSigned;
public static boolean isMultipartEncryptedOpenPgpProtocol(Part part) {
if (!isSameMimeType(part.getMimeType(), MULTIPART_ENCRYPTED)) {
throw new IllegalArgumentException("Part is not multipart/encrypted!");
}
String protocolParameter = MimeUtility.getHeaderParameter(part.getContentType(), PROTOCOL_PARAMETER);
return APPLICATION_PGP_ENCRYPTED.equalsIgnoreCase(protocolParameter);
}
public static boolean isMultipartSignedOpenPgpProtocol(Part part) {
if (!isSameMimeType(part.getMimeType(), MULTIPART_SIGNED)) {
throw new IllegalArgumentException("Part is not multipart/signed!");
}
String protocolParameter = MimeUtility.getHeaderParameter(part.getContentType(), PROTOCOL_PARAMETER);
return APPLICATION_PGP_SIGNATURE.equalsIgnoreCase(protocolParameter);
}
@VisibleForTesting

View file

@ -28,6 +28,7 @@ import com.fsck.k9.message.html.HtmlProcessor;
import com.fsck.k9.ui.crypto.MessageCryptoAnnotations;
import com.fsck.k9.ui.crypto.MessageCryptoSplitter;
import com.fsck.k9.ui.crypto.MessageCryptoSplitter.CryptoMessageParts;
import org.openintents.openpgp.util.OpenPgpUtils;
import timber.log.Timber;
import static com.fsck.k9.mail.internet.MimeUtility.getHeaderParameter;
@ -227,7 +228,7 @@ public class MessageViewInfoExtractor {
Part part = ((Textual)viewable).getPart();
addHtmlDivider(html, part, prependDivider);
String t = MessageExtractor.getTextFromPart(part);
String t = getTextFromPart(part);
if (t == null) {
t = "";
} else if (viewable instanceof Flowed) {
@ -264,7 +265,7 @@ public class MessageViewInfoExtractor {
Part part = ((Textual)viewable).getPart();
addTextDivider(text, part, prependDivider);
String t = MessageExtractor.getTextFromPart(part);
String t = getTextFromPart(part);
if (t == null) {
t = "";
} else if (viewable instanceof Html) {
@ -315,6 +316,17 @@ public class MessageViewInfoExtractor {
}
}
private String getTextFromPart(Part part) {
String textFromPart = MessageExtractor.getTextFromPart(part);
String extractedClearsignedMessage = OpenPgpUtils.extractClearsignedMessage(textFromPart);
if (extractedClearsignedMessage != null) {
textFromPart = extractedClearsignedMessage;
}
return textFromPart;
}
/**
* Get the name of the message part.
*
@ -504,7 +516,7 @@ public class MessageViewInfoExtractor {
public final String text;
public final String html;
public ViewableExtractedText(String text, String html) {
ViewableExtractedText(String text, String html) {
this.text = text;
this.html = html;
}

View file

@ -3,7 +3,7 @@ package com.fsck.k9.message;
import java.util.List;
import com.fsck.k9.crypto.MessageDecryptVerifier;
import com.fsck.k9.crypto.MessageCryptoStructureDetector;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.Part;
@ -14,7 +14,7 @@ public class ComposePgpEnableByDefaultDecider {
}
private boolean messageIsEncrypted(Message localMessage) {
List<Part> encryptedParts = MessageDecryptVerifier.findEncryptedParts(localMessage);
List<Part> encryptedParts = MessageCryptoStructureDetector.findMultipartEncryptedParts(localMessage);
return !encryptedParts.isEmpty();
}
}

View file

@ -3,7 +3,7 @@ package com.fsck.k9.message;
import java.util.List;
import com.fsck.k9.crypto.MessageDecryptVerifier;
import com.fsck.k9.crypto.MessageCryptoStructureDetector;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.Part;
@ -15,7 +15,7 @@ public class ComposePgpInlineDecider {
}
private boolean messageHasPgpInlineParts(Message localMessage) {
List<Part> inlineParts = MessageDecryptVerifier.findPgpInlineParts(localMessage);
List<Part> inlineParts = MessageCryptoStructureDetector.findPgpInlineParts(localMessage);
return !inlineParts.isEmpty();
}
}

View file

@ -3,7 +3,7 @@ package com.fsck.k9.message.extractors;
import android.support.annotation.NonNull;
import com.fsck.k9.crypto.MessageDecryptVerifier;
import com.fsck.k9.crypto.MessageCryptoStructureDetector;
import com.fsck.k9.mail.Body;
import com.fsck.k9.mail.BodyPart;
import com.fsck.k9.mail.Message;
@ -31,7 +31,7 @@ class EncryptionDetector {
private boolean containsInlinePgpEncryptedText(Message message) {
Part textPart = textPartFinder.findFirstTextPart(message);
return MessageDecryptVerifier.isPartPgpInlineEncrypted(textPart);
return MessageCryptoStructureDetector.isPartPgpInlineEncrypted(textPart);
}
private boolean containsPartWithMimeType(Part part, String... wantedMimeTypes) {

View file

@ -19,7 +19,7 @@ import android.support.annotation.WorkerThread;
import com.fsck.k9.K9;
import com.fsck.k9.autocrypt.AutocryptOperations;
import com.fsck.k9.crypto.MessageDecryptVerifier;
import com.fsck.k9.crypto.MessageCryptoStructureDetector;
import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.Body;
import com.fsck.k9.mail.BodyPart;
@ -52,7 +52,6 @@ import org.openintents.openpgp.util.OpenPgpApi.OpenPgpDataSink;
import org.openintents.openpgp.util.OpenPgpApi.OpenPgpDataSource;
import org.openintents.openpgp.util.OpenPgpServiceConnection;
import org.openintents.openpgp.util.OpenPgpServiceConnection.OnBound;
import org.openintents.openpgp.util.OpenPgpUtils;
import timber.log.Timber;
@ -84,6 +83,7 @@ public class MessageCryptoHelper {
private State state;
private CancelableBackgroundOperation cancelableBackgroundOperation;
private boolean isCancelled;
private boolean processSignedOnly;
private OpenPgpApi openPgpApi;
private OpenPgpServiceConnection openPgpServiceConnection;
@ -108,7 +108,7 @@ public class MessageCryptoHelper {
}
public void asyncStartOrResumeProcessingMessage(Message message, MessageCryptoCallback callback,
OpenPgpDecryptionResult cachedDecryptionResult) {
OpenPgpDecryptionResult cachedDecryptionResult, boolean processSignedOnly) {
if (this.currentMessage != null) {
reattachCallback(message, callback);
return;
@ -119,21 +119,72 @@ public class MessageCryptoHelper {
this.currentMessage = message;
this.cachedDecryptionResult = cachedDecryptionResult;
this.callback = callback;
this.processSignedOnly = processSignedOnly;
nextStep();
}
private void findPartsForEncryptionPass() {
List<Part> encryptedParts = MessageDecryptVerifier.findEncryptedParts(currentMessage);
processFoundEncryptedParts(encryptedParts);
private void findPartsForMultipartEncryptionPass() {
List<Part> encryptedParts = MessageCryptoStructureDetector.findMultipartEncryptedParts(currentMessage);
for (Part part : encryptedParts) {
if (!MessageHelper.isCompletePartAvailable(part)) {
addErrorAnnotation(part, CryptoError.OPENPGP_ENCRYPTED_BUT_INCOMPLETE, MessageHelper.createEmptyPart());
continue;
}
if (MessageCryptoStructureDetector.isMultipartEncryptedOpenPgpProtocol(part)) {
CryptoPart cryptoPart = new CryptoPart(CryptoPartType.PGP_ENCRYPTED, part);
partsToProcess.add(cryptoPart);
continue;
}
addErrorAnnotation(part, CryptoError.ENCRYPTED_BUT_UNSUPPORTED, MessageHelper.createEmptyPart());
}
}
private void findPartsForSignaturePass() {
List<Part> signedParts = MessageDecryptVerifier.findSignedParts(currentMessage, messageAnnotations);
processFoundSignedParts(signedParts);
private void findPartsForMultipartSignaturePass() {
List<Part> signedParts = MessageCryptoStructureDetector
.findMultipartSignedParts(currentMessage, messageAnnotations);
for (Part part : signedParts) {
if (!processSignedOnly) {
boolean isEncapsulatedSignature =
messageAnnotations.findKeyForAnnotationWithReplacementPart(part) != null;
if (!isEncapsulatedSignature) {
continue;
}
}
if (!MessageHelper.isCompletePartAvailable(part)) {
MimeBodyPart replacementPart = getMultipartSignedContentPartIfAvailable(part);
addErrorAnnotation(part, CryptoError.OPENPGP_SIGNED_BUT_INCOMPLETE, replacementPart);
continue;
}
if (MessageCryptoStructureDetector.isMultipartSignedOpenPgpProtocol(part)) {
CryptoPart cryptoPart = new CryptoPart(CryptoPartType.PGP_SIGNED, part);
partsToProcess.add(cryptoPart);
continue;
}
MimeBodyPart replacementPart = getMultipartSignedContentPartIfAvailable(part);
addErrorAnnotation(part, CryptoError.SIGNED_BUT_UNSUPPORTED, replacementPart);
}
}
List<Part> inlineParts = MessageDecryptVerifier.findPgpInlineParts(currentMessage);
processFoundInlinePgpParts(inlineParts);
private void findPartsForPgpInlinePass() {
List<Part> inlineParts = MessageCryptoStructureDetector.findPgpInlineParts(currentMessage);
for (Part part : inlineParts) {
if (!processSignedOnly && !MessageCryptoStructureDetector.isPartPgpInlineEncrypted(part)) {
continue;
}
if (!currentMessage.getFlags().contains(Flag.X_DOWNLOADED_FULL)) {
if (MessageCryptoStructureDetector.isPartPgpInlineEncrypted(part)) {
addErrorAnnotation(part, CryptoError.OPENPGP_ENCRYPTED_BUT_INCOMPLETE, NO_REPLACEMENT_PART);
} else {
addErrorAnnotation(part, CryptoError.OPENPGP_SIGNED_BUT_INCOMPLETE, NO_REPLACEMENT_PART);
}
continue;
}
CryptoPart cryptoPart = new CryptoPart(CryptoPartType.PGP_INLINE, part);
partsToProcess.add(cryptoPart);
}
}
private void findPartsForAutocryptPass() {
@ -148,60 +199,11 @@ public class MessageCryptoHelper {
}
}
private void processFoundEncryptedParts(List<Part> foundParts) {
for (Part part : foundParts) {
if (!MessageHelper.isCompletePartAvailable(part)) {
addErrorAnnotation(part, CryptoError.OPENPGP_ENCRYPTED_BUT_INCOMPLETE, MessageHelper.createEmptyPart());
continue;
}
if (MessageDecryptVerifier.isPgpMimeEncryptedOrSignedPart(part)) {
CryptoPart cryptoPart = new CryptoPart(CryptoPartType.PGP_ENCRYPTED, part);
partsToProcess.add(cryptoPart);
continue;
}
addErrorAnnotation(part, CryptoError.ENCRYPTED_BUT_UNSUPPORTED, MessageHelper.createEmptyPart());
}
}
private void processFoundSignedParts(List<Part> foundParts) {
for (Part part : foundParts) {
if (!MessageHelper.isCompletePartAvailable(part)) {
MimeBodyPart replacementPart = getMultipartSignedContentPartIfAvailable(part);
addErrorAnnotation(part, CryptoError.OPENPGP_SIGNED_BUT_INCOMPLETE, replacementPart);
continue;
}
if (MessageDecryptVerifier.isPgpMimeEncryptedOrSignedPart(part)) {
CryptoPart cryptoPart = new CryptoPart(CryptoPartType.PGP_SIGNED, part);
partsToProcess.add(cryptoPart);
continue;
}
MimeBodyPart replacementPart = getMultipartSignedContentPartIfAvailable(part);
addErrorAnnotation(part, CryptoError.SIGNED_BUT_UNSUPPORTED, replacementPart);
}
}
private void addErrorAnnotation(Part part, CryptoError error, MimeBodyPart replacementPart) {
CryptoResultAnnotation annotation = CryptoResultAnnotation.createErrorAnnotation(error, replacementPart);
messageAnnotations.put(part, annotation);
}
private void processFoundInlinePgpParts(List<Part> foundParts) {
for (Part part : foundParts) {
if (!currentMessage.getFlags().contains(Flag.X_DOWNLOADED_FULL)) {
if (MessageDecryptVerifier.isPartPgpInlineEncrypted(part)) {
addErrorAnnotation(part, CryptoError.OPENPGP_ENCRYPTED_BUT_INCOMPLETE, NO_REPLACEMENT_PART);
} else {
MimeBodyPart replacementPart = extractClearsignedTextReplacementPart(part);
addErrorAnnotation(part, CryptoError.OPENPGP_SIGNED_BUT_INCOMPLETE, replacementPart);
}
continue;
}
CryptoPart cryptoPart = new CryptoPart(CryptoPartType.PGP_INLINE, part);
partsToProcess.add(cryptoPart);
}
}
private void nextStep() {
if (isCancelled) {
return;
@ -396,7 +398,7 @@ public class MessageCryptoHelper {
private void callAsyncDetachedVerify(Intent intent) throws IOException, MessagingException {
OpenPgpDataSource dataSource = getDataSourceForSignedData(currentCryptoPart.part);
byte[] signatureData = MessageDecryptVerifier.getSignatureData(currentCryptoPart.part);
byte[] signatureData = MessageCryptoStructureDetector.getSignatureData(currentCryptoPart.part);
intent.putExtra(OpenPgpApi.EXTRA_DETACHED_SIGNATURE, signatureData);
openPgpApi.executeApiAsync(intent, dataSource, new IOpenPgpSinkResultCallback<Void>() {
@ -645,17 +647,19 @@ public class MessageCryptoHelper {
case START: {
state = State.ENCRYPTION;
findPartsForEncryptionPass();
findPartsForMultipartEncryptionPass();
return;
}
case ENCRYPTION: {
state = State.SIGNATURES;
state = State.SIGNATURES_AND_INLINE;
findPartsForSignaturePass();
findPartsForMultipartSignaturePass();
findPartsForPgpInlinePass();
return;
}
case SIGNATURES: {
case SIGNATURES_AND_INLINE: {
state = State.AUTOCRYPT;
findPartsForAutocryptPass();
@ -780,22 +784,7 @@ public class MessageCryptoHelper {
return replacementPart;
}
private static MimeBodyPart extractClearsignedTextReplacementPart(Part part) {
try {
String clearsignedText = MessageExtractor.getTextFromPart(part);
String replacementText = OpenPgpUtils.extractClearsignedMessage(clearsignedText);
if (replacementText == null) {
Timber.e("failed to extract clearsigned text for replacement part");
return NO_REPLACEMENT_PART;
}
return new MimeBodyPart(new TextBody(replacementText), "text/plain");
} catch (MessagingException e) {
Timber.e(e, "failed to create clearsigned text replacement part");
return NO_REPLACEMENT_PART;
}
}
private enum State {
START, ENCRYPTION, SIGNATURES, AUTOCRYPT, FINISHED
START, ENCRYPTION, SIGNATURES_AND_INLINE, AUTOCRYPT, FINISHED
}
}

View file

@ -8,7 +8,7 @@ import java.util.List;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.fsck.k9.crypto.MessageDecryptVerifier;
import com.fsck.k9.crypto.MessageCryptoStructureDetector;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.Part;
import com.fsck.k9.mailstore.CryptoResultAnnotation;
@ -21,7 +21,7 @@ public class MessageCryptoSplitter {
@Nullable
public static CryptoMessageParts split(@NonNull Message message, @Nullable MessageCryptoAnnotations annotations) {
ArrayList<Part> extraParts = new ArrayList<>();
Part primaryPart = MessageDecryptVerifier.findPrimaryEncryptedOrSignedPart(message, extraParts);
Part primaryPart = MessageCryptoStructureDetector.findPrimaryEncryptedOrSignedPart(message, extraParts);
if (primaryPart == null) {
return null;
}

View file

@ -7,20 +7,19 @@ import java.util.List;
import com.fsck.k9.K9RobolectricTestRunner;
import com.fsck.k9.mail.BodyPart;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Multipart;
import com.fsck.k9.mail.Part;
import com.fsck.k9.mail.internet.MimeBodyPart;
import com.fsck.k9.mail.internet.MimeHeader;
import com.fsck.k9.mail.internet.MimeMessage;
import com.fsck.k9.mail.internet.MimeMessageHelper;
import com.fsck.k9.mail.internet.MimeMultipart;
import com.fsck.k9.mail.internet.TextBody;
import com.fsck.k9.ui.crypto.MessageCryptoAnnotations;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.annotation.Config;
import static com.fsck.k9.message.TestMessageConstructionUtils.bodypart;
import static com.fsck.k9.message.TestMessageConstructionUtils.messageFromBody;
import static com.fsck.k9.message.TestMessageConstructionUtils.multipart;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
@ -29,13 +28,12 @@ import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
@SuppressWarnings("WeakerAccess")
@RunWith(K9RobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class MessageDecryptVerifierTest {
private static final String MIME_TYPE_MULTIPART_ENCRYPTED = "multipart/encrypted";
private MessageCryptoAnnotations messageCryptoAnnotations = mock(MessageCryptoAnnotations.class);
private static final String PROTCOL_PGP_ENCRYPTED = "application/pgp-encrypted";
private static final String PGP_INLINE_DATA = "" +
public class MessageCryptoStructureDetectorTest {
MessageCryptoAnnotations messageCryptoAnnotations = mock(MessageCryptoAnnotations.class);
static final String PGP_INLINE_DATA = "" +
"-----BEGIN PGP MESSAGE-----\n" +
"Header: Value\n" +
"\n" +
@ -49,7 +47,7 @@ public class MessageDecryptVerifierTest {
Message message = new MimeMessage();
MimeMessageHelper.setBody(message, new TextBody(PGP_INLINE_DATA));
Part cryptoPart = MessageDecryptVerifier.findPrimaryEncryptedOrSignedPart(message, outputExtraParts);
Part cryptoPart = MessageCryptoStructureDetector.findPrimaryEncryptedOrSignedPart(message, outputExtraParts);
assertSame(message, cryptoPart);
}
@ -65,7 +63,7 @@ public class MessageDecryptVerifierTest {
)
);
Part cryptoPart = MessageDecryptVerifier.findPrimaryEncryptedOrSignedPart(message, outputExtraParts);
Part cryptoPart = MessageCryptoStructureDetector.findPrimaryEncryptedOrSignedPart(message, outputExtraParts);
assertSame(pgpInlinePart, cryptoPart);
}
@ -81,7 +79,7 @@ public class MessageDecryptVerifierTest {
)
);
Part cryptoPart = MessageDecryptVerifier.findPrimaryEncryptedOrSignedPart(message, outputExtraParts);
Part cryptoPart = MessageCryptoStructureDetector.findPrimaryEncryptedOrSignedPart(message, outputExtraParts);
assertSame(pgpInlinePart, cryptoPart);
}
@ -101,7 +99,7 @@ public class MessageDecryptVerifierTest {
)
);
Part cryptoPart = MessageDecryptVerifier.findPrimaryEncryptedOrSignedPart(message, outputExtraParts);
Part cryptoPart = MessageCryptoStructureDetector.findPrimaryEncryptedOrSignedPart(message, outputExtraParts);
assertSame(pgpInlinePart, cryptoPart);
}
@ -113,7 +111,7 @@ public class MessageDecryptVerifierTest {
multipart("alternative")
);
Part cryptoPart = MessageDecryptVerifier.findPrimaryEncryptedOrSignedPart(message, outputExtraParts);
Part cryptoPart = MessageCryptoStructureDetector.findPrimaryEncryptedOrSignedPart(message, outputExtraParts);
assertNull(cryptoPart);
}
@ -125,7 +123,7 @@ public class MessageDecryptVerifierTest {
multipart("mixed")
);
Part cryptoPart = MessageDecryptVerifier.findPrimaryEncryptedOrSignedPart(message, outputExtraParts);
Part cryptoPart = MessageCryptoStructureDetector.findPrimaryEncryptedOrSignedPart(message, outputExtraParts);
assertNull(cryptoPart);
}
@ -140,7 +138,7 @@ public class MessageDecryptVerifierTest {
)
);
Part cryptoPart = MessageDecryptVerifier.findPrimaryEncryptedOrSignedPart(message, outputExtraParts);
Part cryptoPart = MessageCryptoStructureDetector.findPrimaryEncryptedOrSignedPart(message, outputExtraParts);
assertNull(cryptoPart);
}
@ -149,7 +147,7 @@ public class MessageDecryptVerifierTest {
public void findEncryptedPartsShouldReturnEmptyListForEmptyMessage() throws Exception {
MimeMessage emptyMessage = new MimeMessage();
List<Part> encryptedParts = MessageDecryptVerifier.findEncryptedParts(emptyMessage);
List<Part> encryptedParts = MessageCryptoStructureDetector.findMultipartEncryptedParts(emptyMessage);
assertEquals(0, encryptedParts.size());
}
@ -159,56 +157,42 @@ public class MessageDecryptVerifierTest {
MimeMessage message = new MimeMessage();
message.setBody(new TextBody("message text"));
List<Part> encryptedParts = MessageDecryptVerifier.findEncryptedParts(message);
List<Part> encryptedParts = MessageCryptoStructureDetector.findMultipartEncryptedParts(message);
assertEquals(0, encryptedParts.size());
}
@Test
public void findEncryptedPartsShouldReturnEmptyEncryptedPart() throws Exception {
MimeMessage message = new MimeMessage();
MimeMultipart multipartEncrypted = MimeMultipart.newInstance();
multipartEncrypted.setSubType("encrypted");
MimeMessageHelper.setBody(message, multipartEncrypted);
setContentTypeWithProtocol(message, MIME_TYPE_MULTIPART_ENCRYPTED, PROTCOL_PGP_ENCRYPTED);
public void findEncrypted__withMultipartEncrypted__shouldReturnRoot() throws Exception {
Message message = messageFromBody(
multipart("encrypted", "protocol=\"application/pgp-encrypted\"",
bodypart("application/pgp-encrypted"),
bodypart("application/octet-stream")
)
);
List<Part> encryptedParts = MessageDecryptVerifier.findEncryptedParts(message);
List<Part> encryptedParts = MessageCryptoStructureDetector.findMultipartEncryptedParts(message);
assertEquals(1, encryptedParts.size());
assertSame(message, encryptedParts.get(0));
}
@Test
public void findEncryptedPartsShouldReturnMultipleEncryptedParts() throws Exception {
MimeMessage message = new MimeMessage();
MimeMultipart multipartMixed = MimeMultipart.newInstance();
multipartMixed.setSubType("mixed");
MimeMessageHelper.setBody(message, multipartMixed);
public void findEncrypted__withBadProtocol__shouldReturnEmpty() throws Exception {
Message message = messageFromBody(
multipart("encrypted", "protocol=\"application/not-pgp-encrypted\"",
bodypart("application/pgp-encrypted"),
bodypart("application/octet-stream", "content")
)
);
MimeMultipart multipartEncryptedOne = MimeMultipart.newInstance();
multipartEncryptedOne.setSubType("encrypted");
MimeBodyPart bodyPartOne = new MimeBodyPart(multipartEncryptedOne);
setContentTypeWithProtocol(bodyPartOne, MIME_TYPE_MULTIPART_ENCRYPTED, PROTCOL_PGP_ENCRYPTED);
multipartMixed.addBodyPart(bodyPartOne);
List<Part> encryptedParts = MessageCryptoStructureDetector.findMultipartEncryptedParts(message);
MimeBodyPart bodyPartTwo = new MimeBodyPart(null, "text/plain");
multipartMixed.addBodyPart(bodyPartTwo);
MimeMultipart multipartEncryptedThree = MimeMultipart.newInstance();
multipartEncryptedThree.setSubType("encrypted");
MimeBodyPart bodyPartThree = new MimeBodyPart(multipartEncryptedThree);
setContentTypeWithProtocol(bodyPartThree, MIME_TYPE_MULTIPART_ENCRYPTED, PROTCOL_PGP_ENCRYPTED);
multipartMixed.addBodyPart(bodyPartThree);
List<Part> encryptedParts = MessageDecryptVerifier.findEncryptedParts(message);
assertEquals(2, encryptedParts.size());
assertSame(bodyPartOne, encryptedParts.get(0));
assertSame(bodyPartThree, encryptedParts.get(1));
assertTrue(encryptedParts.isEmpty());
}
@Test
public void findEncrypted__withMultipartEncrypted__shouldReturnRoot() throws Exception {
public void findEncrypted__withBadProtocolAndNoBody__shouldReturnRoot() throws Exception {
Message message = messageFromBody(
multipart("encrypted",
bodypart("application/pgp-encrypted"),
@ -216,24 +200,64 @@ public class MessageDecryptVerifierTest {
)
);
List<Part> encryptedParts = MessageDecryptVerifier.findEncryptedParts(message);
List<Part> encryptedParts = MessageCryptoStructureDetector.findMultipartEncryptedParts(message);
assertEquals(1, encryptedParts.size());
assertSame(message, encryptedParts.get(0));
}
@Test
public void findEncrypted__withEmptyProtocol__shouldReturnEmpty() throws Exception {
Message message = messageFromBody(
multipart("encrypted",
bodypart("application/pgp-encrypted"),
bodypart("application/octet-stream", "content")
)
);
List<Part> encryptedParts = MessageCryptoStructureDetector.findMultipartEncryptedParts(message);
assertTrue(encryptedParts.isEmpty());
}
@Test
public void findEncrypted__withMissingEncryptedBody__shouldReturnEmpty() throws Exception {
Message message = messageFromBody(
multipart("encrypted", "protocol=\"application/pgp-encrypted\"",
bodypart("application/pgp-encrypted")
)
);
List<Part> encryptedParts = MessageCryptoStructureDetector.findMultipartEncryptedParts(message);
assertTrue(encryptedParts.isEmpty());
}
@Test
public void findEncrypted__withBadStructure__shouldReturnEmpty() throws Exception {
Message message = messageFromBody(
multipart("encrypted", "protocol=\"application/pgp-encrypted\"",
bodypart("application/octet-stream")
)
);
List<Part> encryptedParts = MessageCryptoStructureDetector.findMultipartEncryptedParts(message);
assertTrue(encryptedParts.isEmpty());
}
@Test
public void findEncrypted__withMultipartMixedSubEncrypted__shouldReturnRoot() throws Exception {
Message message = messageFromBody(
multipart("mixed",
multipart("encrypted",
multipart("encrypted", "protocol=\"application/pgp-encrypted\"",
bodypart("application/pgp-encrypted"),
bodypart("application/octet-stream")
)
)
);
List<Part> encryptedParts = MessageDecryptVerifier.findEncryptedParts(message);
List<Part> encryptedParts = MessageCryptoStructureDetector.findMultipartEncryptedParts(message);
assertEquals(1, encryptedParts.size());
assertSame(getPart(message, 0), encryptedParts.get(0));
@ -244,18 +268,18 @@ public class MessageDecryptVerifierTest {
throws Exception {
Message message = messageFromBody(
multipart("mixed",
multipart("encrypted",
multipart("encrypted", "protocol=\"application/pgp-encrypted\"",
bodypart("application/pgp-encrypted"),
bodypart("application/octet-stream")
),
multipart("encrypted",
multipart("encrypted", "protocol=\"application/pgp-encrypted\"",
bodypart("application/pgp-encrypted"),
bodypart("application/octet-stream")
)
)
);
List<Part> encryptedParts = MessageDecryptVerifier.findEncryptedParts(message);
List<Part> encryptedParts = MessageCryptoStructureDetector.findMultipartEncryptedParts(message);
assertEquals(2, encryptedParts.size());
assertSame(getPart(message, 0), encryptedParts.get(0));
@ -267,14 +291,14 @@ public class MessageDecryptVerifierTest {
Message message = messageFromBody(
multipart("mixed",
bodypart("text/plain"),
multipart("encrypted",
multipart("encrypted", "protocol=\"application/pgp-encrypted\"",
bodypart("application/pgp-encrypted"),
bodypart("application/octet-stream")
)
)
);
List<Part> encryptedParts = MessageDecryptVerifier.findEncryptedParts(message);
List<Part> encryptedParts = MessageCryptoStructureDetector.findMultipartEncryptedParts(message);
assertEquals(1, encryptedParts.size());
assertSame(getPart(message, 1), encryptedParts.get(0));
@ -284,7 +308,7 @@ public class MessageDecryptVerifierTest {
public void findEncrypted__withMultipartMixedSubEncryptedAndText__shouldReturnEncrypted() throws Exception {
Message message = messageFromBody(
multipart("mixed",
multipart("encrypted",
multipart("encrypted", "protocol=\"application/pgp-encrypted\"",
bodypart("application/pgp-encrypted"),
bodypart("application/octet-stream")
),
@ -292,7 +316,7 @@ public class MessageDecryptVerifierTest {
)
);
List<Part> encryptedParts = MessageDecryptVerifier.findEncryptedParts(message);
List<Part> encryptedParts = MessageCryptoStructureDetector.findMultipartEncryptedParts(message);
assertEquals(1, encryptedParts.size());
assertSame(getPart(message, 0), encryptedParts.get(0));
@ -301,22 +325,83 @@ public class MessageDecryptVerifierTest {
@Test
public void findSigned__withSimpleMultipartSigned__shouldReturnRoot() throws Exception {
Message message = messageFromBody(
multipart("signed",
multipart("signed", "protocol=\"application/pgp-signature\"",
bodypart("text/plain"),
bodypart("application/pgp-signature")
)
);
List<Part> signedParts = MessageDecryptVerifier.findSignedParts(message, messageCryptoAnnotations);
List<Part> signedParts = MessageCryptoStructureDetector
.findMultipartSignedParts(message, messageCryptoAnnotations);
assertEquals(1, signedParts.size());
assertSame(message, signedParts.get(0));
}
@Test
public void findSigned__withComplexMultipartSigned__shouldReturnRoot() throws Exception {
public void findSigned__withNoProtocolAndNoBody__shouldReturnRoot() throws Exception {
Message message = messageFromBody(
multipart("signed",
bodypart("text/plain"),
bodypart("application/pgp-signature")
)
);
List<Part> signedParts = MessageCryptoStructureDetector
.findMultipartSignedParts(message, messageCryptoAnnotations);
assertEquals(1, signedParts.size());
assertSame(message, signedParts.get(0));
}
@Test
public void findSigned__withBadProtocol__shouldReturnNothing() throws Exception {
Message message = messageFromBody(
multipart("signed", "protocol=\"application/not-pgp-signature\"",
bodypart("text/plain", "content"),
bodypart("application/pgp-signature")
)
);
List<Part> signedParts = MessageCryptoStructureDetector
.findMultipartSignedParts(message, messageCryptoAnnotations);
assertTrue(signedParts.isEmpty());
}
@Test
public void findSigned__withEmptyProtocol__shouldReturnRoot() throws Exception {
Message message = messageFromBody(
multipart("signed",
bodypart("text/plain", "content"),
bodypart("application/pgp-signature")
)
);
List<Part> signedParts = MessageCryptoStructureDetector
.findMultipartSignedParts(message, messageCryptoAnnotations);
assertTrue(signedParts.isEmpty());
}
@Test
public void findSigned__withMissingSignature__shouldReturnEmpty() throws Exception {
Message message = messageFromBody(
multipart("signed", "protocol=\"application/pgp-signature\"",
bodypart("text/plain")
)
);
List<Part> signedParts = MessageCryptoStructureDetector
.findMultipartSignedParts(message, messageCryptoAnnotations);
assertTrue(signedParts.isEmpty());
}
@Test
public void findSigned__withComplexMultipartSigned__shouldReturnRoot() throws Exception {
Message message = messageFromBody(
multipart("signed", "protocol=\"application/pgp-signature\"",
multipart("mixed",
bodypart("text/plain"),
bodypart("application/pdf")
@ -325,7 +410,8 @@ public class MessageDecryptVerifierTest {
)
);
List<Part> signedParts = MessageDecryptVerifier.findSignedParts(message, messageCryptoAnnotations);
List<Part> signedParts = MessageCryptoStructureDetector
.findMultipartSignedParts(message, messageCryptoAnnotations);
assertEquals(1, signedParts.size());
assertSame(message, signedParts.get(0));
@ -335,14 +421,15 @@ public class MessageDecryptVerifierTest {
public void findEncrypted__withMultipartMixedSubSigned__shouldReturnSigned() throws Exception {
Message message = messageFromBody(
multipart("mixed",
multipart("signed",
multipart("signed", "protocol=\"application/pgp-signature\"",
bodypart("text/plain"),
bodypart("application/pgp-signature")
)
)
);
List<Part> signedParts = MessageDecryptVerifier.findSignedParts(message, messageCryptoAnnotations);
List<Part> signedParts = MessageCryptoStructureDetector
.findMultipartSignedParts(message, messageCryptoAnnotations);
assertEquals(1, signedParts.size());
assertSame(getPart(message, 0), signedParts.get(0));
@ -352,7 +439,7 @@ public class MessageDecryptVerifierTest {
public void findEncrypted__withMultipartMixedSubSignedAndText__shouldReturnSigned() throws Exception {
Message message = messageFromBody(
multipart("mixed",
multipart("signed",
multipart("signed", "application/pgp-signature",
bodypart("text/plain"),
bodypart("application/pgp-signature")
),
@ -360,7 +447,8 @@ public class MessageDecryptVerifierTest {
)
);
List<Part> signedParts = MessageDecryptVerifier.findSignedParts(message, messageCryptoAnnotations);
List<Part> signedParts = MessageCryptoStructureDetector
.findMultipartSignedParts(message, messageCryptoAnnotations);
assertEquals(1, signedParts.size());
assertSame(getPart(message, 0), signedParts.get(0));
@ -371,14 +459,15 @@ public class MessageDecryptVerifierTest {
Message message = messageFromBody(
multipart("mixed",
bodypart("text/plain"),
multipart("signed",
multipart("signed", "application/pgp-signature",
bodypart("text/plain"),
bodypart("application/pgp-signature")
)
)
);
List<Part> signedParts = MessageDecryptVerifier.findSignedParts(message, messageCryptoAnnotations);
List<Part> signedParts = MessageCryptoStructureDetector
.findMultipartSignedParts(message, messageCryptoAnnotations);
assertEquals(1, signedParts.size());
assertSame(getPart(message, 1), signedParts.get(0));
@ -395,7 +484,7 @@ public class MessageDecryptVerifierTest {
MimeMessage message = new MimeMessage();
message.setBody(new TextBody(pgpInlineData));
assertTrue(MessageDecryptVerifier.isPartPgpInlineEncrypted(message));
assertTrue(MessageCryptoStructureDetector.isPartPgpInlineEncrypted(message));
}
@Test
@ -410,8 +499,8 @@ public class MessageDecryptVerifierTest {
MimeMessage message = new MimeMessage();
message.setBody(new TextBody(pgpInlineData));
assertTrue(MessageDecryptVerifier.isPartPgpInlineEncryptedOrSigned(message));
assertTrue(MessageDecryptVerifier.isPartPgpInlineEncrypted(message));
assertTrue(MessageCryptoStructureDetector.isPartPgpInlineEncryptedOrSigned(message));
assertTrue(MessageCryptoStructureDetector.isPartPgpInlineEncrypted(message));
}
@Test
@ -426,8 +515,8 @@ public class MessageDecryptVerifierTest {
MimeMessage message = new MimeMessage();
message.setBody(new TextBody(pgpInlineData));
assertFalse(MessageDecryptVerifier.isPartPgpInlineEncryptedOrSigned(message));
assertFalse(MessageDecryptVerifier.isPartPgpInlineEncrypted(message));
assertFalse(MessageCryptoStructureDetector.isPartPgpInlineEncryptedOrSigned(message));
assertFalse(MessageCryptoStructureDetector.isPartPgpInlineEncrypted(message));
}
@Test
@ -444,7 +533,7 @@ public class MessageDecryptVerifierTest {
MimeMessage message = new MimeMessage();
message.setBody(new TextBody(pgpInlineData));
assertTrue(MessageDecryptVerifier.isPartPgpInlineEncryptedOrSigned(message));
assertTrue(MessageCryptoStructureDetector.isPartPgpInlineEncryptedOrSigned(message));
}
@Test
@ -461,44 +550,14 @@ public class MessageDecryptVerifierTest {
MimeMessage message = new MimeMessage();
message.setBody(new TextBody(pgpInlineData));
assertFalse(MessageDecryptVerifier.isPartPgpInlineEncrypted(message));
assertFalse(MessageCryptoStructureDetector.isPartPgpInlineEncrypted(message));
}
MimeMessage messageFromBody(BodyPart bodyPart) throws MessagingException {
MimeMessage message = new MimeMessage();
MimeMessageHelper.setBody(message, bodyPart.getBody());
return message;
}
MimeBodyPart multipart(String type, BodyPart... subParts) throws MessagingException {
MimeMultipart multiPart = MimeMultipart.newInstance();
multiPart.setSubType(type);
for (BodyPart subPart : subParts) {
multiPart.addBodyPart(subPart);
}
return new MimeBodyPart(multiPart);
}
BodyPart bodypart(String type) throws MessagingException {
return new MimeBodyPart(null, type);
}
BodyPart bodypart(String type, String text) throws MessagingException {
TextBody textBody = new TextBody(text);
return new MimeBodyPart(textBody, type);
}
public static Part getPart(Part searchRootPart, int... indexes) {
static Part getPart(Part searchRootPart, int... indexes) {
Part part = searchRootPart;
for (int index : indexes) {
part = ((Multipart) part.getBody()).getBodyPart(index);
}
return part;
}
//TODO: Find a cleaner way to do this
private static void setContentTypeWithProtocol(Part part, String mimeType, String protocol)
throws MessagingException {
part.setHeader(MimeHeader.HEADER_CONTENT_TYPE, mimeType + "; protocol=\"" + protocol + "\"");
}
}

View file

@ -0,0 +1,56 @@
package com.fsck.k9.message;
import com.fsck.k9.mail.Body;
import com.fsck.k9.mail.BodyPart;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.internet.MimeBodyPart;
import com.fsck.k9.mail.internet.MimeHeader;
import com.fsck.k9.mail.internet.MimeMessage;
import com.fsck.k9.mail.internet.MimeMessageHelper;
import com.fsck.k9.mail.internet.MimeMultipart;
import com.fsck.k9.mail.internet.TextBody;
public class TestMessageConstructionUtils {
public static MimeMessage messageFromBody(BodyPart bodyPart) throws MessagingException {
MimeMessage message = new MimeMessage();
MimeMessageHelper.setBody(message, bodyPart.getBody());
if (bodyPart.getContentType() != null) {
message.setHeader("Content-Type", bodyPart.getContentType());
}
message.setUid("msguid");
return message;
}
public static MimeBodyPart multipart(String type, BodyPart... subParts) throws MessagingException {
return multipart(type, null, subParts);
}
public static MimeBodyPart multipart(String type, String typeParameters, BodyPart... subParts) throws MessagingException {
MimeMultipart multiPart = MimeMultipart.newInstance();
multiPart.setSubType(type);
for (BodyPart subPart : subParts) {
multiPart.addBodyPart(subPart);
}
MimeBodyPart mimeBodyPart = new MimeBodyPart(multiPart);
if (typeParameters != null) {
mimeBodyPart.setHeader(MimeHeader.HEADER_CONTENT_TYPE,
mimeBodyPart.getContentType() + "; " + typeParameters);
}
return mimeBodyPart;
}
public static BodyPart bodypart(String type) throws MessagingException {
return new MimeBodyPart(null, type);
}
public static BodyPart bodypart(String type, String text) throws MessagingException {
TextBody textBody = new TextBody(text);
return new MimeBodyPart(textBody, type);
}
public static BodyPart bodypart(String type, Body body) throws MessagingException {
return new MimeBodyPart(body, type);
}
}

View file

@ -14,10 +14,8 @@ 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;
@ -37,11 +35,13 @@ import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import static com.fsck.k9.message.TestMessageConstructionUtils.bodypart;
import static com.fsck.k9.message.TestMessageConstructionUtils.messageFromBody;
import static com.fsck.k9.message.TestMessageConstructionUtils.multipart;
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.Matchers.eq;
import static org.mockito.Matchers.same;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
@ -84,7 +84,7 @@ public class MessageCryptoHelperTest {
message.setHeader("Content-Type", "text/plain");
MessageCryptoCallback messageCryptoCallback = mock(MessageCryptoCallback.class);
messageCryptoHelper.asyncStartOrResumeProcessingMessage(message, messageCryptoCallback, null);
messageCryptoHelper.asyncStartOrResumeProcessingMessage(message, messageCryptoCallback, null, false);
ArgumentCaptor<MessageCryptoAnnotations> captor = ArgumentCaptor.forClass(MessageCryptoAnnotations.class);
verify(messageCryptoCallback).onCryptoOperationsFinished(captor.capture());
@ -107,7 +107,7 @@ public class MessageCryptoHelperTest {
MessageCryptoCallback messageCryptoCallback = mock(MessageCryptoCallback.class);
messageCryptoHelper.asyncStartOrResumeProcessingMessage(message, messageCryptoCallback, null);
messageCryptoHelper.asyncStartOrResumeProcessingMessage(message, messageCryptoCallback, null, false);
ArgumentCaptor<MessageCryptoAnnotations> captor = ArgumentCaptor.forClass(MessageCryptoAnnotations.class);
@ -124,12 +124,15 @@ public class MessageCryptoHelperTest {
@Test
public void multipartSigned__withNullBody__shouldReturnSignedIncomplete() throws Exception {
MimeMessage message = new MimeMessage();
message.setUid("msguid");
message.setHeader("Content-Type", "multipart/signed");
Message message = messageFromBody(
multipart("signed", "protocol=\"application/pgp-signature\"",
bodypart("text/plain"),
bodypart("application/pgp-signature")
)
);
MessageCryptoCallback messageCryptoCallback = mock(MessageCryptoCallback.class);
messageCryptoHelper.asyncStartOrResumeProcessingMessage(message, messageCryptoCallback, null);
messageCryptoHelper.asyncStartOrResumeProcessingMessage(message, messageCryptoCallback, null, true);
assertPartAnnotationHasState(message, messageCryptoCallback, CryptoError.OPENPGP_SIGNED_BUT_INCOMPLETE, null,
null, null, null);
@ -137,12 +140,15 @@ public class MessageCryptoHelperTest {
@Test
public void multipartEncrypted__withNullBody__shouldReturnEncryptedIncomplete() throws Exception {
MimeMessage message = new MimeMessage();
message.setUid("msguid");
message.setHeader("Content-Type", "multipart/encrypted");
Message message = messageFromBody(
multipart("encrypted", "protocol=\"application/pgp-encrypted\"",
bodypart("application/pgp-encrypted"),
bodypart("application/octet-stream")
)
);
MessageCryptoCallback messageCryptoCallback = mock(MessageCryptoCallback.class);
messageCryptoHelper.asyncStartOrResumeProcessingMessage(message, messageCryptoCallback, null);
messageCryptoHelper.asyncStartOrResumeProcessingMessage(message, messageCryptoCallback, null, false);
assertPartAnnotationHasState(
message, messageCryptoCallback, CryptoError.OPENPGP_ENCRYPTED_BUT_INCOMPLETE, null, null, null, null);
@ -150,13 +156,15 @@ public class MessageCryptoHelperTest {
@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", "--------"));
Message message = messageFromBody(
multipart("encrypted", "protocol=\"application/bad-protocol\"",
bodypart("application/bad-protocol", "content"),
bodypart("application/octet-stream", "content")
)
);
MessageCryptoCallback messageCryptoCallback = mock(MessageCryptoCallback.class);
messageCryptoHelper.asyncStartOrResumeProcessingMessage(message, messageCryptoCallback, null);
messageCryptoHelper.asyncStartOrResumeProcessingMessage(message, messageCryptoCallback, null, false);
assertPartAnnotationHasState(message, messageCryptoCallback, CryptoError.ENCRYPTED_BUT_UNSUPPORTED, null, null,
null, null);
@ -164,13 +172,15 @@ public class MessageCryptoHelperTest {
@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", "--------"));
Message message = messageFromBody(
multipart("signed", "protocol=\"application/bad-protocol\"",
bodypart("text/plain", "content"),
bodypart("application/bad-protocol", "content")
)
);
MessageCryptoCallback messageCryptoCallback = mock(MessageCryptoCallback.class);
messageCryptoHelper.asyncStartOrResumeProcessingMessage(message, messageCryptoCallback, null);
messageCryptoHelper.asyncStartOrResumeProcessingMessage(message, messageCryptoCallback, null, true);
assertPartAnnotationHasState(message, messageCryptoCallback, CryptoError.SIGNED_BUT_UNSUPPORTED, null, null,
null, null);
@ -178,18 +188,14 @@ public class MessageCryptoHelperTest {
@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\"");
BodyPart signedBodyPart = spy(bodypart("text/plain", "content"));
Message message = messageFromBody(
multipart("signed", "protocol=\"application/pgp-signature\"",
signedBodyPart,
bodypart("application/pgp-signature", "content")
)
);
message.setFrom(Address.parse("Test <test@example.org>")[0]);
message.setBody(messageBody);
OutputStream outputStream = mock(OutputStream.class);
@ -204,21 +210,46 @@ public class MessageCryptoHelperTest {
verifyNoMoreInteractions(autocryptOperations);
}
@Test
public void multipartSigned__withSignOnlyDisabled__shouldReturnNothing() throws Exception {
Message message = messageFromBody(
multipart("signed", "protocol=\"application/pgp-signature\"",
bodypart("text/plain", "content"),
bodypart("application/pgp-signature", "content")
)
);
MessageCryptoCallback messageCryptoCallback = mock(MessageCryptoCallback.class);
messageCryptoHelper.asyncStartOrResumeProcessingMessage(message, messageCryptoCallback, null, false);
assertReturnsWithNoCryptoAnnotations(messageCryptoCallback);
}
@Test
public void multipartSigned__withSignOnlyDisabledAndNullBody__shouldReturnNothing() throws Exception {
Message message = messageFromBody(
multipart("signed", "protocol=\"application/pgp-signature\"",
bodypart("text/plain"),
bodypart("application/pgp-signature")
)
);
MessageCryptoCallback messageCryptoCallback = mock(MessageCryptoCallback.class);
messageCryptoHelper.asyncStartOrResumeProcessingMessage(message, messageCryptoCallback, null, false);
assertReturnsWithNoCryptoAnnotations(messageCryptoCallback);
}
@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 message = messageFromBody(
multipart("encrypted", "protocol=\"application/pgp-encrypted\"",
bodypart("application/pgp-encrypted", "content"),
bodypart("application/octet-stream", encryptedBody)
)
);
message.setFrom(Address.parse("Test <test@example.org>")[0]);
message.setBody(messageBody);
OutputStream outputStream = mock(OutputStream.class);
@ -248,7 +279,7 @@ public class MessageCryptoHelperTest {
private void processEncryptedMessageAndCaptureMocks(Message message, Body encryptedBody, OutputStream outputStream)
throws Exception {
messageCryptoHelper.asyncStartOrResumeProcessingMessage(message, messageCryptoCallback, null);
messageCryptoHelper.asyncStartOrResumeProcessingMessage(message, messageCryptoCallback, null, false);
ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
ArgumentCaptor<OpenPgpDataSource> dataSourceCaptor = ArgumentCaptor.forClass(OpenPgpDataSource.class);
@ -266,7 +297,7 @@ public class MessageCryptoHelperTest {
private void processSignedMessageAndCaptureMocks(Message message, BodyPart signedBodyPart,
OutputStream outputStream) throws Exception {
messageCryptoHelper.asyncStartOrResumeProcessingMessage(message, messageCryptoCallback, null);
messageCryptoHelper.asyncStartOrResumeProcessingMessage(message, messageCryptoCallback, null, true);
ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
ArgumentCaptor<OpenPgpDataSource> dataSourceCaptor = ArgumentCaptor.forClass(OpenPgpDataSource.class);
@ -282,6 +313,15 @@ public class MessageCryptoHelperTest {
verify(signedBodyPart).writeTo(outputStream);
}
private void assertReturnsWithNoCryptoAnnotations(MessageCryptoCallback messageCryptoCallback) {
ArgumentCaptor<MessageCryptoAnnotations> captor = ArgumentCaptor.forClass(MessageCryptoAnnotations.class);
verify(messageCryptoCallback).onCryptoOperationsFinished(captor.capture());
verifyNoMoreInteractions(messageCryptoCallback);
MessageCryptoAnnotations annotations = captor.getValue();
assertTrue(annotations.isEmpty());
}
private void assertPartAnnotationHasState(Message message, MessageCryptoCallback messageCryptoCallback,
CryptoError cryptoErrorState, MimeBodyPart replacementPart, OpenPgpDecryptionResult openPgpDecryptionResult,
OpenPgpSignatureResult openPgpSignatureResult, PendingIntent openPgpPendingIntent) {

View file

@ -80,7 +80,7 @@ public class OpenPgpUtils {
}
public static String extractClearsignedMessage(String text) {
if (!text.startsWith(PGP_MARKER_CLEARSIGN_BEGIN_MESSAGE)) {
if (text == null || !text.startsWith(PGP_MARKER_CLEARSIGN_BEGIN_MESSAGE)) {
return null;
}
int endOfHeader = text.indexOf("\r\n\r\n") +4;