package com.fsck.k9.ui.crypto;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.List;
import android.app.Activity;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.WorkerThread;
import timber.log.Timber;
import com.fsck.k9.K9;
import com.fsck.k9.crypto.MessageDecryptVerifier;
import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.Body;
import com.fsck.k9.mail.BodyPart;
import com.fsck.k9.mail.Flag;
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.MimeMultipart;
import com.fsck.k9.mail.internet.SizeAware;
import com.fsck.k9.mail.internet.TextBody;
import com.fsck.k9.mailstore.CryptoResultAnnotation;
import com.fsck.k9.mailstore.CryptoResultAnnotation.CryptoError;
import com.fsck.k9.mailstore.LocalMessage;
import com.fsck.k9.mailstore.MessageHelper;
import com.fsck.k9.mailstore.MimePartStreamParser;
import com.fsck.k9.mailstore.util.FileFactory;
import com.fsck.k9.provider.DecryptedFileProvider;
import org.apache.commons.io.IOUtils;
import org.openintents.openpgp.IOpenPgpService2;
import org.openintents.openpgp.OpenPgpDecryptionResult;
import org.openintents.openpgp.OpenPgpError;
import org.openintents.openpgp.OpenPgpSignatureResult;
import org.openintents.openpgp.util.OpenPgpApi;
import org.openintents.openpgp.util.OpenPgpApi.CancelableBackgroundOperation;
import org.openintents.openpgp.util.OpenPgpApi.IOpenPgpSinkResultCallback;
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;
public class MessageCryptoHelper {
private static final int INVALID_OPENPGP_RESULT_CODE = -1;
private static final MimeBodyPart NO_REPLACEMENT_PART = null;
private static final int REQUEST_CODE_USER_INTERACTION = 124;
private static final int PROGRESS_SIZE_THRESHOLD = 4096;
private final Context context;
private final String openPgpProviderPackage;
private final Object callbackLock = new Object();
private final Deque<CryptoPart> partsToDecryptOrVerify = new ArrayDeque<>();
@Nullable
private MessageCryptoCallback callback;
private LocalMessage currentMessage;
private OpenPgpDecryptionResult cachedDecryptionResult;
private MessageCryptoAnnotations queuedResult;
private PendingIntent queuedPendingIntent;
private MessageCryptoAnnotations messageAnnotations;
private CryptoPart currentCryptoPart;
private Intent currentCryptoResult;
private Intent userInteractionResultIntent;
private boolean secondPassStarted;
private CancelableBackgroundOperation cancelableBackgroundOperation;
private boolean isCancelled;
private OpenPgpApi openPgpApi;
private OpenPgpServiceConnection openPgpServiceConnection;
public MessageCryptoHelper(Context context) {
this.context = context.getApplicationContext();
if (!K9.isOpenPgpProviderConfigured()) {
throw new IllegalStateException("MessageCryptoHelper must only be called with a openpgp provider!");
}
openPgpProviderPackage = K9.getOpenPgpProvider();
}
public boolean isConfiguredForOutdatedCryptoProvider() {
return !openPgpProviderPackage.equals(K9.getOpenPgpProvider());
}
public void asyncStartOrResumeProcessingMessage(LocalMessage message, MessageCryptoCallback callback,
OpenPgpDecryptionResult cachedDecryptionResult) {
if (this.currentMessage != null) {
reattachCallback(message, callback);
return;
}
this.messageAnnotations = new MessageCryptoAnnotations();
this.currentMessage = message;
this.cachedDecryptionResult = cachedDecryptionResult;
this.callback = callback;
runFirstPass();
}
private void runFirstPass() {
List<Part> encryptedParts = MessageDecryptVerifier.findEncryptedParts(currentMessage);
processFoundEncryptedParts(encryptedParts);
decryptOrVerifyNextPart();
}
private void runSecondPass() {
List<Part> signedParts = MessageDecryptVerifier.findSignedParts(currentMessage, messageAnnotations);
processFoundSignedParts(signedParts);
List<Part> inlineParts = MessageDecryptVerifier.findPgpInlineParts(currentMessage);
addFoundInlinePgpParts(inlineParts);
decryptOrVerifyNextPart();
}
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);
partsToDecryptOrVerify.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);
partsToDecryptOrVerify.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 addFoundInlinePgpParts(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);
partsToDecryptOrVerify.add(cryptoPart);
}
}
private void decryptOrVerifyNextPart() {
if (isCancelled) {
return;
}
if (partsToDecryptOrVerify.isEmpty()) {
runSecondPassOrReturnResultToFragment();
return;
}
CryptoPart cryptoPart = partsToDecryptOrVerify.peekFirst();
startDecryptingOrVerifyingPart(cryptoPart);
}
private void startDecryptingOrVerifyingPart(CryptoPart cryptoPart) {
if (!isBoundToCryptoProviderService()) {
connectToCryptoProviderService();
} else {
decryptOrVerifyPart(cryptoPart);
}
}
private boolean isBoundToCryptoProviderService() {
return openPgpApi != null;
}
private void connectToCryptoProviderService() {
openPgpServiceConnection = new OpenPgpServiceConnection(context, openPgpProviderPackage,
new OnBound() {
@Override
public void onBound(IOpenPgpService2 service) {
openPgpApi = new OpenPgpApi(context, service);
decryptOrVerifyNextPart();
}
@Override
public void onError(Exception e) {
// TODO actually handle (hand to ui, offer retry?)
Timber.e(e, "Couldn't connect to OpenPgpService");
}
});
openPgpServiceConnection.bindToService();
}
private void decryptOrVerifyPart(CryptoPart cryptoPart) {
currentCryptoPart = cryptoPart;
Intent decryptIntent = userInteractionResultIntent;
userInteractionResultIntent = null;
if (decryptIntent == null) {
decryptIntent = getDecryptionIntent();
}
decryptVerify(decryptIntent);
}
@NonNull
private Intent getDecryptionIntent() {
Intent decryptIntent = new Intent(OpenPgpApi.ACTION_DECRYPT_VERIFY);
Address[] from = currentMessage.getFrom();
if (from.length > 0) {
decryptIntent.putExtra(OpenPgpApi.EXTRA_SENDER_ADDRESS, from[0].getAddress());
}
decryptIntent.putExtra(OpenPgpApi.EXTRA_SUPPORT_OVERRIDE_CRYPTO_WARNING, true);
decryptIntent.putExtra(OpenPgpApi.EXTRA_DECRYPTION_RESULT, cachedDecryptionResult);
return decryptIntent;
}
private void decryptVerify(Intent intent) {
try {
CryptoPartType cryptoPartType = currentCryptoPart.type;
switch (cryptoPartType) {
case PGP_SIGNED: {
callAsyncDetachedVerify(intent);
return;
}
case PGP_ENCRYPTED: {
callAsyncDecrypt(intent);
return;
}
case PGP_INLINE: {
callAsyncInlineOperation(intent);
return;
}
}
throw new IllegalStateException("Unknown crypto part type: " + cryptoPartType);
} catch (IOException e) {
Timber.e(e, "IOException");
} catch (MessagingException e) {
Timber.e(e, "MessagingException");
}
}
private void callAsyncInlineOperation(Intent intent) throws IOException {
OpenPgpDataSource dataSource = getDataSourceForEncryptedOrInlineData();
OpenPgpDataSink<MimeBodyPart> dataSink = getDataSinkForDecryptedInlineData();
cancelableBackgroundOperation = openPgpApi.executeApiAsync(intent, dataSource, dataSink,
new IOpenPgpSinkResultCallback<MimeBodyPart>() {
@Override
public void onProgress(int current, int max) {
Timber.d("received progress status: %d / %d", current, max);
callbackProgress(current, max);
}
@Override
public void onReturn(Intent result, MimeBodyPart bodyPart) {
cancelableBackgroundOperation = null;
currentCryptoResult = result;
onCryptoOperationReturned(bodyPart);
}
});
}
public void cancelIfRunning() {
detachCallback();
isCancelled = true;
if (cancelableBackgroundOperation != null) {
cancelableBackgroundOperation.cancelOperation();
}
}
private OpenPgpDataSink<MimeBodyPart> getDataSinkForDecryptedInlineData() {
return new OpenPgpDataSink<MimeBodyPart>() {
@Override
public MimeBodyPart processData(InputStream is) throws IOException {
try {
ByteArrayOutputStream decryptedByteOutputStream = new ByteArrayOutputStream();
IOUtils.copy(is, decryptedByteOutputStream);
TextBody body = new TextBody(new String(decryptedByteOutputStream.toByteArray()));
return new MimeBodyPart(body, "text/plain");
} catch (MessagingException e) {
Timber.e(e, "MessagingException");
}
return null;
}
};
}
private void callAsyncDecrypt(Intent intent) throws IOException {
OpenPgpDataSource dataSource = getDataSourceForEncryptedOrInlineData();
OpenPgpDataSink<MimeBodyPart> openPgpDataSink = getDataSinkForDecryptedData();
cancelableBackgroundOperation = openPgpApi.executeApiAsync(intent, dataSource, openPgpDataSink,
new IOpenPgpSinkResultCallback<MimeBodyPart>() {
@Override
public void onReturn(Intent result, MimeBodyPart decryptedPart) {
cancelableBackgroundOperation = null;
currentCryptoResult = result;
onCryptoOperationReturned(decryptedPart);
}
@Override
public void onProgress(int current, int max) {
Timber.d("received progress status: %d / %d", current, max);
callbackProgress(current, max);
}
});
}
private void callAsyncDetachedVerify(Intent intent) throws IOException, MessagingException {
OpenPgpDataSource dataSource = getDataSourceForSignedData(currentCryptoPart.part);
byte[] signatureData = MessageDecryptVerifier.getSignatureData(currentCryptoPart.part);
intent.putExtra(OpenPgpApi.EXTRA_DETACHED_SIGNATURE, signatureData);
openPgpApi.executeApiAsync(intent, dataSource, new IOpenPgpSinkResultCallback<Void>() {
@Override
public void onReturn(Intent result, Void dummy) {
cancelableBackgroundOperation = null;
currentCryptoResult = result;
onCryptoOperationReturned(null);
}
@Override
public void onProgress(int current, int max) {
Timber.d("received progress status: %d / %d", current, max);
callbackProgress(current, max);
}
});
}
private OpenPgpDataSource getDataSourceForSignedData(final Part signedPart) throws IOException {
return new OpenPgpDataSource() {
@Override
public void writeTo(OutputStream os) throws IOException {
try {
Multipart multipartSignedMultipart = (Multipart) signedPart.getBody();
BodyPart signatureBodyPart = multipartSignedMultipart.getBodyPart(0);
Timber.d("signed data type: %s", signatureBodyPart.getMimeType());
signatureBodyPart.writeTo(os);
} catch (MessagingException e) {
Timber.e(e, "Exception while writing message to crypto provider");
}
}
};
}
private OpenPgpDataSource getDataSourceForEncryptedOrInlineData() throws IOException {
return new OpenPgpApi.OpenPgpDataSource() {
@Override
public Long getSizeForProgress() {
Part part = currentCryptoPart.part;
CryptoPartType cryptoPartType = currentCryptoPart.type;
Body body;
if (cryptoPartType == CryptoPartType.PGP_ENCRYPTED) {
Multipart multipartEncryptedMultipart = (Multipart) part.getBody();
BodyPart encryptionPayloadPart = multipartEncryptedMultipart.getBodyPart(1);
body = encryptionPayloadPart.getBody();
} else if (cryptoPartType == CryptoPartType.PGP_INLINE) {
body = part.getBody();
} else {
throw new IllegalStateException("part to stream must be encrypted or inline!");
}
if (body instanceof SizeAware) {
long bodySize = ((SizeAware) body).getSize();
if (bodySize > PROGRESS_SIZE_THRESHOLD) {
return bodySize;
}
}
return null;
}
@Override
@WorkerThread
public void writeTo(OutputStream os) throws IOException {
try {
Part part = currentCryptoPart.part;
CryptoPartType cryptoPartType = currentCryptoPart.type;
if (cryptoPartType == CryptoPartType.PGP_ENCRYPTED) {
Multipart multipartEncryptedMultipart = (Multipart) part.getBody();
BodyPart encryptionPayloadPart = multipartEncryptedMultipart.getBodyPart(1);
Body encryptionPayloadBody = encryptionPayloadPart.getBody();
encryptionPayloadBody.writeTo(os);
} else if (cryptoPartType == CryptoPartType.PGP_INLINE) {
String text = MessageExtractor.getTextFromPart(part);
os.write(text.getBytes());
} else {
throw new IllegalStateException("part to stream must be encrypted or inline!");
}
} catch (MessagingException e) {
Timber.e(e, "MessagingException while writing message to crypto provider");
}
}
};
}
private OpenPgpDataSink<MimeBodyPart> getDataSinkForDecryptedData() throws IOException {
return new OpenPgpDataSink<MimeBodyPart>() {
@Override
@WorkerThread
public MimeBodyPart processData(InputStream is) throws IOException {
try {
FileFactory fileFactory =
DecryptedFileProvider.getFileFactory(context);
return MimePartStreamParser.parse(fileFactory, is);
} catch (MessagingException e) {
Timber.e(e, "Something went wrong while parsing the decrypted MIME part");
//TODO: pass error to main thread and display error message to user
return null;
}
}
};
}
private void onCryptoOperationReturned(MimeBodyPart decryptedPart) {
if (currentCryptoResult == null) {
Timber.e("Internal error: we should have a result here!");
return;
}
try {
handleCryptoOperationResult(decryptedPart);
} finally {
currentCryptoResult = null;
}
}
private void handleCryptoOperationResult(MimeBodyPart outputPart) {
int resultCode = currentCryptoResult.getIntExtra(OpenPgpApi.RESULT_CODE, INVALID_OPENPGP_RESULT_CODE);
Timber.d("OpenPGP API decryptVerify result code: %d", resultCode);
switch (resultCode) {
case INVALID_OPENPGP_RESULT_CODE: {
Timber.e("Internal error: no result code!");
break;
}
case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED: {
handleUserInteractionRequest();
break;
}
case OpenPgpApi.RESULT_CODE_ERROR: {
handleCryptoOperationError();
break;
}
case OpenPgpApi.RESULT_CODE_SUCCESS: {
handleCryptoOperationSuccess(outputPart);
break;
}
}
}
private void handleUserInteractionRequest() {
PendingIntent pendingIntent = currentCryptoResult.getParcelableExtra(OpenPgpApi.RESULT_INTENT);
if (pendingIntent == null) {
throw new AssertionError("Expecting PendingIntent on USER_INTERACTION_REQUIRED!");
}
callbackPendingIntent(pendingIntent);
}
private void handleCryptoOperationError() {
OpenPgpError error = currentCryptoResult.getParcelableExtra(OpenPgpApi.RESULT_ERROR);
Timber.w("OpenPGP API error: %s", error.getMessage());
onCryptoOperationFailed(error);
}
private void handleCryptoOperationSuccess(MimeBodyPart outputPart) {
OpenPgpDecryptionResult decryptionResult =
currentCryptoResult.getParcelableExtra(OpenPgpApi.RESULT_DECRYPTION);
OpenPgpSignatureResult signatureResult =
currentCryptoResult.getParcelableExtra(OpenPgpApi.RESULT_SIGNATURE);
PendingIntent pendingIntent = currentCryptoResult.getParcelableExtra(OpenPgpApi.RESULT_INTENT);
PendingIntent insecureWarningPendingIntent = currentCryptoResult.getParcelableExtra(OpenPgpApi.RESULT_INSECURE_DETAIL_INTENT);
boolean overrideCryptoWarning = currentCryptoResult.getBooleanExtra(
OpenPgpApi.RESULT_OVERRIDE_CRYPTO_WARNING, false);
CryptoResultAnnotation resultAnnotation = CryptoResultAnnotation.createOpenPgpResultAnnotation(decryptionResult,
signatureResult, pendingIntent, insecureWarningPendingIntent, outputPart, overrideCryptoWarning);
onCryptoOperationSuccess(resultAnnotation);
}
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (isCancelled) {
return;
}
if (requestCode != REQUEST_CODE_USER_INTERACTION) {
throw new IllegalStateException("got an activity result that wasn't meant for us. this is a bug!");
}
if (resultCode == Activity.RESULT_OK) {
userInteractionResultIntent = data;
decryptOrVerifyNextPart();
} else {
onCryptoOperationCanceled();
}
}
private void onCryptoOperationSuccess(CryptoResultAnnotation resultAnnotation) {
addCryptoResultAnnotationToMessage(resultAnnotation);
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 onCryptoOperationCanceled() {
// there are weird states that get us here when we're not actually processing any part. just skip in that case
// see https://github.com/k9mail/k-9/issues/1878
if (currentCryptoPart != null) {
CryptoResultAnnotation errorPart = CryptoResultAnnotation.createOpenPgpCanceledAnnotation();
addCryptoResultAnnotationToMessage(errorPart);
}
onCryptoFinished();
}
private void onCryptoOperationFailed(OpenPgpError error) {
CryptoResultAnnotation annotation;
if (currentCryptoPart.type == CryptoPartType.PGP_SIGNED) {
MimeBodyPart replacementPart = getMultipartSignedContentPartIfAvailable(currentCryptoPart.part);
annotation = CryptoResultAnnotation.createOpenPgpSignatureErrorAnnotation(error, replacementPart);
} else {
annotation = CryptoResultAnnotation.createOpenPgpEncryptionErrorAnnotation(error);
}
addCryptoResultAnnotationToMessage(annotation);
onCryptoFinished();
}
private void addCryptoResultAnnotationToMessage(CryptoResultAnnotation resultAnnotation) {
Part part = currentCryptoPart.part;
messageAnnotations.put(part, resultAnnotation);
propagateEncapsulatedSignedPart(resultAnnotation, part);
}
private void onCryptoFinished() {
boolean currentPartIsFirstInQueue = partsToDecryptOrVerify.peekFirst() == currentCryptoPart;
if (!currentPartIsFirstInQueue) {
throw new IllegalStateException(
"Trying to remove part from queue that is not the currently processed one!");
}
if (currentCryptoPart != null) {
partsToDecryptOrVerify.removeFirst();
currentCryptoPart = null;
} else {
Timber.e(new Throwable(), "Got to onCryptoFinished() with no part in processing!");
}
decryptOrVerifyNextPart();
}
private void runSecondPassOrReturnResultToFragment() {
if (secondPassStarted) {
callbackReturnResult();
return;
}
secondPassStarted = true;
runSecondPass();
}
private void cleanupAfterProcessingFinished() {
partsToDecryptOrVerify.clear();
openPgpApi = null;
if (openPgpServiceConnection != null) {
openPgpServiceConnection.unbindFromService();
}
openPgpServiceConnection = null;
}
public void detachCallback() {
synchronized (callbackLock) {
callback = null;
}
}
private void reattachCallback(LocalMessage message, MessageCryptoCallback callback) {
if (!message.equals(currentMessage)) {
throw new AssertionError("Callback may only be reattached for the same message!");
}
synchronized (callbackLock) {
this.callback = callback;
boolean hasCachedResult = queuedResult != null || queuedPendingIntent != null;
if (hasCachedResult) {
Timber.d("Returning cached result or pending intent to reattached callback");
deliverResult();
}
}
}
private void callbackPendingIntent(PendingIntent pendingIntent) {
synchronized (callbackLock) {
queuedPendingIntent = pendingIntent;
deliverResult();
}
}
private void callbackReturnResult() {
synchronized (callbackLock) {
cleanupAfterProcessingFinished();
queuedResult = messageAnnotations;
messageAnnotations = null;
deliverResult();
}
}
private void callbackProgress(int current, int max) {
synchronized (callbackLock) {
if (callback != null) {
callback.onCryptoHelperProgress(current, max);
}
}
}
// This method must only be called inside a synchronized(callbackLock) block!
private void deliverResult() {
if (isCancelled) {
return;
}
if (callback == null) {
Timber.d("Keeping crypto helper result in queue for later delivery");
return;
}
if (queuedResult != null) {
callback.onCryptoOperationsFinished(queuedResult);
} else if (queuedPendingIntent != null) {
callback.startPendingIntentForCryptoHelper(
queuedPendingIntent.getIntentSender(), REQUEST_CODE_USER_INTERACTION, null, 0, 0, 0);
queuedPendingIntent = null;
} else {
throw new IllegalStateException("deliverResult() called with no result!");
}
}
private static class CryptoPart {
public final CryptoPartType type;
public final Part part;
CryptoPart(CryptoPartType type, Part part) {
this.type = type;
this.part = part;
}
}
private enum CryptoPartType {
PGP_INLINE,
PGP_ENCRYPTED,
PGP_SIGNED
}
@Nullable
private static MimeBodyPart getMultipartSignedContentPartIfAvailable(Part part) {
MimeBodyPart replacementPart = NO_REPLACEMENT_PART;
Body body = part.getBody();
if (body instanceof MimeMultipart) {
MimeMultipart multipart = ((MimeMultipart) part.getBody());
if (multipart.getCount() >= 1) {
replacementPart = (MimeBodyPart) multipart.getBodyPart(0);
}
}
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;
}
}
}