diff options
author | Tor Brede Vekterli <vekterli@yahooinc.com> | 2022-11-17 14:51:24 +0100 |
---|---|---|
committer | Tor Brede Vekterli <vekterli@yahooinc.com> | 2022-11-17 14:58:27 +0100 |
commit | bb9b37dd7fe05a54ec90f03fdea96a571a76451f (patch) | |
tree | 593a45db8ed0b0b6d1e9d3f7678b6dd66eaf182b /vespaclient-java | |
parent | 9eb4b08fdb6e01f23ebc68b1085a7241ad5824f2 (diff) |
Support auto-resolving private key files based on token key ID
Lets a user specify a private key directory either with a command
line argument or via an environment variable. If a directory is
provided, the private key to use will be attempted auto-resolved
based on the key ID stored in the token. This only applies if the
key ID is comprised of exclusively path-safe characters.
Diffstat (limited to 'vespaclient-java')
8 files changed, 240 insertions, 26 deletions
diff --git a/vespaclient-java/src/main/java/com/yahoo/vespa/security/tool/ToolInvocation.java b/vespaclient-java/src/main/java/com/yahoo/vespa/security/tool/ToolInvocation.java index d1ff2687137..13df714a268 100644 --- a/vespaclient-java/src/main/java/com/yahoo/vespa/security/tool/ToolInvocation.java +++ b/vespaclient-java/src/main/java/com/yahoo/vespa/security/tool/ToolInvocation.java @@ -6,6 +6,7 @@ import org.apache.commons.cli.CommandLine; import java.io.InputStream; import java.io.PrintStream; import java.util.Map; +import java.util.function.Supplier; /** * @author vekterli @@ -17,4 +18,10 @@ public record ToolInvocation(CommandLine arguments, PrintStream stdError, boolean debugMode) { + public void printIfDebug(Supplier<String> stringSupplier) { + if (debugMode) { + stdError.println(stringSupplier.get()); + } + } + } diff --git a/vespaclient-java/src/main/java/com/yahoo/vespa/security/tool/crypto/CipherUtils.java b/vespaclient-java/src/main/java/com/yahoo/vespa/security/tool/crypto/CipherUtils.java index 87d3cb4d9f0..5cb40aa8f3b 100644 --- a/vespaclient-java/src/main/java/com/yahoo/vespa/security/tool/crypto/CipherUtils.java +++ b/vespaclient-java/src/main/java/com/yahoo/vespa/security/tool/crypto/CipherUtils.java @@ -19,7 +19,7 @@ public class CipherUtils { * * @param input source stream to read from * @param output destination stream to write to - * @param cipher an {@link AeadCipher} created with for either encryption or decryption + * @param cipher an {@link AeadCipher} created for either encryption or decryption * @throws IOException if any file operation fails */ public static void streamEncipher(InputStream input, OutputStream output, AeadCipher cipher) throws IOException { diff --git a/vespaclient-java/src/main/java/com/yahoo/vespa/security/tool/crypto/DecryptTool.java b/vespaclient-java/src/main/java/com/yahoo/vespa/security/tool/crypto/DecryptTool.java index 2cc724538d4..b22afb7e5fb 100644 --- a/vespaclient-java/src/main/java/com/yahoo/vespa/security/tool/crypto/DecryptTool.java +++ b/vespaclient-java/src/main/java/com/yahoo/vespa/security/tool/crypto/DecryptTool.java @@ -1,8 +1,6 @@ // 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.KeyId; -import com.yahoo.security.KeyUtils; import com.yahoo.security.SealedSharedKey; import com.yahoo.security.SharedKeyGenerator; import com.yahoo.vespa.security.tool.CliUtils; @@ -12,11 +10,12 @@ import com.yahoo.vespa.security.tool.ToolInvocation; import org.apache.commons.cli.Option; import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Paths; import java.util.List; import java.util.Optional; +import static com.yahoo.vespa.security.tool.crypto.ToolUtils.PRIVATE_KEY_DIR_OPTION; +import static com.yahoo.vespa.security.tool.crypto.ToolUtils.PRIVATE_KEY_FILE_OPTION; + /** * Tooling for decrypting a file using a private key that corresponds to the public key used * to originally encrypt the file. @@ -28,7 +27,6 @@ import java.util.Optional; public class DecryptTool implements Tool { static final String OUTPUT_FILE_OPTION = "output-file"; - static final String PRIVATE_KEY_FILE_OPTION = "private-key-file"; static final String EXPECTED_KEY_ID_OPTION = "expected-key-id"; static final String TOKEN_OPTION = "token"; @@ -46,6 +44,13 @@ public class DecryptTool implements Tool { .required(false) .desc("Private key file in Base58 encoded format") .build(), + Option.builder("d") + .longOpt(PRIVATE_KEY_DIR_OPTION) + .hasArg(true) + .required(false) + .desc("Private key file directory used for automatically looking up " + + "private keys based on the key ID specified as part of a token.") + .build(), Option.builder("e") .longOpt(EXPECTED_KEY_ID_OPTION) .hasArg(true) @@ -83,17 +88,14 @@ public class DecryptTool implements Tool { if (leftoverArgs.length != 1) { throw new IllegalArgumentException("Expected exactly 1 file argument to decrypt"); } - var inputArg = leftoverArgs[0]; - var maybeKeyId = Optional.ofNullable(arguments.hasOption(EXPECTED_KEY_ID_OPTION) - ? arguments.getOptionValue(EXPECTED_KEY_ID_OPTION) - : null); + var inputArg = leftoverArgs[0]; + var maybeKeyId = Optional.ofNullable(arguments.getOptionValue(EXPECTED_KEY_ID_OPTION)); var outputArg = CliUtils.optionOrThrow(arguments, OUTPUT_FILE_OPTION); var tokenString = CliUtils.optionOrThrow(arguments, TOKEN_OPTION); var sealedSharedKey = SealedSharedKey.fromTokenString(tokenString.strip()); ToolUtils.verifyExpectedKeyId(sealedSharedKey, maybeKeyId); - var privKeyPath = Paths.get(CliUtils.optionOrThrow(arguments, PRIVATE_KEY_FILE_OPTION)); - var privateKey = KeyUtils.fromBase58EncodedX25519PrivateKey(Files.readString(privKeyPath).strip()); + var privateKey = ToolUtils.resolvePrivateKeyFromInvocation(invocation, sealedSharedKey.keyId()); var secretShared = SharedKeyGenerator.fromSealedKey(sealedSharedKey, privateKey); var cipher = SharedKeyGenerator.makeAesGcmDecryptionCipher(secretShared); diff --git a/vespaclient-java/src/main/java/com/yahoo/vespa/security/tool/crypto/ResealTool.java b/vespaclient-java/src/main/java/com/yahoo/vespa/security/tool/crypto/ResealTool.java index e9bc0ae8fee..83fdf6998df 100644 --- a/vespaclient-java/src/main/java/com/yahoo/vespa/security/tool/crypto/ResealTool.java +++ b/vespaclient-java/src/main/java/com/yahoo/vespa/security/tool/crypto/ResealTool.java @@ -12,11 +12,12 @@ import com.yahoo.vespa.security.tool.ToolInvocation; import org.apache.commons.cli.Option; import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Paths; import java.util.List; import java.util.Optional; +import static com.yahoo.vespa.security.tool.crypto.ToolUtils.PRIVATE_KEY_DIR_OPTION; +import static com.yahoo.vespa.security.tool.crypto.ToolUtils.PRIVATE_KEY_FILE_OPTION; + /** * Tooling for resealing a token for another recipient. This allows for delegating * decryption to another party without having to reveal the private key of the original @@ -26,7 +27,6 @@ import java.util.Optional; */ public class ResealTool implements Tool { - static final String PRIVATE_KEY_FILE_OPTION = "private-key-file"; static final String EXPECTED_KEY_ID_OPTION = "expected-key-id"; static final String RECIPIENT_KEY_ID_OPTION = "key-id"; static final String RECIPIENT_PUBLIC_KEY_OPTION = "recipient-public-key"; @@ -38,6 +38,13 @@ public class ResealTool implements Tool { .required(false) .desc("Private key file in Base58 encoded format") .build(), + Option.builder("d") + .longOpt(PRIVATE_KEY_DIR_OPTION) + .hasArg(true) + .required(false) + .desc("Private key file directory used for automatically looking up " + + "private keys based on the key ID specified as part of a token.") + .build(), Option.builder("e") .longOpt(EXPECTED_KEY_ID_OPTION) .hasArg(true) @@ -90,8 +97,7 @@ public class ResealTool implements Tool { var recipientPubKey = KeyUtils.fromBase58EncodedX25519PublicKey(CliUtils.optionOrThrow(arguments, RECIPIENT_PUBLIC_KEY_OPTION).strip()); var recipientKeyId = KeyId.ofString(CliUtils.optionOrThrow(arguments, RECIPIENT_KEY_ID_OPTION)); - var privKeyPath = Paths.get(CliUtils.optionOrThrow(arguments, PRIVATE_KEY_FILE_OPTION)); - var privateKey = KeyUtils.fromBase58EncodedX25519PrivateKey(Files.readString(privKeyPath).strip()); + var privateKey = ToolUtils.resolvePrivateKeyFromInvocation(invocation, sealedSharedKey.keyId()); var secretShared = SharedKeyGenerator.fromSealedKey(sealedSharedKey, privateKey); var resealedShared = SharedKeyGenerator.reseal(secretShared, recipientPubKey, recipientKeyId); diff --git a/vespaclient-java/src/main/java/com/yahoo/vespa/security/tool/crypto/ToolUtils.java b/vespaclient-java/src/main/java/com/yahoo/vespa/security/tool/crypto/ToolUtils.java index 32e9c6679f6..2a25832708c 100644 --- a/vespaclient-java/src/main/java/com/yahoo/vespa/security/tool/crypto/ToolUtils.java +++ b/vespaclient-java/src/main/java/com/yahoo/vespa/security/tool/crypto/ToolUtils.java @@ -2,15 +2,31 @@ package com.yahoo.vespa.security.tool.crypto; import com.yahoo.security.KeyId; +import com.yahoo.security.KeyUtils; import com.yahoo.security.SealedSharedKey; +import com.yahoo.vespa.security.tool.CliUtils; +import com.yahoo.vespa.security.tool.ToolInvocation; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.attribute.PosixFilePermission; +import java.security.interfaces.XECPrivateKey; import java.util.Optional; +import java.util.regex.Pattern; /** * @author vekterli */ public class ToolUtils { + static final String PRIVATE_KEY_FILE_OPTION = "private-key-file"; + static final String PRIVATE_KEY_DIR_OPTION = "private-key-dir"; + static final String PRIVATE_KEY_DIR_ENV_VAR = "VESPA_CRYPTO_CLI_PRIVATE_KEY_DIR"; + + static final Pattern SAFE_KEY_ID_PATTERN = Pattern.compile("^[a-zA-Z0-9_-]+$"); + static void verifyExpectedKeyId(SealedSharedKey sealedSharedKey, Optional<String> maybeKeyId) { if (maybeKeyId.isPresent()) { var myKeyId = KeyId.ofString(maybeKeyId.get()); @@ -22,4 +38,65 @@ public class ToolUtils { } } + private static void verifyKeyIdIsPathSafe(KeyId keyId) { + String keyIdStr = keyId.asString(); + if (!SAFE_KEY_ID_PATTERN.matcher(keyIdStr).matches()) { + throw new IllegalArgumentException("The token key ID is not comprised of path-safe characters; refusing " + + "to auto-deduce key file name"); + } + } + + private static void verifyPrivateKeyFileNotWorldReadable(Path keyPath) throws IOException { + var privKeyPerms = Files.getPosixFilePermissions(keyPath); + if (privKeyPerms.contains(PosixFilePermission.OTHERS_READ)) { + throw new IllegalArgumentException("Private key file '%s' is insecurely world-readable; refusing to read it" + .formatted(keyPath.toAbsolutePath())); + } + } + + private static XECPrivateKey attemptResolvePrivateKeyFromDir(Path privKeyDirPath, KeyId tokenKeyId) throws IOException { + if (!Files.isDirectory(privKeyDirPath)) { + throw new IllegalArgumentException("'%s' is not a valid directory".formatted(privKeyDirPath.toAbsolutePath())); + } + verifyKeyIdIsPathSafe(tokenKeyId); + var keyPath = privKeyDirPath.resolve(tokenKeyId.asString() + ".key"); + if (!Files.exists(keyPath)) { + // We've verified the key ID contents, so we know it's safe to print here + throw new IllegalArgumentException("Could not find a private key file matching token key ID '%s'" + .formatted(tokenKeyId.asString())); + } + verifyPrivateKeyFileNotWorldReadable(keyPath); + return KeyUtils.fromBase58EncodedX25519PrivateKey(Files.readString(keyPath).strip()); + } + + public static XECPrivateKey resolvePrivateKeyFromInvocation(ToolInvocation invocation, KeyId tokenKeyId) throws IOException { + var arguments = invocation.arguments(); + var envVars = invocation.envVars(); + + if (arguments.hasOption(PRIVATE_KEY_FILE_OPTION)) { + if (arguments.hasOption(PRIVATE_KEY_DIR_OPTION)) { + throw new IllegalArgumentException("--%s and --%s cannot be specified at the same time" + .formatted(PRIVATE_KEY_FILE_OPTION, PRIVATE_KEY_DIR_OPTION)); + } + var privKeyFilePath = Paths.get(arguments.getOptionValue(PRIVATE_KEY_FILE_OPTION)); + invocation.printIfDebug(() -> "Using private key file '%s'".formatted(privKeyFilePath)); + if (!Files.exists(privKeyFilePath)) { + throw new IllegalArgumentException("Specified private key file '%s' does not exist" + .formatted(privKeyFilePath.toAbsolutePath())); + } + verifyPrivateKeyFileNotWorldReadable(privKeyFilePath); + return KeyUtils.fromBase58EncodedX25519PrivateKey(Files.readString(privKeyFilePath).strip()); + } else if (arguments.hasOption(PRIVATE_KEY_DIR_OPTION) || envVars.containsKey(PRIVATE_KEY_DIR_ENV_VAR)) { + // Explicitly provided command line directory is preferred over env var, if set + var privKeyDirPath = Paths.get(arguments.hasOption(PRIVATE_KEY_DIR_OPTION) + ? arguments.getOptionValue(PRIVATE_KEY_DIR_OPTION) + : envVars.get(PRIVATE_KEY_DIR_ENV_VAR)); + invocation.printIfDebug(() -> "Using private key lookup directory '%s'".formatted(privKeyDirPath)); + return attemptResolvePrivateKeyFromDir(privKeyDirPath, tokenKeyId); + } else { + throw new IllegalArgumentException("No private key specified. Must specify either --%s or --%s" + .formatted(PRIVATE_KEY_FILE_OPTION, PRIVATE_KEY_DIR_OPTION)); + } + } + } 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 42b747df678..d6dd65e1b2c 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 @@ -17,6 +17,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.attribute.PosixFilePermissions; +import java.util.Base64; import java.util.List; import java.util.Map; @@ -57,6 +58,12 @@ public class CryptoToolsTest { assertEquals(expectedMessage, procOut.stdErr()); } + private static void writePrivateKeyFile(Path keyPath, String contents) throws IOException { + var privFilePerms = PosixFilePermissions.fromString("rw-------"); + Files.createFile(keyPath, PosixFilePermissions.asFileAttribute(privFilePerms)); + Files.writeString(keyPath, contents); + } + @Test void top_level_help_page_printed_if_help_option_given() throws IOException { verifyStdoutMatchesFile(List.of("--help"), "expected-help-output.txt"); @@ -114,7 +121,7 @@ public class CryptoToolsTest { void keygen_fails_by_default_if_output_file_exists() throws IOException { Path privKeyFile = pathInTemp("priv.txt"); Path pubKeyFile = pathInTemp("pub.txt"); - Files.writeString(privKeyFile, TEST_PRIV_KEY); + writePrivateKeyFile(privKeyFile, TEST_PRIV_KEY); verifyStderrEquals(List.of("keygen", "--private-out-file", absPathOf(privKeyFile), @@ -149,7 +156,7 @@ public class CryptoToolsTest { void keygen_allowed_if_output_file_exists_and_explicit_overwrite_option_specified() throws IOException { Path privKeyFile = pathInTemp("priv.txt"); Path pubKeyFile = pathInTemp("pub.txt"); - Files.writeString(privKeyFile, TEST_PRIV_KEY); + writePrivateKeyFile(privKeyFile, TEST_PRIV_KEY); Files.writeString(pubKeyFile, TEST_PUB_KEY); var procOut = runMain(List.of("keygen", @@ -208,7 +215,7 @@ public class CryptoToolsTest { @Test void decrypt_fails_with_error_message_if_no_input_file_is_given() throws IOException { Path privKeyFile = pathInTemp("priv.txt"); - Files.writeString(privKeyFile, TEST_PRIV_KEY); + writePrivateKeyFile(privKeyFile, TEST_PRIV_KEY); verifyStderrEquals(List.of("decrypt", "--output-file", "foo", @@ -221,7 +228,7 @@ public class CryptoToolsTest { @Test void decrypt_fails_with_error_message_if_input_file_does_not_exist() throws IOException { Path privKeyFile = pathInTemp("priv.txt"); - Files.writeString(privKeyFile, TEST_PRIV_KEY); + writePrivateKeyFile(privKeyFile, TEST_PRIV_KEY); verifyStderrEquals(List.of("decrypt", "no-such-file", @@ -235,7 +242,7 @@ public class CryptoToolsTest { @Test void decrypt_fails_with_error_message_if_expected_key_id_does_not_match_key_id_in_token() throws IOException { Path privKeyFile = pathInTemp("priv.txt"); - Files.writeString(privKeyFile, TEST_PRIV_KEY); + writePrivateKeyFile(privKeyFile, TEST_PRIV_KEY); Path inputFile = pathInTemp("input.txt"); Files.writeString(inputFile, "dummy-not-actually-encrypted-data"); @@ -251,6 +258,23 @@ public class CryptoToolsTest { } @Test + void decrypt_fails_with_error_message_if_private_key_file_is_world_readable() throws IOException { + Path privKeyFile = pathInTemp("priv.txt"); + Files.writeString(privKeyFile, TEST_PRIV_KEY); // Don't restrict file permissions + + Path inputFile = pathInTemp("input.txt"); + Files.writeString(inputFile, "dummy-not-actually-encrypted-data"); + + verifyStderrEquals(List.of("decrypt", + absPathOf(inputFile), + "--output-file", "foo", + "--private-key-file", absPathOf(privKeyFile), + "--token", TEST_TOKEN), + ("Invalid command line arguments: Private key file '%s' is insecurely " + + "world-readable; refusing to read it\n").formatted(absPathOf(privKeyFile))); + } + + @Test void token_info_fails_with_error_message_if_no_token_string_given() throws IOException { verifyStderrEquals(List.of("token-info"), "Invalid command line arguments: Expected exactly 1 token string argument\n"); @@ -287,7 +311,7 @@ public class CryptoToolsTest { String recipientPubKeyStr = "AiUirFvFuLJ6s71QBNxiRcctB4umzM6r2roP4Rf8WDKM"; Path privKeyFile = pathInTemp("my-priv.txt"); - Files.writeString(privKeyFile, TEST_PRIV_KEY); + writePrivateKeyFile(privKeyFile, TEST_PRIV_KEY); var procOut = runMain(List.of( "reseal", @@ -407,14 +431,105 @@ public class CryptoToolsTest { assertEquals(greatSecret, procOut.stdOut()); } + @Test + void can_look_up_private_key_by_token_key_id() throws Exception { + var bobPrivKeyStr = "DDYLatvM8NAs2hvccBPCJf7GvxK97AYhG4vf4Vz61mKX"; + var alicePrivKeyStr = "AiKHsqgVXVe1dDYAtH23Hn1m82iCJSseiS9aZpam9sPG"; + var plaintextData = "the seagulls will attack the discarded french fries at dawn\n"; + var encryptedDataB64 = "tLh3dL7Ecq/l4E35IHjS9/oCU2wTVv4KSk+dBVIQxl9PFnCLgpTF" + + "OkhpCLQC9hqHfvco1bsV/+Gq6x2W8tpUoIR1X8GU04rIyBDYaw=="; + // Token with key id "bobs-key-1" + var tokenToBob = "1emu5Os1qeuJSkPeyYFKKBQl3r7a9GKyBR0k7QAcqTSmdm6XAPSVn" + + "JJYI2RKVLodmB1ZUAwLDMMrvKtmY2d8Seo0VwA8rzUHTPDI8jO"; + // Token with key id "alices-key-1" + var tokenToAlice = "STZC6ERSqAu1wWbV0Dvsw68iMMTye15sNbXz7cU8cuGMARZX5HGBO" + + "qiGwz3O4CmjaaeYfMnDqMJ7rKAA2GUIsKLio8Wp2gjgf3rJjZ3ha"; + // Token with key id "eves-key" + var tokenToEve = "5z0i4pTaWzlYFDDyIPl0wO2WWRuL0RTlP3fmM1mcWBYUg28C7jY3R" + + "Kc6ymz3omho75jWR0v6AmcCHioRUwfOGMskAQGG27Lqfhvp"; + // Token with key id "/etc/passwd"... + var unsafeIdToken = "6rh1141HZxvToeRYZsTE7K6vmihcrLgBHj75XvTBdkw9nMVvAqXU3" + + "7TkL8vuR40iCQYQFpyDqoorTF4HcfKLNsl7vqgkcje3kE6wCtPa"; + + var keyDirPath = pathInTemp("my-priv-keys"); + Files.createDirectory(keyDirPath); + writePrivateKeyFile(keyDirPath.resolve("bobs-key-1.key"), bobPrivKeyStr); + writePrivateKeyFile(keyDirPath.resolve("alices-key-1.key"), alicePrivKeyStr); + + Path encryptedFile = pathInTemp("secret.enc"); + Files.write(encryptedFile, Base64.getDecoder().decode(encryptedDataB64)); + + // First test with explicit key dir argument. + // This shall look up 'bobs-key-1.key' in the private key directory + var procOut = runMain(List.of( + "decrypt", + absPathOf(encryptedFile), + "--output-file", "-", + "--private-key-dir", absPathOf(keyDirPath), + "--token", tokenToBob + )); + assertEquals("", procOut.stdErr()); + assertEquals(0, procOut.exitCode()); + assertEquals(plaintextData, procOut.stdOut()); + + // This shall look up 'alices-key-1.key' in the private key directory + procOut = runMain(List.of( + "decrypt", + absPathOf(encryptedFile), + "--output-file", "-", + "--private-key-dir", absPathOf(keyDirPath), + "--token", tokenToAlice + )); + assertEquals("", procOut.stdErr()); + assertEquals(0, procOut.exitCode()); + assertEquals(plaintextData, procOut.stdOut()); + + // This shall look up 'eves-key.key' in the private key directory, which does not exist. + procOut = runMain(List.of( + "decrypt", + absPathOf(encryptedFile), + "--output-file", "-", + "--private-key-dir", absPathOf(keyDirPath), + "--token", tokenToEve + )); + assertEquals("Invalid command line arguments: Could not find a private key " + + "file matching token key ID 'eves-key'\n", + procOut.stdErr()); + assertEquals(1, procOut.exitCode()); + assertEquals("", procOut.stdOut()); + + // Should also work if environment variable is set instead of key dir argument + var env = Map.of("VESPA_CRYPTO_CLI_PRIVATE_KEY_DIR", absPathOf(keyDirPath)); + procOut = runMain(List.of( + "decrypt", + absPathOf(encryptedFile), + "--output-file", "-", + "--token", tokenToBob + ), EMPTY_BYTES, env); + assertEquals("", procOut.stdErr()); + assertEquals(0, procOut.exitCode()); + assertEquals(plaintextData, procOut.stdOut()); + + // Path-unsafe token key IDs are not automatically looked up, but failed. + procOut = runMain(List.of( + "decrypt", + absPathOf(encryptedFile), + "--output-file", "-", + "--token", unsafeIdToken + ), EMPTY_BYTES, env); + assertEquals("Invalid command line arguments: The token key ID is not comprised " + + "of path-safe characters; refusing to auto-deduce key file name\n", + procOut.stdErr()); + assertEquals(1, procOut.exitCode()); + assertEquals("", procOut.stdOut()); + } + private ProcessOutput runMain(List<String> args) { return runMain(args, EMPTY_BYTES); } private ProcessOutput runMain(List<String> args, byte[] stdInBytes) { - // Expect that this is used for running a command that is not supposed to fail. But if it does, - // include exception trace in stderr to make it easier to debug. - return runMain(args, stdInBytes, Map.of("VESPA_DEBUG", "true")); + return runMain(args, stdInBytes, Map.of()); } private ProcessOutput runMain(List<String> args, byte[] stdInBytes, Map<String, String> env) { diff --git a/vespaclient-java/src/test/resources/expected-decrypt-help-output.txt b/vespaclient-java/src/test/resources/expected-decrypt-help-output.txt index 291fc3f4b96..2ccb93e9a33 100644 --- a/vespaclient-java/src/test/resources/expected-decrypt-help-output.txt +++ b/vespaclient-java/src/test/resources/expected-decrypt-help-output.txt @@ -5,6 +5,9 @@ given private key. To decrypt the contents of STDIN, specify an input file of '-' (without the quotes). + -d,--private-key-dir <arg> Private key file directory used for + automatically looking up private keys based + on the key ID specified as part of a token. -e,--expected-key-id <arg> Expected key ID in token. If this is not provided, the key ID is not verified. -h,--help Show help diff --git a/vespaclient-java/src/test/resources/expected-reseal-help-output.txt b/vespaclient-java/src/test/resources/expected-reseal-help-output.txt index dcfea6c28bc..7aad08b3f9c 100644 --- a/vespaclient-java/src/test/resources/expected-reseal-help-output.txt +++ b/vespaclient-java/src/test/resources/expected-reseal-help-output.txt @@ -2,6 +2,10 @@ usage: vespa-security reseal <token> <options> Reseals the input token for another recipient, allowing that recipient to decrypt the file that the input token was originally created for. Prints new token to STDOUT. + -d,--private-key-dir <arg> Private key file directory used for + automatically looking up private keys + based on the key ID specified as part + of a token. -e,--expected-key-id <arg> Expected key ID in token. If this is not provided, the key ID is not verified. |