Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b57ef10e authored by Vincent Breitmoser's avatar Vincent Breitmoser
Browse files

messageview: add support for encapsulated signed parts (fixes #576)

parent 6ceec725
Loading
Loading
Loading
Loading
+11 −1
Original line number Diff line number Diff line
@@ -15,7 +15,10 @@ import com.fsck.k9.mail.MessagingException;
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.MimeUtility;
import com.fsck.k9.mailstore.CryptoResultAnnotation;
import com.fsck.k9.ui.crypto.MessageCryptoAnnotations;

import static com.fsck.k9.mail.internet.MimeUtility.isSameMimeType;

@@ -83,13 +86,20 @@ public class MessageDecryptVerifier {
        return encryptedParts;
    }

    public static List<Part> findSignedParts(Part startPart) {
    public static List<Part> findSignedParts(Part startPart, MessageCryptoAnnotations messageCryptoAnnotations) {
        List<Part> signedParts = new ArrayList<>();
        Stack<Part> partsToCheck = new Stack<>();
        partsToCheck.push(startPart);

        while (!partsToCheck.isEmpty()) {
            Part part = partsToCheck.pop();
            if (messageCryptoAnnotations.has(part)) {
                CryptoResultAnnotation resultAnnotation = messageCryptoAnnotations.get(part);
                MimeBodyPart replacementData = resultAnnotation.getReplacementData();
                if (replacementData != null) {
                    part = replacementData;
                }
            }
            Body body = part.getBody();

            if (isPartMultipartSigned(part)) {
+42 −0
Original line number Diff line number Diff line
@@ -21,6 +21,8 @@ public final class CryptoResultAnnotation {
    private final OpenPgpError openPgpError;
    private final PendingIntent openPgpPendingIntent;

    private final CryptoResultAnnotation encapsulatedResult;

    private CryptoResultAnnotation(@NonNull CryptoError errorType, MimeBodyPart replacementData,
            OpenPgpDecryptionResult openPgpDecryptionResult,
            OpenPgpSignatureResult openPgpSignatureResult,
@@ -32,6 +34,24 @@ public final class CryptoResultAnnotation {
        this.openPgpSignatureResult = openPgpSignatureResult;
        this.openPgpPendingIntent = openPgpPendingIntent;
        this.openPgpError = openPgpError;

        this.encapsulatedResult = null;
    }

    private CryptoResultAnnotation(CryptoResultAnnotation annotation, CryptoResultAnnotation encapsulatedResult) {
        if (annotation.encapsulatedResult != null) {
            throw new AssertionError("cannot replace an encapsulated result, this is a bug!");
        }

        this.errorType = annotation.errorType;
        this.replacementData = annotation.replacementData;

        this.openPgpDecryptionResult = annotation.openPgpDecryptionResult;
        this.openPgpSignatureResult = annotation.openPgpSignatureResult;
        this.openPgpPendingIntent = annotation.openPgpPendingIntent;
        this.openPgpError = annotation.openPgpError;

        this.encapsulatedResult = encapsulatedResult;
    }


@@ -52,6 +72,15 @@ public final class CryptoResultAnnotation {
        return new CryptoResultAnnotation(CryptoError.OPENPGP_API_RETURNED_ERROR, null, null, null, null, error);
    }

    public boolean isOpenPgpResult() {
        return openPgpDecryptionResult != null && openPgpSignatureResult != null;
    }

    public boolean hasSignatureResult() {
        return openPgpSignatureResult != null &&
                openPgpSignatureResult.getResult() != OpenPgpSignatureResult.RESULT_NO_SIGNATURE;
    }

    @Nullable
    public OpenPgpDecryptionResult getOpenPgpDecryptionResult() {
        return openPgpDecryptionResult;
@@ -86,6 +115,19 @@ public final class CryptoResultAnnotation {
        return replacementData;
    }

    @NonNull
    public CryptoResultAnnotation withEncapsulatedResult(CryptoResultAnnotation resultAnnotation) {
        return new CryptoResultAnnotation(this, resultAnnotation);
    }

    public boolean hasEncapsulatedResult() {
        return encapsulatedResult != null;
    }

    public CryptoResultAnnotation getEncapsulatedResult() {
        return encapsulatedResult;
    }


    public enum CryptoError {
        NONE,
+11 −0
Original line number Diff line number Diff line
@@ -3,6 +3,8 @@ package com.fsck.k9.ui.crypto;

import java.util.HashMap;

import android.support.annotation.VisibleForTesting;

import com.fsck.k9.mail.Part;
import com.fsck.k9.mailstore.CryptoResultAnnotation;

@@ -25,4 +27,13 @@ public class MessageCryptoAnnotations {
    public boolean has(Part part) {
        return annotations.containsKey(part);
    }

    public Part findKeyForAnnotationWithReplacementPart(Part part) {
        for (HashMap.Entry<Part, CryptoResultAnnotation> entry : annotations.entrySet()) {
            if (part == entry.getValue().getReplacementData()) {
                return entry.getKey();
            }
        }
        return null;
    }
}
+40 −9
Original line number Diff line number Diff line
@@ -68,6 +68,8 @@ public class MessageCryptoHelper {

    private MessageCryptoAnnotations messageAnnotations;
    private Intent userInteractionResultIntent;
    private LocalMessage currentMessage;
    private boolean secondPassStarted;


    public MessageCryptoHelper(Activity activity, Account account, MessageCryptoCallback callback) {
@@ -75,8 +77,6 @@ public class MessageCryptoHelper {
        this.activity = activity;
        this.callback = callback;
        this.account = account;

        this.messageAnnotations = new MessageCryptoAnnotations();
    }

    public void decryptOrVerifyMessagePartsIfNecessary(LocalMessage message) {
@@ -85,14 +85,23 @@ public class MessageCryptoHelper {
            return;
        }

        List<Part> encryptedParts = MessageDecryptVerifier.findEncryptedParts(message);
        processFoundEncryptedParts(encryptedParts,
                MessageHelper.createEmptyPart());
        this.messageAnnotations = new MessageCryptoAnnotations();
        this.currentMessage = message;

        runFirstPass();
    }

        List<Part> signedParts = MessageDecryptVerifier.findSignedParts(message);
        processFoundSignedParts(signedParts);
    private void runFirstPass() {
        List<Part> encryptedParts = MessageDecryptVerifier.findEncryptedParts(currentMessage);
        processFoundEncryptedParts(encryptedParts, MessageHelper.createEmptyPart());

        List<Part> inlineParts = MessageDecryptVerifier.findPgpInlineParts(message);
        decryptOrVerifyNextPart();
    }

    private void runSecondPass() {
        List<Part> signedParts = MessageDecryptVerifier.findSignedParts(currentMessage, messageAnnotations);
        processFoundSignedParts(signedParts);
        List<Part> inlineParts = MessageDecryptVerifier.findPgpInlineParts(currentMessage);
        addFoundInlinePgpParts(inlineParts);

        decryptOrVerifyNextPart();
@@ -144,7 +153,7 @@ public class MessageCryptoHelper {

    private void decryptOrVerifyNextPart() {
        if (partsToDecryptOrVerify.isEmpty()) {
            returnResultToFragment();
            runSecondPassOrReturnResultToFragment();
            return;
        }

@@ -465,6 +474,17 @@ public class MessageCryptoHelper {
        onCryptoFinished();
    }

    private void propagateEncapsulatedSignedPart(CryptoResultAnnotation resultAnnotation, Part part) {
        Part encapsulatingPart = messageAnnotations.findKeyForAnnotationWithReplacementPart(part);
        CryptoResultAnnotation encapsulatingPartAnnotation = messageAnnotations.get(encapsulatingPart);

        if (encapsulatingPart != null && resultAnnotation.hasSignatureResult()) {
            CryptoResultAnnotation replacementAnnotation =
                    encapsulatingPartAnnotation.withEncapsulatedResult(resultAnnotation);
            messageAnnotations.put(encapsulatingPart, replacementAnnotation);
        }
    }

    private void onCryptoFailed(OpenPgpError error) {
        CryptoResultAnnotation errorPart = CryptoResultAnnotation.createOpenPgpErrorAnnotation(error);
        addCryptoResultAnnotationToMessage(errorPart);
@@ -474,6 +494,8 @@ public class MessageCryptoHelper {
    private void addCryptoResultAnnotationToMessage(CryptoResultAnnotation resultAnnotation) {
        Part part = currentCryptoPart.part;
        messageAnnotations.put(part, resultAnnotation);

        propagateEncapsulatedSignedPart(resultAnnotation, part);
    }

    private void onCryptoFinished() {
@@ -481,6 +503,15 @@ public class MessageCryptoHelper {
        decryptOrVerifyNextPart();
    }

    private void runSecondPassOrReturnResultToFragment() {
        if (secondPassStarted) {
            callback.onCryptoOperationsFinished(messageAnnotations);
            return;
        }
        secondPassStarted = true;
        runSecondPass();
    }

    private void returnResultToFragment() {
        callback.onCryptoOperationsFinished(messageAnnotations);
    }
+9 −5
Original line number Diff line number Diff line
@@ -14,6 +14,7 @@ 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.RobolectricTestRunner;
@@ -21,12 +22,15 @@ import org.robolectric.annotation.Config;

import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertSame;
import static org.mockito.Mockito.mock;


@RunWith(RobolectricTestRunner.class)
@Config(manifest = Config.NONE, sdk = 21)
public class MessageDecryptVerifierTest {

    private MessageCryptoAnnotations messageCryptoAnnotations = mock(MessageCryptoAnnotations.class);

    @Test
    public void findEncryptedPartsShouldReturnEmptyListForEmptyMessage() throws Exception {
        MimeMessage emptyMessage = new MimeMessage();
@@ -189,7 +193,7 @@ public class MessageDecryptVerifierTest {
                )
        );

        List<Part> signedParts = MessageDecryptVerifier.findSignedParts(message);
        List<Part> signedParts = MessageDecryptVerifier.findSignedParts(message, messageCryptoAnnotations);

        assertEquals(1, signedParts.size());
        assertSame(message, signedParts.get(0));
@@ -207,7 +211,7 @@ public class MessageDecryptVerifierTest {
                )
        );

        List<Part> signedParts = MessageDecryptVerifier.findSignedParts(message);
        List<Part> signedParts = MessageDecryptVerifier.findSignedParts(message, messageCryptoAnnotations);

        assertEquals(1, signedParts.size());
        assertSame(message, signedParts.get(0));
@@ -224,7 +228,7 @@ public class MessageDecryptVerifierTest {
                )
        );

        List<Part> signedParts = MessageDecryptVerifier.findSignedParts(message);
        List<Part> signedParts = MessageDecryptVerifier.findSignedParts(message, messageCryptoAnnotations);

        assertEquals(1, signedParts.size());
        assertSame(getPart(message, 0), signedParts.get(0));
@@ -242,7 +246,7 @@ public class MessageDecryptVerifierTest {
                )
        );

        List<Part> signedParts = MessageDecryptVerifier.findSignedParts(message);
        List<Part> signedParts = MessageDecryptVerifier.findSignedParts(message, messageCryptoAnnotations);

        assertEquals(1, signedParts.size());
        assertSame(getPart(message, 0), signedParts.get(0));
@@ -260,7 +264,7 @@ public class MessageDecryptVerifierTest {
                )
        );

        List<Part> signedParts = MessageDecryptVerifier.findSignedParts(message);
        List<Part> signedParts = MessageDecryptVerifier.findSignedParts(message, messageCryptoAnnotations);

        assertEquals(1, signedParts.size());
        assertSame(getPart(message, 1), signedParts.get(0));