From 03da9bbfb705c67906c9904204b7375d7a382a7c Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 15 Nov 2023 17:04:00 +0100 Subject: [PATCH] Kotlin conversion: DecryptExternal --- .../external/operation/DecryptExternal.java | 185 ------------------ .../sop/external/operation/DecryptExternal.kt | 133 +++++++++++++ 2 files changed, 133 insertions(+), 185 deletions(-) delete mode 100644 external-sop/src/main/java/sop/external/operation/DecryptExternal.java create mode 100644 external-sop/src/main/kotlin/sop/external/operation/DecryptExternal.kt diff --git a/external-sop/src/main/java/sop/external/operation/DecryptExternal.java b/external-sop/src/main/java/sop/external/operation/DecryptExternal.java deleted file mode 100644 index a1c4016..0000000 --- a/external-sop/src/main/java/sop/external/operation/DecryptExternal.java +++ /dev/null @@ -1,185 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.external.operation; - -import sop.DecryptionResult; -import sop.ReadyWithResult; -import sop.SessionKey; -import sop.Verification; -import sop.exception.SOPGPException; -import sop.external.ExternalSOP; -import sop.operation.Decrypt; -import sop.util.UTCUtil; - -import javax.annotation.Nonnull; -import java.io.BufferedReader; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.Properties; - -/** - * Implementation of the {@link Decrypt} operation using an external SOP binary. - */ -public class DecryptExternal implements Decrypt { - - private final ExternalSOP.TempDirProvider tempDirProvider; - private final List commandList = new ArrayList<>(); - private final List envList; - - private int verifyWithCounter = 0; - private int withSessionKeyCounter = 0; - private int withPasswordCounter = 0; - private int keyCounter = 0; - private int withKeyPasswordCounter = 0; - - public DecryptExternal(String binary, Properties environment, ExternalSOP.TempDirProvider tempDirProvider) { - this.tempDirProvider = tempDirProvider; - this.commandList.add(binary); - this.commandList.add("decrypt"); - this.envList = ExternalSOP.propertiesToEnv(environment); - } - - @Override - @Nonnull - public Decrypt verifyNotBefore(@Nonnull Date timestamp) - throws SOPGPException.UnsupportedOption { - this.commandList.add("--verify-not-before=" + UTCUtil.formatUTCDate(timestamp)); - return this; - } - - @Override - @Nonnull - public Decrypt verifyNotAfter(@Nonnull Date timestamp) - throws SOPGPException.UnsupportedOption { - this.commandList.add("--verify-not-after=" + UTCUtil.formatUTCDate(timestamp)); - return this; - } - - @Override - @Nonnull - public Decrypt verifyWithCert(@Nonnull InputStream cert) - throws SOPGPException.BadData, SOPGPException.UnsupportedAsymmetricAlgo, IOException { - String envVar = "VERIFY_WITH_" + verifyWithCounter++; - commandList.add("--verify-with=@ENV:" + envVar); - envList.add(envVar + "=" + ExternalSOP.readString(cert)); - return this; - } - - @Override - @Nonnull - public Decrypt withSessionKey(@Nonnull SessionKey sessionKey) - throws SOPGPException.UnsupportedOption { - String envVar = "SESSION_KEY_" + withSessionKeyCounter++; - commandList.add("--with-session-key=@ENV:" + envVar); - envList.add(envVar + "=" + sessionKey); - return this; - } - - @Override - @Nonnull - public Decrypt withPassword(@Nonnull String password) - throws SOPGPException.PasswordNotHumanReadable, SOPGPException.UnsupportedOption { - String envVar = "PASSWORD_" + withPasswordCounter++; - commandList.add("--with-password=@ENV:" + envVar); - envList.add(envVar + "=" + password); - return this; - } - - @Override - @Nonnull - public Decrypt withKey(@Nonnull InputStream key) - throws SOPGPException.BadData, SOPGPException.UnsupportedAsymmetricAlgo, IOException { - String envVar = "KEY_" + keyCounter++; - commandList.add("@ENV:" + envVar); - envList.add(envVar + "=" + ExternalSOP.readString(key)); - return this; - } - - @Override - @Nonnull - public Decrypt withKeyPassword(@Nonnull byte[] password) - throws SOPGPException.UnsupportedOption, SOPGPException.PasswordNotHumanReadable { - String envVar = "KEY_PASSWORD_" + withKeyPasswordCounter++; - commandList.add("--with-key-password=@ENV:" + envVar); - envList.add(envVar + "=" + new String(password)); - return this; - } - - @Override - @Nonnull - public ReadyWithResult ciphertext(@Nonnull InputStream ciphertext) - throws SOPGPException.BadData, SOPGPException.MissingArg, SOPGPException.CannotDecrypt, - SOPGPException.KeyIsProtected, IOException { - File tempDir = tempDirProvider.provideTempDirectory(); - - File sessionKeyOut = new File(tempDir, "session-key-out"); - sessionKeyOut.delete(); - commandList.add("--session-key-out=" + sessionKeyOut.getAbsolutePath()); - - File verifyOut = new File(tempDir, "verifications-out"); - verifyOut.delete(); - if (verifyWithCounter != 0) { - commandList.add("--verify-out=" + verifyOut.getAbsolutePath()); - } - - String[] command = commandList.toArray(new String[0]); - String[] env = envList.toArray(new String[0]); - try { - Process process = Runtime.getRuntime().exec(command, env); - OutputStream processOut = process.getOutputStream(); - InputStream processIn = process.getInputStream(); - - return new ReadyWithResult() { - @Override - public DecryptionResult writeTo(@Nonnull OutputStream outputStream) throws IOException { - byte[] buf = new byte[4096]; - int r; - while ((r = ciphertext.read(buf)) > 0) { - processOut.write(buf, 0, r); - } - - ciphertext.close(); - processOut.close(); - - while ((r = processIn.read(buf)) > 0) { - outputStream.write(buf, 0 , r); - } - - processIn.close(); - outputStream.close(); - - ExternalSOP.finish(process); - - FileInputStream sessionKeyOutIn = new FileInputStream(sessionKeyOut); - String line = ExternalSOP.readString(sessionKeyOutIn); - SessionKey sessionKey = SessionKey.fromString(line.trim()); - sessionKeyOutIn.close(); - sessionKeyOut.delete(); - - List verifications = new ArrayList<>(); - if (verifyWithCounter != 0) { - FileInputStream verifyOutIn = new FileInputStream(verifyOut); - BufferedReader reader = new BufferedReader(new InputStreamReader(verifyOutIn)); - while ((line = reader.readLine()) != null) { - verifications.add(Verification.fromString(line.trim())); - } - reader.close(); - } - - return new DecryptionResult(sessionKey, verifications); - } - }; - } catch (IOException e) { - throw new RuntimeException(e); - } - } -} diff --git a/external-sop/src/main/kotlin/sop/external/operation/DecryptExternal.kt b/external-sop/src/main/kotlin/sop/external/operation/DecryptExternal.kt new file mode 100644 index 0000000..b68d3a6 --- /dev/null +++ b/external-sop/src/main/kotlin/sop/external/operation/DecryptExternal.kt @@ -0,0 +1,133 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.external.operation + +import java.io.* +import java.util.* +import sop.DecryptionResult +import sop.ReadyWithResult +import sop.SessionKey +import sop.Verification +import sop.external.ExternalSOP +import sop.external.ExternalSOP.Companion.finish +import sop.external.ExternalSOP.Companion.readString +import sop.operation.Decrypt +import sop.util.UTCUtil + +/** Implementation of the [Decrypt] operation using an external SOP binary. */ +class DecryptExternal( + binary: String, + environment: Properties, + private val tempDirProvider: ExternalSOP.TempDirProvider +) : Decrypt { + + private val commandList = mutableListOf(binary, "decrypt") + private val envList = ExternalSOP.propertiesToEnv(environment).toMutableList() + + private var argCounter = 0 + private var requireVerification = false + + override fun verifyNotBefore(timestamp: Date): Decrypt = apply { + commandList.add("--verify-not-before=${UTCUtil.formatUTCDate(timestamp)}") + } + + override fun verifyNotAfter(timestamp: Date): Decrypt = apply { + commandList.add("--verify-not-after=${UTCUtil.formatUTCDate(timestamp)}") + } + + override fun verifyWithCert(cert: InputStream): Decrypt = apply { + commandList.add("--verify-with=@ENV:VERIFY_WITH_$argCounter") + envList.add("VERIFY_WITH_$argCounter=${readString(cert)}") + argCounter += 1 + requireVerification = true + } + + override fun withSessionKey(sessionKey: SessionKey): Decrypt = apply { + commandList.add("--with-session-key=@ENV:SESSION_KEY_$argCounter") + envList.add("SESSION_KEY_$argCounter=$sessionKey") + argCounter += 1 + } + + override fun withPassword(password: String): Decrypt = apply { + commandList.add("--with-password=@ENV:PASSWORD_$argCounter") + envList.add("PASSWORD_$argCounter=$password") + argCounter += 1 + } + + override fun withKey(key: InputStream): Decrypt = apply { + commandList.add("@ENV:KEY_$argCounter") + envList.add("KEY_$argCounter=${readString(key)}") + argCounter += 1 + } + + override fun withKeyPassword(password: ByteArray): Decrypt = apply { + commandList.add("--with-key-password=@ENV:KEY_PASSWORD_$argCounter") + envList.add("KEY_PASSWORD_$argCounter=${String(password)}") + argCounter += 1 + } + + override fun ciphertext(ciphertext: InputStream): ReadyWithResult { + val tempDir = tempDirProvider.provideTempDirectory() + + val sessionKeyOut = File(tempDir, "session-key-out") + sessionKeyOut.delete() + commandList.add("--session-key-out=${sessionKeyOut.absolutePath}") + + val verifyOut = File(tempDir, "verifications-out") + verifyOut.delete() + if (requireVerification) { + commandList.add("--verify-out=${verifyOut.absolutePath}") + } + + try { + val process = + Runtime.getRuntime().exec(commandList.toTypedArray(), envList.toTypedArray()) + val processOut = process.outputStream + val processIn = process.inputStream + + return object : ReadyWithResult() { + override fun writeTo(outputStream: OutputStream): DecryptionResult { + val buf = ByteArray(4096) + var r: Int + while (ciphertext.read(buf).also { r = it } > 0) { + processOut.write(buf, 0, r) + } + + ciphertext.close() + processOut.close() + + while (processIn.read(buf).also { r = it } > 0) { + outputStream.write(buf, 0, r) + } + + processIn.close() + outputStream.close() + + finish(process) + + val sessionKeyOutIn = FileInputStream(sessionKeyOut) + var line = readString(sessionKeyOutIn) + val sessionKey = SessionKey.fromString(line.trim { it <= ' ' }) + sessionKeyOutIn.close() + sessionKeyOut.delete() + + val verifications: MutableList = ArrayList() + if (requireVerification) { + val verifyOutIn = FileInputStream(verifyOut) + val reader = BufferedReader(InputStreamReader(verifyOutIn)) + while (reader.readLine().also { line = it } != null) { + verifications.add(Verification.fromString(line.trim())) + } + reader.close() + } + + return DecryptionResult(sessionKey, verifications) + } + } + } catch (e: IOException) { + throw RuntimeException(e) + } + } +}