pgpainless/pgpainless-sop/src/main/kotlin/org/pgpainless/sop/DecryptImpl.kt

126 lines
4.8 KiB
Kotlin

// SPDX-FileCopyrightText: 2024 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.sop
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
import java.util.*
import org.bouncycastle.openpgp.PGPException
import org.bouncycastle.util.io.Streams
import org.pgpainless.PGPainless
import org.pgpainless.algorithm.SymmetricKeyAlgorithm
import org.pgpainless.decryption_verification.ConsumerOptions
import org.pgpainless.exception.MalformedOpenPgpMessageException
import org.pgpainless.exception.MissingDecryptionMethodException
import org.pgpainless.exception.WrongPassphraseException
import org.pgpainless.util.Passphrase
import sop.DecryptionResult
import sop.ReadyWithResult
import sop.SessionKey
import sop.exception.SOPGPException
import sop.operation.Decrypt
import sop.util.UTF8Util
/** Implementation of the `decrypt` operation using PGPainless. */
class DecryptImpl : Decrypt {
private val consumerOptions = ConsumerOptions.get()
private val protector = MatchMakingSecretKeyRingProtector()
override fun ciphertext(ciphertext: InputStream): ReadyWithResult<DecryptionResult> {
if (consumerOptions.getDecryptionKeys().isEmpty() &&
consumerOptions.getDecryptionPassphrases().isEmpty() &&
consumerOptions.getSessionKey() == null) {
throw SOPGPException.MissingArg("Missing decryption key, passphrase or session key.")
}
val decryptionStream =
try {
PGPainless.decryptAndOrVerify()
.onInputStream(ciphertext)
.withOptions(consumerOptions)
} catch (e: MissingDecryptionMethodException) {
throw SOPGPException.CannotDecrypt(
"No usable decryption key or password provided.", e)
} catch (e: WrongPassphraseException) {
throw SOPGPException.KeyIsProtected()
} catch (e: MalformedOpenPgpMessageException) {
throw SOPGPException.BadData(e)
} catch (e: PGPException) {
throw SOPGPException.BadData(e)
} catch (e: IOException) {
throw SOPGPException.BadData(e)
} finally {
// Forget passphrases after decryption
protector.clear()
}
return object : ReadyWithResult<DecryptionResult>() {
override fun writeTo(outputStream: OutputStream): DecryptionResult {
Streams.pipeAll(decryptionStream, outputStream)
decryptionStream.close()
val metadata = decryptionStream.metadata
if (!metadata.isEncrypted) {
throw SOPGPException.BadData("Data is not encrypted.")
}
val verificationList =
metadata.verifiedInlineSignatures.map { VerificationHelper.mapVerification(it) }
var sessionKey: SessionKey? = null
if (metadata.sessionKey != null) {
sessionKey =
SessionKey(
metadata.sessionKey!!.algorithm.algorithmId.toByte(),
metadata.sessionKey!!.key)
}
return DecryptionResult(sessionKey, verificationList)
}
}
}
override fun verifyNotAfter(timestamp: Date): Decrypt = apply {
consumerOptions.verifyNotAfter(timestamp)
}
override fun verifyNotBefore(timestamp: Date): Decrypt = apply {
consumerOptions.verifyNotBefore(timestamp)
}
override fun verifyWithCert(cert: InputStream): Decrypt = apply {
KeyReader.readPublicKeys(cert, true)?.let { consumerOptions.addVerificationCerts(it) }
}
override fun withKey(key: InputStream): Decrypt = apply {
KeyReader.readSecretKeys(key, true).forEach {
protector.addSecretKey(it)
consumerOptions.addDecryptionKey(it, protector)
}
}
override fun withKeyPassword(password: ByteArray): Decrypt = apply {
protector.addPassphrase(Passphrase.fromPassword(String(password, UTF8Util.UTF8)))
}
override fun withPassword(password: String): Decrypt = apply {
consumerOptions.addDecryptionPassphrase(Passphrase.fromPassword(password))
password.trimEnd().let {
if (it != password) {
consumerOptions.addDecryptionPassphrase(Passphrase.fromPassword(it))
}
}
}
override fun withSessionKey(sessionKey: SessionKey): Decrypt = apply {
consumerOptions.setSessionKey(mapSessionKey(sessionKey))
}
private fun mapSessionKey(sessionKey: SessionKey): org.pgpainless.util.SessionKey =
org.pgpainless.util.SessionKey(
SymmetricKeyAlgorithm.requireFromId(sessionKey.algorithm.toInt()), sessionKey.key)
}