diff options
author | Tor Brede Vekterli <vekterli@yahooinc.com> | 2022-11-11 12:58:46 +0100 |
---|---|---|
committer | Tor Brede Vekterli <vekterli@yahooinc.com> | 2022-11-11 13:17:28 +0100 |
commit | 7855a0d37241e87afe514ec25bf7a00289b556d7 (patch) | |
tree | c21ced3800c3f846cdd55bcf5c15355330a7eb42 /vespaclient-java/src/main/java/com/yahoo | |
parent | f2ce165982217902ea84bcb12e7a10fe008bacd4 (diff) |
Add support for token resealing
Adds underlying support--and 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 (or having to send the raw underlying secret key over a
potentially insecure channel). Key ID can/should change as part of
this operation.
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"); + } + } + } + +} |