Repo created

This commit is contained in:
Fr4nz D13trich 2025-11-24 18:55:42 +01:00
parent a629de6271
commit 3cef7c5092
2161 changed files with 246605 additions and 2 deletions

View file

@ -0,0 +1,11 @@
plugins {
id(ThunderbirdPlugins.Library.android)
}
dependencies {
implementation(projects.app.core)
}
android {
namespace = "com.fsck.k9.crypto.openpgp"
}

View file

@ -0,0 +1,67 @@
package com.fsck.k9.crypto.openpgp;
import androidx.annotation.NonNull;
import com.fsck.k9.crypto.MessageCryptoStructureDetector;
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.Part;
import com.fsck.k9.message.extractors.TextPartFinder;
import static com.fsck.k9.mail.internet.MimeUtility.isSameMimeType;
//FIXME: Make this only detect OpenPGP messages. Move support for S/MIME messages to separate module.
class EncryptionDetector {
private final TextPartFinder textPartFinder;
EncryptionDetector(TextPartFinder textPartFinder) {
this.textPartFinder = textPartFinder;
}
public boolean isEncrypted(@NonNull Message message) {
return isPgpMimeOrSMimeEncrypted(message) || containsInlinePgpEncryptedText(message);
}
private boolean isPgpMimeOrSMimeEncrypted(Message message) {
return containsPartWithMimeType(message, "multipart/encrypted", "application/pkcs7-mime");
}
private boolean containsInlinePgpEncryptedText(Message message) {
Part textPart = textPartFinder.findFirstTextPart(message);
return MessageCryptoStructureDetector.isPartPgpInlineEncrypted(textPart);
}
private boolean containsPartWithMimeType(Part part, String... wantedMimeTypes) {
String mimeType = part.getMimeType();
if (isMimeTypeAnyOf(mimeType, wantedMimeTypes)) {
return true;
}
Body body = part.getBody();
if (body instanceof Multipart) {
Multipart multipart = (Multipart) body;
for (BodyPart bodyPart : multipart.getBodyParts()) {
if (containsPartWithMimeType(bodyPart, wantedMimeTypes)) {
return true;
}
}
}
return false;
}
private boolean isMimeTypeAnyOf(String mimeType, String... wantedMimeTypes) {
for (String wantedMimeType : wantedMimeTypes) {
if (isSameMimeType(mimeType, wantedMimeType)) {
return true;
}
}
return false;
}
}

View file

@ -0,0 +1,30 @@
package com.fsck.k9.crypto.openpgp
import com.fsck.k9.crypto.EncryptionExtractor
import com.fsck.k9.crypto.EncryptionResult
import com.fsck.k9.mail.Message
import com.fsck.k9.message.extractors.TextPartFinder
class OpenPgpEncryptionExtractor internal constructor(
private val encryptionDetector: EncryptionDetector
) : EncryptionExtractor {
override fun extractEncryption(message: Message): EncryptionResult? {
return if (encryptionDetector.isEncrypted(message)) {
EncryptionResult(ENCRYPTION_TYPE, 0)
} else {
null
}
}
companion object {
const val ENCRYPTION_TYPE = "openpgp"
@JvmStatic
fun newInstance(): OpenPgpEncryptionExtractor {
val textPartFinder = TextPartFinder()
val encryptionDetector = EncryptionDetector(textPartFinder)
return OpenPgpEncryptionExtractor(encryptionDetector)
}
}
}

View file

