From 9cd9f151c9b6138b3e2fe5999efea347922ee9ae Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 15 Nov 2023 17:13:28 +0100 Subject: [PATCH] Kotlin conversion: DetachedSignExternal --- .../operation/DetachedSignExternal.java | 142 ------------------ .../operation/DetachedSignExternal.kt | 104 +++++++++++++ 2 files changed, 104 insertions(+), 142 deletions(-) delete mode 100644 external-sop/src/main/java/sop/external/operation/DetachedSignExternal.java create mode 100644 external-sop/src/main/kotlin/sop/external/operation/DetachedSignExternal.kt diff --git a/external-sop/src/main/java/sop/external/operation/DetachedSignExternal.java b/external-sop/src/main/java/sop/external/operation/DetachedSignExternal.java deleted file mode 100644 index 2ef2714..0000000 --- a/external-sop/src/main/java/sop/external/operation/DetachedSignExternal.java +++ /dev/null @@ -1,142 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.external.operation; - -import sop.MicAlg; -import sop.ReadyWithResult; -import sop.SigningResult; -import sop.enums.SignAs; -import sop.exception.SOPGPException; -import sop.external.ExternalSOP; -import sop.operation.DetachedSign; - -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.List; -import java.util.Properties; - -/** - * Implementation of the {@link DetachedSign} operation using an external SOP binary. - */ -public class DetachedSignExternal implements DetachedSign { - - private final ExternalSOP.TempDirProvider tempDirProvider; - private final List commandList = new ArrayList<>(); - private final List envList; - - private int withKeyPasswordCounter = 0; - private int keyCounter = 0; - - public DetachedSignExternal(String binary, Properties properties, ExternalSOP.TempDirProvider tempDirProvider) { - this.tempDirProvider = tempDirProvider; - commandList.add(binary); - commandList.add("sign"); - envList = ExternalSOP.propertiesToEnv(properties); - } - - @Override - @Nonnull - public DetachedSign noArmor() { - commandList.add("--no-armor"); - return this; - } - - @Override - @Nonnull - public DetachedSign key(@Nonnull InputStream key) throws SOPGPException.KeyCannotSign, SOPGPException.BadData, SOPGPException.UnsupportedAsymmetricAlgo, IOException { - String envVar = "KEY_" + keyCounter++; - commandList.add("@ENV:" + envVar); - envList.add(envVar + "=" + ExternalSOP.readString(key)); - return this; - } - - @Override - @Nonnull - public DetachedSign withKeyPassword(@Nonnull byte[] password) throws SOPGPException.UnsupportedOption, SOPGPException.PasswordNotHumanReadable { - String envVar = "WITH_KEY_PASSWORD_" + withKeyPasswordCounter++; - commandList.add("--with-key-password=@ENV:" + envVar); - envList.add(envVar + "=" + new String(password)); - return this; - } - - @Override - @Nonnull - public DetachedSign mode(@Nonnull SignAs mode) throws SOPGPException.UnsupportedOption { - commandList.add("--as=" + mode); - return this; - } - - @Override - @Nonnull - public ReadyWithResult data(@Nonnull InputStream data) - throws IOException, SOPGPException.KeyIsProtected, SOPGPException.ExpectedText { - - File tempDir = tempDirProvider.provideTempDirectory(); - File micAlgOut = new File(tempDir, "micAlgOut"); - micAlgOut.delete(); - commandList.add("--micalg-out=" + micAlgOut.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 SigningResult writeTo(@Nonnull OutputStream outputStream) throws IOException { - byte[] buf = new byte[4096]; - int r; - while ((r = data.read(buf)) > 0) { - processOut.write(buf, 0, r); - } - - data.close(); - try { - processOut.close(); - } catch (IOException e) { - // Ignore Stream closed - if (!"Stream closed".equals(e.getMessage())) { - throw e; - } - } - - while ((r = processIn.read(buf)) > 0) { - outputStream.write(buf, 0 , r); - } - - processIn.close(); - outputStream.close(); - - ExternalSOP.finish(process); - - SigningResult.Builder builder = SigningResult.builder(); - if (micAlgOut.exists()) { - BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(micAlgOut))); - String line = reader.readLine(); - if (line != null && !line.trim().isEmpty()) { - MicAlg micAlg = new MicAlg(line.trim()); - builder.setMicAlg(micAlg); - } - reader.close(); - micAlgOut.delete(); - } - - return builder.build(); - } - }; - } catch (IOException e) { - throw new RuntimeException(e); - } - } -} diff --git a/external-sop/src/main/kotlin/sop/external/operation/DetachedSignExternal.kt b/external-sop/src/main/kotlin/sop/external/operation/DetachedSignExternal.kt new file mode 100644 index 0000000..66d1db8 --- /dev/null +++ b/external-sop/src/main/kotlin/sop/external/operation/DetachedSignExternal.kt @@ -0,0 +1,104 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.external.operation + +import java.io.* +import java.util.* +import sop.MicAlg +import sop.ReadyWithResult +import sop.SigningResult +import sop.SigningResult.Companion.builder +import sop.enums.SignAs +import sop.external.ExternalSOP +import sop.external.ExternalSOP.Companion.finish +import sop.operation.DetachedSign + +/** Implementation of the [DetachedSign] operation using an external SOP binary. */ +class DetachedSignExternal( + binary: String, + environment: Properties, + private val tempDirProvider: ExternalSOP.TempDirProvider +) : DetachedSign { + + private val commandList = mutableListOf(binary, "sign") + private val envList = ExternalSOP.propertiesToEnv(environment).toMutableList() + + private var argCounter = 0 + + override fun mode(mode: SignAs): DetachedSign = apply { commandList.add("--as=$mode") } + + override fun data(data: InputStream): ReadyWithResult { + val tempDir = tempDirProvider.provideTempDirectory() + val micAlgOut = File(tempDir, "micAlgOut") + micAlgOut.delete() + commandList.add("--micalg-out=${micAlgOut.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): SigningResult { + val buf = ByteArray(4096) + var r: Int + while (data.read(buf).also { r = it } > 0) { + processOut.write(buf, 0, r) + } + + data.close() + try { + processOut.close() + } catch (e: IOException) { + // Ignore Stream closed + if ("Stream closed" != e.message) { + throw e + } + } + + while (processIn.read(buf).also { r = it } > 0) { + outputStream.write(buf, 0, r) + } + + processIn.close() + outputStream.close() + + finish(process) + + val builder = builder() + if (micAlgOut.exists()) { + val reader = BufferedReader(InputStreamReader(FileInputStream(micAlgOut))) + val line = reader.readLine() + if (line != null && line.isNotBlank()) { + val micAlg = MicAlg(line.trim()) + builder.setMicAlg(micAlg) + } + reader.close() + micAlgOut.delete() + } + + return builder.build() + } + } + } catch (e: IOException) { + throw RuntimeException(e) + } + } + + override fun noArmor(): DetachedSign = apply { commandList.add("--no-armor") } + + override fun key(key: InputStream): DetachedSign = apply { + commandList.add("@ENV:KEY_$argCounter") + envList.add("KEY_$argCounter=${ExternalSOP.readString(key)}") + argCounter += 1 + } + + override fun withKeyPassword(password: ByteArray): DetachedSign = apply { + commandList.add("--with-key-password=@ENV:WITH_KEY_PASSWORD_$argCounter") + envList.add("WITH_KEY_PASSWORD_$argCounter=${String(password)}") + argCounter += 1 + } +}