Reimplement PGPainless encryption logic (#1955)

* crypto-pgpainless: reimplement encryption logic

* crypto-pgpainless: add an explicit error type for empty keyset
This commit is contained in:
Harsh Shandilya 2022-06-14 13:00:54 +05:30 committed by GitHub
parent 2ba3a08622
commit d65fc88a14
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 24 additions and 12 deletions

View file

@ -37,5 +37,8 @@ public sealed class CryptoHandlerException(message: String? = null, cause: Throw
/** The passphrase provided for decryption was incorrect. */ /** The passphrase provided for decryption was incorrect. */
public class IncorrectPassphraseException(cause: Throwable) : CryptoHandlerException(null, cause) public class IncorrectPassphraseException(cause: Throwable) : CryptoHandlerException(null, cause)
/** No keys were provided for encryption. */
public class NoKeysProvided(message: String?) : CryptoHandlerException(message, null)
/** An unexpected error that cannot be mapped to a known type. */ /** An unexpected error that cannot be mapped to a known type. */
public class UnknownError(cause: Throwable) : CryptoHandlerException(null, cause) public class UnknownError(cause: Throwable) : CryptoHandlerException(null, cause)

View file

@ -10,12 +10,12 @@ import com.github.michaelbull.result.mapError
import com.github.michaelbull.result.runCatching import com.github.michaelbull.result.runCatching
import dev.msfjarvis.aps.crypto.errors.CryptoHandlerException import dev.msfjarvis.aps.crypto.errors.CryptoHandlerException
import dev.msfjarvis.aps.crypto.errors.IncorrectPassphraseException import dev.msfjarvis.aps.crypto.errors.IncorrectPassphraseException
import dev.msfjarvis.aps.crypto.errors.NoKeysProvided
import dev.msfjarvis.aps.crypto.errors.UnknownError import dev.msfjarvis.aps.crypto.errors.UnknownError
import java.io.ByteArrayInputStream 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.bcpg.ArmoredInputStream
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
@ -64,24 +64,33 @@ public class PGPainlessCryptoHandler @Inject constructor() : CryptoHandler<PGPKe
outputStream: OutputStream, outputStream: OutputStream,
): Result<Unit, CryptoHandlerException> = ): Result<Unit, CryptoHandlerException> =
runCatching { runCatching {
if (keys.isEmpty()) throw NoKeysProvided("No keys provided for encryption")
val armoredKeys = keys.map { key -> key.contents.decodeToString() } val armoredKeys = keys.map { key -> key.contents.decodeToString() }
val pubKeysStream = ByteArrayInputStream(armoredKeys.joinToString("\n").toByteArray()) val pubKeysStream = ByteArrayInputStream(armoredKeys.joinToString("\n").toByteArray())
val publicKeyRingCollection = val publicKeyRingCollection =
pubKeysStream.use { pubKeysStream.use { PGPainless.readKeyRing().publicKeyRingCollection(pubKeysStream) }
ArmoredInputStream(it).use { armoredInputStream -> val encryptionOptions =
PGPainless.readKeyRing().publicKeyRingCollection(armoredInputStream) EncryptionOptions.encryptCommunications()
} .addRecipients(publicKeyRingCollection.asIterable())
val producerOptions = ProducerOptions.encrypt(encryptionOptions).setAsciiArmor(true)
val encryptor =
PGPainless.encryptAndOrSign().onOutputStream(outputStream).withOptions(producerOptions)
plaintextStream.copyTo(encryptor)
encryptor.close()
val result = encryptor.result
publicKeyRingCollection.keyRings.forEach { keyRing ->
require(result.isEncryptedFor(keyRing)) {
"Stream should be encrypted for ${keyRing.publicKey.keyID} but wasn't"
} }
val encOpt =
EncryptionOptions().apply { publicKeyRingCollection.forEach { addRecipient(it) } }
val prodOpt = ProducerOptions.encrypt(encOpt).setAsciiArmor(true)
PGPainless.encryptAndOrSign().onOutputStream(outputStream).withOptions(prodOpt).use {
encryptionStream ->
plaintextStream.copyTo(encryptionStream)
} }
return@runCatching return@runCatching
} }
.mapError { error -> UnknownError(error) } .mapError { error ->
when (error) {
is CryptoHandlerException -> error
else -> UnknownError(error)
}
}
public override fun canHandle(fileName: String): Boolean { public override fun canHandle(fileName: String): Boolean {
return fileName.split('.').lastOrNull() == "gpg" return fileName.split('.').lastOrNull() == "gpg"