diff options
Diffstat (limited to 'vespaclient-java/src/main/java/com/yahoo/vespa/security/tool/crypto/DecryptTool.java')
-rw-r--r-- | vespaclient-java/src/main/java/com/yahoo/vespa/security/tool/crypto/DecryptTool.java | 111 |
1 files changed, 111 insertions, 0 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 new file mode 100644 index 00000000000..b307ab76da8 --- /dev/null +++ b/vespaclient-java/src/main/java/com/yahoo/vespa/security/tool/crypto/DecryptTool.java @@ -0,0 +1,111 @@ +// 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.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 decrypting a file using a private key that corresponds to the public key used + * to originally encrypt the file. + * + * Uses the opaque token abstraction from {@link SharedKeyGenerator}. + * + * @author vekterli + */ +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"; + + private static final List<Option> OPTIONS = List.of( + Option.builder("o") + .longOpt(OUTPUT_FILE_OPTION) + .hasArg(true) + .required(false) + .desc("Output file for decrypted plaintext") + .build(), + Option.builder("k") + .longOpt(RECIPIENT_PRIVATE_KEY_FILE_OPTION) + .hasArg(true) + .required(false) + .desc("Recipient private key file") + .build(), + Option.builder("i") + .longOpt(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.") + .build(), + Option.builder("t") + .longOpt(TOKEN_OPTION) + .hasArg(true) + .required(false) + .desc("Token generated when the input file was encrypted") + .build()); + + @Override + public String name() { + return "decrypt"; + } + + @Override + public ToolDescription description() { + return new ToolDescription( + "<encrypted file> <options>", + "Decrypts a file using a provided token and a secret private key. The file must " + + "previously have been encrypted using the public key component of the given private key.", + "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 file argument to decrypt"); + } + var inputPath = Paths.get(leftoverArgs[0]); + if (!inputPath.toFile().exists()) { + throw new IllegalArgumentException("Cannot decrypt file '%s' as it does not exist".formatted(inputPath.toString())); + } + var maybeKeyId = Optional.ofNullable(arguments.hasOption(KEY_ID_OPTION) + ? Integer.parseInt(arguments.getOptionValue(KEY_ID_OPTION)) + : null); + var outputPath = Paths.get(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() && (maybeKeyId.get() != sealedSharedKey.keyId())) { + throw new IllegalArgumentException(("Key ID specified with --key-id (%d) does not match key ID " + + "used when generating the supplied token (%d)") + .formatted(maybeKeyId.get(), sealedSharedKey.keyId())); + } + var privateKey = KeyUtils.fromBase64EncodedX25519PrivateKey(Files.readString(privKeyPath).strip()); + var secretShared = SharedKeyGenerator.fromSealedKey(sealedSharedKey, privateKey); + var cipher = SharedKeyGenerator.makeAesGcmDecryptionCipher(secretShared); + + CipherUtils.streamEncipherFileContents(inputPath, outputPath, cipher); + + } catch (IOException e) { + throw new RuntimeException(e); + } + return 0; + } +} |