summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--security-utils/src/main/java/com/yahoo/security/SharedKeyGenerator.java16
-rw-r--r--security-utils/src/test/java/com/yahoo/security/SharedKeyTest.java16
-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
-rw-r--r--vespaclient-java/src/test/java/com/yahoo/vespa/security/tool/CryptoToolsTest.java81
-rw-r--r--vespaclient-java/src/test/resources/expected-decrypt-help-output.txt22
-rw-r--r--vespaclient-java/src/test/resources/expected-encrypt-help-output.txt2
-rw-r--r--vespaclient-java/src/test/resources/expected-help-output.txt3
-rw-r--r--vespaclient-java/src/test/resources/expected-reseal-help-output.txt15
12 files changed, 260 insertions, 65 deletions
diff --git a/security-utils/src/main/java/com/yahoo/security/SharedKeyGenerator.java b/security-utils/src/main/java/com/yahoo/security/SharedKeyGenerator.java
index 8a1a7dd3688..66a87a94707 100644
--- a/security-utils/src/main/java/com/yahoo/security/SharedKeyGenerator.java
+++ b/security-utils/src/main/java/com/yahoo/security/SharedKeyGenerator.java
@@ -62,10 +62,7 @@ public class SharedKeyGenerator {
public static SecretSharedKey generateForReceiverPublicKey(PublicKey receiverPublicKey, KeyId keyId) {
var secretKey = generateRandomSecretAesKey();
- // We protect the integrity of the key ID by passing it as AAD.
- var sealed = HPKE.sealBase((XECPublicKey) receiverPublicKey, EMPTY_BYTES, keyId.asBytes(), secretKey.getEncoded());
- var sealedSharedKey = new SealedSharedKey(keyId, sealed.enc(), sealed.ciphertext());
- return new SecretSharedKey(secretKey, sealedSharedKey);
+ return internalSealSecretKeyForReceiver(secretKey, receiverPublicKey, keyId);
}
public static SecretSharedKey fromSealedKey(SealedSharedKey sealedKey, PrivateKey receiverPrivateKey) {
@@ -74,6 +71,17 @@ public class SharedKeyGenerator {
return new SecretSharedKey(new SecretKeySpec(secretKeyBytes, "AES"), sealedKey);
}
+ public static SecretSharedKey reseal(SecretSharedKey secret, PublicKey receiverPublicKey, KeyId keyId) {
+ return internalSealSecretKeyForReceiver(secret.secretKey(), receiverPublicKey, keyId);
+ }
+
+ private static SecretSharedKey internalSealSecretKeyForReceiver(SecretKey secretKey, PublicKey receiverPublicKey, KeyId keyId) {
+ // We protect the integrity of the key ID by passing it as AAD.
+ var sealed = HPKE.sealBase((XECPublicKey) receiverPublicKey, EMPTY_BYTES, keyId.asBytes(), secretKey.getEncoded());
+ var sealedSharedKey = new SealedSharedKey(keyId, sealed.enc(), sealed.ciphertext());
+ return new SecretSharedKey(secretKey, sealedSharedKey);
+ }
+
// A given key+IV pair can only be used for one single encryption session, ever.
// Since our keys are intended to be inherently single-use, we can satisfy that
// requirement even with a fixed IV. This avoids the need for explicitly including
diff --git a/security-utils/src/test/java/com/yahoo/security/SharedKeyTest.java b/security-utils/src/test/java/com/yahoo/security/SharedKeyTest.java
index 4e64bc3e9aa..23e22345cc6 100644
--- a/security-utils/src/test/java/com/yahoo/security/SharedKeyTest.java
+++ b/security-utils/src/test/java/com/yahoo/security/SharedKeyTest.java
@@ -21,6 +21,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
public class SharedKeyTest {
private static final KeyId KEY_ID_1 = KeyId.ofString("1");
+ private static final KeyId KEY_ID_2 = KeyId.ofString("2");
@Test
void generated_secret_key_is_128_bit_aes() {
@@ -45,6 +46,21 @@ public class SharedKeyTest {
}
@Test
+ void secret_key_can_be_resealed_for_another_receiver() {
+ var originalReceiverKp = KeyUtils.generateX25519KeyPair();
+ var secondaryReceiverKp = KeyUtils.generateX25519KeyPair();
+ var myShared = SharedKeyGenerator.generateForReceiverPublicKey(originalReceiverKp.getPublic(), KEY_ID_1);
+ var theirShared = SharedKeyGenerator.reseal(myShared, secondaryReceiverKp.getPublic(), KEY_ID_2);
+
+ var publicToken = theirShared.sealedSharedKey().toTokenString();
+ var theirSealed = SealedSharedKey.fromTokenString(publicToken);
+ assertEquals(KEY_ID_2, theirSealed.keyId());
+ theirShared = SharedKeyGenerator.fromSealedKey(theirSealed, secondaryReceiverKp.getPrivate());
+ // Should be same internal secret key
+ assertArrayEquals(myShared.secretKey().getEncoded(), theirShared.secretKey().getEncoded());
+ }
+
+ @Test
void token_v1_representation_is_stable() {
var receiverPrivate = KeyUtils.fromBase58EncodedX25519PrivateKey("GFg54SaGNCmcSGufZCx68SKLGuAFrASoDeMk3t5AjU6L");
var receiverPublic = KeyUtils.fromBase58EncodedX25519PublicKey( "5drrkakYLjYSBpr5Haknh13EiCYL36ndMzK4gTJo6pwh");
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");
+ }
+ }
+ }
+
+}
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 d4992e89802..42b747df678 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
@@ -1,6 +1,10 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.security.tool;
+import com.yahoo.security.KeyId;
+import com.yahoo.security.KeyUtils;
+import com.yahoo.security.SealedSharedKey;
+import com.yahoo.security.SharedKeyGenerator;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
@@ -16,6 +20,7 @@ import java.nio.file.attribute.PosixFilePermissions;
import java.util.List;
import java.util.Map;
+import static com.yahoo.security.ArrayUtils.hex;
import static com.yahoo.security.ArrayUtils.toUtf8Bytes;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
@@ -27,7 +32,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
*/
public class CryptoToolsTest {
- private static record ProcessOutput(int exitCode, String stdOut, String stdErr) {}
+ private record ProcessOutput(int exitCode, String stdOut, String stdErr) {}
private static final byte[] EMPTY_BYTES = new byte[0];
@@ -88,6 +93,11 @@ public class CryptoToolsTest {
}
@Test
+ void reseal_help_printed_if_help_option_given_to_subtool() throws IOException {
+ verifyStdoutMatchesFile(List.of("reseal", "--help"), "expected-reseal-help-output.txt");
+ }
+
+ @Test
void missing_required_parameter_prints_error_message() throws IOException {
// We don't test all possible input arguments to all tools, since it'd be too closely
// bound to the order in which the implementation checks for argument presence.
@@ -174,6 +184,7 @@ public class CryptoToolsTest {
private static final String TEST_TOKEN = "OntP9gRVAjXeZIr4zkYqRJFcnA993v7ZEE7VbcNs1NcR3HdE7Mp" +
"wlwi3r3anF1kVa5fn7O1CyeHQpBWpdayUTKkrtyFepG6WJrZdE";
private static final String TEST_TOKEN_KEY_ID = "my key ID";
+ private static final String TEST_TOKEN_SECRET = "1b33b4dcd6a94e5a4a1ee6d208197d01";
@Test
void encrypt_fails_with_error_message_if_no_input_file_is_given() throws IOException {
@@ -200,10 +211,10 @@ public class CryptoToolsTest {
Files.writeString(privKeyFile, TEST_PRIV_KEY);
verifyStderrEquals(List.of("decrypt",
- "--output-file", "foo",
- "--recipient-private-key-file", absPathOf(privKeyFile),
- "--token", TEST_TOKEN,
- "--key-id", TEST_TOKEN_KEY_ID),
+ "--output-file", "foo",
+ "--private-key-file", absPathOf(privKeyFile),
+ "--token", TEST_TOKEN,
+ "--expected-key-id", TEST_TOKEN_KEY_ID),
"Invalid command line arguments: Expected exactly 1 file argument to decrypt\n");
}
@@ -214,10 +225,10 @@ public class CryptoToolsTest {
verifyStderrEquals(List.of("decrypt",
"no-such-file",
- "--output-file", "foo",
- "--recipient-private-key-file", absPathOf(privKeyFile),
- "--token", TEST_TOKEN,
- "--key-id", TEST_TOKEN_KEY_ID),
+ "--output-file", "foo",
+ "--private-key-file", absPathOf(privKeyFile),
+ "--token", TEST_TOKEN,
+ "--expected-key-id", TEST_TOKEN_KEY_ID),
"Invalid command line arguments: Input file 'no-such-file' does not exist\n");
}
@@ -231,11 +242,11 @@ public class CryptoToolsTest {
verifyStderrEquals(List.of("decrypt",
absPathOf(inputFile),
- "--output-file", "foo",
- "--recipient-private-key-file", absPathOf(privKeyFile),
- "--token", TEST_TOKEN,
- "--key-id", TEST_TOKEN_KEY_ID + "-wrong"),
- "Invalid command line arguments: Key ID specified with --key-id does not " +
+ "--output-file", "foo",
+ "--private-key-file", absPathOf(privKeyFile),
+ "--token", TEST_TOKEN,
+ "--expected-key-id", TEST_TOKEN_KEY_ID + "-wrong"),
+ "Invalid command line arguments: Key ID specified with --expected-key-id does not " +
"match key ID used when generating the supplied token\n");
}
@@ -271,6 +282,32 @@ public class CryptoToolsTest {
}
@Test
+ void can_reseal_a_token_to_another_recipient() throws IOException {
+ String recipientPrivKeyStr = "GdgfBZzPDqrCVs5f1xaYJpXVGwJzgdTAF1NNWiDk16YZ";
+ String recipientPubKeyStr = "AiUirFvFuLJ6s71QBNxiRcctB4umzM6r2roP4Rf8WDKM";
+
+ Path privKeyFile = pathInTemp("my-priv.txt");
+ Files.writeString(privKeyFile, TEST_PRIV_KEY);
+
+ var procOut = runMain(List.of(
+ "reseal",
+ TEST_TOKEN,
+ "--private-key-file", absPathOf(privKeyFile),
+ "--recipient-public-key", recipientPubKeyStr,
+ "--expected-key-id", TEST_TOKEN_KEY_ID,
+ "--key-id", "some-recipient-key-id"));
+ assertEquals(0, procOut.exitCode());
+ assertEquals("", procOut.stdErr());
+ var resealedToken = procOut.stdOut().strip();
+
+ // Verify that the resealed token wraps the same secret as the original one
+ var recipientPrivKey = KeyUtils.fromBase58EncodedX25519PrivateKey(recipientPrivKeyStr);
+ var recvShared = SharedKeyGenerator.fromSealedKey(SealedSharedKey.fromTokenString(resealedToken), recipientPrivKey);
+ assertEquals(KeyId.ofString("some-recipient-key-id"), recvShared.sealedSharedKey().keyId());
+ assertEquals(TEST_TOKEN_SECRET, hex(recvShared.secretKey().getEncoded()));
+ }
+
+ @Test
void can_end_to_end_keygen_encrypt_and_decrypt_via_files() throws IOException {
String greatSecret = "Dogs can't look up";
@@ -310,10 +347,10 @@ public class CryptoToolsTest {
procOut = runMain(List.of(
"decrypt",
absPathOf(encryptedPath),
- "--output-file", absPathOf(decryptedPath),
- "--recipient-private-key-file", absPathOf(privPath),
- "--key-id", "1234",
- "--token", token
+ "--output-file", absPathOf(decryptedPath),
+ "--private-key-file", absPathOf(privPath),
+ "--expected-key-id", "1234",
+ "--token", token
));
assertEquals(0, procOut.exitCode());
assertEquals("", procOut.stdOut());
@@ -359,10 +396,10 @@ public class CryptoToolsTest {
procOut = runMain(List.of(
"decrypt",
"-", // Decrypt stdin
- "--output-file", "-", // Plaintext to stdout
- "--recipient-private-key-file", absPathOf(privPath),
- "--key-id", "1234",
- "--token", token
+ "--output-file", "-", // Plaintext to stdout
+ "--private-key-file", absPathOf(privPath),
+ "--expected-key-id", "1234",
+ "--token", token
), Files.readAllBytes(encryptedPath));
assertEquals(0, procOut.exitCode());
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 ddf91c779e2..291fc3f4b96 100644
--- a/vespaclient-java/src/test/resources/expected-decrypt-help-output.txt
+++ b/vespaclient-java/src/test/resources/expected-decrypt-help-output.txt
@@ -5,18 +5,14 @@ given private key.
To decrypt the contents of STDIN, specify an input file of '-' (without
the quotes).
- -h,--help Show help
- -i,--key-id <arg> Numeric ID of recipient key. If
- this is not provided, the key ID
- stored as part of the token is
- not verified.
- -k,--recipient-private-key-file <arg> Recipient private key file in
- Base58 encoded format
- -o,--output-file <arg> Output file for decrypted
- plaintext. Specify '-' (without
- the quotes) to write plaintext to
- STDOUT instead of a file.
- -t,--token <arg> Token generated when the input
- file was encrypted
+ -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
+ -k,--private-key-file <arg> Private key file in Base58 encoded format
+ -o,--output-file <arg> Output file for decrypted plaintext.
+ Specify '-' (without the quotes) to write
+ plaintext to STDOUT instead of a file.
+ -t,--token <arg> Token generated when the input file was
+ encrypted
Note: this is a BETA tool version; its interface may be changed at any
time
diff --git a/vespaclient-java/src/test/resources/expected-encrypt-help-output.txt b/vespaclient-java/src/test/resources/expected-encrypt-help-output.txt
index beddc69855b..46185b29986 100644
--- a/vespaclient-java/src/test/resources/expected-encrypt-help-output.txt
+++ b/vespaclient-java/src/test/resources/expected-encrypt-help-output.txt
@@ -7,7 +7,7 @@ kept secret.
To encrypt the contents of STDIN, specify an input file of '-' (without
the quotes).
-h,--help Show help
- -i,--key-id <arg> Numeric ID of recipient key
+ -i,--key-id <arg> ID of recipient key
-o,--output-file <arg> Output file (will be truncated if it
already exists)
-r,--recipient-public-key <arg> Recipient X25519 public key in Base58
diff --git a/vespaclient-java/src/test/resources/expected-help-output.txt b/vespaclient-java/src/test/resources/expected-help-output.txt
index 8cea1366973..e3183f54a7c 100644
--- a/vespaclient-java/src/test/resources/expected-help-output.txt
+++ b/vespaclient-java/src/test/resources/expected-help-output.txt
@@ -1,4 +1,5 @@
usage: vespa-security <tool> [TOOL OPTIONS]
-Where <tool> is one of: keygen, encrypt, decrypt, token-info, convert-base
+Where <tool> is one of: keygen, encrypt, decrypt, token-info,
+convert-base, reseal
-h,--help Show help
Invoke vespa-security <tool> --help for tool-specific 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
new file mode 100644
index 00000000000..dcfea6c28bc
--- /dev/null
+++ b/vespaclient-java/src/test/resources/expected-reseal-help-output.txt
@@ -0,0 +1,15 @@
+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.
+ -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
+ -i,--key-id <arg> ID of recipient key
+ -k,--private-key-file <arg> Private key file in Base58 encoded
+ format
+ -r,--recipient-public-key <arg> Recipient X25519 public key in Base58
+ encoded format
+Note: this is a BETA tool version; its interface may be changed at any
+time