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); Alternative alternative = new Alternative(text, html);
outputViewableParts.add(alternative); outputViewableParts.add(alternative);
} }
} else if (isSameMimeType(part.getMimeType(), "multipart/signed")) {
if (multipart.getCount() > 0) {
BodyPart bodyPart = multipart.getBodyPart(0);
findViewablesAndAttachments(bodyPart, outputViewableParts, outputNonViewableParts);
}
} else { } else {
// For all other multipart parts we recurse to grab all viewable children. // For all other multipart parts we recurse to grab all viewable children.
for (Part bodyPart : multipart.getBodyParts()) { for (Part bodyPart : multipart.getBodyParts()) {

View file

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

View file

@ -81,6 +81,7 @@ public class MessageLoaderHelper {
private LoaderManager loaderManager; private LoaderManager loaderManager;
@Nullable // make this explicitly nullable, make sure to cancel/ignore any operation if this is null @Nullable // make this explicitly nullable, make sure to cancel/ignore any operation if this is null
private MessageLoaderCallbacks callback; private MessageLoaderCallbacks callback;
private final boolean processSignedOnly;
// transient state // transient state
@ -100,6 +101,8 @@ public class MessageLoaderHelper {
this.loaderManager = loaderManager; this.loaderManager = loaderManager;
this.fragmentManager = fragmentManager; this.fragmentManager = fragmentManager;
this.callback = callback; this.callback = callback;
processSignedOnly = K9.getOpenPgpSupportSignOnly();
} }
@ -276,7 +279,7 @@ public class MessageLoaderHelper {
retainCryptoHelperFragment.setData(messageCryptoHelper); retainCryptoHelperFragment.setData(messageCryptoHelper);
} }
messageCryptoHelper.asyncStartOrResumeProcessingMessage( messageCryptoHelper.asyncStartOrResumeProcessingMessage(
localMessage, messageCryptoCallback, cachedDecryptionResult); localMessage, messageCryptoCallback, cachedDecryptionResult, processSignedOnly);
} }
private void cancelAndClearCryptoOperation() { 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.Part;
import com.fsck.k9.mail.internet.MessageExtractor; import com.fsck.k9.mail.internet.MessageExtractor;
import com.fsck.k9.mail.internet.MimeBodyPart; 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.mail.internet.MimeUtility;
import com.fsck.k9.mailstore.CryptoResultAnnotation; import com.fsck.k9.mailstore.CryptoResultAnnotation;
import com.fsck.k9.ui.crypto.MessageCryptoAnnotations; 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; 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_ENCRYPTED = "multipart/encrypted";
private static final String MULTIPART_SIGNED = "multipart/signed"; private static final String MULTIPART_SIGNED = "multipart/signed";
private static final String PROTOCOL_PARAMETER = "protocol"; private static final String PROTOCOL_PARAMETER = "protocol";
@ -109,7 +110,7 @@ public class MessageDecryptVerifier {
return null; return null;
} }
public static List<Part> findEncryptedParts(Part startPart) { public static List<Part> findMultipartEncryptedParts(Part startPart) {
List<Part> encryptedParts = new ArrayList<>(); List<Part> encryptedParts = new ArrayList<>();
Stack<Part> partsToCheck = new Stack<>(); Stack<Part> partsToCheck = new Stack<>();
partsToCheck.push(startPart); partsToCheck.push(startPart);
@ -135,7 +136,7 @@ public class MessageDecryptVerifier {
return encryptedParts; 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<>(); List<Part> signedParts = new ArrayList<>();
Stack<Part> partsToCheck = new Stack<>(); Stack<Part> partsToCheck = new Stack<>();
partsToCheck.push(startPart); partsToCheck.push(startPart);
@ -214,24 +215,59 @@ public class MessageDecryptVerifier {
} }
private static boolean isPartMultipartSigned(Part part) { 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) { 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;
}
String protocolParameter = MimeUtility.getHeaderParameter(part.getContentType(), PROTOCOL_PARAMETER);
boolean dataUnavailable = protocolParameter == null && mimeMultipart.getBodyPart(1).getBody() == null;
boolean protocolMatches = isSameMimeType(protocolParameter, mimeMultipart.getBodyPart(0).getMimeType());
return dataUnavailable || protocolMatches;
} }
// TODO also guess by mime-type of contained part? public static boolean isMultipartEncryptedOpenPgpProtocol(Part part) {
public static boolean isPgpMimeEncryptedOrSignedPart(Part part) { if (!isSameMimeType(part.getMimeType(), MULTIPART_ENCRYPTED)) {
String contentType = part.getContentType(); throw new IllegalArgumentException("Part is not multipart/encrypted!");
String protocolParameter = MimeUtility.getHeaderParameter(contentType, PROTOCOL_PARAMETER); }
boolean isPgpEncrypted = isSameMimeType(part.getMimeType(), MULTIPART_ENCRYPTED) && String protocolParameter = MimeUtility.getHeaderParameter(part.getContentType(), PROTOCOL_PARAMETER);
APPLICATION_PGP_ENCRYPTED.equalsIgnoreCase(protocolParameter); return APPLICATION_PGP_ENCRYPTED.equalsIgnoreCase(protocolParameter);
boolean isPgpSigned = isSameMimeType(part.getMimeType(), MULTIPART_SIGNED) && }
APPLICATION_PGP_SIGNATURE.equalsIgnoreCase(protocolParameter);
return isPgpEncrypted || isPgpSigned; 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 @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.MessageCryptoAnnotations;
import com.fsck.k9.ui.crypto.MessageCryptoSplitter; import com.fsck.k9.ui.crypto.MessageCryptoSplitter;
import com.fsck.k9.ui.crypto.MessageCryptoSplitter.CryptoMessageParts; import com.fsck.k9.ui.crypto.MessageCryptoSplitter.CryptoMessageParts;
import org.openintents.openpgp.util.OpenPgpUtils;
import timber.log.Timber; import timber.log.Timber;
import static com.fsck.k9.mail.internet.MimeUtility.getHeaderParameter; import static com.fsck.k9.mail.internet.MimeUtility.getHeaderParameter;
@ -227,7 +228,7 @@ public class MessageViewInfoExtractor {
Part part = ((Textual)viewable).getPart(); Part part = ((Textual)viewable).getPart();
addHtmlDivider(html, part, prependDivider); addHtmlDivider(html, part, prependDivider);
String t = MessageExtractor.getTextFromPart(part); String t = getTextFromPart(part);
if (t == null) { if (t == null) {
t = ""; t = "";
} else if (viewable instanceof Flowed) { } else if (viewable instanceof Flowed) {
@ -264,7 +265,7 @@ public class MessageViewInfoExtractor {
Part part = ((Textual)viewable).getPart(); Part part = ((Textual)viewable).getPart();
addTextDivider(text, part, prependDivider); addTextDivider(text, part, prependDivider);
String t = MessageExtractor.getTextFromPart(part); String t = getTextFromPart(part);
if (t == null) { if (t == null) {
t = ""; t = "";
} else if (viewable instanceof Html) { } 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. * Get the name of the message part.
* *
@ -504,7 +516,7 @@ public class MessageViewInfoExtractor {
public final String text; public final String text;
public final String html; public final String html;
public ViewableExtractedText(String text, String html) { ViewableExtractedText(String text, String html) {
this.text = text; this.text = text;
this.html = html; this.html = html;
} }

View file

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

View file

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

View file

@ -3,7 +3,7 @@ package com.fsck.k9.message.extractors;
import android.support.annotation.NonNull; 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.Body;
import com.fsck.k9.mail.BodyPart; import com.fsck.k9.mail.BodyPart;
import com.fsck.k9.mail.Message; import com.fsck.k9.mail.Message;
@ -31,7 +31,7 @@ class EncryptionDetector {
private boolean containsInlinePgpEncryptedText(Message message) { private boolean containsInlinePgpEncryptedText(Message message) {
Part textPart = textPartFinder.findFirstTextPart(message); Part textPart = textPartFinder.findFirstTextPart(message);
return MessageDecryptVerifier.isPartPgpInlineEncrypted(textPart); return MessageCryptoStructureDetector.isPartPgpInlineEncrypted(textPart);
} }
private boolean containsPartWithMimeType(Part part, String... wantedMimeTypes) { 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.K9;
import com.fsck.k9.autocrypt.AutocryptOperations; 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.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;
@ -52,7 +52,6 @@ import org.openintents.openpgp.util.OpenPgpApi.OpenPgpDataSink;
import org.openintents.openpgp.util.OpenPgpApi.OpenPgpDataSource; 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 timber.log.Timber; import timber.log.Timber;
@ -84,6 +83,7 @@ public class MessageCryptoHelper {
private State state; private State state;
private CancelableBackgroundOperation cancelableBackgroundOperation; private CancelableBackgroundOperation cancelableBackgroundOperation;
private boolean isCancelled; private boolean isCancelled;
private boolean processSignedOnly;
private OpenPgpApi openPgpApi; private OpenPgpApi openPgpApi;
private OpenPgpServiceConnection openPgpServiceConnection; private OpenPgpServiceConnection openPgpServiceConnection;
@ -108,7 +108,7 @@ public class MessageCryptoHelper {
} }
public void asyncStartOrResumeProcessingMessage(Message message, MessageCryptoCallback callback, public void asyncStartOrResumeProcessingMessage(Message message, MessageCryptoCallback callback,
OpenPgpDecryptionResult cachedDecryptionResult) { OpenPgpDecryptionResult cachedDecryptionResult, boolean processSignedOnly) {
if (this.currentMessage != null) { if (this.currentMessage != null) {
reattachCallback(message, callback); reattachCallback(message, callback);
return; return;
@ -119,21 +119,72 @@ public class MessageCryptoHelper {
this.currentMessage = message; this.currentMessage = message;
this.cachedDecryptionResult = cachedDecryptionResult; this.cachedDecryptionResult = cachedDecryptionResult;
this.callback = callback; this.callback = callback;
this.processSignedOnly = processSignedOnly;
nextStep(); nextStep();
} }
private void findPartsForEncryptionPass() { private void findPartsForMultipartEncryptionPass() {
List<Part> encryptedParts = MessageDecryptVerifier.findEncryptedParts(currentMessage); List<Part> encryptedParts = MessageCryptoStructureDetector.findMultipartEncryptedParts(currentMessage);
processFoundEncryptedParts(encryptedParts); 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() { private void findPartsForMultipartSignaturePass() {
List<Part> signedParts = MessageDecryptVerifier.findSignedParts(currentMessage, messageAnnotations); List<Part> signedParts = MessageCryptoStructureDetector
processFoundSignedParts(signedParts); .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); private void findPartsForPgpInlinePass() {
processFoundInlinePgpParts(inlineParts); 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() { 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) { private void addErrorAnnotation(Part part, CryptoError error, MimeBodyPart replacementPart) {
CryptoResultAnnotation annotation = CryptoResultAnnotation.createErrorAnnotation(error, replacementPart); CryptoResultAnnotation annotation = CryptoResultAnnotation.createErrorAnnotation(error, replacementPart);
messageAnnotations.put(part, annotation); 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() { private void nextStep() {
if (isCancelled) { if (isCancelled) {
return; return;
@ -396,7 +398,7 @@ public class MessageCryptoHelper {
private void callAsyncDetachedVerify(Intent intent) throws IOException, MessagingException { private void callAsyncDetachedVerify(Intent intent) throws IOException, MessagingException {
OpenPgpDataSource dataSource = getDataSourceForSignedData(currentCryptoPart.part); OpenPgpDataSource dataSource = getDataSourceForSignedData(currentCryptoPart.part);
byte[] signatureData = MessageDecryptVerifier.getSignatureData(currentCryptoPart.part); byte[] signatureData = MessageCryptoStructureDetector.getSignatureData(currentCryptoPart.part);
intent.putExtra(OpenPgpApi.EXTRA_DETACHED_SIGNATURE, signatureData); intent.putExtra(OpenPgpApi.EXTRA_DETACHED_SIGNATURE, signatureData);
openPgpApi.executeApiAsync(intent, dataSource, new IOpenPgpSinkResultCallback<Void>() { openPgpApi.executeApiAsync(intent, dataSource, new IOpenPgpSinkResultCallback<Void>() {
@ -645,17 +647,19 @@ public class MessageCryptoHelper {
case START: { case START: {
state = State.ENCRYPTION; state = State.ENCRYPTION;
findPartsForEncryptionPass(); findPartsForMultipartEncryptionPass();
return; return;
} }
case ENCRYPTION: { case ENCRYPTION: {
state = State.SIGNATURES; state = State.SIGNATURES_AND_INLINE;
findPartsForSignaturePass(); findPartsForMultipartSignaturePass();
findPartsForPgpInlinePass();
return; return;
} }
case SIGNATURES: {
case SIGNATURES_AND_INLINE: {
state = State.AUTOCRYPT; state = State.AUTOCRYPT;
findPartsForAutocryptPass(); findPartsForAutocryptPass();
@ -780,22 +784,7 @@ public class MessageCryptoHelper {
return replacementPart; 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 { 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.NonNull;
import android.support.annotation.Nullable; 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.Message;
import com.fsck.k9.mail.Part; import com.fsck.k9.mail.Part;
import com.fsck.k9.mailstore.CryptoResultAnnotation; import com.fsck.k9.mailstore.CryptoResultAnnotation;
@ -21,7 +21,7 @@ public class MessageCryptoSplitter {
@Nullable @Nullable
public static CryptoMessageParts split(@NonNull Message message, @Nullable MessageCryptoAnnotations annotations) { public static CryptoMessageParts split(@NonNull Message message, @Nullable MessageCryptoAnnotations annotations) {
ArrayList<Part> extraParts = new ArrayList<>(); ArrayList<Part> extraParts = new ArrayList<>();
Part primaryPart = MessageDecryptVerifier.findPrimaryEncryptedOrSignedPart(message, extraParts); Part primaryPart = MessageCryptoStructureDetector.findPrimaryEncryptedOrSignedPart(message, extraParts);
if (primaryPart == null) { if (primaryPart == null) {
return null; return null;
} }

View file

@ -7,20 +7,19 @@ import java.util.List;
import com.fsck.k9.K9RobolectricTestRunner; import com.fsck.k9.K9RobolectricTestRunner;
import com.fsck.k9.mail.BodyPart; import com.fsck.k9.mail.BodyPart;
import com.fsck.k9.mail.Message; import com.fsck.k9.mail.Message;
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;
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.MimeMessage;
import com.fsck.k9.mail.internet.MimeMessageHelper; 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.mail.internet.TextBody;
import com.fsck.k9.ui.crypto.MessageCryptoAnnotations; import com.fsck.k9.ui.crypto.MessageCryptoAnnotations;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.robolectric.annotation.Config; 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.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
@ -29,13 +28,12 @@ import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
@SuppressWarnings("WeakerAccess")
@RunWith(K9RobolectricTestRunner.class) @RunWith(K9RobolectricTestRunner.class)
@Config(manifest = Config.NONE) @Config(manifest = Config.NONE)
public class MessageDecryptVerifierTest { public class MessageCryptoStructureDetectorTest {
private static final String MIME_TYPE_MULTIPART_ENCRYPTED = "multipart/encrypted"; MessageCryptoAnnotations messageCryptoAnnotations = mock(MessageCryptoAnnotations.class);
private MessageCryptoAnnotations messageCryptoAnnotations = mock(MessageCryptoAnnotations.class); static final String PGP_INLINE_DATA = "" +
private static final String PROTCOL_PGP_ENCRYPTED = "application/pgp-encrypted";
private static final String PGP_INLINE_DATA = "" +
"-----BEGIN PGP MESSAGE-----\n" + "-----BEGIN PGP MESSAGE-----\n" +
"Header: Value\n" + "Header: Value\n" +
"\n" + "\n" +
@ -49,7 +47,7 @@ public class MessageDecryptVerifierTest {
Message message = new MimeMessage(); Message message = new MimeMessage();
MimeMessageHelper.setBody(message, new TextBody(PGP_INLINE_DATA)); MimeMessageHelper.setBody(message, new TextBody(PGP_INLINE_DATA));
Part cryptoPart = MessageDecryptVerifier.findPrimaryEncryptedOrSignedPart(message, outputExtraParts); Part cryptoPart = MessageCryptoStructureDetector.findPrimaryEncryptedOrSignedPart(message, outputExtraParts);
assertSame(message, cryptoPart); assertSame(message, cryptoPart);
} }
@ -60,12 +58,12 @@ public class MessageDecryptVerifierTest {
BodyPart pgpInlinePart = bodypart("text/plain", PGP_INLINE_DATA); BodyPart pgpInlinePart = bodypart("text/plain", PGP_INLINE_DATA);
Message message = messageFromBody( Message message = messageFromBody(
multipart("alternative", multipart("alternative",
pgpInlinePart, pgpInlinePart,
bodypart("text/html") bodypart("text/html")
) )
); );
Part cryptoPart = MessageDecryptVerifier.findPrimaryEncryptedOrSignedPart(message, outputExtraParts); Part cryptoPart = MessageCryptoStructureDetector.findPrimaryEncryptedOrSignedPart(message, outputExtraParts);
assertSame(pgpInlinePart, cryptoPart); 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); 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); assertSame(pgpInlinePart, cryptoPart);
} }
@ -113,7 +111,7 @@ public class MessageDecryptVerifierTest {
multipart("alternative") multipart("alternative")
); );
Part cryptoPart = MessageDecryptVerifier.findPrimaryEncryptedOrSignedPart(message, outputExtraParts); Part cryptoPart = MessageCryptoStructureDetector.findPrimaryEncryptedOrSignedPart(message, outputExtraParts);
assertNull(cryptoPart); assertNull(cryptoPart);
} }
@ -125,7 +123,7 @@ public class MessageDecryptVerifierTest {
multipart("mixed") multipart("mixed")
); );
Part cryptoPart = MessageDecryptVerifier.findPrimaryEncryptedOrSignedPart(message, outputExtraParts); Part cryptoPart = MessageCryptoStructureDetector.findPrimaryEncryptedOrSignedPart(message, outputExtraParts);
assertNull(cryptoPart); assertNull(cryptoPart);
} }
@ -140,7 +138,7 @@ public class MessageDecryptVerifierTest {
) )
); );
Part cryptoPart = MessageDecryptVerifier.findPrimaryEncryptedOrSignedPart(message, outputExtraParts); Part cryptoPart = MessageCryptoStructureDetector.findPrimaryEncryptedOrSignedPart(message, outputExtraParts);
assertNull(cryptoPart); assertNull(cryptoPart);
} }
@ -149,7 +147,7 @@ public class MessageDecryptVerifierTest {
public void findEncryptedPartsShouldReturnEmptyListForEmptyMessage() throws Exception { public void findEncryptedPartsShouldReturnEmptyListForEmptyMessage() throws Exception {
MimeMessage emptyMessage = new MimeMessage(); MimeMessage emptyMessage = new MimeMessage();
List<Part> encryptedParts = MessageDecryptVerifier.findEncryptedParts(emptyMessage); List<Part> encryptedParts = MessageCryptoStructureDetector.findMultipartEncryptedParts(emptyMessage);
assertEquals(0, encryptedParts.size()); assertEquals(0, encryptedParts.size());
} }
@ -159,56 +157,42 @@ public class MessageDecryptVerifierTest {
MimeMessage message = new MimeMessage(); MimeMessage message = new MimeMessage();
message.setBody(new TextBody("message text")); message.setBody(new TextBody("message text"));
List<Part> encryptedParts = MessageDecryptVerifier.findEncryptedParts(message); List<Part> encryptedParts = MessageCryptoStructureDetector.findMultipartEncryptedParts(message);
assertEquals(0, encryptedParts.size()); assertEquals(0, encryptedParts.size());
} }
@Test @Test
public void findEncryptedPartsShouldReturnEmptyEncryptedPart() throws Exception { public void findEncrypted__withMultipartEncrypted__shouldReturnRoot() throws Exception {
MimeMessage message = new MimeMessage(); Message message = messageFromBody(
MimeMultipart multipartEncrypted = MimeMultipart.newInstance(); multipart("encrypted", "protocol=\"application/pgp-encrypted\"",
multipartEncrypted.setSubType("encrypted"); bodypart("application/pgp-encrypted"),
MimeMessageHelper.setBody(message, multipartEncrypted); bodypart("application/octet-stream")
setContentTypeWithProtocol(message, MIME_TYPE_MULTIPART_ENCRYPTED, PROTCOL_PGP_ENCRYPTED); )
);
List<Part> encryptedParts = MessageDecryptVerifier.findEncryptedParts(message); List<Part> encryptedParts = MessageCryptoStructureDetector.findMultipartEncryptedParts(message);
assertEquals(1, encryptedParts.size()); assertEquals(1, encryptedParts.size());
assertSame(message, encryptedParts.get(0)); assertSame(message, encryptedParts.get(0));
} }
@Test @Test
public void findEncryptedPartsShouldReturnMultipleEncryptedParts() throws Exception { public void findEncrypted__withBadProtocol__shouldReturnEmpty() throws Exception {
MimeMessage message = new MimeMessage(); Message message = messageFromBody(
MimeMultipart multipartMixed = MimeMultipart.newInstance(); multipart("encrypted", "protocol=\"application/not-pgp-encrypted\"",
multipartMixed.setSubType("mixed"); bodypart("application/pgp-encrypted"),
MimeMessageHelper.setBody(message, multipartMixed); bodypart("application/octet-stream", "content")
)
);
MimeMultipart multipartEncryptedOne = MimeMultipart.newInstance(); List<Part> encryptedParts = MessageCryptoStructureDetector.findMultipartEncryptedParts(message);
multipartEncryptedOne.setSubType("encrypted");
MimeBodyPart bodyPartOne = new MimeBodyPart(multipartEncryptedOne);
setContentTypeWithProtocol(bodyPartOne, MIME_TYPE_MULTIPART_ENCRYPTED, PROTCOL_PGP_ENCRYPTED);
multipartMixed.addBodyPart(bodyPartOne);
MimeBodyPart bodyPartTwo = new MimeBodyPart(null, "text/plain"); assertTrue(encryptedParts.isEmpty());
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));
} }
@Test @Test
public void findEncrypted__withMultipartEncrypted__shouldReturnRoot() throws Exception { public void findEncrypted__withBadProtocolAndNoBody__shouldReturnRoot() throws Exception {
Message message = messageFromBody( Message message = messageFromBody(
multipart("encrypted", multipart("encrypted",
bodypart("application/pgp-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()); assertEquals(1, encryptedParts.size());
assertSame(message, encryptedParts.get(0)); 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 @Test
public void findEncrypted__withMultipartMixedSubEncrypted__shouldReturnRoot() throws Exception { public void findEncrypted__withMultipartMixedSubEncrypted__shouldReturnRoot() throws Exception {
Message message = messageFromBody( Message message = messageFromBody(
multipart("mixed", multipart("mixed",
multipart("encrypted", multipart("encrypted", "protocol=\"application/pgp-encrypted\"",
bodypart("application/pgp-encrypted"), bodypart("application/pgp-encrypted"),
bodypart("application/octet-stream") bodypart("application/octet-stream")
) )
) )
); );
List<Part> encryptedParts = MessageDecryptVerifier.findEncryptedParts(message); List<Part> encryptedParts = MessageCryptoStructureDetector.findMultipartEncryptedParts(message);
assertEquals(1, encryptedParts.size()); assertEquals(1, encryptedParts.size());
assertSame(getPart(message, 0), encryptedParts.get(0)); assertSame(getPart(message, 0), encryptedParts.get(0));
@ -244,18 +268,18 @@ public class MessageDecryptVerifierTest {
throws Exception { throws Exception {
Message message = messageFromBody( Message message = messageFromBody(
multipart("mixed", multipart("mixed",
multipart("encrypted", multipart("encrypted", "protocol=\"application/pgp-encrypted\"",
bodypart("application/pgp-encrypted"), bodypart("application/pgp-encrypted"),
bodypart("application/octet-stream") bodypart("application/octet-stream")
), ),
multipart("encrypted", multipart("encrypted", "protocol=\"application/pgp-encrypted\"",
bodypart("application/pgp-encrypted"), bodypart("application/pgp-encrypted"),
bodypart("application/octet-stream") bodypart("application/octet-stream")
) )
) )
); );
List<Part> encryptedParts = MessageDecryptVerifier.findEncryptedParts(message); List<Part> encryptedParts = MessageCryptoStructureDetector.findMultipartEncryptedParts(message);
assertEquals(2, encryptedParts.size()); assertEquals(2, encryptedParts.size());
assertSame(getPart(message, 0), encryptedParts.get(0)); assertSame(getPart(message, 0), encryptedParts.get(0));
@ -267,14 +291,14 @@ public class MessageDecryptVerifierTest {
Message message = messageFromBody( Message message = messageFromBody(
multipart("mixed", multipart("mixed",
bodypart("text/plain"), bodypart("text/plain"),
multipart("encrypted", multipart("encrypted", "protocol=\"application/pgp-encrypted\"",
bodypart("application/pgp-encrypted"), bodypart("application/pgp-encrypted"),
bodypart("application/octet-stream") bodypart("application/octet-stream")
) )
) )
); );
List<Part> encryptedParts = MessageDecryptVerifier.findEncryptedParts(message); List<Part> encryptedParts = MessageCryptoStructureDetector.findMultipartEncryptedParts(message);
assertEquals(1, encryptedParts.size()); assertEquals(1, encryptedParts.size());
assertSame(getPart(message, 1), encryptedParts.get(0)); assertSame(getPart(message, 1), encryptedParts.get(0));
@ -284,7 +308,7 @@ public class MessageDecryptVerifierTest {
public void findEncrypted__withMultipartMixedSubEncryptedAndText__shouldReturnEncrypted() throws Exception { public void findEncrypted__withMultipartMixedSubEncryptedAndText__shouldReturnEncrypted() throws Exception {
Message message = messageFromBody( Message message = messageFromBody(
multipart("mixed", multipart("mixed",
multipart("encrypted", multipart("encrypted", "protocol=\"application/pgp-encrypted\"",
bodypart("application/pgp-encrypted"), bodypart("application/pgp-encrypted"),
bodypart("application/octet-stream") 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()); assertEquals(1, encryptedParts.size());
assertSame(getPart(message, 0), encryptedParts.get(0)); assertSame(getPart(message, 0), encryptedParts.get(0));
@ -301,22 +325,83 @@ public class MessageDecryptVerifierTest {
@Test @Test
public void findSigned__withSimpleMultipartSigned__shouldReturnRoot() throws Exception { public void findSigned__withSimpleMultipartSigned__shouldReturnRoot() throws Exception {
Message message = messageFromBody( Message message = messageFromBody(
multipart("signed", multipart("signed", "protocol=\"application/pgp-signature\"",
bodypart("text/plain"), bodypart("text/plain"),
bodypart("application/pgp-signature") bodypart("application/pgp-signature")
) )
); );
List<Part> signedParts = MessageDecryptVerifier.findSignedParts(message, messageCryptoAnnotations); List<Part> signedParts = MessageCryptoStructureDetector
.findMultipartSignedParts(message, messageCryptoAnnotations);
assertEquals(1, signedParts.size()); assertEquals(1, signedParts.size());
assertSame(message, signedParts.get(0)); assertSame(message, signedParts.get(0));
} }
@Test @Test
public void findSigned__withComplexMultipartSigned__shouldReturnRoot() throws Exception { public void findSigned__withNoProtocolAndNoBody__shouldReturnRoot() throws Exception {
Message message = messageFromBody( Message message = messageFromBody(
multipart("signed", 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", multipart("mixed",
bodypart("text/plain"), bodypart("text/plain"),
bodypart("application/pdf") 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()); assertEquals(1, signedParts.size());
assertSame(message, signedParts.get(0)); assertSame(message, signedParts.get(0));
@ -335,14 +421,15 @@ public class MessageDecryptVerifierTest {
public void findEncrypted__withMultipartMixedSubSigned__shouldReturnSigned() throws Exception { public void findEncrypted__withMultipartMixedSubSigned__shouldReturnSigned() throws Exception {
Message message = messageFromBody( Message message = messageFromBody(
multipart("mixed", multipart("mixed",
multipart("signed", multipart("signed", "protocol=\"application/pgp-signature\"",
bodypart("text/plain"), bodypart("text/plain"),
bodypart("application/pgp-signature") bodypart("application/pgp-signature")
) )
) )
); );
List<Part> signedParts = MessageDecryptVerifier.findSignedParts(message, messageCryptoAnnotations); List<Part> signedParts = MessageCryptoStructureDetector
.findMultipartSignedParts(message, messageCryptoAnnotations);
assertEquals(1, signedParts.size()); assertEquals(1, signedParts.size());
assertSame(getPart(message, 0), signedParts.get(0)); assertSame(getPart(message, 0), signedParts.get(0));
@ -352,7 +439,7 @@ public class MessageDecryptVerifierTest {
public void findEncrypted__withMultipartMixedSubSignedAndText__shouldReturnSigned() throws Exception { public void findEncrypted__withMultipartMixedSubSignedAndText__shouldReturnSigned() throws Exception {
Message message = messageFromBody( Message message = messageFromBody(
multipart("mixed", multipart("mixed",
multipart("signed", multipart("signed", "application/pgp-signature",
bodypart("text/plain"), bodypart("text/plain"),
bodypart("application/pgp-signature") 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()); assertEquals(1, signedParts.size());
assertSame(getPart(message, 0), signedParts.get(0)); assertSame(getPart(message, 0), signedParts.get(0));
@ -371,14 +459,15 @@ public class MessageDecryptVerifierTest {
Message message = messageFromBody( Message message = messageFromBody(
multipart("mixed", multipart("mixed",
bodypart("text/plain"), bodypart("text/plain"),
multipart("signed", multipart("signed", "application/pgp-signature",
bodypart("text/plain"), bodypart("text/plain"),
bodypart("application/pgp-signature") bodypart("application/pgp-signature")
) )
) )
); );
List<Part> signedParts = MessageDecryptVerifier.findSignedParts(message, messageCryptoAnnotations); List<Part> signedParts = MessageCryptoStructureDetector
.findMultipartSignedParts(message, messageCryptoAnnotations);
assertEquals(1, signedParts.size()); assertEquals(1, signedParts.size());
assertSame(getPart(message, 1), signedParts.get(0)); assertSame(getPart(message, 1), signedParts.get(0));
@ -395,7 +484,7 @@ public class MessageDecryptVerifierTest {
MimeMessage message = new MimeMessage(); MimeMessage message = new MimeMessage();
message.setBody(new TextBody(pgpInlineData)); message.setBody(new TextBody(pgpInlineData));
assertTrue(MessageDecryptVerifier.isPartPgpInlineEncrypted(message)); assertTrue(MessageCryptoStructureDetector.isPartPgpInlineEncrypted(message));
} }
@Test @Test
@ -410,8 +499,8 @@ public class MessageDecryptVerifierTest {
MimeMessage message = new MimeMessage(); MimeMessage message = new MimeMessage();
message.setBody(new TextBody(pgpInlineData)); message.setBody(new TextBody(pgpInlineData));
assertTrue(MessageDecryptVerifier.isPartPgpInlineEncryptedOrSigned(message)); assertTrue(MessageCryptoStructureDetector.isPartPgpInlineEncryptedOrSigned(message));
assertTrue(MessageDecryptVerifier.isPartPgpInlineEncrypted(message)); assertTrue(MessageCryptoStructureDetector.isPartPgpInlineEncrypted(message));
} }
@Test @Test
@ -426,8 +515,8 @@ public class MessageDecryptVerifierTest {
MimeMessage message = new MimeMessage(); MimeMessage message = new MimeMessage();
message.setBody(new TextBody(pgpInlineData)); message.setBody(new TextBody(pgpInlineData));
assertFalse(MessageDecryptVerifier.isPartPgpInlineEncryptedOrSigned(message)); assertFalse(MessageCryptoStructureDetector.isPartPgpInlineEncryptedOrSigned(message));
assertFalse(MessageDecryptVerifier.isPartPgpInlineEncrypted(message)); assertFalse(MessageCryptoStructureDetector.isPartPgpInlineEncrypted(message));
} }
@Test @Test
@ -444,7 +533,7 @@ public class MessageDecryptVerifierTest {
MimeMessage message = new MimeMessage(); MimeMessage message = new MimeMessage();
message.setBody(new TextBody(pgpInlineData)); message.setBody(new TextBody(pgpInlineData));
assertTrue(MessageDecryptVerifier.isPartPgpInlineEncryptedOrSigned(message)); assertTrue(MessageCryptoStructureDetector.isPartPgpInlineEncryptedOrSigned(message));
} }
@Test @Test
@ -461,44 +550,14 @@ public class MessageDecryptVerifierTest {
MimeMessage message = new MimeMessage(); MimeMessage message = new MimeMessage();
message.setBody(new TextBody(pgpInlineData)); message.setBody(new TextBody(pgpInlineData));
assertFalse(MessageDecryptVerifier.isPartPgpInlineEncrypted(message)); assertFalse(MessageCryptoStructureDetector.isPartPgpInlineEncrypted(message));
} }
MimeMessage messageFromBody(BodyPart bodyPart) throws MessagingException { static Part getPart(Part searchRootPart, int... indexes) {
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) {
Part part = searchRootPart; Part part = searchRootPart;
for (int index : indexes) { for (int index : indexes) {
part = ((Multipart) part.getBody()).getBodyPart(index); part = ((Multipart) part.getBody()).getBodyPart(index);
} }
return part; 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.Body;
import com.fsck.k9.mail.BodyPart; import com.fsck.k9.mail.BodyPart;
import com.fsck.k9.mail.Message; 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.MimeBodyPart;
import com.fsck.k9.mail.internet.MimeMessage; 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.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;
@ -37,11 +35,13 @@ import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment; import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config; 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.assertEquals;
import static junit.framework.Assert.assertSame; import static junit.framework.Assert.assertSame;
import static junit.framework.Assert.assertTrue; import static junit.framework.Assert.assertTrue;
import static org.mockito.Matchers.any; import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Matchers.same; import static org.mockito.Matchers.same;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy; import static org.mockito.Mockito.spy;
@ -84,7 +84,7 @@ public class MessageCryptoHelperTest {
message.setHeader("Content-Type", "text/plain"); message.setHeader("Content-Type", "text/plain");
MessageCryptoCallback messageCryptoCallback = mock(MessageCryptoCallback.class); MessageCryptoCallback messageCryptoCallback = mock(MessageCryptoCallback.class);
messageCryptoHelper.asyncStartOrResumeProcessingMessage(message, messageCryptoCallback, null); messageCryptoHelper.asyncStartOrResumeProcessingMessage(message, messageCryptoCallback, null, false);
ArgumentCaptor<MessageCryptoAnnotations> captor = ArgumentCaptor.forClass(MessageCryptoAnnotations.class); ArgumentCaptor<MessageCryptoAnnotations> captor = ArgumentCaptor.forClass(MessageCryptoAnnotations.class);
verify(messageCryptoCallback).onCryptoOperationsFinished(captor.capture()); verify(messageCryptoCallback).onCryptoOperationsFinished(captor.capture());
@ -107,7 +107,7 @@ public class MessageCryptoHelperTest {
MessageCryptoCallback messageCryptoCallback = mock(MessageCryptoCallback.class); MessageCryptoCallback messageCryptoCallback = mock(MessageCryptoCallback.class);
messageCryptoHelper.asyncStartOrResumeProcessingMessage(message, messageCryptoCallback, null); messageCryptoHelper.asyncStartOrResumeProcessingMessage(message, messageCryptoCallback, null, false);
ArgumentCaptor<MessageCryptoAnnotations> captor = ArgumentCaptor.forClass(MessageCryptoAnnotations.class); ArgumentCaptor<MessageCryptoAnnotations> captor = ArgumentCaptor.forClass(MessageCryptoAnnotations.class);
@ -124,12 +124,15 @@ public class MessageCryptoHelperTest {
@Test @Test
public void multipartSigned__withNullBody__shouldReturnSignedIncomplete() throws Exception { public void multipartSigned__withNullBody__shouldReturnSignedIncomplete() throws Exception {
MimeMessage message = new MimeMessage(); Message message = messageFromBody(
message.setUid("msguid"); multipart("signed", "protocol=\"application/pgp-signature\"",
message.setHeader("Content-Type", "multipart/signed"); bodypart("text/plain"),
bodypart("application/pgp-signature")
)
);
MessageCryptoCallback messageCryptoCallback = mock(MessageCryptoCallback.class); 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, assertPartAnnotationHasState(message, messageCryptoCallback, CryptoError.OPENPGP_SIGNED_BUT_INCOMPLETE, null,
null, null, null); null, null, null);
@ -137,12 +140,15 @@ public class MessageCryptoHelperTest {
@Test @Test
public void multipartEncrypted__withNullBody__shouldReturnEncryptedIncomplete() throws Exception { public void multipartEncrypted__withNullBody__shouldReturnEncryptedIncomplete() throws Exception {
MimeMessage message = new MimeMessage(); Message message = messageFromBody(
message.setUid("msguid"); multipart("encrypted", "protocol=\"application/pgp-encrypted\"",
message.setHeader("Content-Type", "multipart/encrypted"); bodypart("application/pgp-encrypted"),
bodypart("application/octet-stream")
)
);
MessageCryptoCallback messageCryptoCallback = mock(MessageCryptoCallback.class); MessageCryptoCallback messageCryptoCallback = mock(MessageCryptoCallback.class);
messageCryptoHelper.asyncStartOrResumeProcessingMessage(message, messageCryptoCallback, null); messageCryptoHelper.asyncStartOrResumeProcessingMessage(message, messageCryptoCallback, null, false);
assertPartAnnotationHasState( assertPartAnnotationHasState(
message, messageCryptoCallback, CryptoError.OPENPGP_ENCRYPTED_BUT_INCOMPLETE, null, null, null, null); message, messageCryptoCallback, CryptoError.OPENPGP_ENCRYPTED_BUT_INCOMPLETE, null, null, null, null);
@ -150,13 +156,15 @@ public class MessageCryptoHelperTest {
@Test @Test
public void multipartEncrypted__withUnknownProtocol__shouldReturnEncryptedUnsupported() throws Exception { public void multipartEncrypted__withUnknownProtocol__shouldReturnEncryptedUnsupported() throws Exception {
MimeMessage message = new MimeMessage(); Message message = messageFromBody(
message.setUid("msguid"); multipart("encrypted", "protocol=\"application/bad-protocol\"",
message.setHeader("Content-Type", "multipart/encrypted; protocol=\"unknown protocol\""); bodypart("application/bad-protocol", "content"),
message.setBody(new MimeMultipart("multipart/encrypted", "--------")); bodypart("application/octet-stream", "content")
)
);
MessageCryptoCallback messageCryptoCallback = mock(MessageCryptoCallback.class); 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, assertPartAnnotationHasState(message, messageCryptoCallback, CryptoError.ENCRYPTED_BUT_UNSUPPORTED, null, null,
null, null); null, null);
@ -164,13 +172,15 @@ public class MessageCryptoHelperTest {
@Test @Test
public void multipartSigned__withUnknownProtocol__shouldReturnSignedUnsupported() throws Exception { public void multipartSigned__withUnknownProtocol__shouldReturnSignedUnsupported() throws Exception {
MimeMessage message = new MimeMessage(); Message message = messageFromBody(
message.setUid("msguid"); multipart("signed", "protocol=\"application/bad-protocol\"",
message.setHeader("Content-Type", "multipart/signed; protocol=\"unknown protocol\""); bodypart("text/plain", "content"),
message.setBody(new MimeMultipart("multipart/encrypted", "--------")); bodypart("application/bad-protocol", "content")
)
);
MessageCryptoCallback messageCryptoCallback = mock(MessageCryptoCallback.class); 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, assertPartAnnotationHasState(message, messageCryptoCallback, CryptoError.SIGNED_BUT_UNSUPPORTED, null, null,
null, null); null, null);
@ -178,18 +188,14 @@ public class MessageCryptoHelperTest {
@Test @Test
public void multipartSigned__shouldCallOpenPgpApiAsync() throws Exception { public void multipartSigned__shouldCallOpenPgpApiAsync() throws Exception {
BodyPart signedBodyPart = spy(new MimeBodyPart(new TextBody("text"))); BodyPart signedBodyPart = spy(bodypart("text/plain", "content"));
BodyPart signatureBodyPart = new MimeBodyPart(new TextBody("text")); Message message = messageFromBody(
multipart("signed", "protocol=\"application/pgp-signature\"",
Multipart messageBody = new MimeMultipart("boundary1"); signedBodyPart,
messageBody.addBodyPart(signedBodyPart); bodypart("application/pgp-signature", "content")
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.setFrom(Address.parse("Test <test@example.org>")[0]);
message.setBody(messageBody);
OutputStream outputStream = mock(OutputStream.class); OutputStream outputStream = mock(OutputStream.class);
@ -204,21 +210,46 @@ public class MessageCryptoHelperTest {
verifyNoMoreInteractions(autocryptOperations); 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 @Test
public void multipartEncrypted__shouldCallOpenPgpApiAsync() throws Exception { public void multipartEncrypted__shouldCallOpenPgpApiAsync() throws Exception {
BodyPart dummyBodyPart = new MimeBodyPart(new TextBody("text"));
Body encryptedBody = spy(new TextBody("encrypted data")); Body encryptedBody = spy(new TextBody("encrypted data"));
BodyPart encryptedBodyPart = spy(new MimeBodyPart(encryptedBody)); Message message = messageFromBody(
multipart("encrypted", "protocol=\"application/pgp-encrypted\"",
Multipart messageBody = new MimeMultipart("boundary1"); bodypart("application/pgp-encrypted", "content"),
messageBody.addBodyPart(dummyBodyPart); bodypart("application/octet-stream", encryptedBody)
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.setFrom(Address.parse("Test <test@example.org>")[0]);
message.setBody(messageBody);
OutputStream outputStream = mock(OutputStream.class); OutputStream outputStream = mock(OutputStream.class);
@ -248,7 +279,7 @@ public class MessageCryptoHelperTest {
private void processEncryptedMessageAndCaptureMocks(Message message, Body encryptedBody, OutputStream outputStream) private void processEncryptedMessageAndCaptureMocks(Message message, Body encryptedBody, OutputStream outputStream)
throws Exception { throws Exception {
messageCryptoHelper.asyncStartOrResumeProcessingMessage(message, messageCryptoCallback, null); messageCryptoHelper.asyncStartOrResumeProcessingMessage(message, messageCryptoCallback, null, false);
ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class); ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
ArgumentCaptor<OpenPgpDataSource> dataSourceCaptor = ArgumentCaptor.forClass(OpenPgpDataSource.class); ArgumentCaptor<OpenPgpDataSource> dataSourceCaptor = ArgumentCaptor.forClass(OpenPgpDataSource.class);
@ -266,7 +297,7 @@ public class MessageCryptoHelperTest {
private void processSignedMessageAndCaptureMocks(Message message, BodyPart signedBodyPart, private void processSignedMessageAndCaptureMocks(Message message, BodyPart signedBodyPart,
OutputStream outputStream) throws Exception { OutputStream outputStream) throws Exception {
messageCryptoHelper.asyncStartOrResumeProcessingMessage(message, messageCryptoCallback, null); messageCryptoHelper.asyncStartOrResumeProcessingMessage(message, messageCryptoCallback, null, true);
ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class); ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
ArgumentCaptor<OpenPgpDataSource> dataSourceCaptor = ArgumentCaptor.forClass(OpenPgpDataSource.class); ArgumentCaptor<OpenPgpDataSource> dataSourceCaptor = ArgumentCaptor.forClass(OpenPgpDataSource.class);
@ -282,6 +313,15 @@ public class MessageCryptoHelperTest {
verify(signedBodyPart).writeTo(outputStream); 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, private void assertPartAnnotationHasState(Message message, MessageCryptoCallback messageCryptoCallback,
CryptoError cryptoErrorState, MimeBodyPart replacementPart, OpenPgpDecryptionResult openPgpDecryptionResult, CryptoError cryptoErrorState, MimeBodyPart replacementPart, OpenPgpDecryptionResult openPgpDecryptionResult,
OpenPgpSignatureResult openPgpSignatureResult, PendingIntent openPgpPendingIntent) { OpenPgpSignatureResult openPgpSignatureResult, PendingIntent openPgpPendingIntent) {

View file

@ -80,7 +80,7 @@ public class OpenPgpUtils {
} }
public static String extractClearsignedMessage(String text) { 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; return null;
} }
int endOfHeader = text.indexOf("\r\n\r\n") +4; int endOfHeader = text.indexOf("\r\n\r\n") +4;