From a63b29fe80678dc7dd212639994384cdb85dbb04 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 9 Jan 2023 19:48:25 +0100 Subject: [PATCH] WiP: Implement first prototypes of all SOP commands for external binaries --- external-sop/build.gradle | 3 + .../main/java/sop/external/ExternalSOP.java | 104 +++++- .../sop/external/operation/ArmorExternal.java | 40 +++ .../external/operation/DearmorExternal.java | 33 ++ .../external/operation/DecryptExternal.java | 139 ++++++++ .../operation/DetachedSignExternal.java | 75 ++++- .../operation/DetachedVerifyExternal.java | 108 ++++++ .../external/operation/EncryptExternal.java | 90 +++++ .../operation/ExtractCertExternal.java | 61 +--- .../operation/GenerateKeyExternal.java | 71 +--- .../operation/InlineDetachExternal.java | 75 +++++ .../operation/InlineSignExternal.java | 65 ++++ .../operation/InlineVerifyExternal.java | 94 ++++++ .../sop/external/AbstractExternalSOPTest.java | 76 +++++ .../external/EncryptDecryptRoundTripTest.java | 70 ++++ .../sop/external/ExternalExtractCertTest.java | 66 +++- .../sop/external/ExternalGenerateKeyTest.java | 77 ++++- .../sop/external/ExternalVersionTest.java | 8 +- .../src/test/java/sop/external/JUtils.java | 53 +++ .../src/test/java/sop/external/TestKeys.java | 307 ++++++++++++++++++ .../java/sop/exception/SOPGPException.java | 4 + 21 files changed, 1473 insertions(+), 146 deletions(-) create mode 100644 external-sop/src/main/java/sop/external/operation/ArmorExternal.java create mode 100644 external-sop/src/main/java/sop/external/operation/DearmorExternal.java create mode 100644 external-sop/src/main/java/sop/external/operation/DecryptExternal.java create mode 100644 external-sop/src/main/java/sop/external/operation/DetachedVerifyExternal.java create mode 100644 external-sop/src/main/java/sop/external/operation/EncryptExternal.java create mode 100644 external-sop/src/main/java/sop/external/operation/InlineDetachExternal.java create mode 100644 external-sop/src/main/java/sop/external/operation/InlineSignExternal.java create mode 100644 external-sop/src/main/java/sop/external/operation/InlineVerifyExternal.java create mode 100644 external-sop/src/test/java/sop/external/EncryptDecryptRoundTripTest.java create mode 100644 external-sop/src/test/java/sop/external/JUtils.java create mode 100644 external-sop/src/test/java/sop/external/TestKeys.java diff --git a/external-sop/build.gradle b/external-sop/build.gradle index 0989747..83bfe3c 100644 --- a/external-sop/build.gradle +++ b/external-sop/build.gradle @@ -20,6 +20,9 @@ dependencies { api "org.slf4j:slf4j-api:$slf4jVersion" testImplementation "ch.qos.logback:logback-classic:$logbackVersion" + + // Compare version strings + implementation 'org.apache.maven:maven-artifact:3.6.3' } test { diff --git a/external-sop/src/main/java/sop/external/ExternalSOP.java b/external-sop/src/main/java/sop/external/ExternalSOP.java index 28fda4d..b4763c6 100644 --- a/external-sop/src/main/java/sop/external/ExternalSOP.java +++ b/external-sop/src/main/java/sop/external/ExternalSOP.java @@ -4,10 +4,20 @@ package sop.external; +import sop.Ready; import sop.SOP; import sop.exception.SOPGPException; +import sop.external.operation.ArmorExternal; +import sop.external.operation.DearmorExternal; +import sop.external.operation.DecryptExternal; +import sop.external.operation.DetachedSignExternal; +import sop.external.operation.DetachedVerifyExternal; +import sop.external.operation.EncryptExternal; import sop.external.operation.ExtractCertExternal; import sop.external.operation.GenerateKeyExternal; +import sop.external.operation.InlineDetachExternal; +import sop.external.operation.InlineSignExternal; +import sop.external.operation.InlineVerifyExternal; import sop.external.operation.VersionExternal; import sop.operation.Armor; import sop.operation.Dearmor; @@ -25,9 +35,9 @@ import sop.operation.Version; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; import java.util.ArrayList; import java.util.List; -import java.util.Objects; import java.util.Properties; public class ExternalSOP implements SOP { @@ -61,47 +71,47 @@ public class ExternalSOP implements SOP { @Override public DetachedSign detachedSign() { - return null; + return new DetachedSignExternal(binaryName, properties); } @Override public InlineSign inlineSign() { - return null; + return new InlineSignExternal(binaryName, properties); } @Override public DetachedVerify detachedVerify() { - return null; + return new DetachedVerifyExternal(binaryName, properties); } @Override public InlineVerify inlineVerify() { - return null; + return new InlineVerifyExternal(binaryName, properties); } @Override public InlineDetach inlineDetach() { - return null; + return new InlineDetachExternal(binaryName, properties); } @Override public Encrypt encrypt() { - return null; + return new EncryptExternal(binaryName, properties); } @Override public Decrypt decrypt() { - return null; + return new DecryptExternal(binaryName, properties); } @Override public Armor armor() { - return null; + return new ArmorExternal(binaryName, properties); } @Override public Dearmor dearmor() { - return null; + return new DearmorExternal(binaryName, properties); } public static void finish(Process process) throws IOException { @@ -112,7 +122,7 @@ public class ExternalSOP implements SOP { ByteArrayOutputStream errOut = new ByteArrayOutputStream(); byte[] buf = new byte[512]; int r; - while ((r = errIn.read(buf)) > 0 ) { + while ((r = errIn.read(buf)) > 0) { errOut.write(buf, 0, r); } @@ -135,7 +145,7 @@ public class ExternalSOP implements SOP { ByteArrayOutputStream errOut = new ByteArrayOutputStream(); byte[] buf = new byte[512]; int r; - while ((r = errIn.read(buf)) > 0 ) { + while ((r = errIn.read(buf)) > 0) { errOut.write(buf, 0, r); } @@ -223,4 +233,74 @@ public class ExternalSOP implements SOP { } return env; } + + public static String readFully(InputStream inputStream) throws IOException { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + byte[] buf = new byte[4096]; + int r; + while ((r = inputStream.read(buf)) > 0) { + bOut.write(buf, 0, r); + } + return bOut.toString(); + } + + public static Ready ready(Runtime runtime, List commandList, List envList) { + String[] command = commandList.toArray(new String[0]); + String[] env = envList.toArray(new String[0]); + + try { + Process process = runtime.exec(command, env); + InputStream stdIn = process.getInputStream(); + + return new Ready() { + @Override + public void writeTo(OutputStream outputStream) throws IOException { + byte[] buf = new byte[4096]; + int r; + while ((r = stdIn.read(buf)) >= 0) { + outputStream.write(buf, 0, r); + } + + outputStream.close(); + ExternalSOP.finish(process); + } + }; + } catch (IOException e) { + throw new RuntimeException(e); + } + } + public static Ready ready(Runtime runtime, List commandList, List envList, InputStream standardIn) { + String[] command = commandList.toArray(new String[0]); + String[] env = envList.toArray(new String[0]); + try { + Process process = runtime.exec(command, env); + OutputStream processOut = process.getOutputStream(); + InputStream processIn = process.getInputStream(); + + return new Ready() { + @Override + public void writeTo(OutputStream outputStream) throws IOException { + byte[] buf = new byte[4096]; + int r; + while ((r = standardIn.read(buf)) > 0) { + processOut.write(buf, 0, r); + } + + standardIn.close(); + processOut.close(); + + while ((r = processIn.read(buf)) > 0) { + outputStream.write(buf, 0 , r); + } + + processIn.close(); + outputStream.close(); + + finish(process); + } + }; + } catch (IOException e) { + throw new RuntimeException(e); + } + } } diff --git a/external-sop/src/main/java/sop/external/operation/ArmorExternal.java b/external-sop/src/main/java/sop/external/operation/ArmorExternal.java new file mode 100644 index 0000000..5149792 --- /dev/null +++ b/external-sop/src/main/java/sop/external/operation/ArmorExternal.java @@ -0,0 +1,40 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.external.operation; + +import sop.Ready; +import sop.enums.ArmorLabel; +import sop.exception.SOPGPException; +import sop.external.ExternalSOP; +import sop.operation.Armor; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Properties; +import java.util.List; + +public class ArmorExternal implements Armor { + + private final List commandList = new ArrayList<>(); + private final List envList; + + public ArmorExternal(String binary, Properties environment) { + commandList.add(binary); + commandList.add("armor"); + envList = ExternalSOP.propertiesToEnv(environment); + } + + @Override + public Armor label(ArmorLabel label) throws SOPGPException.UnsupportedOption { + commandList.add("--label=" + label); + return this; + } + + @Override + public Ready data(InputStream data) throws SOPGPException.BadData, IOException { + return ExternalSOP.ready(Runtime.getRuntime(), commandList, envList); + } +} diff --git a/external-sop/src/main/java/sop/external/operation/DearmorExternal.java b/external-sop/src/main/java/sop/external/operation/DearmorExternal.java new file mode 100644 index 0000000..936746f --- /dev/null +++ b/external-sop/src/main/java/sop/external/operation/DearmorExternal.java @@ -0,0 +1,33 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.external.operation; + +import sop.Ready; +import sop.exception.SOPGPException; +import sop.external.ExternalSOP; +import sop.operation.Dearmor; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; + +public class DearmorExternal implements Dearmor { + + private final List commandList = new ArrayList<>(); + private final List envList; + + public DearmorExternal(String binary, Properties environment) { + commandList.add(binary); + commandList.add("dearmor"); + envList = ExternalSOP.propertiesToEnv(environment); + } + + @Override + public Ready data(InputStream data) throws SOPGPException.BadData, IOException { + return ExternalSOP.ready(Runtime.getRuntime(), commandList, envList); + } +} diff --git a/external-sop/src/main/java/sop/external/operation/DecryptExternal.java b/external-sop/src/main/java/sop/external/operation/DecryptExternal.java new file mode 100644 index 0000000..9373bcd --- /dev/null +++ b/external-sop/src/main/java/sop/external/operation/DecryptExternal.java @@ -0,0 +1,139 @@ +// 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.exception.SOPGPException; +import sop.external.ExternalSOP; +import sop.operation.Decrypt; +import sop.util.UTCUtil; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.Properties; + +public class DecryptExternal implements Decrypt { + + 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) { + this.commandList.add(binary); + this.commandList.add("decrypt"); + this.envList = ExternalSOP.propertiesToEnv(environment); + } + + @Override + public Decrypt verifyNotBefore(Date timestamp) + throws SOPGPException.UnsupportedOption { + this.commandList.add("--not-before=" + UTCUtil.formatUTCDate(timestamp)); + return this; + } + + @Override + public Decrypt verifyNotAfter(Date timestamp) + throws SOPGPException.UnsupportedOption { + this.commandList.add("--not-after=" + UTCUtil.formatUTCDate(timestamp)); + return this; + } + + @Override + public Decrypt verifyWithCert(InputStream cert) + throws SOPGPException.BadData, SOPGPException.UnsupportedAsymmetricAlgo, IOException { + String envVar = "VERIFY_WITH_" + verifyWithCounter++; + commandList.add("--verify-with=@ENV:" + envVar); + envList.add(envVar + "=" + ExternalSOP.readFully(cert)); + return this; + } + + @Override + public Decrypt withSessionKey(SessionKey sessionKey) + throws SOPGPException.UnsupportedOption { + String envVar = "SESSION_KEY_" + withSessionKeyCounter++; + commandList.add("--with-session-key=@ENV:" + envVar); + envList.add(envVar + "=" + sessionKey); + return this; + } + + @Override + public Decrypt withPassword(String password) + throws SOPGPException.PasswordNotHumanReadable, SOPGPException.UnsupportedOption { + String envVar = "PASSWORD_" + withPasswordCounter++; + commandList.add("--with-password=@ENV:" + envVar); + envList.add(envVar + "=" + password); + return this; + } + + @Override + public Decrypt withKey(InputStream key) + throws SOPGPException.BadData, SOPGPException.UnsupportedAsymmetricAlgo, IOException { + String envVar = "KEY_" + keyCounter++; + commandList.add("@ENV:" + envVar); + envList.add(envVar + "=" + ExternalSOP.readFully(key)); + return this; + } + + @Override + public Decrypt withKeyPassword(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 + public ReadyWithResult ciphertext(InputStream ciphertext) + throws SOPGPException.BadData, SOPGPException.MissingArg, SOPGPException.CannotDecrypt, + SOPGPException.KeyIsProtected, IOException { + 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(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); + + return new DecryptionResult(null, Collections.emptyList()); // TODO + } + }; + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/external-sop/src/main/java/sop/external/operation/DetachedSignExternal.java b/external-sop/src/main/java/sop/external/operation/DetachedSignExternal.java index 2ab126f..d82950b 100644 --- a/external-sop/src/main/java/sop/external/operation/DetachedSignExternal.java +++ b/external-sop/src/main/java/sop/external/operation/DetachedSignExternal.java @@ -1,43 +1,102 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + package sop.external.operation; import sop.ReadyWithResult; import sop.SigningResult; import sop.enums.SignAs; import sop.exception.SOPGPException; +import sop.external.ExternalSOP; import sop.operation.DetachedSign; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; public class DetachedSignExternal implements DetachedSign { - private boolean noArmor; - private byte[] keyPassword; + private final List commandList = new ArrayList<>(); + private final List envList; + + private int withKeyPasswordCounter = 0; + private int keyCounter = 0; + + public DetachedSignExternal(String binary, Properties properties) { + commandList.add(binary); + commandList.add("sign"); + envList = ExternalSOP.propertiesToEnv(properties); + } @Override public DetachedSign noArmor() { - this.noArmor = true; + commandList.add("--no-armor"); return this; } @Override public DetachedSign key(InputStream key) throws SOPGPException.KeyCannotSign, SOPGPException.BadData, SOPGPException.UnsupportedAsymmetricAlgo, IOException { - return null; + String envVar = "KEY_" + keyCounter++; + commandList.add("@ENV:" + envVar); + envList.add(envVar + "=" + ExternalSOP.readFully(key)); + return this; } @Override public DetachedSign withKeyPassword(byte[] password) throws SOPGPException.UnsupportedOption, SOPGPException.PasswordNotHumanReadable { - this.keyPassword = password; + String envVar = "WITH_KEY_PASSWORD_" + withKeyPasswordCounter++; + commandList.add("--with-key-password=@ENV:" + envVar); + envList.add(envVar + "=" + new String(password)); return this; } @Override public DetachedSign mode(SignAs mode) throws SOPGPException.UnsupportedOption { - return null; + commandList.add("--as=" + mode); + return this; } @Override - public ReadyWithResult data(InputStream data) throws IOException, SOPGPException.KeyIsProtected, SOPGPException.ExpectedText { - return null; + public ReadyWithResult data(InputStream data) + throws IOException, SOPGPException.KeyIsProtected, SOPGPException.ExpectedText { + + 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(OutputStream outputStream) throws IOException { + byte[] buf = new byte[4096]; + int r; + while ((r = data.read(buf)) > 0) { + processOut.write(buf, 0, r); + } + + data.close(); + processOut.close(); + + while ((r = processIn.read(buf)) > 0) { + outputStream.write(buf, 0 , r); + } + + processIn.close(); + outputStream.close(); + + ExternalSOP.finish(process); + + return SigningResult.builder().build(); + } + }; + } catch (IOException e) { + throw new RuntimeException(e); + } } } diff --git a/external-sop/src/main/java/sop/external/operation/DetachedVerifyExternal.java b/external-sop/src/main/java/sop/external/operation/DetachedVerifyExternal.java new file mode 100644 index 0000000..d95c9dc --- /dev/null +++ b/external-sop/src/main/java/sop/external/operation/DetachedVerifyExternal.java @@ -0,0 +1,108 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.external.operation; + +import sop.Verification; +import sop.exception.SOPGPException; +import sop.external.ExternalSOP; +import sop.operation.DetachedVerify; +import sop.operation.VerifySignatures; +import sop.util.UTCUtil; + +import java.io.BufferedReader; +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.HashSet; +import java.util.List; +import java.util.Properties; +import java.util.Set; + +public class DetachedVerifyExternal implements DetachedVerify { + + private final List commandList = new ArrayList<>(); + private final List envList; + + private Set certs = new HashSet<>(); + private InputStream signatures; + private int certCounter = 0; + + public DetachedVerifyExternal(String binary, Properties environment) { + commandList.add(binary); + commandList.add("verify"); + envList = ExternalSOP.propertiesToEnv(environment); + } + + @Override + public DetachedVerify notBefore(Date timestamp) throws SOPGPException.UnsupportedOption { + commandList.add("--not-before=" + UTCUtil.formatUTCDate(timestamp)); + return this; + } + + @Override + public DetachedVerify notAfter(Date timestamp) throws SOPGPException.UnsupportedOption { + commandList.add("--not-after=" + UTCUtil.formatUTCDate(timestamp)); + return this; + } + + @Override + public DetachedVerify cert(InputStream cert) throws SOPGPException.BadData, IOException { + this.certs.add(cert); + return this; + } + + @Override + public VerifySignatures signatures(InputStream signatures) throws SOPGPException.BadData, IOException { + this.signatures = signatures; + return this; + } + + @Override + public List data(InputStream data) throws IOException, SOPGPException.NoSignature, SOPGPException.BadData { + commandList.add("@ENV:SIGNATURE"); + envList.add("SIGNATURE=" + ExternalSOP.readFully(signatures)); + + for (InputStream cert : certs) { + String envVar = "CERT_" + certCounter++; + commandList.add("@ENV:" + envVar); + envList.add(envVar + "=" + ExternalSOP.readFully(cert)); + } + + 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(); + + byte[] buf = new byte[4096]; + int r; + while ((r = data.read(buf)) > 0) { + processOut.write(buf, 0, r); + } + + data.close(); + processOut.close(); + + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(processIn)); + List verifications = new ArrayList<>(); + + String line = null; + while ((line = bufferedReader.readLine()) != null) { + verifications.add(Verification.fromString(line)); + } + + ExternalSOP.finish(process); + + return verifications; + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/external-sop/src/main/java/sop/external/operation/EncryptExternal.java b/external-sop/src/main/java/sop/external/operation/EncryptExternal.java new file mode 100644 index 0000000..7096842 --- /dev/null +++ b/external-sop/src/main/java/sop/external/operation/EncryptExternal.java @@ -0,0 +1,90 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.external.operation; + +import sop.Ready; +import sop.enums.EncryptAs; +import sop.exception.SOPGPException; +import sop.external.ExternalSOP; +import sop.operation.Encrypt; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; + +public class EncryptExternal implements Encrypt { + + private final List commandList = new ArrayList<>(); + private final List envList; + private int SIGN_WITH_COUNTER = 0; + private int KEY_PASSWORD_COUNTER = 0; + private int PASSWORD_COUNTER = 0; + private int CERT_COUNTER = 0; + + public EncryptExternal(String binary, Properties environment) { + this.commandList.add(binary); + this.commandList.add("encrypt"); + this.envList = ExternalSOP.propertiesToEnv(environment); + } + + @Override + public Encrypt noArmor() { + this.commandList.add("--no-armor"); + return this; + } + + @Override + public Encrypt mode(EncryptAs mode) + throws SOPGPException.UnsupportedOption { + this.commandList.add("--as=" + mode); + return this; + } + + @Override + public Encrypt signWith(InputStream key) + throws SOPGPException.KeyCannotSign, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.BadData, + IOException { + String envVar = "SIGN_WITH_" + SIGN_WITH_COUNTER++; + commandList.add("--sign-with=@ENV:" + envVar); + envList.add(envVar + "=" + ExternalSOP.readFully(key)); + return this; + } + + @Override + public Encrypt withKeyPassword(byte[] password) + throws SOPGPException.PasswordNotHumanReadable, SOPGPException.UnsupportedOption { + String envVar = "KEY_PASSWORD_" + KEY_PASSWORD_COUNTER++; + commandList.add("--with-key-password=@ENV:" + envVar); + envList.add(envVar + "=" + new String(password)); + return this; + } + + @Override + public Encrypt withPassword(String password) + throws SOPGPException.PasswordNotHumanReadable, SOPGPException.UnsupportedOption { + String envVar = "PASSWORD_" + PASSWORD_COUNTER++; + commandList.add("--with-password=@ENV:" + envVar); + envList.add(envVar + "=" + password); + return this; + } + + @Override + public Encrypt withCert(InputStream cert) + throws SOPGPException.CertCannotEncrypt, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.BadData, + IOException { + String envVar = "CERT_" + CERT_COUNTER++; + commandList.add("@ENV:" + envVar); + envList.add(envVar + "=" + ExternalSOP.readFully(cert)); + return this; + } + + @Override + public Ready plaintext(InputStream plaintext) + throws IOException, SOPGPException.KeyIsProtected { + return ExternalSOP.ready(Runtime.getRuntime(), commandList, envList, plaintext); + } +} diff --git a/external-sop/src/main/java/sop/external/operation/ExtractCertExternal.java b/external-sop/src/main/java/sop/external/operation/ExtractCertExternal.java index da7f3db..68d0750 100644 --- a/external-sop/src/main/java/sop/external/operation/ExtractCertExternal.java +++ b/external-sop/src/main/java/sop/external/operation/ExtractCertExternal.java @@ -9,77 +9,30 @@ import sop.exception.SOPGPException; import sop.external.ExternalSOP; import sop.operation.ExtractCert; -import java.io.IOException; import java.io.InputStream; -import java.io.OutputStream; import java.util.ArrayList; import java.util.List; import java.util.Properties; public class ExtractCertExternal implements ExtractCert { - private final String binary; - private final Runtime runtime = Runtime.getRuntime(); - private final Properties environment; - - private boolean noArmor; + private final List commandList = new ArrayList<>(); + private final List envList; public ExtractCertExternal(String binary, Properties properties) { - this.binary = binary; - this.environment = properties; + this.commandList.add(binary); + this.commandList.add("extract-cert"); + this.envList = ExternalSOP.propertiesToEnv(properties); } @Override public ExtractCert noArmor() { - this.noArmor = true; + this.commandList.add("--no-armor"); return this; } @Override public Ready key(InputStream keyInputStream) throws SOPGPException.BadData { - List commandList = new ArrayList<>(); - - commandList.add(binary); - commandList.add("extract-cert"); - - if (noArmor) { - commandList.add("--no-armor"); - } - - List envList = ExternalSOP.propertiesToEnv(environment); - - String[] command = commandList.toArray(new String[0]); - String[] env = envList.toArray(new String[0]); - - try { - Process process = runtime.exec(command, env); - OutputStream extractOut = process.getOutputStream(); - InputStream extractIn = process.getInputStream(); - - return new Ready() { - @Override - public void writeTo(OutputStream outputStream) throws IOException { - byte[] buf = new byte[4096]; - int r; - while ((r = keyInputStream.read(buf)) > 0) { - extractOut.write(buf, 0, r); - } - - keyInputStream.close(); - extractOut.close(); - - while ((r = extractIn.read(buf)) > 0) { - outputStream.write(buf, 0 , r); - } - - extractIn.close(); - outputStream.close(); - - ExternalSOP.finish(process); - } - }; - } catch (IOException e) { - throw new RuntimeException(e); - } + return ExternalSOP.ready(Runtime.getRuntime(), commandList, envList, keyInputStream); } } diff --git a/external-sop/src/main/java/sop/external/operation/GenerateKeyExternal.java b/external-sop/src/main/java/sop/external/operation/GenerateKeyExternal.java index 0c09304..b348fa4 100644 --- a/external-sop/src/main/java/sop/external/operation/GenerateKeyExternal.java +++ b/external-sop/src/main/java/sop/external/operation/GenerateKeyExternal.java @@ -9,93 +9,48 @@ import sop.exception.SOPGPException; import sop.external.ExternalSOP; import sop.operation.GenerateKey; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; import java.util.ArrayList; import java.util.List; import java.util.Properties; public class GenerateKeyExternal implements GenerateKey { - private final String binary; - private boolean noArmor = false; - private List userIds = new ArrayList<>(); - private String keyPassword; + private final List commandList = new ArrayList<>(); + private final List envList; - private final Runtime runtime = Runtime.getRuntime(); - private final Properties properties; + private int keyPasswordCounter = 0; public GenerateKeyExternal(String binary, Properties environment) { - this.binary = binary; - this.properties = environment; + this.commandList.add(binary); + this.commandList.add("generate-key"); + this.envList = ExternalSOP.propertiesToEnv(environment); } @Override public GenerateKey noArmor() { - this.noArmor = true; + this.commandList.add("--no-armor"); return this; } @Override public GenerateKey userId(String userId) { - this.userIds.add(userId); + this.commandList.add(userId); return this; } @Override public GenerateKey withKeyPassword(String password) throws SOPGPException.PasswordNotHumanReadable, SOPGPException.UnsupportedOption { - this.keyPassword = password; + this.commandList.add("--with-key-password=@ENV:KEY_PASSWORD_" + keyPasswordCounter); + this.envList.add("KEY_PASSWORD_" + keyPasswordCounter + "=" + password); + keyPasswordCounter++; + return this; } @Override public Ready generate() throws SOPGPException.MissingArg, SOPGPException.UnsupportedAsymmetricAlgo { - List commandList = new ArrayList<>(); - - commandList.add(binary); - commandList.add("generate-key"); - - if (noArmor) { - commandList.add("--no-armor"); - } - - if (keyPassword != null) { - commandList.add("--with-key-password"); - commandList.add("@ENV:key_password"); - } - - for (String userId : userIds) { - commandList.add(userId); - } - - List envList = ExternalSOP.propertiesToEnv(properties); - if (keyPassword != null) { - envList.add("key_password=" + keyPassword); - } - - String[] command = commandList.toArray(new String[0]); - String[] env = envList.toArray(new String[0]); - try { - Process process = runtime.exec(command, env); - InputStream stdIn = process.getInputStream(); - - return new Ready() { - @Override - public void writeTo(OutputStream outputStream) throws IOException { - byte[] buf = new byte[4096]; - int r; - while ((r = stdIn.read(buf)) >= 0) { - outputStream.write(buf, 0, r); - } - - ExternalSOP.finish(process); - } - }; - } catch (IOException e) { - throw new RuntimeException(e); - } + return ExternalSOP.ready(Runtime.getRuntime(), commandList, envList); } } diff --git a/external-sop/src/main/java/sop/external/operation/InlineDetachExternal.java b/external-sop/src/main/java/sop/external/operation/InlineDetachExternal.java new file mode 100644 index 0000000..f8ebe09 --- /dev/null +++ b/external-sop/src/main/java/sop/external/operation/InlineDetachExternal.java @@ -0,0 +1,75 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.external.operation; + +import sop.ReadyWithResult; +import sop.Signatures; +import sop.exception.SOPGPException; +import sop.external.ExternalSOP; +import sop.operation.InlineDetach; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; + +public class InlineDetachExternal implements InlineDetach { + + private final List commandList = new ArrayList<>(); + private final List envList; + + public InlineDetachExternal(String binary, Properties environment) { + commandList.add(binary); + commandList.add("inline-detach"); + envList = ExternalSOP.propertiesToEnv(environment); + } + + @Override + public InlineDetach noArmor() { + commandList.add("--no-armor"); + return this; + } + + @Override + public ReadyWithResult message(InputStream messageInputStream) throws IOException, SOPGPException.BadData { + 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 Signatures writeTo(OutputStream outputStream) throws IOException { + byte[] buf = new byte[4096]; + int r; + while ((r = messageInputStream.read(buf)) > 0) { + processOut.write(buf, 0, r); + } + + messageInputStream.close(); + processOut.close(); + + while ((r = processIn.read(buf)) > 0) { + outputStream.write(buf, 0 , r); + } + + processIn.close(); + outputStream.close(); + + ExternalSOP.finish(process); + + return null; // TODO + } + }; + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/external-sop/src/main/java/sop/external/operation/InlineSignExternal.java b/external-sop/src/main/java/sop/external/operation/InlineSignExternal.java new file mode 100644 index 0000000..2a49ad5 --- /dev/null +++ b/external-sop/src/main/java/sop/external/operation/InlineSignExternal.java @@ -0,0 +1,65 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.external.operation; + +import sop.Ready; +import sop.enums.InlineSignAs; +import sop.exception.SOPGPException; +import sop.external.ExternalSOP; +import sop.operation.InlineSign; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; + +public class InlineSignExternal implements InlineSign { + + private final List commandList = new ArrayList<>(); + private final List envList; + + private int keyCounter = 0; + private int withKeyPasswordCounter = 0; + + public InlineSignExternal(String binary, Properties environment) { + commandList.add(binary); + commandList.add("inline-sign"); + envList = ExternalSOP.propertiesToEnv(environment); + } + + @Override + public InlineSign noArmor() { + commandList.add("--no-armor"); + return this; + } + + @Override + public InlineSign key(InputStream key) throws SOPGPException.KeyCannotSign, SOPGPException.BadData, SOPGPException.UnsupportedAsymmetricAlgo, IOException { + String envVar = "KEY_" + keyCounter++; + commandList.add("@ENV:" + envVar); + envList.add(envVar + "=" + ExternalSOP.readFully(key)); + return this; + } + + @Override + public InlineSign withKeyPassword(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 + public InlineSign mode(InlineSignAs mode) throws SOPGPException.UnsupportedOption { + commandList.add("--as=" + mode); + return this; + } + + @Override + public Ready data(InputStream data) throws IOException, SOPGPException.KeyIsProtected, SOPGPException.ExpectedText { + return ExternalSOP.ready(Runtime.getRuntime(), commandList, envList, data); + } +} diff --git a/external-sop/src/main/java/sop/external/operation/InlineVerifyExternal.java b/external-sop/src/main/java/sop/external/operation/InlineVerifyExternal.java new file mode 100644 index 0000000..b18cc4c --- /dev/null +++ b/external-sop/src/main/java/sop/external/operation/InlineVerifyExternal.java @@ -0,0 +1,94 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.external.operation; + +import sop.ReadyWithResult; +import sop.Verification; +import sop.exception.SOPGPException; +import sop.external.ExternalSOP; +import sop.operation.InlineVerify; +import sop.util.UTCUtil; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Properties; + +public class InlineVerifyExternal implements InlineVerify { + + private final List commandList = new ArrayList<>(); + private final List envList; + + private int certCounter = 0; + + public InlineVerifyExternal(String binary, Properties environment) { + commandList.add(binary); + commandList.add("inline-verify"); + envList = ExternalSOP.propertiesToEnv(environment); + } + + @Override + public InlineVerify notBefore(Date timestamp) throws SOPGPException.UnsupportedOption { + commandList.add("--not-before=" + UTCUtil.formatUTCDate(timestamp)); + return this; + } + + @Override + public InlineVerify notAfter(Date timestamp) throws SOPGPException.UnsupportedOption { + commandList.add("--not-after=" + UTCUtil.formatUTCDate(timestamp)); + return this; + } + + @Override + public InlineVerify cert(InputStream cert) throws SOPGPException.BadData, IOException { + String envVar = "CERT_" + certCounter++; + commandList.add("@ENV:" + envVar); + envList.add(envVar + "=" + ExternalSOP.readFully(cert)); + return this; + } + + @Override + public ReadyWithResult> data(InputStream data) throws IOException, SOPGPException.NoSignature, SOPGPException.BadData { + 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 List writeTo(OutputStream outputStream) throws IOException, SOPGPException.NoSignature { + byte[] buf = new byte[4096]; + int r; + while ((r = data.read(buf)) > 0) { + processOut.write(buf, 0, r); + } + + data.close(); + processOut.close(); + + + while ((r = processIn.read(buf)) > 0) { + outputStream.write(buf, 0 , r); + } + + processIn.close(); + outputStream.close(); + + ExternalSOP.finish(process); + + return null; // TODO + } + }; + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/external-sop/src/test/java/sop/external/AbstractExternalSOPTest.java b/external-sop/src/test/java/sop/external/AbstractExternalSOPTest.java index 3a779ca..d557fd8 100644 --- a/external-sop/src/test/java/sop/external/AbstractExternalSOPTest.java +++ b/external-sop/src/test/java/sop/external/AbstractExternalSOPTest.java @@ -4,6 +4,7 @@ package sop.external; +import org.apache.maven.artifact.versioning.ComparableVersion; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import sop.SOP; @@ -14,6 +15,8 @@ import java.io.IOException; import java.io.InputStream; import java.util.Properties; +import static org.junit.jupiter.api.Assumptions.assumeFalse; + public abstract class AbstractExternalSOPTest { private static final Logger LOGGER = LoggerFactory.getLogger(AbstractExternalSOPTest.class); @@ -26,10 +29,20 @@ public abstract class AbstractExternalSOPTest { sop = new ExternalSOP(backend, environment); } + /** + * Return the SOP backend. + * + * @return SOP backend + */ public SOP getSop() { return sop; } + /** + * Return
true
iff the specified SOP backend binary is available and accessible. + * + * @return true if external SOP backend is usable + */ public static boolean isExternalSopInstalled() { String binary = readSopBackendFromProperties(); if (binary == null) { @@ -38,6 +51,69 @@ public abstract class AbstractExternalSOPTest { return new File(binary).exists(); } + public enum Is { + le("<"), + leq("<="), + eq("=="), + geq(">="), + ge(">"), + ; + + private final String display; + + Is(String display) { + this.display = display; + } + + public String toDisplay() { + return display; + } + } + + /** + * Ignore a test if the tested binary version matches a version criterion. + * Example: + * If the installed version of example-sop is 0.1.3,
ignoreIf("example-sop", Is.le, "0.1.4")
will + * make the test be ignored. + *
ignoreIf("example-sop", Is.eq, "0.1.3")
will skip the test as well. + *
ignoreIf("another-sop", Is.gt, "0.0.0")
will not skip the test, since the binary name does not match. + * + * @param name name of the binary + * @param is relation of the version + * @param version the reference version + */ + public void ignoreIf(String name, Is is, String version) { + String actualName = getSop().version().getName(); + String actualVersion = getSop().version().getVersion(); + + if (!name.matches(actualName)) { + // Name mismatch, do not ignore + return; + } + + ComparableVersion reference = new ComparableVersion(version); + ComparableVersion actual = new ComparableVersion(actualVersion); + + int res = actual.compareTo(reference); + String msg = "Skip since installed " + name + " " + actual + " " + is.toDisplay() + " " + reference; + switch (is) { + case le: + assumeFalse(res < 0, msg); + break; + case leq: + assumeFalse(res <= 0, msg); + case eq: + assumeFalse(res == 0, msg); + break; + case geq: + assumeFalse(res >= 0, msg); + break; + case ge: + assumeFalse(res > 0, msg); + break; + } + } + private static String readSopBackendFromProperties() { Properties properties = new Properties(); try { diff --git a/external-sop/src/test/java/sop/external/EncryptDecryptRoundTripTest.java b/external-sop/src/test/java/sop/external/EncryptDecryptRoundTripTest.java new file mode 100644 index 0000000..de16215 --- /dev/null +++ b/external-sop/src/test/java/sop/external/EncryptDecryptRoundTripTest.java @@ -0,0 +1,70 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.external; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledIf; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; + +@EnabledIf("sop.external.AbstractExternalSOPTest#isExternalSopInstalled") +public class EncryptDecryptRoundTripTest extends AbstractExternalSOPTest { + + @Test + public void encryptDecryptRoundTripAliceTest() throws IOException { + byte[] message = "Hello, World!\n".getBytes(StandardCharsets.UTF_8); + byte[] ciphertext = getSop().encrypt() + .withCert(TestKeys.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) + .plaintext(message) + .getBytes(); + + byte[] plaintext = getSop().decrypt() + .withKey(TestKeys.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) + .ciphertext(ciphertext) + .toByteArrayAndResult() + .getBytes(); + + assertArrayEquals(message, plaintext); + } + + @Test + public void encryptDecryptRoundTripBobTest() throws IOException { + byte[] message = "Hello, World!\n".getBytes(StandardCharsets.UTF_8); + byte[] ciphertext = getSop().encrypt() + .withCert(TestKeys.BOB_CERT.getBytes(StandardCharsets.UTF_8)) + .plaintext(message) + .getBytes(); + + byte[] plaintext = getSop().decrypt() + .withKey(TestKeys.BOB_KEY.getBytes(StandardCharsets.UTF_8)) + .ciphertext(ciphertext) + .toByteArrayAndResult() + .getBytes(); + + assertArrayEquals(message, plaintext); + } + + @Test + public void encryptDecryptRoundTripCarolTest() throws IOException { + ignoreIf("sqop", Is.geq, "0.0.0"); // sqop reports cert not encryption capable + + byte[] message = "Hello, World!\n".getBytes(StandardCharsets.UTF_8); + byte[] ciphertext = getSop().encrypt() + .withCert(TestKeys.CAROL_CERT.getBytes(StandardCharsets.UTF_8)) + .plaintext(message) + .getBytes(); + + byte[] plaintext = getSop().decrypt() + .withKey(TestKeys.CAROL_KEY.getBytes(StandardCharsets.UTF_8)) + .ciphertext(ciphertext) + .toByteArrayAndResult() + .getBytes(); + + assertArrayEquals(message, plaintext); + } +} diff --git a/external-sop/src/test/java/sop/external/ExternalExtractCertTest.java b/external-sop/src/test/java/sop/external/ExternalExtractCertTest.java index 6849179..559316f 100644 --- a/external-sop/src/test/java/sop/external/ExternalExtractCertTest.java +++ b/external-sop/src/test/java/sop/external/ExternalExtractCertTest.java @@ -9,16 +9,72 @@ import org.junit.jupiter.api.condition.EnabledIf; import java.io.IOException; import java.io.InputStream; +import java.nio.charset.StandardCharsets; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static sop.external.JUtils.arrayStartsWith; +import static sop.external.JUtils.assertArrayStartsWith; @EnabledIf("sop.external.AbstractExternalSOPTest#isExternalSopInstalled") public class ExternalExtractCertTest extends AbstractExternalSOPTest { + private static final String BEGIN_PGP_PUBLIC_KEY_BLOCK = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n"; + private static final byte[] BEGIN_PGP_PUBLIC_KEY_BLOCK_BYTES = BEGIN_PGP_PUBLIC_KEY_BLOCK.getBytes(StandardCharsets.UTF_8); + @Test - public void extractCertTest() throws IOException { - InputStream keyIn = getSop().generateKey().userId("Alice").generate().getInputStream(); - String cert = new String(getSop().extractCert().key(keyIn).getBytes()); - assertTrue(cert.startsWith("-----BEGIN PGP PUBLIC KEY BLOCK-----\n")); + public void extractArmoredCertFromArmoredKeyTest() throws IOException { + InputStream keyIn = getSop().generateKey() + .userId("Alice ") + .generate() + .getInputStream(); + + byte[] cert = getSop().extractCert().key(keyIn).getBytes(); + assertArrayStartsWith(cert, BEGIN_PGP_PUBLIC_KEY_BLOCK_BYTES); + } + + @Test + public void extractUnarmoredCertFromArmoredKeyTest() throws IOException { + InputStream keyIn = getSop().generateKey() + .userId("Alice ") + .generate() + .getInputStream(); + + byte[] cert = getSop().extractCert() + .noArmor() + .key(keyIn) + .getBytes(); + + assertFalse(arrayStartsWith(cert, BEGIN_PGP_PUBLIC_KEY_BLOCK_BYTES)); + } + + @Test + public void extractArmoredCertFromUnarmoredKeyTest() throws IOException { + InputStream keyIn = getSop().generateKey() + .userId("Alice ") + .noArmor() + .generate() + .getInputStream(); + + byte[] cert = getSop().extractCert() + .key(keyIn) + .getBytes(); + + assertArrayStartsWith(cert, BEGIN_PGP_PUBLIC_KEY_BLOCK_BYTES); + } + + @Test + public void extractUnarmoredCertFromUnarmoredKeyTest() throws IOException { + InputStream keyIn = getSop().generateKey() + .noArmor() + .userId("Alice ") + .generate() + .getInputStream(); + + byte[] cert = getSop().extractCert() + .noArmor() + .key(keyIn) + .getBytes(); + + assertFalse(arrayStartsWith(cert, BEGIN_PGP_PUBLIC_KEY_BLOCK_BYTES)); } } diff --git a/external-sop/src/test/java/sop/external/ExternalGenerateKeyTest.java b/external-sop/src/test/java/sop/external/ExternalGenerateKeyTest.java index 1ad4e51..c492ebe 100644 --- a/external-sop/src/test/java/sop/external/ExternalGenerateKeyTest.java +++ b/external-sop/src/test/java/sop/external/ExternalGenerateKeyTest.java @@ -8,23 +8,88 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledIf; import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static sop.external.JUtils.assertArrayStartsWith; @EnabledIf("sop.external.AbstractExternalSOPTest#isExternalSopInstalled") public class ExternalGenerateKeyTest extends AbstractExternalSOPTest { + private static final Charset UTF8 = StandardCharsets.UTF_8; + private static final String BEGIN_PGP_PRIVATE_KEY_BLOCK = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n"; + byte[] BEGIN_PGP_PRIVATE_KEY_BLOCK_BYTES = BEGIN_PGP_PRIVATE_KEY_BLOCK.getBytes(UTF8); + @Test public void generateKeyTest() throws IOException { - String key = new String(getSop().generateKey().userId("Alice").generate().getBytes()); - assertTrue(key.startsWith("-----BEGIN PGP PRIVATE KEY BLOCK-----\n")); + byte[] key = getSop().generateKey() + .userId("Alice ") + .generate() + .getBytes(); + + assertArrayStartsWith(key, BEGIN_PGP_PRIVATE_KEY_BLOCK_BYTES); + } + + @Test + public void generateKeyNoArmor() throws IOException { + byte[] key = getSop().generateKey() + .userId("Alice ") + .noArmor() + .generate() + .getBytes(); + + assertFalse(JUtils.arrayStartsWith(key, BEGIN_PGP_PRIVATE_KEY_BLOCK_BYTES)); + } + + @Test + public void generateKeyWithMultipleUserIdsTest() throws IOException { + byte[] key = getSop().generateKey() + .userId("Alice ") + .userId("Bob ") + .generate() + .getBytes(); + + assertArrayStartsWith(key, BEGIN_PGP_PRIVATE_KEY_BLOCK_BYTES); + } + + @Test + public void generateKeyWithoutUserIdTest() throws IOException { + ignoreIf("pgpainless-cli", Is.le, "1.3.15"); + + byte[] key = getSop().generateKey() + .generate() + .getBytes(); + + assertArrayStartsWith(key, BEGIN_PGP_PRIVATE_KEY_BLOCK_BYTES); } @Test public void generateKeyWithPasswordTest() throws IOException { - String key = new String(getSop().generateKey().userId("Alice").withKeyPassword("swßrdf1sh").generate().getBytes()); - assertEquals("asd", key); + ignoreIf("sqop", Is.le, "0.27.0"); + ignoreIf("pgpainless-cli", Is.le, "1.3.0"); + + byte[] key = getSop().generateKey() + .userId("Alice ") + .withKeyPassword("sw0rdf1sh") + .generate() + .getBytes(); + + assertArrayStartsWith(key, BEGIN_PGP_PRIVATE_KEY_BLOCK_BYTES); } + @Test + public void generateKeyWithMultipleUserIdsAndPassword() throws IOException { + ignoreIf("sqop", Is.le, "0.27.0"); + ignoreIf("pgpainless-cli", Is.le, "1.3.15"); + + byte[] key = getSop().generateKey() + .userId("Alice ") + .userId("Bob ") + .withKeyPassword("sw0rdf1sh") + .generate() + .getBytes(); + + assertArrayStartsWith(key, BEGIN_PGP_PRIVATE_KEY_BLOCK_BYTES); + } } diff --git a/external-sop/src/test/java/sop/external/ExternalVersionTest.java b/external-sop/src/test/java/sop/external/ExternalVersionTest.java index 8fbf371..bde6cb5 100644 --- a/external-sop/src/test/java/sop/external/ExternalVersionTest.java +++ b/external-sop/src/test/java/sop/external/ExternalVersionTest.java @@ -7,8 +7,8 @@ package sop.external; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledIf; -import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; @EnabledIf("sop.external.AbstractExternalSOPTest#isExternalSopInstalled") @@ -16,13 +16,15 @@ public class ExternalVersionTest extends AbstractExternalSOPTest { @Test public void versionNameTest() { - assertEquals("sqop", getSop().version().getName()); + String name = getSop().version().getName(); + assertNotNull(name); + assertFalse(name.isEmpty()); } @Test public void versionVersionTest() { String version = getSop().version().getVersion(); - assertTrue(version.matches("\\d+(\\.\\d+)*")); + assertTrue(version.matches("\\d+(\\.\\d+)*\\S*")); } @Test diff --git a/external-sop/src/test/java/sop/external/JUtils.java b/external-sop/src/test/java/sop/external/JUtils.java new file mode 100644 index 0000000..36d6d85 --- /dev/null +++ b/external-sop/src/test/java/sop/external/JUtils.java @@ -0,0 +1,53 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.external; + +import java.util.Arrays; + +import static org.junit.jupiter.api.Assertions.fail; + +public class JUtils { + + public static boolean arrayStartsWith(byte[] array, byte[] start) { + return arrayStartsWith(array, start, 0); + } + + public static boolean arrayStartsWith(byte[] array, byte[] start, int offset) { + if (offset < 0) { + throw new IllegalArgumentException("Offset cannot be negative"); + } + + if (start.length + offset > array.length) { + return false; + } + + for (int i = 0; i < start.length; i++) { + if (array[offset + i] != start[i]) { + return false; + } + } + return true; + } + + public static void assertArrayStartsWith(byte[] array, byte[] start) { + if (!arrayStartsWith(array, start)) { + byte[] actual = new byte[Math.min(start.length, array.length)]; + System.arraycopy(array, 0, actual, 0, actual.length); + fail("Array does not start with expected bytes.\n" + + "Expected: <" + Arrays.toString(start) + ">\n" + + "Actual: <" + Arrays.toString(actual) + ">"); + } + } + + public static void assertArrayStartsWith(byte[] array, byte[] start, int offset) { + if (!arrayStartsWith(array, start, offset)) { + byte[] actual = new byte[Math.min(start.length, array.length - offset)]; + System.arraycopy(array, offset, actual, 0, actual.length); + fail("Array does not start with expected bytes at offset " + offset + ".\n" + + "Expected: <" + Arrays.toString(start) + ">\n" + + "Actual: <" + Arrays.toString(actual) + ">"); + } + } +} diff --git a/external-sop/src/test/java/sop/external/TestKeys.java b/external-sop/src/test/java/sop/external/TestKeys.java new file mode 100644 index 0000000..1a2bb94 --- /dev/null +++ b/external-sop/src/test/java/sop/external/TestKeys.java @@ -0,0 +1,307 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.external; + +public class TestKeys { + + // 'Alice' key from draft-bre-openpgp-samples-00 + public static final String ALICE_CERT = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + + "Comment: EB85 BB5F A33A 75E1 5E94 4E63 F231 550C 4F47 E38E\n" + + "Comment: Alice Lovelace \n" + + "\n" + + "xjMEXEcE6RYJKwYBBAHaRw8BAQdArjWwk3FAqyiFbFBKT4TzXcVBqPTB3gmzlC/U\n" + + "b7O1u13NJkFsaWNlIExvdmVsYWNlIDxhbGljZUBvcGVucGdwLmV4YW1wbGU+wpAE\n" + + "ExYIADgCGwMFCwkIBwIGFQoJCAsCBBYCAwECHgECF4AWIQTrhbtfozp14V6UTmPy\n" + + "MVUMT0fjjgUCXaWfOgAKCRDyMVUMT0fjjukrAPoDnHBSogOmsHOsd9qGsiZpgRnO\n" + + "dypvbm+QtXZqth9rvwD9HcDC0tC+PHAsO7OTh1S1TC9RiJsvawAfCPaQZoed8gLO\n" + + "OARcRwTpEgorBgEEAZdVAQUBAQdAQv8GIa2rSTzgqbXCpDDYMiKRVitCsy203x3s\n" + + "E9+eviIDAQgHwngEGBYIACAWIQTrhbtfozp14V6UTmPyMVUMT0fjjgUCXEcE6QIb\n" + + "DAAKCRDyMVUMT0fjjlnQAQDFHUs6TIcxrNTtEZFjUFm1M0PJ1Dng/cDW4xN80fsn\n" + + "0QEA22Kr7VkCjeAEC08VSTeV+QFsmz55/lntWkwYWhmvOgE=\n" + + "=QX3Q\n" + + "-----END PGP PUBLIC KEY BLOCK-----\n"; + + public static final String ALICE_KEY = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" + + "Comment: EB85 BB5F A33A 75E1 5E94 4E63 F231 550C 4F47 E38E\n" + + "Comment: Alice Lovelace \n" + + "\n" + + "xVgEXEcE6RYJKwYBBAHaRw8BAQdArjWwk3FAqyiFbFBKT4TzXcVBqPTB3gmzlC/U\n" + + "b7O1u10AAP9XBeW6lzGOLx7zHH9AsUDUTb2pggYGMzd0P3ulJ2AfvQ4RzSZBbGlj\n" + + "ZSBMb3ZlbGFjZSA8YWxpY2VAb3BlbnBncC5leGFtcGxlPsKQBBMWCAA4AhsDBQsJ\n" + + "CAcCBhUKCQgLAgQWAgMBAh4BAheAFiEE64W7X6M6deFelE5j8jFVDE9H444FAl2l\n" + + "nzoACgkQ8jFVDE9H447pKwD6A5xwUqIDprBzrHfahrImaYEZzncqb25vkLV2arYf\n" + + "a78A/R3AwtLQvjxwLDuzk4dUtUwvUYibL2sAHwj2kGaHnfICx10EXEcE6RIKKwYB\n" + + "BAGXVQEFAQEHQEL/BiGtq0k84Km1wqQw2DIikVYrQrMttN8d7BPfnr4iAwEIBwAA\n" + + "/3/xFPG6U17rhTuq+07gmEvaFYKfxRB6sgAYiW6TMTpQEK7CeAQYFggAIBYhBOuF\n" + + "u1+jOnXhXpROY/IxVQxPR+OOBQJcRwTpAhsMAAoJEPIxVQxPR+OOWdABAMUdSzpM\n" + + "hzGs1O0RkWNQWbUzQ8nUOeD9wNbjE3zR+yfRAQDbYqvtWQKN4AQLTxVJN5X5AWyb\n" + + "Pnn+We1aTBhaGa86AQ==\n" + + "=3GfK\n" + + "-----END PGP PRIVATE KEY BLOCK-----\n"; + + // 'Bob' key from draft-bre-openpgp-samples-00 + public static final String BOB_CERT = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + + "Comment: D1A6 6E1A 23B1 82C9 980F 788C FBFC C82A 015E 7330\n" + + "Comment: Bob Babbage \n" + + "\n" + + "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\n" + + "NEJd3XZRzaXZE2aAMQ==\n" + + "=F9yX\n" + + "-----END PGP PUBLIC KEY BLOCK-----\n"; + + public static final String BOB_KEY = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" + + "Comment: D1A6 6E1A 23B1 82C9 980F 788C FBFC C82A 015E 7330\n" + + "Comment: Bob Babbage \n" + + "\n" + + "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\n" + + "=FAzO\n" + + "-----END PGP PRIVATE KEY BLOCK-----\n"; + + // 'Carol' key from draft-bre-openpgp-samples-00 + public static final String CAROL_CERT = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + + "Comment: 71FF DA00 4409 E5DD B0C3 E8F1 9BA7 89DC 76D6 849A\n" + + "Comment: Carol Oldstyle \n" + + "\n" + + "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\n" + + "=pa/S\n" + + "-----END PGP PUBLIC KEY BLOCK-----\n"; + + public static final String CAROL_KEY = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" + + "Comment: 71FF DA00 4409 E5DD B0C3 E8F1 9BA7 89DC 76D6 849A\n" + + "Comment: Carol Oldstyle \n" + + "\n" + + "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\n" + + "1FkOSekLi8WNMdUx3XMyvP8nJ65P2Q==\n" + + "=Xj8h\n" + + "-----END PGP PRIVATE KEY BLOCK-----\n"; +} diff --git a/sop-java/src/main/java/sop/exception/SOPGPException.java b/sop-java/src/main/java/sop/exception/SOPGPException.java index b7032c3..0e71d6b 100644 --- a/sop-java/src/main/java/sop/exception/SOPGPException.java +++ b/sop-java/src/main/java/sop/exception/SOPGPException.java @@ -219,6 +219,10 @@ public abstract class SOPGPException extends RuntimeException { public static final int EXIT_CODE = 53; + public ExpectedText() { + super(); + } + public ExpectedText(String message) { super(message); }