Skip to content

Commit b1aab3b

Browse files
committed
Migrated code to use PGPainless 2.0.1
1 parent 175ea33 commit b1aab3b

13 files changed

Lines changed: 134 additions & 81 deletions

File tree

FlowCrypt/src/main/java/com/flowcrypt/email/FlowCryptApplication.kt

Lines changed: 27 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import org.acra.data.StringFormat
4242
import org.acra.ktx.initAcra
4343
import org.acra.sender.HttpSender
4444
import org.pgpainless.PGPainless
45+
import org.pgpainless.policy.Policy
4546
import org.pgpainless.policy.Policy.HashAlgorithmPolicy
4647
import java.util.Calendar
4748
import java.util.concurrent.TimeUnit
@@ -84,10 +85,12 @@ class FlowCryptApplication : Application(), Configuration.Provider {
8485
}
8586

8687
private fun setupPGPainless() {
87-
enableDeprecatedSHA1ForPGPainlessPolicy()
88-
89-
//https://github.com/FlowCrypt/flowcrypt-android/issues/2111
90-
PGPainless.getPolicy().enableKeyParameterValidation = true
88+
PGPainless.setInstance(
89+
PGPainless(algorithmPolicy = generatePGPainlessPolicy().apply {
90+
//https://github.com/FlowCrypt/flowcrypt-android/issues/2111
91+
PGPainless.getInstance().algorithmPolicy.enableKeyParameterValidation = true
92+
})
93+
)
9194
}
9295

