Fix PGPainless backend key handling (#2000)

This commit is contained in:
Harsh Shandilya 2022-07-14 00:42:23 +05:30 committed by GitHub
parent b7e291450b
commit d23b0c5d6f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 36 additions and 12 deletions

View File

@ -18,4 +18,5 @@ dependencies {
implementation(libs.thirdparty.pgpainless) implementation(libs.thirdparty.pgpainless)
testImplementation(libs.bundles.testDependencies) testImplementation(libs.bundles.testDependencies)
testImplementation(libs.kotlin.coroutines.test) testImplementation(libs.kotlin.coroutines.test)
testImplementation(libs.testing.testparameterinjector)
} }

View File

@ -16,6 +16,8 @@ import java.io.ByteArrayInputStream
import java.io.InputStream import java.io.InputStream
import java.io.OutputStream import java.io.OutputStream
import javax.inject.Inject import javax.inject.Inject
import org.bouncycastle.openpgp.PGPPublicKeyRing
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection import org.bouncycastle.openpgp.PGPSecretKeyRingCollection
import org.pgpainless.PGPainless import org.pgpainless.PGPainless
import org.pgpainless.decryption_verification.ConsumerOptions import org.pgpainless.decryption_verification.ConsumerOptions
@ -65,14 +67,25 @@ public class PGPainlessCryptoHandler @Inject constructor() : CryptoHandler<PGPKe
): Result<Unit, CryptoHandlerException> = ): Result<Unit, CryptoHandlerException> =
runCatching { runCatching {
if (keys.isEmpty()) throw NoKeysProvided("No keys provided for encryption") if (keys.isEmpty()) throw NoKeysProvided("No keys provided for encryption")
val armoredKeys = keys.map { key -> key.contents.decodeToString() } val publicKeyRings = arrayListOf<PGPPublicKeyRing>()
val pubKeysStream = ByteArrayInputStream(armoredKeys.joinToString("\n").toByteArray()) val armoredKeys =
keys.joinToString("\n") { key -> key.contents.decodeToString() }.toByteArray()
val secKeysStream = ByteArrayInputStream(armoredKeys)
val secretKeyRingCollection =
PGPainless.readKeyRing().secretKeyRingCollection(secKeysStream)
secretKeyRingCollection.forEach { secretKeyRing ->
publicKeyRings.add(PGPainless.extractCertificate(secretKeyRing))
}
if (publicKeyRings.isEmpty()) {
val pubKeysStream = ByteArrayInputStream(armoredKeys)
val publicKeyRingCollection = val publicKeyRingCollection =
pubKeysStream.use { PGPainless.readKeyRing().publicKeyRingCollection(pubKeysStream) } PGPainless.readKeyRing().publicKeyRingCollection(pubKeysStream)
val encryptionOptions = publicKeyRings.addAll(publicKeyRingCollection)
EncryptionOptions.encryptCommunications() }
.addRecipients(publicKeyRingCollection.asIterable()) require(publicKeyRings.isNotEmpty()) { "No public keys to encrypt message to" }
val producerOptions = ProducerOptions.encrypt(encryptionOptions).setAsciiArmor(true) val publicKeyRingCollection = PGPPublicKeyRingCollection(publicKeyRings)
val encryptionOptions = EncryptionOptions().addRecipients(publicKeyRingCollection)
val producerOptions = ProducerOptions.encrypt(encryptionOptions).setAsciiArmor(false)
val encryptor = val encryptor =
PGPainless.encryptAndOrSign().onOutputStream(outputStream).withOptions(producerOptions) PGPainless.encryptAndOrSign().onOutputStream(outputStream).withOptions(producerOptions)
plaintextStream.copyTo(encryptor) plaintextStream.copyTo(encryptor)
@ -83,7 +96,6 @@ public class PGPainlessCryptoHandler @Inject constructor() : CryptoHandler<PGPKe
"Stream should be encrypted for ${keyRing.publicKey.keyID} but wasn't" "Stream should be encrypted for ${keyRing.publicKey.keyID} but wasn't"
} }
} }
return@runCatching
} }
.mapError { error -> .mapError { error ->
when (error) { when (error) {

View File

@ -7,6 +7,8 @@ package dev.msfjarvis.aps.crypto
import com.github.michaelbull.result.Err import com.github.michaelbull.result.Err
import com.github.michaelbull.result.getError import com.github.michaelbull.result.getError
import com.google.testing.junit.testparameterinjector.TestParameter
import com.google.testing.junit.testparameterinjector.TestParameterInjector
import dev.msfjarvis.aps.crypto.errors.IncorrectPassphraseException import dev.msfjarvis.aps.crypto.errors.IncorrectPassphraseException
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import kotlin.test.Test import kotlin.test.Test
@ -14,18 +16,26 @@ import kotlin.test.assertEquals
import kotlin.test.assertFalse import kotlin.test.assertFalse
import kotlin.test.assertIs import kotlin.test.assertIs
import kotlin.test.assertTrue import kotlin.test.assertTrue
import org.junit.runner.RunWith
@Suppress("Unused") // Test runner handles it internally
enum class EncryptionKey(val key: PGPKey) {
PUBLIC(PGPKey(TestUtils.getArmoredPublicKey())),
SECRET(PGPKey(TestUtils.getArmoredPrivateKey())),
}
@RunWith(TestParameterInjector::class)
class PGPainlessCryptoHandlerTest { class PGPainlessCryptoHandlerTest {
@TestParameter private lateinit var encryptionKey: EncryptionKey
private val cryptoHandler = PGPainlessCryptoHandler() private val cryptoHandler = PGPainlessCryptoHandler()
private val privateKey = PGPKey(TestUtils.getArmoredPrivateKey()) private val privateKey = PGPKey(TestUtils.getArmoredPrivateKey())
private val publicKey = PGPKey(TestUtils.getArmoredPublicKey())
@Test @Test
fun encryptAndDecrypt() { fun encryptAndDecrypt() {
val ciphertextStream = ByteArrayOutputStream() val ciphertextStream = ByteArrayOutputStream()
cryptoHandler.encrypt( cryptoHandler.encrypt(
listOf(publicKey), listOf(encryptionKey.key),
CryptoConstants.PLAIN_TEXT.byteInputStream(Charsets.UTF_8), CryptoConstants.PLAIN_TEXT.byteInputStream(Charsets.UTF_8),
ciphertextStream, ciphertextStream,
) )
@ -43,7 +53,7 @@ class PGPainlessCryptoHandlerTest {
fun decryptWithWrongPassphrase() { fun decryptWithWrongPassphrase() {
val ciphertextStream = ByteArrayOutputStream() val ciphertextStream = ByteArrayOutputStream()
cryptoHandler.encrypt( cryptoHandler.encrypt(
listOf(publicKey), listOf(encryptionKey.key),
CryptoConstants.PLAIN_TEXT.byteInputStream(Charsets.UTF_8), CryptoConstants.PLAIN_TEXT.byteInputStream(Charsets.UTF_8),
ciphertextStream, ciphertextStream,
) )

View File

@ -70,6 +70,7 @@ testing-junit = "junit:junit:4.13.2"
testing-kotlintest-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" } testing-kotlintest-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" }
testing-robolectric = "org.robolectric:robolectric:4.8.1" testing-robolectric = "org.robolectric:robolectric:4.8.1"
testing-sharedPrefsMock = "com.github.android-password-store:shared-preferences-fake:2.0.0" testing-sharedPrefsMock = "com.github.android-password-store:shared-preferences-fake:2.0.0"
testing-testparameterinjector = "com.google.testparameterinjector:test-parameter-injector:1.8"
testing-turbine = "app.cash.turbine:turbine:0.8.0" testing-turbine = "app.cash.turbine:turbine:0.8.0"
thirdparty-bouncycastle-bcpkix = { module = "org.bouncycastle:bcpkix-jdk15to18", version.ref = "bouncycastle" } thirdparty-bouncycastle-bcpkix = { module = "org.bouncycastle:bcpkix-jdk15to18", version.ref = "bouncycastle" }
thirdparty-bouncycastle-bcprov = { module = "org.bouncycastle:bcprov-jdk15to18", version.ref = "bouncycastle" } thirdparty-bouncycastle-bcprov = { module = "org.bouncycastle:bcprov-jdk15to18", version.ref = "bouncycastle" }