Skip to content
Merged
2 changes: 1 addition & 1 deletion FlowCrypt/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -514,7 +514,7 @@ dependencies {
//kotlinx-serialization-core added to fix runtime issue with dependencies conflict.
//Maybe it will be removed in future.
implementation("org.jetbrains.kotlinx:kotlinx-serialization-core:1.9.0")
implementation("org.pgpainless:pgpainless-core:1.7.6")
implementation("org.pgpainless:pgpainless-core:2.0.1")
implementation("org.eclipse.angus:angus-mail:2.0.5")
implementation("org.eclipse.angus:gimap:2.0.5")
implementation("commons-io:commons-io:2.20.0")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ class WkdClientTest {
genLookupUrlPath(EXISTING_EMAIL) -> {
return MockResponse().setResponseCode(HttpURLConnection.HTTP_OK)
.setBody(
PGPainless.generateKeyRing().simpleEcKeyRing(EXISTING_EMAIL).publicKey.armor()
PGPainless.getInstance().generateKey().simpleEcKeyRing(EXISTING_EMAIL).armor()
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ class ComposeScreenReloadPublicKeyFlowTest : BaseComposeScreenTest() {
override val activityScenario: ActivityScenario<*>?
get() = activityScenarioRule.scenario

private val pgpKeyRingDetails = PGPainless.generateKeyRing()
.simpleEcKeyRing(RECIPIENT, TestConstants.DEFAULT_PASSWORD).toPgpKeyRingDetails()
private val pgpKeyRingDetails = PGPainless.getInstance().generateKey()
.simpleEcKeyRing(RECIPIENT, TestConstants.DEFAULT_PASSWORD).pgpKeyRing.toPgpKeyRingDetails()

private val addRecipientsToDatabaseRule = AddRecipientsToDatabaseRule(
listOf(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,11 @@ class SubmitPublicKeyToAttesterForImportedKeyDuringSetupFlowTest : BaseSignTest(

@Before
fun prepareResources() {
val generatedKey = PGPainless.generateKeyRing().simpleEcKeyRing(
UserId.nameAndEmail(USER_ENFORCE_ATTESTER_SUBMIT, USER_ENFORCE_ATTESTER_SUBMIT),
val generatedKey = PGPainless.getInstance().generateKey()
.simpleEcKeyRing(
UserId.nameAndEmail(USER_ENFORCE_ATTESTER_SUBMIT, USER_ENFORCE_ATTESTER_SUBMIT),
TestConstants.DEFAULT_STRONG_PASSWORD
).toPgpKeyRingDetails()
).pgpKeyRing.toPgpKeyRingDetails()

privateKey = requireNotNull(generatedKey.privateKey)
fileWithPrivateKey = TestGeneralUtil.createFileWithTextContent(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1766,26 +1766,31 @@ abstract class BaseGmailApiTest(val accountEntity: AccountEntity = BASE_ACCOUNT_
const val DEFAULT_CC_RECIPIENT = "Cc <default_cc@flowcrypt.test>"
const val DEFAULT_BCC_RECIPIENT = "Bcc <default_bcc@flowcrypt.test>"

val existingCcPgpKeyDetails = PGPainless.generateKeyRing().simpleEcKeyRing(
val existingCcPgpKeyDetails = PGPainless.getInstance().generateKey()
.simpleEcKeyRing(
EXISTING_MESSAGE_CC_RECIPIENT,
TestConstants.DEFAULT_PASSWORD
).toPgpKeyRingDetails()
val defaultFromPgpKeyDetails = PGPainless.generateKeyRing().simpleEcKeyRing(
).pgpKeyRing.toPgpKeyRingDetails()
val defaultFromPgpKeyDetails = PGPainless.getInstance().generateKey()
.simpleEcKeyRing(
DEFAULT_FROM_RECIPIENT,
TestConstants.DEFAULT_PASSWORD
).toPgpKeyRingDetails()
val defaultToPgpKeyDetails = PGPainless.generateKeyRing().simpleEcKeyRing(
).pgpKeyRing.toPgpKeyRingDetails()
val defaultToPgpKeyDetails = PGPainless.getInstance().generateKey()
.simpleEcKeyRing(
DEFAULT_TO_RECIPIENT,
TestConstants.DEFAULT_PASSWORD
).toPgpKeyRingDetails()
val defaultCcPgpKeyDetails = PGPainless.generateKeyRing().simpleEcKeyRing(
).pgpKeyRing.toPgpKeyRingDetails()
val defaultCcPgpKeyDetails = PGPainless.getInstance().generateKey()
.simpleEcKeyRing(
DEFAULT_CC_RECIPIENT,
TestConstants.DEFAULT_PASSWORD
).toPgpKeyRingDetails()
val defaultBccPgpKeyDetails = PGPainless.generateKeyRing().simpleEcKeyRing(
).pgpKeyRing.toPgpKeyRingDetails()
val defaultBccPgpKeyDetails = PGPainless.getInstance().generateKey()
.simpleEcKeyRing(
DEFAULT_BCC_RECIPIENT,
TestConstants.DEFAULT_PASSWORD
).toPgpKeyRingDetails()
).pgpKeyRing.toPgpKeyRingDetails()

val secretKeyRingProtector = SecretKeyRingProtector.unlockAnyKeyWith(
Passphrase.fromPassword(TestConstants.DEFAULT_PASSWORD)
Expand Down Expand Up @@ -1836,4 +1841,4 @@ abstract class BaseGmailApiTest(val accountEntity: AccountEntity = BASE_ACCOUNT_
), useAPI = true, useCustomerFesUrl = true
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ abstract class BasePublicKeyDetailsTest : BaseTest(), AddAccountToDatabaseRuleIn
)

val bitStrength =
if (keyRingInfo.publicKey.bitStrength != -1) keyRingInfo.publicKey.bitStrength else null
if (keyRingInfo.primaryKey.pgpPublicKey.bitStrength != -1) keyRingInfo.primaryKey.pgpPublicKey.bitStrength else null
val algoWithBits = keyRingInfo.algorithm.name + (bitStrength?.let { "/$it" } ?: "")

onView(withId(R.id.textViewPrimaryKeyAlgorithm))
Expand Down Expand Up @@ -251,4 +251,4 @@ abstract class BasePublicKeyDetailsTest : BaseTest(), AddAccountToDatabaseRuleIn
)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
/*
* © 2016-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com
* Contributors: DenBond7
* Contributors: denbond7
*/

package com.flowcrypt.email.ui.fragment.isolation.incontainer

import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.*
import androidx.test.espresso.matcher.ViewMatchers.Visibility
import androidx.test.espresso.matcher.ViewMatchers.hasSibling
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withChild
import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import com.flowcrypt.email.R
Expand Down Expand Up @@ -219,9 +225,9 @@ class PrivateKeysListFragmentInIsolationTest : BaseTest() {
),
statusLabelText = getResString(R.string.valid),
statusLabelIconResId = R.drawable.ic_baseline_gpp_good_16,
statusLabelTintColorResId = R.color.colorAccent,
statusLabelTintColorResId = R.color.gray,
usableForEncryption = false,
usableForSigning = true
usableForSigning = false
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import org.acra.data.StringFormat
import org.acra.ktx.initAcra
import org.acra.sender.HttpSender
import org.pgpainless.PGPainless
import org.pgpainless.policy.Policy
import org.pgpainless.policy.Policy.HashAlgorithmPolicy
import java.util.Calendar
import java.util.concurrent.TimeUnit
Expand Down Expand Up @@ -84,10 +85,12 @@ class FlowCryptApplication : Application(), Configuration.Provider {
}

private fun setupPGPainless() {
enableDeprecatedSHA1ForPGPainlessPolicy()

//https://github.com/FlowCrypt/flowcrypt-android/issues/2111
PGPainless.getPolicy().enableKeyParameterValidation = true
PGPainless.setInstance(
PGPainless(algorithmPolicy = generatePGPainlessPolicy().apply {
//https://github.com/FlowCrypt/flowcrypt-android/issues/2111
PGPainless.getInstance().algorithmPolicy.enableKeyParameterValidation = true
})
)
}

private fun setupGlobalSettingsForJavaMail() {
Expand All @@ -106,21 +109,20 @@ class FlowCryptApplication : Application(), Configuration.Provider {
* More details here https://github.com/FlowCrypt/flowcrypt-android/issues/1478 and here
* https://github.com/pgpainless/pgpainless/issues/158
*/
private fun enableDeprecatedSHA1ForPGPainlessPolicy() {
@Suppress("KotlinConstantConditions")
if (BuildConfig.FLAVOR == Constants.FLAVOR_NAME_ENTERPRISE) {
PGPainless.getPolicy().dataSignatureHashAlgorithmPolicy =
HashAlgorithmPolicy.static2022SignatureHashAlgorithmPolicy()

PGPainless.getPolicy().certificationSignatureHashAlgorithmPolicy =
HashAlgorithmPolicy.static2022SignatureHashAlgorithmPolicy()
} else {
PGPainless.getPolicy().dataSignatureHashAlgorithmPolicy =
HashAlgorithmPolicy.static2022RevocationSignatureHashAlgorithmPolicy()

PGPainless.getPolicy().certificationSignatureHashAlgorithmPolicy =
HashAlgorithmPolicy.static2022RevocationSignatureHashAlgorithmPolicy()
}
@Suppress("KotlinConstantConditions")
private fun generatePGPainlessPolicy(): Policy {
val isEnterpriseBuild = BuildConfig.FLAVOR == Constants.FLAVOR_NAME_ENTERPRISE
val strongPolicySince2022 = HashAlgorithmPolicy.static2022SignatureHashAlgorithmPolicy()
val policyBefore2022Standard =
HashAlgorithmPolicy.static2022RevocationSignatureHashAlgorithmPolicy()
return Policy.Builder(PGPainless.getInstance().algorithmPolicy)
.withDataSignatureHashAlgorithmPolicy(
if (isEnterpriseBuild) strongPolicySince2022 else policyBefore2022Standard
)
.withCertificationSignatureHashAlgorithmPolicy(
if (isEnterpriseBuild) strongPolicySince2022 else policyBefore2022Standard
)
.build()
}

private fun setupKeysStorage() {
Expand All @@ -134,6 +136,7 @@ class FlowCryptApplication : Application(), Configuration.Provider {
}
}

@Suppress("KotlinConstantConditions")
private fun initACRA() {
if (GeneralUtil.isDebugBuild()) {
val isAcraEnabled = SharedPreferencesHelper.getBoolean(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* © 2016-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com
* Contributors: denbond7
*/

package com.flowcrypt.email.extensions.org.bouncycastle.openpgp

import com.flowcrypt.email.security.SecurityUtils
import org.bouncycastle.openpgp.api.OpenPGPCertificate
import org.pgpainless.bouncycastle.extensions.encode
import java.io.IOException

/**
* @author Denys Bondarenko
*/
@Throws(IOException::class)
fun OpenPGPCertificate.armor(hideArmorMeta: Boolean = false): String =
SecurityUtils.armor(hideArmorMeta) { this.encode(it) }
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ import java.time.Instant
@WorkerThread
fun PGPKeyRing.toPgpKeyRingDetails(hideArmorMeta: Boolean = false): PgpKeyRingDetails {
if (containsHashAlgorithmWithSHA1()) {
val sigHashAlgoPolicy = PGPainless.getPolicy().certificationSignatureHashAlgorithmPolicy
val sigHashAlgoPolicy =
PGPainless.getInstance().algorithmPolicy.certificationSignatureHashAlgorithmPolicy
if (!sigHashAlgoPolicy.isAcceptable(HashAlgorithm.SHA1)) {
throw PGPException("Unsupported signature(HashAlgorithm = SHA1)")
}
Expand All @@ -50,7 +51,11 @@ fun PGPKeyRing.toPgpKeyRingDetails(hideArmorMeta: Boolean = false): PgpKeyRingDe
val algo = Algo(
algorithm = keyRingInfo.algorithm.name,
algorithmId = keyRingInfo.algorithm.algorithmId,
bits = if (keyRingInfo.publicKey.bitStrength != -1) keyRingInfo.publicKey.bitStrength else 0,
bits = if (keyRingInfo.primaryKey.pgpPublicKey.bitStrength != -1) {
keyRingInfo.primaryKey.pgpPublicKey.bitStrength
} else {
0
},
curve = runCatching { publicKey.getCurveName() }.getOrNull()
)

Expand Down Expand Up @@ -85,11 +90,13 @@ fun PGPKeyRing.toPgpKeyRingDetails(hideArmorMeta: Boolean = false): PgpKeyRingDe
lastModified = keyRingInfo.lastModified.time,
expiration = keyRingInfo.primaryKeyExpirationDate?.time,
algo = algo,
primaryKeyId = keyRingInfo.keyId,
primaryKeyId = keyRingInfo.keyIdentifier.keyId,
possibilities = mutableSetOf<Int>().apply {
addAll(
keyRingInfo.publicKeys.flatMap { keyRingInfo.getKeyFlagsOf(it.keyID) }.toSet()
.map { it.flag })
keyRingInfo.publicKeys.flatMap { openPGPComponentKey ->
keyRingInfo.getKeyFlagsOf(openPGPComponentKey.keyIdentifier)
}.toSet().map { it.flag }
)
}
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* © 2016-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com
* Contributors: DenBond7
* Contributors: denbond7
*/

package com.flowcrypt.email.extensions.org.pgpainless.key.info
Expand All @@ -12,6 +12,7 @@ import android.graphics.drawable.LayerDrawable
import android.view.Gravity
import androidx.core.content.ContextCompat
import com.flowcrypt.email.R
import org.bouncycastle.bcpg.KeyIdentifier
import org.bouncycastle.openpgp.PGPPublicKey
import org.pgpainless.algorithm.KeyFlag
import org.pgpainless.key.info.KeyRingInfo
Expand All @@ -21,15 +22,15 @@ import org.pgpainless.key.info.KeyRingInfo
*/
val KeyRingInfo.usableForEncryption: Boolean
get() {
return !publicKey.hasRevocation()
return !primaryKey.pgpPublicKey.hasRevocation()
&& !isExpired
&& isUsableForEncryption
&& primaryUserId?.isNotEmpty() == true
}

val KeyRingInfo.usableForSigning: Boolean
get() {
return !publicKey.hasRevocation()
return !primaryKey.pgpPublicKey.hasRevocation()
&& !isExpired
&& isSigningCapable
&& primaryUserId?.isNotEmpty() == true
Expand All @@ -52,20 +53,20 @@ val KeyRingInfo.isPartiallyEncrypted: Boolean

val KeyRingInfo.isRevoked: Boolean
get() {
return publicKey.hasRevocation()
return primaryKey.pgpPublicKey.hasRevocation()
}

fun KeyRingInfo.getPrimaryKey(): PGPPublicKey? {
return publicKeys.firstOrNull { it.isMasterKey }
return primaryKey.pgpPublicKey
}

fun KeyRingInfo.getPubKeysWithoutPrimary(): Collection<PGPPublicKey> {
val primaryKey = getPrimaryKey() ?: return publicKeys
return publicKeys - setOf(primaryKey)
val primaryKey = getPrimaryKey() ?: return publicKeys.map { it.pgpPublicKey }
return publicKeys.map { it.pgpPublicKey } - setOf(primaryKey)
}

fun KeyRingInfo.generateKeyCapabilitiesDrawable(context: Context, keyId: Long): Drawable? {
val keyFlags = getKeyFlagsOf(keyId)
val keyFlags = getKeyFlagsOf(KeyIdentifier(keyId))
val drawables = listOf(
KeyFlag.CERTIFY_OTHER to R.drawable.ic_possibility_cert,
KeyFlag.ENCRYPT_COMMS to R.drawable.ic_possibility_encryption,
Expand Down Expand Up @@ -117,7 +118,7 @@ fun KeyRingInfo.getStatusIcon(): Int {

fun KeyRingInfo.getStatusText(context: Context): String {
return when {
publicKey.hasRevocation() -> context.getString(R.string.revoked)
primaryKey.pgpPublicKey.hasRevocation() -> context.getString(R.string.revoked)
isExpired -> context.getString(R.string.expired)
isPartiallyEncrypted -> context.getString(R.string.not_valid)
else -> context.getString(R.string.valid)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ class EditPrivateKeyViewModel(val fingerprint: String, application: Application)
roomDatabase.keysDao().getKeyByAccountAndFingerprint(account.email, fingerprint)
?: throw IllegalStateException("Private key with fingerprint = $fingerprint not found")

val pgpKeyRingDetails = modifiedPgpSecretKeyRing.toPgpKeyRingDetails()
val pgpKeyRingDetails = modifiedPgpSecretKeyRing.pgpSecretKeyRing.toPgpKeyRingDetails()
val encryptedPrvKey =
KeyStoreCryptoManager.encryptSuspend(pgpKeyRingDetails.privateKey).toByteArray()
roomDatabase.keysDao().updateSuspend(entity.copy(privateKey = encryptedPrvKey))
Expand Down Expand Up @@ -88,4 +88,4 @@ class EditPrivateKeyViewModel(val fingerprint: String, application: Application)
return@withContext Result.exception(e)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import com.flowcrypt.email.security.pgp.PgpDecryptAndOrVerify
import com.flowcrypt.email.security.pgp.PgpKey
import com.flowcrypt.email.util.exception.DecryptionException
import kotlinx.coroutines.flow.Flow
import org.bouncycastle.bcpg.KeyIdentifier
import org.bouncycastle.openpgp.PGPException
import org.bouncycastle.openpgp.PGPSecretKeyRing
import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator
Expand Down Expand Up @@ -161,12 +162,20 @@ class KeysStorageImpl private constructor(context: Context) : KeysStorage {
override fun getSecretKeyRingProtector(): SecretKeyRingProtector {
val availablePGPSecretKeyRings = getPGPSecretKeyRings()
val passphraseProvider = object : SecretKeyPassphraseProvider {
override fun getPassphraseFor(keyId: Long?): Passphrase? {
return keyId?.let { doGetPassphrase(keyId, true) }
override fun getPassphraseFor(keyId: Long): Passphrase? {
return doGetPassphrase(keyId, true)
}

override fun hasPassphrase(keyId: Long?): Boolean {
return keyId != null && doGetPassphrase(keyId, false) != null
override fun getPassphraseFor(keyIdentifier: KeyIdentifier): Passphrase? {
return getPassphraseFor(keyIdentifier.keyId)
}

override fun hasPassphrase(keyId: Long): Boolean {
return doGetPassphrase(keyId, false) != null
}

override fun hasPassphrase(keyIdentifier: KeyIdentifier): Boolean {
return hasPassphrase(keyIdentifier.keyId)
}

private fun doGetPassphrase(keyId: Long, throwException: Boolean): Passphrase? {
Expand Down
Loading