diff options
author | Tor Brede Vekterli <vekterli@yahooinc.com> | 2023-02-02 11:36:14 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-02-02 11:36:14 +0100 |
commit | dff8e9cdcd7cfb804bf0d6c8ef81bfcd106c6e61 (patch) | |
tree | db952a9e05fa04e838b2e0a0b6c23d2f5b9702e1 | |
parent | c82695bdff7b22d39fb1933bb39474ea682ce0b5 (diff) | |
parent | 5ffdfd6d0bc77eda829054c9c3de6fba950507de (diff) |
Merge pull request #25823 from vespa-engine/vekterli/interactive-token-resealing
Add an "interactive" token resealing protocol and basic tooling support
8 files changed, 286 insertions, 29 deletions
diff --git a/security-utils/src/main/java/com/yahoo/security/SealedSharedKey.java b/security-utils/src/main/java/com/yahoo/security/SealedSharedKey.java index d570cd799cc..99d07465812 100644 --- a/security-utils/src/main/java/com/yahoo/security/SealedSharedKey.java +++ b/security-utils/src/main/java/com/yahoo/security/SealedSharedKey.java @@ -20,11 +20,14 @@ public record SealedSharedKey(int version, KeyId keyId, byte[] enc, byte[] ciphe public static final int CURRENT_TOKEN_VERSION = 2; /** Encryption context for v{1,2} tokens is always a 32-byte X25519 public key */ public static final int MAX_ENC_CONTEXT_LENGTH = 255; + // Expected max decoded size for v1 is 3 + 255 + 32 + 32 = 322. For simplicity, round this + // up to 512 to effectively not have to care about the overhead of any reasonably chosen encoding. + public static final int MAX_TOKEN_STRING_LENGTH = 512; public SealedSharedKey { if (enc.length > MAX_ENC_CONTEXT_LENGTH) { throw new IllegalArgumentException("Encryption context is too large to be encoded (max is %d, got %d)" - .formatted(MAX_ENC_CONTEXT_LENGTH, enc.length)); + .formatted(MAX_ENC_CONTEXT_LENGTH, enc.length)); } } @@ -33,6 +36,10 @@ public record SealedSharedKey(int version, KeyId keyId, byte[] enc, byte[] ciphe * reconstruct the SealedSharedKey instance when passed verbatim to fromTokenString(). */ public String toTokenString() { + return Base62.codec().encode(toSerializedBytes()); + } + + byte[] toSerializedBytes() { byte[] keyIdBytes = keyId.asBytes(); // u8 token version || u8 length(key id) || key id || u8 length(enc) || enc || ciphertext ByteBuffer encoded = ByteBuffer.allocate(1 + 1 + keyIdBytes.length + 1 + enc.length + ciphertext.length); @@ -46,7 +53,7 @@ public record SealedSharedKey(int version, KeyId keyId, byte[] enc, byte[] ciphe byte[] encBytes = new byte[encoded.remaining()]; encoded.get(encBytes); - return Base62.codec().encode(encBytes); + return encBytes; } /** @@ -56,6 +63,10 @@ public record SealedSharedKey(int version, KeyId keyId, byte[] enc, byte[] ciphe public static SealedSharedKey fromTokenString(String tokenString) { verifyInputTokenStringNotTooLarge(tokenString); byte[] rawTokenBytes = Base62.codec().decode(tokenString); + return fromSerializedBytes(rawTokenBytes); + } + + static SealedSharedKey fromSerializedBytes(byte[] rawTokenBytes) { if (rawTokenBytes.length < 1) { throw new IllegalArgumentException("Decoded token too small to contain a version"); } @@ -81,9 +92,7 @@ public record SealedSharedKey(int version, KeyId keyId, byte[] enc, byte[] ciphe public int tokenVersion() { return version; } private static void verifyInputTokenStringNotTooLarge(String tokenString) { - // Expected max decoded size for v1 is 3 + 255 + 32 + 32 = 322. For simplicity, round this - // up to 512 to effectively not have to care about the overhead of any reasonably chosen encoding. - if (tokenString.length() > 512) { + if (tokenString.length() > MAX_TOKEN_STRING_LENGTH) { throw new IllegalArgumentException("Token string is too long to possibly be a valid token"); } } diff --git a/security-utils/src/main/java/com/yahoo/security/SharedKeyResealingSession.java b/security-utils/src/main/java/com/yahoo/security/SharedKeyResealingSession.java new file mode 100644 index 00000000000..6e79b86d832 --- /dev/null +++ b/security-utils/src/main/java/com/yahoo/security/SharedKeyResealingSession.java @@ -0,0 +1,155 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.security; + +import org.bouncycastle.util.Arrays; + +import java.nio.ByteBuffer; +import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.interfaces.XECPublicKey; +import java.util.Optional; + +/** + * <p>Delegated resealing protocol for getting access to a shared secret key of a token + * whose private key we do not possess.</p> + * + * <p>The primary benefit of the interactive resealing protocol is that none of the data + * exchanged can reveal anything about the underlying sealed secret itself.</p> + * + * <p>Note that neither resealing requests nor responses are authenticated (this is a property + * inherited from the sealed shared key tokens themselves). It is assumed that an attacker + * can <em>observe</em> all requests and responses in transit, but cannot modify them.</p> + * + * <h2>Protocol details</h2> + * + * <p>Decryptor (requester):</p> + * <ol> + * <li>Create a resealing session instance that maintains an ephemeral X25519 key pair that + * is valid only for the lifetime of the session.</li> + * <li>Create a resealing request for a token <em>T</em>. The session emits a Base62-encoded + * binary representation of the tuple <em><ephemeral public key, T></em>.</li> + * <li>Send the request to the private key holder. The session must be kept alive until the + * response is received, or the ephemeral private key associated with the public key will + * be irrevocably lost.</li> + * </ol> + * <p>Private key holder (re-sealer):</p> + * <ol> + * <li>Decode Base62-encoded request into tuple <em><ephemeral public key, T></em>.</li> + * <li>Look up the correct private key from the key ID contained in token <em>T</em>.</li> + * <li>Reseal token <em>T</em> for the requested ephemeral public key using the correct private key.</li> + * <li>Return resealed token <em>T<sub>R</sub></em> to requester.</li> + * </ol> + * <p>Decryptor (requester):</p> + * <ol> + * <li>Decrypt token <em>T<sub>R</sub></em> using ephemeral private key.</li> + * <li>Use secret key in token to decrypt the payload protected by original token <em>T</em>.</li> + * </ol> + * + * @author vekterli + */ +public class SharedKeyResealingSession { + + private final KeyPair ephemeralKeyPair; + + SharedKeyResealingSession(KeyPair ephemeralKeyPair) { + this.ephemeralKeyPair = ephemeralKeyPair; + } + + public static SharedKeyResealingSession newEphemeralSession() { + return new SharedKeyResealingSession(KeyUtils.generateX25519KeyPair()); + } + + @FunctionalInterface + public interface PrivateKeyProvider { + Optional<PrivateKey> privateKeyForId(KeyId id); + } + + public record ResealingRequest(XECPublicKey ephemeralPubKey, SealedSharedKey sealedKey) { + + private static final byte[] HEADER_BYTES = new byte[] {'R','S'}; + private static final byte CURRENT_VERSION = 1; + + public String toSerializedString() { + byte[] pubKeyBytes = KeyUtils.toRawX25519PublicKeyBytes(ephemeralPubKey); + byte[] tokenBytes = sealedKey.toSerializedBytes(); + + ByteBuffer encoded = ByteBuffer.allocate(HEADER_BYTES.length + 1 + 1 + pubKeyBytes.length + tokenBytes.length); + encoded.put(HEADER_BYTES); + encoded.put(CURRENT_VERSION); + encoded.put((byte)pubKeyBytes.length); + encoded.put(pubKeyBytes); + encoded.put(tokenBytes); + encoded.flip(); + + byte[] encBytes = new byte[encoded.remaining()]; + encoded.get(encBytes); + return Base62.codec().encode(encBytes); + } + + public static ResealingRequest fromSerializedString(String request) { + verifyInputStringNotTooLarge(request); + byte[] rawBytes = Base62.codec().decode(request); + if (rawBytes.length < HEADER_BYTES.length + 2) { + throw new IllegalArgumentException("Resealing request too short to contain a header and key length"); + } + ByteBuffer decoded = ByteBuffer.wrap(rawBytes); + byte[] header = new byte[2]; + decoded.get(header); + if (!Arrays.areEqual(header, HEADER_BYTES)) { + throw new IllegalArgumentException("No resealing request header found"); + } + byte version = decoded.get(); + if (version != CURRENT_VERSION) { + throw new IllegalArgumentException("Unsupported version in resealing request header"); + } + int pubKeyLen = Byte.toUnsignedInt(decoded.get()); + byte[] pubKeyBytes = new byte[pubKeyLen]; + decoded.get(pubKeyBytes); + + byte[] rawTokenBytes = new byte[decoded.remaining()]; + decoded.get(rawTokenBytes); + + return new ResealingRequest(KeyUtils.fromRawX25519PublicKey(pubKeyBytes), + SealedSharedKey.fromSerializedBytes(rawTokenBytes)); + } + + private static void verifyInputStringNotTooLarge(String tokenString) { + if (tokenString.length() > SealedSharedKey.MAX_TOKEN_STRING_LENGTH + 64) { + throw new IllegalArgumentException("String is too long to possibly be a valid resealing request"); + } + } + + } + + public record ResealingResponse(SealedSharedKey resealedKey) { + + public String toSerializedString() { + return resealedKey.toTokenString(); + } + + public static ResealingResponse fromSerializedString(String response) { + return new ResealingResponse(SealedSharedKey.fromTokenString(response)); + } + + } + + public ResealingRequest resealingRequestFor(SealedSharedKey sealedSharedKey) { + return new ResealingRequest((XECPublicKey) ephemeralKeyPair.getPublic(), sealedSharedKey); + } + + public static ResealingResponse reseal(ResealingRequest request, PrivateKeyProvider privateKeyProvider) { + var privKey = privateKeyProvider.privateKeyForId(request.sealedKey.keyId()).orElseThrow( + () -> new IllegalArgumentException("Could not find a private key for key ID '%s'".formatted(request.sealedKey.keyId()))); + + var secretShared = SharedKeyGenerator.fromSealedKey(request.sealedKey, privKey); + var resealed = SharedKeyGenerator.reseal(secretShared, request.ephemeralPubKey, KeyId.ofString("resealed-token")); // TODO key id + + return new ResealingResponse(resealed.sealedSharedKey()); + } + + + public SecretSharedKey openResealingResponse(ResealingResponse response) { + return SharedKeyGenerator.fromSealedKey(response.resealedKey, ephemeralKeyPair.getPrivate()); + } + +} diff --git a/security-utils/src/main/java/com/yahoo/security/hpke/Hpke.java b/security-utils/src/main/java/com/yahoo/security/hpke/Hpke.java index 133798faa99..51f41ab7da7 100644 --- a/security-utils/src/main/java/com/yahoo/security/hpke/Hpke.java +++ b/security-utils/src/main/java/com/yahoo/security/hpke/Hpke.java @@ -175,7 +175,7 @@ public final class Hpke { } } - private static record ContextBase(byte[] key, byte[] nonce, long seqNum, byte[] exporterSecret) { } + private record ContextBase(byte[] key, byte[] nonce, long seqNum, byte[] exporterSecret) { } /** * Section 5.1 Creating the Encryption Context: @@ -219,8 +219,8 @@ public final class Hpke { return new ContextBase(key, baseNonce, 0, exporterSecret); } - private static record ContextS(byte[] enc, ContextBase base) {} - private static record ContextR(ContextBase base) {} + private record ContextS(byte[] enc, ContextBase base) {} + private record ContextR(ContextBase base) {} /** * Section 5.1.1 Encryption to a Public Key: @@ -253,7 +253,7 @@ public final class Hpke { return new ContextR(keySchedule(MODE_BASE, sharedSecret, info, DEFAULT_PSK, DEFAULT_PSK_ID)); } - public static record Sealed(byte[] enc, byte[] ciphertext) {} + public record Sealed(byte[] enc, byte[] ciphertext) {} /** * Section 6.1 Encryption and Decryption: 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 35b52d13b1d..875877aed6a 100644 --- a/security-utils/src/test/java/com/yahoo/security/SharedKeyTest.java +++ b/security-utils/src/test/java/com/yahoo/security/SharedKeyTest.java @@ -9,7 +9,7 @@ import java.io.IOException; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.Arrays; -import java.util.Base64; +import java.util.Optional; import static com.yahoo.security.ArrayUtils.hex; import static org.junit.jupiter.api.Assertions.assertArrayEquals; @@ -232,6 +232,29 @@ public class SharedKeyTest { assertEquals(terrifyingSecret, decrypted); } + @Test + void shared_key_can_be_resealed_via_interactive_resealing_session() { + var originalReceiverKp = KeyUtils.generateX25519KeyPair(); + var shared = SharedKeyGenerator.generateForReceiverPublicKey(originalReceiverKp.getPublic(), KEY_ID_1); + var secret = hex(shared.secretKey().getEncoded()); + + // Resealing requester side; ask for token to be resealed for ephemeral session public key + var session = SharedKeyResealingSession.newEphemeralSession(); + var wrappedResealRequest = session.resealingRequestFor(shared.sealedSharedKey()); + + // Resealing request handler side; reseal using private key for original token + var unwrappedResealRequest = SharedKeyResealingSession.ResealingRequest.fromSerializedString(wrappedResealRequest.toSerializedString()); + var wrappedResponse = SharedKeyResealingSession.reseal(unwrappedResealRequest, + (keyId) -> Optional.ofNullable(keyId.equals(KEY_ID_1) ? originalReceiverKp.getPrivate() : null)); + + // Back to resealing requester side + var unwrappedResponse = SharedKeyResealingSession.ResealingResponse.fromSerializedString(wrappedResponse.toSerializedString()); + var resealed = session.openResealingResponse(unwrappedResponse); + + var resealedSecret = hex(resealed.secretKey().getEncoded()); + assertEquals(secret, resealedSecret); + } + // javax.crypto.CipherOutputStream swallows exceptions caused by MAC failures in cipher // decryption mode (!) and must therefore _not_ be used for this purpose. This is documented, // but still very surprising behavior. 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 |