@ -0,0 +1,109 @@
package com.fsck.k9.crypto.openpgp;
import com.fsck.k9.mail.Message;
import com.fsck.k9.message.extractors.TextPartFinder;
import org.junit.Before;
import org.junit.Test;
import static com.fsck.k9.crypto.openpgp.MessageCreationHelper.createMessage;
import static com.fsck.k9.crypto.openpgp.MessageCreationHelper.createMultipartMessage;
import static com.fsck.k9.crypto.openpgp.MessageCreationHelper.createPart;
import static com.fsck.k9.crypto.openpgp.MessageCreationHelper.createTextMessage;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class EncryptionDetectorTest {
private static final String CRLF = "\r\n";
private EncryptionDetector encryptionDetector;
@Before
public void setUp() {
encryptionDetector = new EncryptionDetector(new TextPartFinder());
}
@Test
public void isEncrypted_withTextPlain_shouldReturnFalse() {
Message message = createTextMessage("text/plain", "plain text");
boolean encrypted = encryptionDetector.isEncrypted(message);
assertFalse(encrypted);
}
@Test
public void isEncrypted_withMultipartEncrypted_shouldReturnTrue() throws Exception {
Message message = createMultipartMessage("multipart/encrypted",
createPart("application/octet-stream"), createPart("application/octet-stream"));
boolean encrypted = encryptionDetector.isEncrypted(message);
assertTrue(encrypted);
}
@Test
public void isEncrypted_withSMimePart_shouldReturnTrue() {
Message message = createMessage("application/pkcs7-mime");
boolean encrypted = encryptionDetector.isEncrypted(message);
assertTrue(encrypted);
}
@Test
public void isEncrypted_withMultipartMixedContainingSMimePart_shouldReturnTrue() throws Exception {
Message message = createMultipartMessage("multipart/mixed",
createPart("application/pkcs7-mime"), createPart("text/plain"));
boolean encrypted = encryptionDetector.isEncrypted(message);
assertTrue(encrypted);
}
@Test
public void isEncrypted_withInlinePgp_shouldReturnTrue() {
Message message = createTextMessage("text/plain", "" +
"-----BEGIN PGP MESSAGE-----" + CRLF +
"some encrypted stuff here" + CRLF +
"-----END PGP MESSAGE-----");
boolean encrypted = encryptionDetector.isEncrypted(message);
assertTrue(encrypted);
}
@Test
public void isEncrypted_withPlainTextAndPreambleWithInlinePgp_shouldReturnFalse() {
Message message = createTextMessage("text/plain", "" +
"preamble" + CRLF +
"-----BEGIN PGP MESSAGE-----" + CRLF +
"some encrypted stuff here" + CRLF +
"-----END PGP MESSAGE-----" + CRLF +
"epilogue");
boolean encrypted = encryptionDetector.isEncrypted(message);
assertFalse(encrypted);
}
@Test
public void isEncrypted_withQuotedInlinePgp_shouldReturnFalse() {
Message message = createTextMessage("text/plain", "" +
"good talk!" + CRLF +
CRLF +
"> -----BEGIN PGP MESSAGE-----" + CRLF +
"> some encrypted stuff here" + CRLF +
"> -----END PGP MESSAGE-----" + CRLF +
CRLF +
"-- " + CRLF +
"my signature");
boolean encrypted = encryptionDetector.isEncrypted(message);
assertFalse(encrypted);
}
}

View file

@ -0,0 +1,51 @@
package com.fsck.k9.crypto.openpgp;
import com.fsck.k9.mail.Body;
import com.fsck.k9.mail.BodyPart;
import com.fsck.k9.mail.Message;
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.MimeMultipart;
import com.fsck.k9.mail.internet.TextBody;
import com.fsck.k9.mailstore.BinaryMemoryBody;
public class MessageCreationHelper {
public static BodyPart createPart(String mimeType) throws MessagingException {
BinaryMemoryBody body = new BinaryMemoryBody(new byte[0], "utf-8");
return new MimeBodyPart(body, mimeType);
}
public static Message createTextMessage(String mimeType, String text) {
TextBody body = new TextBody(text);
return createMessage(mimeType, body);
}
public static Message createMultipartMessage(String mimeType, BodyPart... parts) {
MimeMultipart body = createMultipartBody(mimeType, parts);
return createMessage(mimeType, body);
}
public static Message createMessage(String mimeType) {
return createMessage(mimeType, null);
}
private static Message createMessage(String mimeType, Body body) {
MimeMessage message = new MimeMessage();
message.setBody(body);
message.setHeader(MimeHeader.HEADER_CONTENT_TYPE, mimeType);
return message;
}
private static MimeMultipart createMultipartBody(String mimeType, BodyPart[] parts) {
MimeMultipart multipart = new MimeMultipart(mimeType, "boundary");
for (BodyPart part : parts) {
multipart.addBodyPart(part);
}
return multipart;
}
}