126 lines
4.8 KiB
Kotlin
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)
|
|
}
|