// SPDX-FileCopyrightText: 2024 Paul Schaub // // 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 { 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() { 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) }