9396
private fun setupGlobalSettingsForJavaMail() {
@@ -106,21 +109,25 @@ class FlowCryptApplication : Application(), Configuration.Provider {
106109
* More details here https://github.com/FlowCrypt/flowcrypt-android/issues/1478 and here
107110
* https://github.com/pgpainless/pgpainless/issues/158
108111
*/
109-
private fun enableDeprecatedSHA1ForPGPainlessPolicy() {
110-
@Suppress("KotlinConstantConditions")
111-
if (BuildConfig.FLAVOR == Constants.FLAVOR_NAME_ENTERPRISE) {
112-
PGPainless.getPolicy().dataSignatureHashAlgorithmPolicy =
113-
HashAlgorithmPolicy.static2022SignatureHashAlgorithmPolicy()
114-
115-
PGPainless.getPolicy().certificationSignatureHashAlgorithmPolicy =
116-
HashAlgorithmPolicy.static2022SignatureHashAlgorithmPolicy()
117-
} else {
118-
PGPainless.getPolicy().dataSignatureHashAlgorithmPolicy =
119-
HashAlgorithmPolicy.static2022RevocationSignatureHashAlgorithmPolicy()
120-
121-
PGPainless.getPolicy().certificationSignatureHashAlgorithmPolicy =
122-
HashAlgorithmPolicy.static2022RevocationSignatureHashAlgorithmPolicy()
123-
}
112+
@Suppress("KotlinConstantConditions")
113+
private fun generatePGPainlessPolicy(): Policy {
114+
val isEnterpriseBuild = BuildConfig.FLAVOR == Constants.FLAVOR_NAME_ENTERPRISE
115+
return Policy.Builder(PGPainless.getInstance().algorithmPolicy)
116+
.withDataSignatureHashAlgorithmPolicy(
117+
if (isEnterpriseBuild) {
118+
HashAlgorithmPolicy.static2022SignatureHashAlgorithmPolicy()
119+
} else {
120+
HashAlgorithmPolicy.static2022RevocationSignatureHashAlgorithmPolicy()
121+
}
122+
)
123+
.withCertificationSignatureHashAlgorithmPolicy(
124+
if (isEnterpriseBuild) {
125+
HashAlgorithmPolicy.static2022SignatureHashAlgorithmPolicy()
126+
} else {
127+
HashAlgorithmPolicy.static2022RevocationSignatureHashAlgorithmPolicy()
128+
}
129+
)
130+
.build()
124131
}
125132

126133
private fun setupKeysStorage() {
@@ -134,6 +141,7 @@ class FlowCryptApplication : Application(), Configuration.Provider {
134141
}
135142
}
136143

144+
@Suppress("KotlinConstantConditions")
137145
private fun initACRA() {
138146
if (GeneralUtil.isDebugBuild()) {
139147
val isAcraEnabled = SharedPreferencesHelper.getBoolean(
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/*
2+
* © 2016-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com
3+
* Contributors: denbond7
4+
*/
5+
6+
package com.flowcrypt.email.extensions.org.bouncycastle.openpgp
7+
8+
import com.flowcrypt.email.security.SecurityUtils
9+
import org.bouncycastle.openpgp.api.OpenPGPCertificate
10+
import org.pgpainless.bouncycastle.extensions.encode
11+
import java.io.IOException
12+
13+
/**
14+
* @author Denys Bondarenko
15+
*/
16+
@Throws(IOException::class)
17+
fun OpenPGPCertificate.armor(hideArmorMeta: Boolean = false): String =
18+
SecurityUtils.armor(hideArmorMeta) { this.encode(it) }

FlowCrypt/src/main/java/com/flowcrypt/email/extensions/org/bouncycastle/openpgp/PGPKeyRingExt.kt

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ import java.time.Instant
3939
@WorkerThread
4040
fun PGPKeyRing.toPgpKeyRingDetails(hideArmorMeta: Boolean = false): PgpKeyRingDetails {
4141
if (containsHashAlgorithmWithSHA1()) {
42-
val sigHashAlgoPolicy = PGPainless.getPolicy().certificationSignatureHashAlgorithmPolicy
42+
val sigHashAlgoPolicy =
43+
PGPainless.getInstance().algorithmPolicy.certificationSignatureHashAlgorithmPolicy
4344
if (!sigHashAlgoPolicy.isAcceptable(HashAlgorithm.SHA1)) {
4445
throw PGPException("Unsupported signature(HashAlgorithm = SHA1)")
4546
}
@@ -50,7 +51,11 @@ fun PGPKeyRing.toPgpKeyRingDetails(hideArmorMeta: Boolean = false): PgpKeyRingDe
5051
val algo = Algo(
5152
algorithm = keyRingInfo.algorithm.name,
5253
algorithmId = keyRingInfo.algorithm.algorithmId,
53-
bits = if (keyRingInfo.publicKey.bitStrength != -1) keyRingInfo.publicKey.bitStrength else 0,
54+
bits = if (keyRingInfo.primaryKey.pgpPublicKey.bitStrength != -1) {
55+
keyRingInfo.primaryKey.pgpPublicKey.bitStrength
56+
} else {
57+
0
58+
},
5459
curve = runCatching { publicKey.getCurveName() }.getOrNull()
5560
)
5661

@@ -85,11 +90,13 @@ fun PGPKeyRing.toPgpKeyRingDetails(hideArmorMeta: Boolean = false): PgpKeyRingDe
8590
lastModified = keyRingInfo.lastModified.time,
8691
expiration = keyRingInfo.primaryKeyExpirationDate?.time,
8792
algo = algo,
88-
primaryKeyId = keyRingInfo.keyId,
93+
primaryKeyId = keyRingInfo.keyIdentifier.keyId,
8994
possibilities = mutableSetOf<Int>().apply {
9095
addAll(
91-
keyRingInfo.publicKeys.flatMap { keyRingInfo.getKeyFlagsOf(it.keyID) }.toSet()
92-
.map { it.flag })
96+
keyRingInfo.publicKeys.flatMap { openPGPComponentKey ->
97+
keyRingInfo.getKeyFlagsOf(openPGPComponentKey.keyIdentifier)
98+
}.toSet().map { it.flag }
99+
)
93100
}
94101
)
95102
}

FlowCrypt/src/main/java/com/flowcrypt/email/extensions/org/pgpainless/key/info/KeyRingInfoExt.kt

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/*
22
* © 2016-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com
3-
* Contributors: DenBond7
3+
* Contributors: denbond7
44
*/
55

66
package com.flowcrypt.email.extensions.org.pgpainless.key.info
@@ -12,6 +12,7 @@ import android.graphics.drawable.LayerDrawable
1212
import android.view.Gravity
1313
import androidx.core.content.ContextCompat
1414
import com.flowcrypt.email.R
15+
import org.bouncycastle.bcpg.KeyIdentifier
1516
import org.bouncycastle.openpgp.PGPPublicKey
1617
import org.pgpainless.algorithm.KeyFlag
1718
import org.pgpainless.key.info.KeyRingInfo
@@ -21,15 +22,15 @@ import org.pgpainless.key.info.KeyRingInfo
2122
*/
2223
val KeyRingInfo.usableForEncryption: Boolean
2324
get() {
24-
return !publicKey.hasRevocation()
25+
return !primaryKey.pgpPublicKey.hasRevocation()
2526
&& !isExpired
2627
&& isUsableForEncryption
2728
&& primaryUserId?.isNotEmpty() == true
2829
}
2930

3031
val KeyRingInfo.usableForSigning: Boolean
3132
get() {
32-
return !publicKey.hasRevocation()
33+
return !primaryKey.pgpPublicKey.hasRevocation()
3334
&& !isExpired
3435
&& isSigningCapable
3536
&& primaryUserId?.isNotEmpty() == true
@@ -52,20 +53,20 @@ val KeyRingInfo.isPartiallyEncrypted: Boolean
5253

5354
val KeyRingInfo.isRevoked: Boolean
5455
get() {
55-
return publicKey.hasRevocation()
56+
return primaryKey.pgpPublicKey.hasRevocation()
5657
}
5758

5859
fun KeyRingInfo.getPrimaryKey(): PGPPublicKey? {
59-
return publicKeys.firstOrNull { it.isMasterKey }
60+
return primaryKey.pgpPublicKey
6061
}
6162

6263
fun KeyRingInfo.getPubKeysWithoutPrimary(): Collection<PGPPublicKey> {
63-
val primaryKey = getPrimaryKey() ?: return publicKeys
64-
return publicKeys - setOf(primaryKey)
64+
val primaryKey = getPrimaryKey() ?: return publicKeys.map { it.pgpPublicKey }
65+
return publicKeys.map { it.pgpPublicKey } - setOf(primaryKey)
6566
}
6667

6768
fun KeyRingInfo.generateKeyCapabilitiesDrawable(context: Context, keyId: Long): Drawable? {
68-
val keyFlags = getKeyFlagsOf(keyId)
69+
val keyFlags = getKeyFlagsOf(KeyIdentifier(keyId))
6970
val drawables = listOf(
7071
KeyFlag.CERTIFY_OTHER to R.drawable.ic_possibility_cert,
7172
KeyFlag.ENCRYPT_COMMS to R.drawable.ic_possibility_encryption,
@@ -117,7 +118,7 @@ fun KeyRingInfo.getStatusIcon(): Int {
117118

118119
fun KeyRingInfo.getStatusText(context: Context): String {
119120
return when {
120-
publicKey.hasRevocation() -> context.getString(R.string.revoked)
121+
primaryKey.pgpPublicKey.hasRevocation() -> context.getString(R.string.revoked)
121122
isExpired -> context.getString(R.string.expired)
122123
isPartiallyEncrypted -> context.getString(R.string.not_valid)
123124
else -> context.getString(R.string.valid)

FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/EditPrivateKeyViewModel.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ class EditPrivateKeyViewModel(val fingerprint: String, application: Application)
6060
roomDatabase.keysDao().getKeyByAccountAndFingerprint(account.email, fingerprint)
6161
?: throw IllegalStateException("Private key with fingerprint = $fingerprint not found")
6262

63-
val pgpKeyRingDetails = modifiedPgpSecretKeyRing.toPgpKeyRingDetails()
63+
val pgpKeyRingDetails = modifiedPgpSecretKeyRing.pgpSecretKeyRing.toPgpKeyRingDetails()
6464
val encryptedPrvKey =
6565
KeyStoreCryptoManager.encryptSuspend(pgpKeyRingDetails.privateKey).toByteArray()
6666
roomDatabase.keysDao().updateSuspend(entity.copy(privateKey = encryptedPrvKey))
@@ -88,4 +88,4 @@ class EditPrivateKeyViewModel(val fingerprint: String, application: Application)
8888
return@withContext Result.exception(e)
8989
}
9090
}
91-
}
91+
}

FlowCrypt/src/main/java/com/flowcrypt/email/security/KeysStorageImpl.kt

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import com.flowcrypt.email.security.pgp.PgpDecryptAndOrVerify
2323
import com.flowcrypt.email.security.pgp.PgpKey
2424
import com.flowcrypt.email.util.exception.DecryptionException
2525
import kotlinx.coroutines.flow.Flow
26+
import org.bouncycastle.bcpg.KeyIdentifier
2627
import org.bouncycastle.openpgp.PGPException
2728
import org.bouncycastle.openpgp.PGPSecretKeyRing
2829
import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator
@@ -161,12 +162,20 @@ class KeysStorageImpl private constructor(context: Context) : KeysStorage {
161162
override fun getSecretKeyRingProtector(): SecretKeyRingProtector {
162163
val availablePGPSecretKeyRings = getPGPSecretKeyRings()
163164
val passphraseProvider = object : SecretKeyPassphraseProvider {
164-
override fun getPassphraseFor(keyId: Long?): Passphrase? {
165-
return keyId?.let { doGetPassphrase(keyId, true) }
165+
override fun getPassphraseFor(keyId: Long): Passphrase? {
166+
return doGetPassphrase(keyId, true)
166167
}
167168

168-
override fun hasPassphrase(keyId: Long?): Boolean {
169-
return keyId != null && doGetPassphrase(keyId, false) != null
169+
override fun getPassphraseFor(keyIdentifier: KeyIdentifier): Passphrase? {
170+
return getPassphraseFor(keyIdentifier.keyId)
171+
}
172+
173+
override fun hasPassphrase(keyId: Long): Boolean {
174+
return doGetPassphrase(keyId, false) != null
175+
}
176+
177+
override fun hasPassphrase(keyIdentifier: KeyIdentifier): Boolean {
178+
return hasPassphrase(keyIdentifier.keyId)
170179
}
171180

172181
private fun doGetPassphrase(keyId: Long, throwException: Boolean): Passphrase? {

FlowCrypt/src/main/java/com/flowcrypt/email/security/SecurityUtils.kt

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
/*
22
* © 2016-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com
3-
* Contributors:
4-
* DenBond7
5-
* Ivan Pizhenko
3+
* Contributors: denbond7
64
*/
75

86
package com.flowcrypt.email.security
@@ -153,7 +151,12 @@ class SecurityUtils {
153151
if (matchingKeyRingInfoList.isEmpty()) {
154152
throw NoKeyAvailableException(context = context, email = senderEmail)
155153
}
156-
return matchingKeyRingInfoList.map { PGPPublicKeyRing(it.publicKeys).armor() }
154+
155+
return matchingKeyRingInfoList.map { keyRingInfo ->
156+
PGPPublicKeyRing(keyRingInfo.publicKeys.map { openPGPComponentKey ->
157+
openPGPComponentKey.pgpPublicKey
158+
}).armor()
159+
}
157160
}
158161

159162
/**

FlowCrypt/src/main/java/com/flowcrypt/email/security/pgp/PgpDecryptAndOrVerify.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/*
22
* © 2016-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com
3-
* Contributors: DenBond7
3+
* Contributors: denbond7
44
*/
55

66
package com.flowcrypt.email.security.pgp
@@ -107,14 +107,14 @@ object PgpDecryptAndOrVerify {
107107
return PGPainless.decryptAndOrVerify()
108108
.onInputStream(srcInputStream)
109109
.withOptions(
110-
ConsumerOptions()
110+
ConsumerOptions.get()
111111
.setMissingKeyPassphraseStrategy(MissingKeyPassphraseStrategy.THROW_EXCEPTION)
112112
.setIgnoreMDCErrors(ignoreMdcErrors)
113113
.apply {
114114
if (secretKeys != null && protector != null) {
115115
addDecryptionKeys(secretKeys, protector)
116116
}
117-
passphrase?.let { addDecryptionPassphrase(it) }
117+
passphrase?.let { addMessagePassphrase(it) }
118118
publicKeys?.let { addVerificationCerts(it) }
119119
}
120120
)

FlowCrypt/src/main/java/com/flowcrypt/email/security/pgp/PgpEncryptAndOrSign.kt

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import org.bouncycastle.openpgp.PGPPublicKeyRingCollection
1111
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection
1212
import org.pgpainless.PGPainless
1313
import org.pgpainless.algorithm.DocumentSignatureType
14+
import org.pgpainless.bouncycastle.extensions.toOpenPGPCertificate
1415
import org.pgpainless.encryption_signing.EncryptionOptions
1516
import org.pgpainless.encryption_signing.EncryptionResult
1617
import org.pgpainless.encryption_signing.EncryptionStream
@@ -75,7 +76,10 @@ object PgpEncryptAndOrSign {
7576
val prvKeysStream = ByteArrayInputStream(prvKeys.joinToString(separator = "\n").toByteArray())
7677
pgpSecretKeyRingCollection = prvKeysStream.use {
7778
ArmoredInputStream(it).use { armoredInputStream ->
78-
PGPainless.readKeyRing().secretKeyRingCollection(armoredInputStream)
79+
PGPSecretKeyRingCollection(
80+
PGPainless.getInstance().readKey().parseKeys(armoredInputStream)
81+
.map { openPGPKey -> openPGPKey.pgpSecretKeyRing }
82+
)
7983
}
8084
}
8185
}
@@ -171,33 +175,34 @@ object PgpEncryptAndOrSign {
171175
fileName: String? = null,
172176
generateDetachedSignatures: Boolean = false,
173177
): EncryptionStream {
174-
val encOpt = EncryptionOptions().apply {
178+
val api = PGPainless.getInstance()
179+
val encOpt = EncryptionOptions.get().apply {
175180
passphrase?.let { addMessagePassphrase(passphrase) }
176181
pgpPublicKeyRingCollection?.forEach {
177-
addRecipient(it)
182+
addRecipient(it.toOpenPGPCertificate(api.implementation))
178183
}
179184

180185
protectedPgpPublicKeyRingCollection?.forEach {
181-
addHiddenRecipient(it)
186+
addHiddenRecipient(it.toOpenPGPCertificate(api.implementation))
182187
}
183188
}
184189

185190
val producerOptions: ProducerOptions =
186191
if (passphrase == null && pgpSecretKeyRingCollection?.any() == true) {
187-
ProducerOptions.signAndEncrypt(encOpt, SigningOptions().apply {
192+
ProducerOptions.signAndEncrypt(encOpt, SigningOptions.get().apply {
188193
pgpSecretKeyRingCollection.forEach { pgpSecretKeyRing ->
189194
secretKeyRingProtector?.let { protector ->
190195
if (generateDetachedSignatures) {
191196
addDetachedSignature(
192-
protector,
193-
pgpSecretKeyRing,
194-
DocumentSignatureType.BINARY_DOCUMENT
197+
signingKeyProtector = protector,
198+
signingKey = api.toKey(pgpSecretKeyRing),
199+
signatureType = DocumentSignatureType.BINARY_DOCUMENT
195200
)
196201
} else {
197202
addInlineSignature(
198-
protector,
199-
pgpSecretKeyRing,
200-
DocumentSignatureType.BINARY_DOCUMENT
203+
signingKeyProtector = protector,
204+
signingKey = api.toKey(pgpSecretKeyRing),
205+
signatureType = DocumentSignatureType.BINARY_DOCUMENT
201206
)
202207
}
203208
}
@@ -212,7 +217,7 @@ object PgpEncryptAndOrSign {
212217

213218
fileName?.let { producerOptions.setFileName(it) }
214219

215-
return PGPainless.encryptAndOrSign()
220+
return api.generateMessage()
216221
.onOutputStream(destOutputStream)
217222
.withOptions(producerOptions)
218223
}

0 commit comments

Comments
 (0)