aboutsummaryrefslogtreecommitdiffstats
path: root/vespaclient-java/src/main/java/com/yahoo
diff options
context:
space:
mode:
authorTor Brede Vekterli <vekterli@yahooinc.com>2022-11-11 12:58:46 +0100
committerTor Brede Vekterli <vekterli@yahooinc.com>2022-11-11 13:17:28 +0100
commit7855a0d37241e87afe514ec25bf7a00289b556d7 (patch)
treec21ced3800c3f846cdd55bcf5c15355330a7eb42 /vespaclient-java/src/main/java/com/yahoo
parentf2ce165982217902ea84bcb12e7a10fe008bacd4 (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')
-rw-r--r--vespaclient-java/src/main/java/com/yahoo/vespa/security/tool/Main.java3
-rw-r--r--vespaclient-java/src/main/java/com/yahoo/vespa/security/tool/crypto/DecryptTool.java36
-rw-r--r--vespaclient-java/src/main/java/com/yahoo/vespa/security/tool/crypto/EncryptTool.java2
-rw-r--r--vespaclient-java/src/main/java/com/yahoo/vespa/security/tool/crypto/ResealTool.java104
-rw-r--r--vespaclient-java/src/main/java/com/yahoo/vespa/security/tool/crypto/ToolUtils.java25
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");
+ }
+ }
+ }
+
+}