summaryrefslogtreecommitdiffstats
path: root/vespaclient-java
diff options
context:
space:
mode:
authorTor Brede Vekterli <vekterli@yahooinc.com>2023-01-30 14:41:01 +0100
committerTor Brede Vekterli <vekterli@yahooinc.com>2023-01-31 17:20:36 +0100
commit5ffdfd6d0bc77eda829054c9c3de6fba950507de (patch)
treeddbb173a6300fce2a7c3cf1ee70878d252f4a947 /vespaclient-java
parent3e54969fc961ee51c93404a37d559ab7ea2f9fe6 (diff)
Add an "interactive" token resealing protocol and basic tooling support
Implements a protocol for delegated access to a shared secret key of a token whose private key we do not possess. This builds directly on top of the existing token resealing mechanisms. The primary benefit of the resealing protocol is that none of the data exchanged can reveal anything about the underlying secret. Security note: neither resealing requests nor responses are explicitly authenticated (this is a property inherited from the sealed shared key tokens themselves). It is assumed that an attacker can observe all requests and responses in transit, but cannot modify them.
Diffstat (limited to 'vespaclient-java')
-rw-r--r--vespaclient-java/src/main/java/com/yahoo/vespa/security/tool/crypto/DecryptTool.java49
-rw-r--r--vespaclient-java/src/main/java/com/yahoo/vespa/security/tool/crypto/ResealTool.java55
-rw-r--r--vespaclient-java/src/test/resources/expected-decrypt-help-output.txt2
-rw-r--r--vespaclient-java/src/test/resources/expected-reseal-help-output.txt2
4 files changed, 89 insertions, 19 deletions
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 4fbe89d4b03..4b3608fc3f7 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
@@ -2,14 +2,18 @@
package com.yahoo.vespa.security.tool.crypto;
import com.yahoo.security.SealedSharedKey;
+import com.yahoo.security.SecretSharedKey;
import com.yahoo.security.SharedKeyGenerator;
+import com.yahoo.security.SharedKeyResealingSession;
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.BufferedReader;
import java.io.IOException;
+import java.io.InputStreamReader;
import java.util.List;
import java.util.Optional;
@@ -31,6 +35,7 @@ public class DecryptTool implements Tool {
static final String EXPECTED_KEY_ID_OPTION = "expected-key-id";
static final String ZSTD_DECOMPRESS_OPTION = "zstd-decompress";
static final String TOKEN_OPTION = "token";
+ static final String RESEAL_REQUEST = "reseal-request";
private static final List<Option> OPTIONS = List.of(
Option.builder("o")
@@ -77,6 +82,12 @@ public class DecryptTool implements Tool {
.hasArg(true)
.required(false)
.desc("Token generated when the input file was encrypted")
+ .build(),
+ Option.builder("r")
+ .longOpt(RESEAL_REQUEST)
+ .hasArg(false)
+ .required(false)
+ .desc("Delegate private key decryption via an interactive resealing session")
.build());
@Override
@@ -110,11 +121,12 @@ public class DecryptTool implements Tool {
var sealedSharedKey = SealedSharedKey.fromTokenString(tokenString.strip());
ToolUtils.verifyExpectedKeyId(sealedSharedKey, maybeKeyId);
- var privateKey = ToolUtils.resolvePrivateKeyFromInvocation(invocation, sealedSharedKey.keyId(),
- !CliUtils.useStdIo(inputArg) && !CliUtils.useStdIo(outputArg));
- var secretShared = SharedKeyGenerator.fromSealedKey(sealedSharedKey, privateKey);
- var cipher = secretShared.makeDecryptionCipher();
- boolean unZstd = arguments.hasOption(ZSTD_DECOMPRESS_OPTION);
+ var secret = arguments.hasOption(RESEAL_REQUEST)
+ ? secretFromInteractiveResealing(invocation, inputArg, outputArg, sealedSharedKey)
+ : secretFromPrivateKey(invocation, inputArg, outputArg, sealedSharedKey);
+
+ var cipher = secret.makeDecryptionCipher();
+ boolean unZstd = arguments.hasOption(ZSTD_DECOMPRESS_OPTION);
try (var inStream = CliUtils.inputStreamFromFileOrStream(inputArg, invocation.stdIn());
var outStream = CliUtils.outputStreamToFileOrStream(outputArg, invocation.stdOut())) {
@@ -125,4 +137,31 @@ public class DecryptTool implements Tool {
}
return 0;
}
+
+ private static SecretSharedKey secretFromPrivateKey(ToolInvocation invocation, String inputArg, String outputArg, SealedSharedKey sealedSharedKey) throws IOException {
+ var privateKey = ToolUtils.resolvePrivateKeyFromInvocation(invocation, sealedSharedKey.keyId(),
+ !CliUtils.useStdIo(inputArg) && !CliUtils.useStdIo(outputArg));
+ return SharedKeyGenerator.fromSealedKey(sealedSharedKey, privateKey);
+ }
+
+ private static SecretSharedKey secretFromInteractiveResealing(ToolInvocation invocation, String inputArg,
+ String outputArg, SealedSharedKey sealedSharedKey) throws IOException {
+ if (!CliUtils.useStdIo(outputArg) || !CliUtils.useStdIo(inputArg)) {
+ throw new IllegalArgumentException("Interactive token resealing not available with redirected I/O");
+ }
+ var session = SharedKeyResealingSession.newEphemeralSession();
+ var req = session.resealingRequestFor(sealedSharedKey);
+
+ invocation.stdOut().format("\nInteractive token resealing request:\n\n%s\n\n", req.toSerializedString());
+ invocation.stdOut().format("Paste response and hit return: ");
+
+ try (var reader = new BufferedReader(new InputStreamReader(invocation.stdIn()))) {
+ var serializedRes = reader.readLine().strip();
+ if (serializedRes.isEmpty()) {
+ throw new IllegalArgumentException("Empty response; aborting");
+ }
+ var res = SharedKeyResealingSession.ResealingResponse.fromSerializedString(serializedRes);
+ return session.openResealingResponse(res);
+ }
+ }
}
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 19be3e9fa51..4fb8083b0f0 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
@@ -5,10 +5,12 @@ import com.yahoo.security.KeyId;
import com.yahoo.security.KeyUtils;
import com.yahoo.security.SealedSharedKey;
import com.yahoo.security.SharedKeyGenerator;
+import com.yahoo.security.SharedKeyResealingSession;
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.CommandLine;
import org.apache.commons.cli.Option;
import java.io.IOException;
@@ -31,6 +33,7 @@ public class ResealTool implements Tool {
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";
+ static final String RESEAL_REQUEST_OPTION = "reseal-request";
private static final List<Option> OPTIONS = List.of(
Option.builder("k")
@@ -70,6 +73,12 @@ public class ResealTool implements Tool {
.hasArg(true)
.required(false)
.desc("ID of recipient key")
+ .build(),
+ Option.builder()
+ .longOpt(RESEAL_REQUEST_OPTION)
+ .hasArg(false)
+ .required(false)
+ .desc("Handle input as a resealing request instead of a token")
.build());
@Override
@@ -96,23 +105,41 @@ public class ResealTool implements Tool {
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 privateKey = ToolUtils.resolvePrivateKeyFromInvocation(invocation, sealedSharedKey.keyId(), true);
- var secretShared = SharedKeyGenerator.fromSealedKey(sealedSharedKey, privateKey);
- var resealedShared = SharedKeyGenerator.reseal(secretShared, recipientPubKey, recipientKeyId);
-
- invocation.stdOut().println(resealedShared.sealedSharedKey().toTokenString());
+ var inputArg = leftoverArgs[0].strip();
+ var maybeKeyId = Optional.ofNullable(arguments.hasOption(EXPECTED_KEY_ID_OPTION)
+ ? arguments.getOptionValue(EXPECTED_KEY_ID_OPTION)
+ : null);
+ if (arguments.hasOption(RESEAL_REQUEST_OPTION)) {
+ handleResealingRequest(invocation, inputArg, maybeKeyId);
+ } else {
+ handleTokenResealing(invocation, arguments, inputArg, maybeKeyId);
+ }
} catch (IOException e) {
throw new RuntimeException(e);
}
return 0;
}
+
+ private static void handleTokenResealing(ToolInvocation invocation, CommandLine arguments, String inputArg, Optional<String> maybeKeyId) throws IOException {
+ var sealedSharedKey = SealedSharedKey.fromTokenString(inputArg);
+ 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 privateKey = ToolUtils.resolvePrivateKeyFromInvocation(invocation, sealedSharedKey.keyId(), true);
+ var secretShared = SharedKeyGenerator.fromSealedKey(sealedSharedKey, privateKey);
+ var resealedShared = SharedKeyGenerator.reseal(secretShared, recipientPubKey, recipientKeyId);
+
+ invocation.stdOut().println(resealedShared.sealedSharedKey().toTokenString());
+ }
+
+ private static void handleResealingRequest(ToolInvocation invocation, String inputArg, Optional<String> maybeKeyId) throws IOException {
+ var request = SharedKeyResealingSession.ResealingRequest.fromSerializedString(inputArg);
+ ToolUtils.verifyExpectedKeyId(request.sealedKey(), maybeKeyId);
+
+ var privateKey = ToolUtils.resolvePrivateKeyFromInvocation(invocation, request.sealedKey().keyId(), true);
+ var resealed = SharedKeyResealingSession.reseal(request, (keyId) -> Optional.of(privateKey));
+
+ invocation.stdOut().println(resealed.toSerializedString());
+ }
}
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 ab47d11c602..a654f801f09 100644
--- a/vespaclient-java/src/test/resources/expected-decrypt-help-output.txt
+++ b/vespaclient-java/src/test/resources/expected-decrypt-help-output.txt
@@ -19,6 +19,8 @@ the quotes).
-o,--output-file <arg> Output file for decrypted plaintext.
Specify '-' (without the quotes) to write
plaintext to STDOUT instead of a file.
+ -r,--reseal-request Delegate private key decryption via an
+ interactive resealing session
-t,--token <arg> Token generated when the input file was
encrypted
-z,--zstd-decompress Decrypted data will be transparently
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 cb82bd434b4..b375147f58c 100644
--- a/vespaclient-java/src/test/resources/expected-reseal-help-output.txt
+++ b/vespaclient-java/src/test/resources/expected-reseal-help-output.txt
@@ -19,5 +19,7 @@ Prints new token to STDOUT.
a console
-r,--recipient-public-key <arg> Recipient X25519 public key in Base58
encoded format
+ --reseal-request Handle input as a resealing request
+ instead of a token
Note: this is a BETA tool version; its interface may be changed at any
time