From 6d06defa6336a0902180007182024219571e12e7 Mon Sep 17 00:00:00 2001 From: Andy LoPresto Date: Thu, 22 Mar 2018 20:47:53 -0700 Subject: [PATCH] NIFI-4942 [WIP] Added skeleton for secure hash handling in encrypt-config toolkit. Added test resource for Python scrypt implementation/verifier. Added unit tests. NIFI-4942 [WIP] More unit tests passing. NIFI-4942 All unit tests pass and test artifacts are cleaned up. NIFI-4942 Added RAT exclusions. NIFI-4942 Added Scrypt hash format checker. Added unit tests. NIFI-4942 Added NiFi hash format checker. Added unit tests. NIFI-4942 Added check for simultaneous use of -z/-y. Added logic to check hashed password/key. Added logic to retrieve secure hash from file to compare. Added unit tests (125/125). NIFI-4942 Added new ExitCode. Added logic to return current hash params in JSON for Ambari to consume. Fixed typos in error messages. Added unit tests (129/129). NIFI-4942 Added Scrypt hash format verification for hash check. Added unit tests. NIFI-4942 Fixed RAT checks. Signed-off-by: Yolanda Davis This closes #2628 --- .../security/util/crypto/scrypt/Scrypt.java | 43 +- .../util/scrypt/ScryptGroovyTest.groovy | 56 ++ .../nifi-toolkit-encrypt-config/pom.xml | 15 +- .../properties/ConfigEncryptionTool.groovy | 373 ++++++- .../ConfigEncryptionToolTest.groovy | 919 +++++++++++++++++- .../src/test/resources/scrypt.py | 18 + .../src/test/resources/secure_hash.key | 2 + .../src/test/resources/secure_hash_128.key | 2 + .../toolkit/tls/commandLine/ExitCode.java | 7 +- 9 files changed, 1398 insertions(+), 37 deletions(-) create mode 100644 nifi-toolkit/nifi-toolkit-encrypt-config/src/test/resources/scrypt.py create mode 100644 nifi-toolkit/nifi-toolkit-encrypt-config/src/test/resources/secure_hash.key create mode 100644 nifi-toolkit/nifi-toolkit-encrypt-config/src/test/resources/secure_hash_128.key diff --git a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/crypto/scrypt/Scrypt.java b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/crypto/scrypt/Scrypt.java index 2aeae3deb5d5..b5622f8a1ba6 100644 --- a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/crypto/scrypt/Scrypt.java +++ b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/crypto/scrypt/Scrypt.java @@ -24,6 +24,7 @@ import java.security.SecureRandom; import java.util.ArrayList; import java.util.List; +import java.util.regex.Pattern; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import org.apache.commons.codec.binary.Base64; @@ -51,6 +52,8 @@ public class Scrypt { private static final int DEFAULT_SALT_LENGTH = 16; + private static final Pattern SCRYPT_PATTERN = Pattern.compile("^\\$\\w{2}\\$\\w{5,}\\$[\\w\\/\\=\\+]{11,64}\\$[\\w\\/\\=\\+]{1,256}$"); + /** * Hash the supplied plaintext password and generate output in the format described * below: @@ -158,12 +161,12 @@ public static boolean check(String password, String hashed) { throw new IllegalArgumentException("Hash cannot be empty"); } - String[] parts = hashed.split("\\$"); - - if (parts.length != 5 || !parts[1].equals("s0")) { + if (!verifyHashFormat(hashed)) { throw new IllegalArgumentException("Hash is not properly formatted"); } + String[] parts = hashed.split("\\$"); + List splitParams = parseParameters(parts[2]); int n = splitParams.get(0); int r = splitParams.get(1); @@ -188,6 +191,38 @@ public static boolean check(String password, String hashed) { } } + /** + * Returns true if the provided hash is a valid scrypt hash. Expected format: + *

+ * {@code $s0$40801$ABCDEFGHIJKLMNOPQRSTUQ$hxU5g0eH6sRkBqcsiApI8jxvKRT+2QMCenV0GToiMQ8} + *

+ * Components: + *

