Skip to content

Commit

Permalink
Fixed lookup public keys and re-verify signature if missing pubkey.| #74
Browse files Browse the repository at this point in the history
  • Loading branch information
DenBond7 committed Nov 19, 2024
1 parent fcc7085 commit 47d50d3
Show file tree
Hide file tree
Showing 10 changed files with 317 additions and 177 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* © 2016-present FlowCrypt a.s. Limitations apply. Contact [email protected]
* Contributors: denbond7
*/

package com.flowcrypt.email.extensions.com.flowcrypt.email.util

import android.content.Context
import com.flowcrypt.email.api.retrofit.response.base.Result
import com.flowcrypt.email.api.retrofit.response.model.MsgBlock
import com.flowcrypt.email.database.entity.AccountEntity
import com.flowcrypt.email.extensions.kotlin.processing
import com.flowcrypt.email.security.pgp.PgpDecryptAndOrVerify
import com.flowcrypt.email.security.pgp.PgpMsg
import com.flowcrypt.email.util.cache.DiskLruCache
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.pgpainless.PGPainless
import org.pgpainless.key.protection.PasswordBasedSecretKeyRingProtector
import org.pgpainless.util.Passphrase

/**
* @author Denys Bondarenko
*/
suspend fun DiskLruCache.Snapshot.processing(
context: Context,
accountEntity: AccountEntity,
skipAttachmentsRawData: Boolean = false,
preResultAction: suspend (blocks: List<MsgBlock>) -> Unit = {}
): Result<PgpMsg.ProcessedMimeMessageResult?> = withContext(Dispatchers.IO) {
val uri = getUri(0)
if (uri != null) {
try {
val inputStream =
context.contentResolver.openInputStream(uri) ?: throw java.lang.IllegalStateException()

val keys = PGPainless.readKeyRing()
.secretKeyRingCollection(accountEntity.servicePgpPrivateKey)

val decryptionStream = PgpDecryptAndOrVerify.genDecryptionStream(
srcInputStream = inputStream,
secretKeys = keys,
protector = PasswordBasedSecretKeyRingProtector.forKey(
keys.first(),
Passphrase.fromPassword(accountEntity.servicePgpPassphrase)
)
)

val processedMimeMessage = PgpMsg.processMimeMessage(
context = context,
inputStream = decryptionStream,
skipAttachmentsRawData = skipAttachmentsRawData
)

preResultAction.invoke(processedMimeMessage.blocks)
return@withContext Result.success(processedMimeMessage)
} catch (e: Exception) {
return@withContext Result.exception(e)
}
} else {
return@withContext getByteArray(0).processing(
context = context,
skipAttachmentsRawData = skipAttachmentsRawData
) { blocks ->
preResultAction.invoke(blocks)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* © 2016-present FlowCrypt a.s. Limitations apply. Contact [email protected]
* Contributors: denbond7
*/

package com.flowcrypt.email.extensions.kotlin

import android.content.Context
import com.flowcrypt.email.api.retrofit.response.base.Result
import com.flowcrypt.email.api.retrofit.response.model.MsgBlock
import com.flowcrypt.email.security.pgp.PgpMsg
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext

/**
* @author Denys Bondarenko
*/
suspend fun ByteArray.processing(
context: Context,
skipAttachmentsRawData: Boolean = false,
preResultAction: suspend (blocks: List<MsgBlock>) -> Unit = {}
): Result<PgpMsg.ProcessedMimeMessageResult?> = withContext(Dispatchers.IO) {
return@withContext if (isEmpty()) {
Result.exception(throwable = IllegalArgumentException("empty byte array"))
} else {
try {
val processedMimeMessageResult = PgpMsg.processMimeMessage(
context = context,
inputStream = inputStream(),
skipAttachmentsRawData = skipAttachmentsRawData
)
preResultAction.invoke(processedMimeMessageResult.blocks)
return@withContext Result.success(processedMimeMessageResult)
} catch (e: Exception) {
return@withContext Result.exception(throwable = e)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,7 @@ class ComposeMsgViewModel(isCandidateToEncrypt: Boolean, application: Applicatio
AccountViewModel(application) {
private val recipientLookUpManager = RecipientLookUpManager(
application = application,
roomDatabase = roomDatabase,
viewModelScope = viewModelScope
roomDatabase = roomDatabase
) { recipientInfo ->
val recipientItem = RecipientItem(
recipientInfo.recipientType,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,10 @@ import com.flowcrypt.email.database.MessageState
import com.flowcrypt.email.database.entity.AccountEntity
import com.flowcrypt.email.database.entity.AttachmentEntity
import com.flowcrypt.email.database.entity.MessageEntity
import com.flowcrypt.email.extensions.com.flowcrypt.email.util.processing
import com.flowcrypt.email.extensions.com.google.api.services.gmail.model.getAttachmentInfoList
import com.flowcrypt.email.extensions.jakarta.mail.isOpenPGPMimeSigned
import com.flowcrypt.email.extensions.kotlin.processing
import com.flowcrypt.email.extensions.uid
import com.flowcrypt.email.jetpack.livedata.SkipInitialValueObserver
import com.flowcrypt.email.jetpack.workmanager.sync.UpdateMsgsSeenStateWorker
Expand Down Expand Up @@ -84,9 +86,6 @@ import kotlinx.coroutines.withContext
import org.eclipse.angus.mail.imap.IMAPBodyPart
import org.eclipse.angus.mail.imap.IMAPFolder
import org.eclipse.angus.mail.imap.IMAPMessage
import org.pgpainless.PGPainless
import org.pgpainless.key.protection.PasswordBasedSecretKeyRingProtector
import org.pgpainless.util.Passphrase
import java.io.BufferedInputStream
import java.io.InputStream
import java.util.Collections
Expand Down Expand Up @@ -161,8 +160,11 @@ class MsgDetailsViewModel(
val byteArray = OutgoingMessagesManager.getOutgoingMessageFromFile(
application,
requireNotNull(messageEntity.id)
)
val processingResult = processingByteArray(byteArray)
) ?: byteArrayOf()
passphraseNeededLiveData.postValue(emptyList())
val processingResult = byteArray.processing(context = getApplication()) { blocks ->
preResultsProcessing(blocks)
}
emit(Result.loading(resultCode = R.id.progress_id_processing, progress = 90.toDouble()))
emit(processingResult)
}
Expand Down Expand Up @@ -547,58 +549,14 @@ class MsgDetailsViewModel(

private suspend fun processingMsgSnapshot(msgSnapshot: DiskLruCache.Snapshot):
Result<PgpMsg.ProcessedMimeMessageResult?> = withContext(Dispatchers.IO) {
val uri = msgSnapshot.getUri(0)
passphraseNeededLiveData.postValue(emptyList())
val accountEntity = getActiveAccountSuspend()
?: throw java.lang.NullPointerException("Account is null")
if (uri != null) {
val context: Context = getApplication()
try {
val inputStream =
context.contentResolver.openInputStream(uri) ?: throw java.lang.IllegalStateException()

val keys = PGPainless.readKeyRing()
.secretKeyRingCollection(accountEntity.servicePgpPrivateKey)

val decryptionStream = PgpDecryptAndOrVerify.genDecryptionStream(
srcInputStream = inputStream,
secretKeys = keys,
protector = PasswordBasedSecretKeyRingProtector.forKey(
keys.first(),
Passphrase.fromPassword(accountEntity.servicePgpPassphrase)
)
)

val processedMimeMessage = PgpMsg.processMimeMessage(
context = getApplication(),
inputStream = decryptionStream
)

preResultsProcessing(processedMimeMessage.blocks)
return@withContext Result.success(processedMimeMessage)
} catch (e: Exception) {
return@withContext Result.exception(e)
}
} else {
val byteArray = msgSnapshot.getByteArray(0)
return@withContext processingByteArray(byteArray)
}
}

private suspend fun processingByteArray(rawMimeBytes: ByteArray?):
Result<PgpMsg.ProcessedMimeMessageResult?> = withContext(Dispatchers.IO) {
passphraseNeededLiveData.postValue(emptyList())
return@withContext if (rawMimeBytes == null) {
Result.exception(throwable = IllegalArgumentException("empty byte array"))
} else {
try {
val processedMimeMessageResult =
PgpMsg.processMimeMessage(getApplication(), rawMimeBytes.inputStream())
preResultsProcessing(processedMimeMessageResult.blocks)
return@withContext Result.success(processedMimeMessageResult)
} catch (e: Exception) {
return@withContext Result.exception(throwable = e)
}
return@withContext msgSnapshot.processing(
context = getApplication(),
accountEntity = accountEntity
) { blocks ->
preResultsProcessing(blocks)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ import com.flowcrypt.email.api.retrofit.response.model.MsgBlock
import com.flowcrypt.email.api.retrofit.response.model.PublicKeyMsgBlock
import com.flowcrypt.email.database.MessageState
import com.flowcrypt.email.database.entity.MessageEntity
import com.flowcrypt.email.extensions.com.flowcrypt.email.util.processing
import com.flowcrypt.email.extensions.java.lang.printStackTraceIfDebugOnly
import com.flowcrypt.email.jetpack.workmanager.sync.UpdateMsgsSeenStateWorker
import com.flowcrypt.email.model.MessageEncryptionType
import com.flowcrypt.email.security.pgp.PgpDecryptAndOrVerify
import com.flowcrypt.email.security.pgp.PgpKey
import com.flowcrypt.email.security.pgp.PgpMsg
import com.flowcrypt.email.ui.adapter.MessagesInThreadListAdapter
Expand All @@ -42,9 +42,6 @@ import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.pgpainless.PGPainless
import org.pgpainless.key.protection.PasswordBasedSecretKeyRingProtector
import org.pgpainless.util.Passphrase

/**
* @author Denys Bondarenko
Expand Down Expand Up @@ -262,60 +259,15 @@ class ProcessMessageViewModel(

private suspend fun processingMsgSnapshot(msgSnapshot: DiskLruCache.Snapshot):
Result<PgpMsg.ProcessedMimeMessageResult?> = withContext(Dispatchers.IO) {
val uri = msgSnapshot.getUri(0)
val accountEntity = getActiveAccountSuspend()
?: throw java.lang.NullPointerException("Account is null")
if (uri != null) {
val context: Context = getApplication()
try {
val inputStream =
context.contentResolver.openInputStream(uri) ?: throw java.lang.IllegalStateException()

val keys = PGPainless.readKeyRing()
.secretKeyRingCollection(accountEntity.servicePgpPrivateKey)

val decryptionStream = PgpDecryptAndOrVerify.genDecryptionStream(
srcInputStream = inputStream,
secretKeys = keys,
protector = PasswordBasedSecretKeyRingProtector.forKey(
keys.first(),
Passphrase.fromPassword(accountEntity.servicePgpPassphrase)
)
)

val processedMimeMessage = PgpMsg.processMimeMessage(
context = getApplication(),
inputStream = decryptionStream,
skipAttachmentsRawData = skipAttachmentsRawData
)

preResultsProcessing(processedMimeMessage.blocks)
return@withContext Result.success(processedMimeMessage)
} catch (e: Exception) {
return@withContext Result.exception(e)
}
} else {
val byteArray = msgSnapshot.getByteArray(0)
return@withContext processingByteArray(byteArray)
}
}

private suspend fun processingByteArray(rawMimeBytes: ByteArray?):
Result<PgpMsg.ProcessedMimeMessageResult?> = withContext(Dispatchers.IO) {
return@withContext if (rawMimeBytes == null) {
Result.exception(throwable = IllegalArgumentException("empty byte array"))
} else {
try {
val processedMimeMessageResult = PgpMsg.processMimeMessage(
context = getApplication(),
inputStream = rawMimeBytes.inputStream(),
skipAttachmentsRawData = skipAttachmentsRawData
)
preResultsProcessing(processedMimeMessageResult.blocks)
return@withContext Result.success(processedMimeMessageResult)
} catch (e: Exception) {
return@withContext Result.exception(throwable = e)
}
return@withContext msgSnapshot.processing(
context = getApplication(),
accountEntity = accountEntity,
skipAttachmentsRawData
) { blocks ->
preResultsProcessing(blocks)
}
}

Expand Down
Loading

0 comments on commit 47d50d3

Please sign in to comment.