diff options
Diffstat (limited to 'vespaclient-java/src/main/java/com/yahoo')
5 files changed, 146 insertions, 24 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 0498154aa91..26868207bd3 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 @@ -5,6 +5,7 @@ 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; +import com.yahoo.vespa.security.tool.crypto.ResealTool; import com.yahoo.vespa.security.tool.crypto.TokenInfoTool; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLineParser; @@ -47,7 +48,7 @@ public class Main { private static final List<Tool> TOOLS = List.of( new KeygenTool(), new EncryptTool(), new DecryptTool(), new TokenInfoTool(), - new ConvertBaseTool()); + new ConvertBaseTool(), new ResealTool()); 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/DecryptTool.java b/vespaclient-java/src/main/java/com/yahoo/vespa/security/tool/crypto/DecryptTool.java index f1c166ba934..2cc724538d4 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 @@ -27,10 +27,10 @@ import java.util.Optional; */ public class DecryptTool implements Tool { - static final String OUTPUT_FILE_OPTION = "output-file"; - static final String RECIPIENT_PRIVATE_KEY_FILE_OPTION = "recipient-private-key-file"; - static final String KEY_ID_OPTION = "key-id"; - static final String TOKEN_OPTION = "token"; + 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"; private static final List<Option> OPTIONS = List.of( Option.builder("o") @@ -41,17 +41,16 @@ public class DecryptTool implements Tool { "quotes) to write plaintext to STDOUT instead of a file.") .build(), Option.builder("k") - .longOpt(RECIPIENT_PRIVATE_KEY_FILE_OPTION) + .longOpt(PRIVATE_KEY_FILE_OPTION) .hasArg(true) .required(false) - .desc("Recipient private key file in Base58 encoded format") + .desc("Private key file in Base58 encoded format") .build(), - Option.builder("i") - .longOpt(KEY_ID_OPTION) + Option.builder("e") + .longOpt(EXPECTED_KEY_ID_OPTION) .hasArg(true) .required(false) - .desc("Numeric ID of recipient key. If this is not provided, " + - "the key ID stored as part of the token is not verified.") + .desc("Expected key ID in token. If this is not provided, the key ID is not verified.") .build(), Option.builder("t") .longOpt(TOKEN_OPTION) @@ -85,21 +84,15 @@ public class DecryptTool implements Tool { throw new IllegalArgumentException("Expected exactly 1 file argument to decrypt"); } var inputArg = leftoverArgs[0]; - var maybeKeyId = Optional.ofNullable(arguments.hasOption(KEY_ID_OPTION) - ? arguments.getOptionValue(KEY_ID_OPTION) + var maybeKeyId = Optional.ofNullable(arguments.hasOption(EXPECTED_KEY_ID_OPTION) + ? arguments.getOptionValue(EXPECTED_KEY_ID_OPTION) : null); var outputArg = CliUtils.optionOrThrow(arguments, OUTPUT_FILE_OPTION); - var privKeyPath = Paths.get(CliUtils.optionOrThrow(arguments, RECIPIENT_PRIVATE_KEY_FILE_OPTION)); var tokenString = CliUtils.optionOrThrow(arguments, TOKEN_OPTION); var sealedSharedKey = SealedSharedKey.fromTokenString(tokenString.strip()); - if (maybeKeyId.isPresent()) { - var myKeyId = KeyId.ofString(maybeKeyId.get()); - if (!myKeyId.equals(sealedSharedKey.keyId())) { - // Don't include raw key bytes array verbatim in message (may contain control chars etc). - throw new IllegalArgumentException("Key ID specified with --key-id does not match key ID " + - "used when generating the supplied token"); - } - } + ToolUtils.verifyExpectedKeyId(sealedSharedKey, maybeKeyId); + + var privKeyPath = Paths.get(CliUtils.optionOrThrow(arguments, PRIVATE_KEY_FILE_OPTION)); var privateKey = KeyUtils.fromBase58EncodedX25519PrivateKey(Files.readString(privKeyPath).strip()); var secretShared = SharedKeyGenerator.fromSealedKey(sealedSharedKey, privateKey); var cipher = SharedKeyGenerator.makeAesGcmDecryptionCipher(secretShared); @@ -108,7 +101,6 @@ public class DecryptTool implements Tool { var outStream = CliUtils.outputStreamToFileOrStream(outputArg, invocation.stdOut())) { CipherUtils.streamEncipher(inStream, outStream, cipher); } - } catch (IOException e) { throw new RuntimeException(e); } diff --git a/vespaclient-java/src/main/java/com/yahoo/vespa/security/tool/crypto/EncryptTool.java b/vespaclient-java/src/main/java/com/yahoo/vespa/security/tool/crypto/EncryptTool.java index 886433f00f8..962b42f4c22 100644 --- a/vespaclient-java/src/main/java/com/yahoo/vespa/security/tool/crypto/EncryptTool.java +++ b/vespaclient-java/src/main/java/com/yahoo/vespa/security/tool/crypto/EncryptTool.java @@ -46,7 +46,7 @@ public class EncryptTool implements Tool { .longOpt(KEY_ID_OPTION) .hasArg(true) .required(false) - .desc("Numeric ID of recipient key") + .desc("ID of recipient key") .build()); @Override 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 new file mode 100644 index 00000000000..e9bc0ae8fee --- /dev/null +++ b/vespaclient-java/src/main/java/com/yahoo/vespa/security/tool/crypto/ResealTool.java @@ -0,0 +1,104 @@ +// 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; +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.nio.file.Files; +import java.nio.file.Paths; +import java.util.List; +import java.util.Optional; + +/** + * 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 + * recipient. + * + * @author vekterli + */ +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"; + + private static final List<Option> OPTIONS = List.of( + Option.builder("k") + .longOpt(PRIVATE_KEY_FILE_OPTION) + .hasArg(true) + .required(false) + .desc("Private key file in Base58 encoded format") + .build(), + Option.builder("e") + .longOpt(EXPECTED_KEY_ID_OPTION) + .hasArg(true) + .required(false) + .desc("Expected key ID in token. If this is not provided, the key ID is not verified.") + .build(), + Option.builder("r") + .longOpt(RECIPIENT_PUBLIC_KEY_OPTION) + .hasArg(true) + .required(false) + .desc("Recipient X25519 public key in Base58 encoded format") + .build(), + Option.builder("i") + .longOpt(RECIPIENT_KEY_ID_OPTION) + .hasArg(true) + .required(false) + .desc("ID of recipient key") + .build()); + + @Override + public String name() { + return "reseal"; + } + + @Override + public ToolDescription description() { + return new ToolDescription( + "<token> <options>", + "Reseals the input token for another recipient, allowing that recipient to " + + "decrypt the file that the input token was originally created for.\n" + + "Prints new token to STDOUT.", + "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 leftoverArgs = arguments.getArgs(); + if (leftoverArgs.length != 1) { + throw new IllegalArgumentException("Expected exactly 1 token argument to re-seal"); + } + var tokenString = leftoverArgs[0]; + var maybeKeyId = Optional.ofNullable(arguments.hasOption(EXPECTED_KEY_ID_OPTION) + ? arguments.getOptionValue(EXPECTED_KEY_ID_OPTION) + : null); + var sealedSharedKey = SealedSharedKey.fromTokenString(tokenString.strip()); + ToolUtils.verifyExpectedKeyId(sealedSharedKey, maybeKeyId); + + 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 secretShared = SharedKeyGenerator.fromSealedKey(sealedSharedKey, privateKey); + var resealedShared = SharedKeyGenerator.reseal(secretShared, recipientPubKey, recipientKeyId); + + invocation.stdOut().println(resealedShared.sealedSharedKey().toTokenString()); + } catch (IOException e) { + throw new RuntimeException(e); + } + return 0; + } +} 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 new file mode 100644 index 00000000000..32e9c6679f6 --- /dev/null +++ b/vespaclient-java/src/main/java/com/yahoo/vespa/security/tool/crypto/ToolUtils.java @@ -0,0 +1,25 @@ +// 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.SealedSharedKey; + +import java.util.Optional; + +/** + * @author vekterli + */ +public class ToolUtils { + + static void verifyExpectedKeyId(SealedSharedKey sealedSharedKey, Optional<String> maybeKeyId) { + if (maybeKeyId.isPresent()) { + var myKeyId = KeyId.ofString(maybeKeyId.get()); + if (!myKeyId.equals(sealedSharedKey.keyId())) { + // Don't include raw key bytes array verbatim in message (may contain control chars etc.) + throw new IllegalArgumentException("Key ID specified with --expected-key-id does not match key ID " + + "used when generating the supplied token"); + } + } + } + +} |