+ * s0 -- version. Currently only "s0" is supported + * 40801 -- hex-encoded N, r, p parameters. {@see Scrypt#encodeParams()} for format + * ABCDEFGHIJKLMNOPQRSTUQ -- Base64-encoded (URL-safe, no padding) salt value. + * By default, 22 characters (16 bytes) but can be an arbitrary length between 11 and 64 characters (8 - 48 bytes) of random salt data + * hxU5g0eH6sRkBqcsiApI8jxvKRT+2QMCenV0GToiMQ8 -- the Base64-encoded (URL-safe, no padding) + * resulting hash component. By default, 43 characters (32 bytes) but can be an arbitrary length between 1 and MAX (depends on implementation, see RFC 7914) + * + * @param hash the hash to verify + * @return true if the format is acceptable + * @see Scrypt#formatSalt(byte[], int, int, int) + */ + public static boolean verifyHashFormat(String hash) { + if (StringUtils.isBlank(hash)) { + return false; + } + + // Currently, only version s0 is supported + if (!hash.startsWith("$s0$")) { + return false; + } + + // Check against the pattern + return SCRYPT_PATTERN.matcher(hash).matches(); + } + /** * Parses the individual values from the encoded params value in the modified-mcrypt format for the salt & hash. *

@@ -285,7 +320,7 @@ protected static byte[] deriveScryptKey(byte[] password, byte[] salt, int n, int logger.warn("An empty salt was used for scrypt key derivation"); // throw new IllegalArgumentException("Salt cannot be empty"); // as the Exception is not being thrown, prevent NPE if salt is null by setting it to empty array - if( salt == null ) salt = new byte[]{}; + if (salt == null) salt = new byte[]{}; } if (saltLength < 8 || saltLength > 32) { diff --git a/nifi-commons/nifi-security-utils/src/test/groovy/org/apache/nifi/security/util/scrypt/ScryptGroovyTest.groovy b/nifi-commons/nifi-security-utils/src/test/groovy/org/apache/nifi/security/util/scrypt/ScryptGroovyTest.groovy index 416d670c52d8..ad222a0d15b7 100644 --- a/nifi-commons/nifi-security-utils/src/test/groovy/org/apache/nifi/security/util/scrypt/ScryptGroovyTest.groovy +++ b/nifi-commons/nifi-security-utils/src/test/groovy/org/apache/nifi/security/util/scrypt/ScryptGroovyTest.groovy @@ -397,4 +397,60 @@ class ScryptGroovyTest { assert msg =~ "Hash cannot be empty|Hash is not properly formatted" } } + + @Test + void testVerifyHashFormatShouldDetectValidHash() throws Exception { + // Arrange + final def VALID_HASHES = [ + "\$s0\$40801\$AAAAAAAAAAAAAAAAAAAAAA\$gLSh7ChbHdOIMvZ74XGjV6qF65d9qvQ8n75FeGnM8YM", + "\$s0\$40801\$ABCDEFGHIJKLMNOPQRSTUQ\$hxU5g0eH6sRkBqcsiApI8jxvKRT+2QMCenV0GToiMQ8", + "\$s0\$40801\$eO+UUcKYL2gnpD51QCc+gnywQ7Eg9tZeLMlf0XXr2zc\$99aTTB39TJo69aZCONQmRdyWOgYsDi+1MI+8D0EgMNM", + "\$s0\$40801\$AAAAAAAAAAAAAAAAAAAAAA\$Gk7K9YmlsWbd8FS7e4RKVWnkg9vlsqYnlD593pJ71gg", + "\$s0\$40801\$ABCDEFGHIJKLMNOPQRSTUQ\$Ri78VZbrp2cCVmGh2a9Nbfdov8LPnFb49MYyzPCaXmE", + "\$s0\$40801\$eO+UUcKYL2gnpD51QCc+gnywQ7Eg9tZeLMlf0XXr2zc\$rZIrP2qdIY7LN4CZAMgbCzl3YhXz6WhaNyXJXqFIjaI", + "\$s0\$40801\$AAAAAAAAAAAAAAAAAAAAAA\$GxH68bGykmPDZ6gaPIGOONOT2omlZ7cd0xlcZ9UsY/0", + "\$s0\$40801\$ABCDEFGHIJKLMNOPQRSTUQ\$KLGZjWlo59sbCbtmTg5b4k0Nu+biWZRRzhPhN7K5kkI", + "\$s0\$40801\$eO+UUcKYL2gnpD51QCc+gnywQ7Eg9tZeLMlf0XXr2zc\$6Ql6Efd2ac44ERoV31CL3Q0J3LffNZKN4elyMHux99Y", + // Uncommon but technically valid + "\$s0\$F0801\$AAAAAAAAAAA\$A", + "\$s0\$40801\$ABCDEFGHIJKLMNOPABCDEFGHIJKLMNOPABCDEFGHIJKLMNOPABCDEFGHIJKLMNOP\$A", + "\$s0\$40801\$ABCDEFGHIJKLMNOPABCDEFGHIJKLMNOPABCDEFGHIJKLMNOPABCDEFGHIJKLMNOP\$ABCDEFGHIJKLMNOPABCDEFGHIJKLMNOPABCDEFGHIJKLMNOPABCDEFGHIJKLMNOP", + "\$s0\$40801\$ABCDEFGHIJKLMNOPABCDEFGHIJKLMNOPABCDEFGHIJKLMNOPABCDEFGHIJKLMNOP\$ABCDEFGHIJKLMNOPABCDEFGHIJKLMNOPABCDEFGHIJKLMNOPABCDEFGHIJKLMNOPABCDEFGHIJKLMNOPABCDEFGHIJKLMNOPABCDEFGHIJKLMNOPABCDEFGHIJKLMNOP", + "\$s0\$F0801\$AAAAAAAAAAA\$A", + "\$s0\$F0801\$AAAAAAAAAAA\$A", + "\$s0\$F0801\$AAAAAAAAAAA\$A", + "\$s0\$F0801\$AAAAAAAAAAA\$A", + "\$s0\$F0801\$AAAAAAAAAAA\$A", + ] + + // Act + VALID_HASHES.each { String validHash -> + logger.info("Using hash: ${validHash}") + + boolean isValidHash = Scrypt.verifyHashFormat(validHash) + logger.info("Hash is valid: ${isValidHash}") + + // Assert + assert isValidHash + } + } + + @Test + void testVerifyHashFormatShouldDetectInvalidHash() throws Exception { + // Arrange + + // Even though the spec allows for empty salts, the JCE does not, so extend enforcement of that to the user boundary + final def INVALID_HASHES = ['', null, '$s0$a0801$', '$s0$a0801$abcdefghijklmnopqrstuv$'] + + // Act + INVALID_HASHES.each { String invalidHash -> + logger.info("Using hash: ${invalidHash}") + + boolean isValidHash = Scrypt.verifyHashFormat(invalidHash) + logger.info("Hash is valid: ${isValidHash}") + + // Assert + assert !isValidHash + } + } } \ No newline at end of file diff --git a/nifi-toolkit/nifi-toolkit-encrypt-config/pom.xml b/nifi-toolkit/nifi-toolkit-encrypt-config/pom.xml index 0bf1f7459889..2163de6483df 100644 --- a/nifi-toolkit/nifi-toolkit-encrypt-config/pom.xml +++ b/nifi-toolkit/nifi-toolkit-encrypt-config/pom.xml @@ -13,7 +13,8 @@ See the License for the specific language governing permissions and limitations under the License. --> - + org.apache.nifi nifi-toolkit @@ -162,7 +163,19 @@ + + org.apache.rat + apache-rat-plugin + + + src/test/resources/scrypt.py + src/test/resources/secure_hash.key + src/test/resources/secure_hash_128.key + + + + \ No newline at end of file diff --git a/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/properties/ConfigEncryptionTool.groovy b/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/properties/ConfigEncryptionTool.groovy index 0e507c87cff6..e8ac6423ff35 100644 --- a/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/properties/ConfigEncryptionTool.groovy +++ b/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/properties/ConfigEncryptionTool.groovy @@ -17,6 +17,7 @@ package org.apache.nifi.properties import groovy.io.GroovyPrintWriter +import groovy.json.JsonBuilder import groovy.xml.XmlUtil import org.apache.commons.cli.CommandLine import org.apache.commons.cli.CommandLineParser @@ -27,6 +28,8 @@ import org.apache.commons.cli.Options import org.apache.commons.cli.ParseException import org.apache.commons.codec.binary.Hex import org.apache.commons.io.IOUtils +import org.apache.nifi.security.util.crypto.CipherUtility +import org.apache.nifi.security.util.crypto.scrypt.Scrypt import org.apache.nifi.toolkit.tls.commandLine.CommandLineParseException import org.apache.nifi.toolkit.tls.commandLine.ExitCode import org.apache.nifi.util.NiFiProperties @@ -44,7 +47,9 @@ import javax.crypto.SecretKeyFactory import javax.crypto.spec.PBEKeySpec import javax.crypto.spec.PBEParameterSpec import java.nio.charset.StandardCharsets +import java.security.InvalidKeyException import java.security.KeyException +import java.security.MessageDigest import java.security.SecureRandom import java.security.Security import java.util.zip.GZIPInputStream @@ -62,11 +67,15 @@ class ConfigEncryptionTool { public String outputAuthorizersPath public String flowXmlPath public String outputFlowXmlPath + // If this value can be set by the running user, it can point to a manipulated file anywhere + private static String secureHashPath = "./secure_hash.key" private String keyHex private String migrationKeyHex private String password private String migrationPassword + private String secureHashKey + private String secureHashPassword // This is the raw value used in nifi.sensitive.props.key private String flowPropertiesPassword @@ -81,6 +90,7 @@ class ConfigEncryptionTool { private boolean usingPassword = true private boolean usingPasswordMigration = true + private boolean usingSecureHash = false private boolean migration = false private boolean isVerbose = false private boolean handlingNiFiProperties = false @@ -88,6 +98,7 @@ class ConfigEncryptionTool { private boolean handlingAuthorizers = false private boolean handlingFlowXml = false private boolean ignorePropertiesFiles = false + private boolean queryingCurrentHashParams = false private static final String HELP_ARG = "help" private static final String VERBOSE_ARG = "verbose" @@ -104,21 +115,30 @@ class ConfigEncryptionTool { private static final String PASSWORD_ARG = "password" private static final String KEY_MIGRATION_ARG = "oldKey" private static final String PASSWORD_MIGRATION_ARG = "oldPassword" + private static final String HASHED_KEY_MIGRATION_ARG = "secureHashKey" + private static final String HASHED_PASSWORD_MIGRATION_ARG = "secureHashPassword" private static final String USE_KEY_ARG = "useRawKey" private static final String MIGRATION_ARG = "migrate" private static final String PROPS_KEY_ARG = "propsKey" private static final String DO_NOT_ENCRYPT_NIFI_PROPERTIES_ARG = "encryptFlowXmlOnly" private static final String NEW_FLOW_ALGORITHM_ARG = "newFlowAlgorithm" private static final String NEW_FLOW_PROVIDER_ARG = "newFlowProvider" + private static final String CURRENT_HASH_PARAMS_ARG = "currentHashParams" + + // Static holder to avoid re-generating the options object multiple times in an invocation + private static Options staticOptions // Hard-coded fallback value from {@link org.apache.nifi.encrypt.StringEncryptor} private static final String DEFAULT_NIFI_SENSITIVE_PROPS_KEY = "nififtw!" private static final int MIN_PASSWORD_LENGTH = 12 - // Strong parameters as of 12 Aug 2016 + // Strong parameters as of 12 Aug 2016 (for key derivation) + // This value can remain an int until best practice specifies a value above 2**32 private static final int SCRYPT_N = 2**16 private static final int SCRYPT_R = 8 private static final int SCRYPT_P = 1 + static final String CURRENT_SCRYPT_VERSION = "s0" + private static final String NIFI_SCRYPT_PATTERN = /^\$\w{2}\$\w{5,}\$[\w\/\=\+]{22,}\$[\w\/\=\+]{43}$/ // Hard-coded values from StandardPBEByteEncryptor which will be removed during refactor of all flow encryption code in NIFI-1465 private static final int DEFAULT_KDF_ITERATIONS = 1000 @@ -142,7 +162,8 @@ class ConfigEncryptionTool { "files or in flow.xml.gz to be encrypted with a new key." private static final String LDAP_PROVIDER_CLASS = "org.apache.nifi.ldap.LdapProvider" - private static final String LDAP_PROVIDER_REGEX = /(?s)(?:(?!).)*?\s*org\.apache\.nifi\.ldap\.LdapProvider.*?<\/provider>/ + private static + final String LDAP_PROVIDER_REGEX = /(?s)(?:(?!).)*?\s*org\.apache\.nifi\.ldap\.LdapProvider.*?<\/provider>/ /* Explanation of LDAP_PROVIDER_REGEX: * (?s) -> single-line mode (i.e., `.` in regex matches newlines) * -> find occurrence of `` literally (case-sensitive) @@ -177,6 +198,7 @@ class ConfigEncryptionTool { private static final String DEFAULT_PROVIDER = BouncyCastleProvider.PROVIDER_NAME private static final String DEFAULT_FLOW_ALGORITHM = "PBEWITHMD5AND256BITAES-CBC-OPENSSL" + static private final int AMBARI_COMPATIBLE_SCRYPT_HASH_LENGTH = 256 private static String buildHeader(String description = DEFAULT_DESCRIPTION) { "${SEP}${description}${SEP * 2}" @@ -196,7 +218,11 @@ class ConfigEncryptionTool { ConfigEncryptionTool(String description) { this.header = buildHeader(description) - this.options = new Options() + this.options = getCliOptions() + } + + static Options buildOptions() { + Options options = new Options() options.addOption(Option.builder("h").longOpt(HELP_ARG).hasArg(false).desc("Show usage information (this message)").build()) options.addOption(Option.builder("v").longOpt(VERBOSE_ARG).hasArg(false).desc("Sets verbose mode (default false)").build()) options.addOption(Option.builder("n").longOpt(NIFI_PROPERTIES_ARG).hasArg(true).argName("file").desc("The nifi.properties file containing unprotected config values (will be overwritten unless -o is specified)").build()) @@ -212,23 +238,30 @@ class ConfigEncryptionTool { options.addOption(Option.builder("e").longOpt(KEY_MIGRATION_ARG).hasArg(true).argName("keyhex").desc("The old raw hexadecimal key to use during key migration").build()) options.addOption(Option.builder("p").longOpt(PASSWORD_ARG).hasArg(true).argName("password").desc("The password from which to derive the key to use to encrypt the sensitive properties").build()) options.addOption(Option.builder("w").longOpt(PASSWORD_MIGRATION_ARG).hasArg(true).argName("password").desc("The old password from which to derive the key during migration").build()) + options.addOption(Option.builder("y").longOpt(HASHED_KEY_MIGRATION_ARG).hasArg(true).argName("hashed_keyhex").desc("The old securely-hashed hexadecimal key to authenticate during key migration (see NiFi Admin Guide)").build()) + options.addOption(Option.builder("z").longOpt(HASHED_PASSWORD_MIGRATION_ARG).hasArg(true).argName("hashed_password").desc("The old securely-hashed password to authenticate during key migration (see NiFi Admin Guide)").build()) options.addOption(Option.builder("r").longOpt(USE_KEY_ARG).hasArg(false).desc("If provided, the secure console will prompt for the raw key value in hexadecimal form").build()) options.addOption(Option.builder("m").longOpt(MIGRATION_ARG).hasArg(false).desc("If provided, the nifi.properties and/or login-identity-providers.xml sensitive properties will be re-encrypted with a new key").build()) options.addOption(Option.builder("x").longOpt(DO_NOT_ENCRYPT_NIFI_PROPERTIES_ARG).hasArg(false).desc("If provided, the properties in flow.xml.gz will be re-encrypted with a new key but the nifi.properties and/or login-identity-providers.xml files will not be modified").build()) options.addOption(Option.builder("s").longOpt(PROPS_KEY_ARG).hasArg(true).argName("password|keyhex").desc("The password or key to use to encrypt the sensitive processor properties in flow.xml.gz").build()) options.addOption(Option.builder("A").longOpt(NEW_FLOW_ALGORITHM_ARG).hasArg(true).argName("algorithm").desc("The algorithm to use to encrypt the sensitive processor properties in flow.xml.gz").build()) options.addOption(Option.builder("P").longOpt(NEW_FLOW_PROVIDER_ARG).hasArg(true).argName("algorithm").desc("The security provider to use to encrypt the sensitive processor properties in flow.xml.gz").build()) + options.addOption(Option.builder().longOpt(CURRENT_HASH_PARAMS_ARG).hasArg(false).desc("Returns the current salt and cost params used to store the hashed key/password").build()) + options } static Options getCliOptions() { - return new ConfigEncryptionTool().options + if (!staticOptions) { + staticOptions = buildOptions() + } + return staticOptions } - /** - * Prints the usage message and available arguments for this tool (along with a specific error message if provided). - * - * @param errorMessage the optional error message - */ +/** + * Prints the usage message and available arguments for this tool (along with a specific error message if provided). + * + * @param errorMessage the optional error message + */ void printUsage(String errorMessage) { if (errorMessage) { System.out.println(errorMessage) @@ -236,7 +269,8 @@ class ConfigEncryptionTool { } HelpFormatter helpFormatter = new HelpFormatter() helpFormatter.setWidth(160) - helpFormatter.setOptionComparator(null) // preserve manual ordering of options when printing instead of alphabetical + helpFormatter.setOptionComparator(null) + // preserve manual ordering of options when printing instead of alphabetical helpFormatter.printHelp(ConfigEncryptionTool.class.getCanonicalName(), header, options, FOOTER, true) } @@ -257,6 +291,17 @@ class ConfigEncryptionTool { isVerbose = commandLine.hasOption(VERBOSE_ARG) + // If this flag is present, ensure no other options are present and then fail/return + if (commandLine.hasOption(CURRENT_HASH_PARAMS_ARG)) { + queryingCurrentHashParams = true + if (commandLineHasActionFlags(commandLine, [CURRENT_HASH_PARAMS_ARG])) { + printUsageAndThrow("When '--${CURRENT_HASH_PARAMS_ARG}' is specified, only '-h'/'--${HELP_ARG}' and '-v'/'--${VERBOSE_ARG}' are allowed", ExitCode.INVALID_ARGS) + } else { + // Otherwise return (avoid unnecessary parsing) + return commandLine + } + } + bootstrapConfPath = commandLine.getOptionValue(BOOTSTRAP_CONF_ARG) // If this flag is provided, the nifi.properties is necessary to read/write the flow encryption key, but the encryption process will not actually be applied to nifi.properties / login-identity-providers.xml @@ -361,6 +406,29 @@ class ConfigEncryptionTool { if (isVerbose) { logger.info("Key migration mode activated") } + if (isSecureHashArgumentPresent(commandLine)) { + logger.info("Secure hash argument present") + + // Check for old key/password and throw error + if (commandLine.hasOption(KEY_MIGRATION_ARG) || commandLine.hasOption(PASSWORD_MIGRATION_ARG)) { + printUsageAndThrow("If the '-w'/'--${PASSWORD_MIGRATION_ARG}' or '-e'/'--${KEY_MIGRATION_ARG}' arguments are present, '-z'/'--${HASHED_PASSWORD_MIGRATION_ARG}' and '-y'/'--${HASHED_KEY_MIGRATION_ARG}' cannot be used", ExitCode.INVALID_ARGS) + } + + // Check for both key and password and throw error + if (commandLine.hasOption(HASHED_KEY_MIGRATION_ARG) && commandLine.hasOption(HASHED_PASSWORD_MIGRATION_ARG)) { + printUsageAndThrow("Only one of '-z'/'--${HASHED_PASSWORD_MIGRATION_ARG}' and '-y'/'--${HASHED_KEY_MIGRATION_ARG}' can be used together", ExitCode.INVALID_ARGS) + } + + // Extract flags to field + if (commandLine.hasOption(HASHED_KEY_MIGRATION_ARG)) { + secureHashKey = commandLine.getOptionValue(HASHED_KEY_MIGRATION_ARG) + } else { + secureHashPassword = commandLine.getOptionValue(HASHED_PASSWORD_MIGRATION_ARG) + } + + // Set boolean flag to true + usingSecureHash = true + } if (commandLine.hasOption(PASSWORD_MIGRATION_ARG)) { usingPasswordMigration = true if (commandLine.hasOption(KEY_MIGRATION_ARG)) { @@ -370,7 +438,8 @@ class ConfigEncryptionTool { } } else { migrationKeyHex = commandLine.getOptionValue(KEY_MIGRATION_ARG) - usingPasswordMigration = !migrationKeyHex + // Use the "migration password" value if the migration key hex is absent and the secure hash password/key hex is absent (if either are present, the migration password is not) + usingPasswordMigration = !migrationKeyHex && !usingSecureHash } } else { if (commandLine.hasOption(PASSWORD_MIGRATION_ARG) || commandLine.hasOption(KEY_MIGRATION_ARG)) { @@ -410,15 +479,43 @@ class ConfigEncryptionTool { return commandLine } - /** - * The method returns the provided, derived, or securely-entered key in hex format. The reason the parameters must be provided instead of read from the fields is because this is used for the regular key/password and the migration key/password. - * - * @param device - * @param keyHex - * @param password - * @param usingPassword - * @return - */ + boolean commandLineHasActionFlags(CommandLine commandLine, List acceptableOptionStrings = []) { + // Resolve the list of Option objects corresponding to "help" and "verbose" + final List