aboutsummaryrefslogtreecommitdiffstats
path: root/vespaclient-java
diff options
context:
space:
mode:
authorTor Brede Vekterli <vekterli@yahooinc.com>2022-11-08 17:06:15 +0100
committerTor Brede Vekterli <vekterli@yahooinc.com>2022-11-08 17:12:54 +0100
commit29993d7fa6af8edbc51dcd903a1e27d3f62b4e82 (patch)
tree19e211cf4be60d056dd4834b532e5027df279260 /vespaclient-java
parentf268379cb63e70cefaea18999767244a7d8bfc7f (diff)
Add a simple base conversion tool
Currently supports converting from and to any combination of base {16, 58, 62, 64}. Input is read from STDIN and is intentionally limited in length due to the algorithmic complexity of base conversions that are not a power of two. Converted value is written to STDOUT.
Diffstat (limited to 'vespaclient-java')
-rw-r--r--vespaclient-java/src/main/java/com/yahoo/vespa/security/tool/Main.java4
-rw-r--r--vespaclient-java/src/main/java/com/yahoo/vespa/security/tool/crypto/ConvertBaseTool.java98
-rw-r--r--vespaclient-java/src/test/java/com/yahoo/vespa/security/tool/CryptoToolsTest.java31
-rw-r--r--vespaclient-java/src/test/resources/expected-convert-base-help-output.txt10
-rw-r--r--vespaclient-java/src/test/resources/expected-help-output.txt2
5 files changed, 143 insertions, 2 deletions
diff --git a/vespaclient-java/src/main/java/com/yahoo/vespa/security/tool/Main.java b/vespaclient-java/src/main/java/com/yahoo/vespa/security/tool/Main.java
index 11bd8815d77..0498154aa91 100644
--- a/vespaclient-java/src/main/java/com/yahoo/vespa/security/tool/Main.java
+++ b/vespaclient-java/src/main/java/com/yahoo/vespa/security/tool/Main.java
@@ -1,6 +1,7 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.security.tool;
+import com.yahoo.vespa.security.tool.crypto.ConvertBaseTool;
import com.yahoo.vespa.security.tool.crypto.DecryptTool;
import com.yahoo.vespa.security.tool.crypto.EncryptTool;
import com.yahoo.vespa.security.tool.crypto.KeygenTool;
@@ -45,7 +46,8 @@ public class Main {
}
private static final List<Tool> TOOLS = List.of(
- new KeygenTool(), new EncryptTool(), new DecryptTool(), new TokenInfoTool());
+ new KeygenTool(), new EncryptTool(), new DecryptTool(), new TokenInfoTool(),
+ new ConvertBaseTool());
private static Optional<Tool> toolFromCliArgs(String[] args) {
if (args.length == 0) {
diff --git a/vespaclient-java/src/main/java/com/yahoo/vespa/security/tool/crypto/ConvertBaseTool.java b/vespaclient-java/src/main/java/com/yahoo/vespa/security/tool/crypto/ConvertBaseTool.java
new file mode 100644
index 00000000000..120fc8a6f98
--- /dev/null
+++ b/vespaclient-java/src/main/java/com/yahoo/vespa/security/tool/crypto/ConvertBaseTool.java
@@ -0,0 +1,98 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.security.tool.crypto;
+
+import com.yahoo.security.Base58;
+import com.yahoo.security.Base62;
+import com.yahoo.vespa.security.tool.CliUtils;
+import com.yahoo.vespa.security.tool.Tool;
+import com.yahoo.vespa.security.tool.ToolDescription;
+import com.yahoo.vespa.security.tool.ToolInvocation;
+import org.apache.commons.cli.Option;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.util.Base64;
+import java.util.List;
+
+import static com.yahoo.security.ArrayUtils.fromUtf8Bytes;
+import static com.yahoo.security.ArrayUtils.hex;
+import static com.yahoo.security.ArrayUtils.unhex;
+
+/**
+ * Simple tool to convert between different Base N encodings, for a fixed set of N.
+ *
+ * @author vekterli
+ */
+public class ConvertBaseTool implements Tool {
+
+ private static final int MAX_IN_BYTES = 1024;
+
+ static final String FROM_OPTION = "from";
+ static final String TO_OPTION = "to";
+
+ private static final List<Option> OPTIONS = List.of(
+ Option.builder("f")
+ .longOpt(FROM_OPTION)
+ .hasArg(true)
+ .required(false)
+ .desc("From base. Supported values: 16, 58, 62, 64")
+ .build(),
+ Option.builder("t")
+ .longOpt(TO_OPTION)
+ .hasArg(true)
+ .required(false)
+ .desc("To base. Supported values: 16, 58, 62, 64")
+ .build());
+
+ @Override
+ public String name() {
+ return "convert-base";
+ }
+
+ @Override
+ public ToolDescription description() {
+ return new ToolDescription(
+ "--from <base N> --to <base M>",
+ ("Reads up to %d bytes of STDIN interpreted as a base N string (ignoring " +
+ "whitespace) and writes to STDOUT as a base M string. Note that base 64 is " +
+ "expected to be in (and is output as) the URL-safe alphabet (padding optional " +
+ "for input, no padding for output).").formatted(MAX_IN_BYTES),
+ "Note: this is a BETA tool version; its interface may be changed at any time.",
+ OPTIONS);
+ }
+
+ @Override
+ public int invoke(ToolInvocation invocation) {
+ try {
+ var arguments = invocation.arguments();
+ var fromBase = Integer.parseInt(CliUtils.optionOrThrow(arguments, FROM_OPTION));
+ var toBase = Integer.parseInt(CliUtils.optionOrThrow(arguments, TO_OPTION));
+ // We cap the input length since non-base(16|64) transforms are O(n^2) and we don't want
+ // to risk melting someone's CPU by them piping something large into the process by accident.
+ byte[] inBytes = invocation.stdIn().readAllBytes();
+ if (inBytes.length > MAX_IN_BYTES) {
+ throw new IllegalArgumentException("Input size is too large (%d), max is %d"
+ .formatted(inBytes.length, MAX_IN_BYTES));
+ }
+ var inString = fromUtf8Bytes(inBytes).strip(); // We ignore whitespace to avoid trailing \n issues
+ byte[] decoded = switch (fromBase) {
+ case 16 -> unhex(inString);
+ case 58 -> Base58.codec().decode(inString);
+ case 62 -> Base62.codec().decode(inString);
+ case 64 -> Base64.getUrlDecoder().decode(inString);
+ default -> throw new IllegalArgumentException("Unsupported from-base: %d".formatted(fromBase));
+ };
+ String encoded = switch (toBase) {
+ case 16 -> hex(decoded);
+ case 58 -> Base58.codec().encode(decoded);
+ case 62 -> Base62.codec().encode(decoded);
+ case 64 -> Base64.getUrlEncoder().withoutPadding().encodeToString(decoded);
+ default -> throw new IllegalArgumentException("Unsupported to-base: %d".formatted(toBase));
+ };
+ invocation.stdOut().println(encoded);
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ return 0;
+ }
+}
diff --git a/vespaclient-java/src/test/java/com/yahoo/vespa/security/tool/CryptoToolsTest.java b/vespaclient-java/src/test/java/com/yahoo/vespa/security/tool/CryptoToolsTest.java
index 8ba24d2cccc..f529ed828ea 100644
--- a/vespaclient-java/src/test/java/com/yahoo/vespa/security/tool/CryptoToolsTest.java
+++ b/vespaclient-java/src/test/java/com/yahoo/vespa/security/tool/CryptoToolsTest.java
@@ -40,6 +40,12 @@ public class CryptoToolsTest {
assertEquals(readTestResource(expectedFile), procOut.stdOut());
}
+ private void verifyStdoutEquals(List<String> args, String stdIn, String expectedMessage) throws IOException {
+ var procOut = runMain(args, toUtf8Bytes(stdIn), Map.of());
+ assertEquals(0, procOut.exitCode());
+ assertEquals(expectedMessage, procOut.stdOut());
+ }
+
private void verifyStderrEquals(List<String> args, String expectedMessage) throws IOException {
var procOut = runMain(args, EMPTY_BYTES, Map.of());
assertEquals(1, procOut.exitCode()); // Assume checking stderr is because of a failure.
@@ -77,6 +83,11 @@ public class CryptoToolsTest {
}
@Test
+ void convert_base_help_printed_if_help_option_given_to_subtool() throws IOException {
+ verifyStdoutMatchesFile(List.of("convert-base", "--help"), "expected-convert-base-help-output.txt");
+ }
+
+ @Test
void missing_required_parameter_prints_error_message() throws IOException {
// We don't test all possible input arguments to all tools, since it'd be too closely
// bound to the order in which the implementation checks for argument presence.
@@ -240,6 +251,26 @@ public class CryptoToolsTest {
}
@Test
+ void convert_base_reads_stdin_and_prints_conversion_on_stdout() throws IOException {
+ // Check all possible output encodings
+ verifyStdoutEquals(List.of("convert-base", "--from", "16", "--to", "16"), "0000287fb4cd", "0000287fb4cd\n");
+ verifyStdoutEquals(List.of("convert-base", "--from", "16", "--to", "58"), "0000287fb4cd", "11233QC4\n");
+ verifyStdoutEquals(List.of("convert-base", "--from", "16", "--to", "62"), "0000287fb4cd", "00jyw3x\n");
+ verifyStdoutEquals(List.of("convert-base", "--from", "16", "--to", "64"), "0000287fb4cd", "AAAof7TN\n");
+
+ // Check a single output encoding for each input encoding, making the simplifying assumption that
+ // decoding and encoding is independent. Base 16 already covered above.
+ verifyStdoutEquals(List.of("convert-base", "--from", "58", "--to", "16"), "11233QC4", "0000287fb4cd\n");
+ verifyStdoutEquals(List.of("convert-base", "--from", "62", "--to", "16"), "00jyw3x", "0000287fb4cd\n");
+ verifyStdoutEquals(List.of("convert-base", "--from", "64", "--to", "16"), "AAAof7TN", "0000287fb4cd\n");
+ }
+
+ @Test
+ void convert_base_tool_ignores_whitespace_on_stdin() throws IOException {
+ verifyStdoutEquals(List.of("convert-base", "--from", "16", "--to", "58"), " 0000287fb4cd\n", "11233QC4\n");
+ }
+
+ @Test
void can_end_to_end_keygen_encrypt_and_decrypt_via_files() throws IOException {
String greatSecret = "Dogs can't look up";
diff --git a/vespaclient-java/src/test/resources/expected-convert-base-help-output.txt b/vespaclient-java/src/test/resources/expected-convert-base-help-output.txt
new file mode 100644
index 00000000000..8a65c2240e0
--- /dev/null
+++ b/vespaclient-java/src/test/resources/expected-convert-base-help-output.txt
@@ -0,0 +1,10 @@
+usage: vespa-security convert-base --from <base N> --to <base M>
+Reads up to 1024 bytes of STDIN interpreted as a base N string (ignoring
+whitespace) and writes to STDOUT as a base M string. Note that base 64 is
+expected to be in (and is output as) the URL-safe alphabet (padding
+optional for input, no padding for output).
+ -f,--from <arg> From base. Supported values: 16, 58, 62, 64
+ -h,--help Show help
+ -t,--to <arg> To base. Supported values: 16, 58, 62, 64
+Note: this is a BETA tool version; its interface may be changed at any
+time.
diff --git a/vespaclient-java/src/test/resources/expected-help-output.txt b/vespaclient-java/src/test/resources/expected-help-output.txt
index c973735ad46..8cea1366973 100644
--- a/vespaclient-java/src/test/resources/expected-help-output.txt
+++ b/vespaclient-java/src/test/resources/expected-help-output.txt
@@ -1,4 +1,4 @@
usage: vespa-security <tool> [TOOL OPTIONS]
-Where <tool> is one of: keygen, encrypt, decrypt, token-info
+Where <tool> is one of: keygen, encrypt, decrypt, token-info, convert-base
-h,--help Show help
Invoke vespa-security <tool> --help for tool-specific help