Skip to content

Commit

Permalink
Support keystore tests on FIPS JVM (#66902) (#66964)
Browse files Browse the repository at this point in the history
As of #64024 we run FIPS CI on a true, FIPS approved only mode JVM.
This mandates that any passwords that are fed into PBKDF2 must have at
least 112 bits of entropy (that is, be 14 characters long).

This commit updates our Keystore CLI tests so that tests either:
1. Use a 14+ character password when in FIPS mode, _or_
2. Are skipped on FIPS mode (because they explicitly test empty
   passwords)

Backport of: #66846

Co-authored-by: Tim Vernum <tim.vernum@elastic.co>
  • Loading branch information
ywangd and tvernum committed Jan 4, 2021
1 parent 13ac977 commit d70e5ca
Show file tree
Hide file tree
Showing 12 changed files with 128 additions and 57 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
*/
package org.elasticsearch.bootstrap;

import org.elasticsearch.common.settings.KeyStoreWrapperTests;
import org.elasticsearch.core.internal.io.IOUtils;
import org.elasticsearch.common.settings.KeyStoreCommandTestCase;
import org.elasticsearch.common.settings.KeyStoreWrapper;
Expand Down Expand Up @@ -58,16 +59,20 @@ public void setupEnv() throws IOException {
}

public void testLoadSecureSettings() throws Exception {
final char[] password = KeyStoreWrapperTests.getPossibleKeystorePassword();
final Path configPath = env.configFile();
final SecureString seed;
try (KeyStoreWrapper keyStoreWrapper = KeyStoreWrapper.create()) {
seed = KeyStoreWrapper.SEED_SETTING.get(Settings.builder().setSecureSettings(keyStoreWrapper).build());
assertNotNull(seed);
assertTrue(seed.length() > 0);
keyStoreWrapper.save(configPath, new char[0]);
keyStoreWrapper.save(configPath, password);
}
final InputStream in = password.length > 0
? new ByteArrayInputStream(new String(password).getBytes(StandardCharsets.UTF_8))
: System.in;
assertTrue(Files.exists(configPath.resolve("elasticsearch.keystore")));
try (SecureSettings secureSettings = Bootstrap.loadSecureSettings(env)) {
try (SecureSettings secureSettings = Bootstrap.loadSecureSettings(env, in)) {
SecureString seedAfterLoad = KeyStoreWrapper.SEED_SETTING.get(Settings.builder().setSecureSettings(secureSettings).build());
assertEquals(seedAfterLoad.toString(), seed.toString());
assertTrue(Files.exists(configPath.resolve("elasticsearch.keystore")));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ private void addFile(KeyStoreWrapper keystore, String setting, Path file, String
}

public void testMissingCreateWithEmptyPasswordWhenPrompted() throws Exception {
assumeFalse("Cannot create unprotected keystore on FIPS JVM", inFipsJvm());
String password = "";
Path file1 = createRandomFile();
terminal.addTextInput("y");
Expand All @@ -72,6 +73,7 @@ public void testMissingCreateWithEmptyPasswordWhenPrompted() throws Exception {
}

public void testMissingCreateWithEmptyPasswordWithoutPromptIfForced() throws Exception {
assumeFalse("Cannot create unprotected keystore on FIPS JVM", inFipsJvm());
String password = "";
Path file1 = createRandomFile();
execute("-f", "foo", file1.toString());
Expand Down Expand Up @@ -211,6 +213,7 @@ public void testIncorrectPassword() throws Exception {
}

public void testAddToUnprotectedKeystore() throws Exception {
assumeFalse("Cannot open unprotected keystore on FIPS JVM", inFipsJvm());
String password = "";
Path file = createRandomFile();
KeyStoreWrapper keystore = createKeystore(password);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,15 @@ public void testInvalidPassphrease() throws Exception {
}

public void testMissingPromptCreateWithoutPasswordWhenPrompted() throws Exception {
assumeFalse("Cannot create unprotected keystore on FIPS JVM", inFipsJvm());
terminal.addTextInput("y");
terminal.addSecretInput("bar");
execute("foo");
assertSecureString("foo", "bar", "");
}

public void testMissingPromptCreateWithoutPasswordWithoutPromptIfForced() throws Exception {
assumeFalse("Cannot create unprotected keystore on FIPS JVM", inFipsJvm());
terminal.addSecretInput("bar");
execute("-f", "foo");
assertSecureString("foo", "bar", "");
Expand Down Expand Up @@ -251,7 +253,9 @@ public void testMissingSettingName() throws Exception {
}

public void testSpecialCharacterInName() throws Exception {
createKeystore("");
String password = randomAlphaOfLengthBetween(14, 24);
createKeystore(password);
terminal.addSecretInput(password);
terminal.addSecretInput("value");
final String key = randomAlphaOfLength(4) + '@' + randomAlphaOfLength(4);
final UserException e = expectThrows(UserException.class, () -> execute(key));
Expand All @@ -260,6 +264,7 @@ public void testSpecialCharacterInName() throws Exception {
}

public void testAddToUnprotectedKeystore() throws Exception {
assumeFalse("Cannot create unprotected keystores in FIPS mode", inFipsJvm());
String password = "";
createKeystore(password, "foo", "bar");
terminal.addTextInput("");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ protected Environment createEnv(Map<String, String> settings) throws UserExcepti
}

public void testSetKeyStorePassword() throws Exception {
assumeFalse("Cannot open unprotected keystore on FIPS JVM", inFipsJvm());
createKeystore("");
loadKeystore("");
terminal.addSecretInput("thepassword");
Expand All @@ -54,14 +55,15 @@ public void testChangeKeyStorePassword() throws Exception {
createKeystore("theoldpassword");
loadKeystore("theoldpassword");
terminal.addSecretInput("theoldpassword");
terminal.addSecretInput("thepassword");
terminal.addSecretInput("thepassword");
terminal.addSecretInput("the-better-password");
terminal.addSecretInput("the-better-password");
// Prompted thrice: Once for the existing and twice for the new password
execute();
loadKeystore("thepassword");
loadKeystore("the-better-password");
}

public void testChangeKeyStorePasswordToEmpty() throws Exception {
assumeFalse("Cannot set empty keystore password on FIPS JVM", inFipsJvm());
createKeystore("theoldpassword");
loadKeystore("theoldpassword");
terminal.addSecretInput("theoldpassword");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,16 @@

package org.elasticsearch.common.settings;

import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Map;

import org.elasticsearch.cli.Command;
import org.elasticsearch.cli.ExitCodes;
import org.elasticsearch.cli.UserException;
import org.elasticsearch.env.Environment;

import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Map;

import static org.hamcrest.Matchers.containsString;

public class CreateKeyStoreCommandTests extends KeyStoreCommandTestCase {
Expand All @@ -44,7 +44,7 @@ protected Environment createEnv(Map<String, String> settings) throws UserExcepti
}

public void testNotMatchingPasswords() throws Exception {
String password = randomFrom("", "keystorepassword");
String password = getPossibleKeystorePassword();
terminal.addSecretInput(password);
terminal.addSecretInput("notthekeystorepasswordyouarelookingfor");
UserException e = expectThrows(UserException.class, () -> execute(randomFrom("-p", "--password")));
Expand All @@ -53,48 +53,67 @@ public void testNotMatchingPasswords() throws Exception {
}

public void testDefaultNotPromptForPassword() throws Exception {
assumeFalse("Cannot open unprotected keystore on FIPS JVM", inFipsJvm());
execute();
Path configDir = env.configFile();
assertNotNull(KeyStoreWrapper.load(configDir));
}

public void testPosix() throws Exception {
String password = randomFrom("", "keystorepassword");
terminal.addSecretInput(password);
terminal.addSecretInput(password);
execute();
final String password = getPossibleKeystorePassword();
// Sometimes (rarely) test with explicit empty password
final boolean withPassword = password.length() > 0 || rarely();
if (withPassword) {
terminal.addSecretInput(password);
terminal.addSecretInput(password);
execute(randomFrom("-p", "--password"));
} else {
execute();
}
Path configDir = env.configFile();
assertNotNull(KeyStoreWrapper.load(configDir));
}

public void testNotPosix() throws Exception {
String password = randomFrom("", "keystorepassword");
terminal.addSecretInput(password);
terminal.addSecretInput(password);
env = setupEnv(false, fileSystems);
execute();
final String password = getPossibleKeystorePassword();
// Sometimes (rarely) test with explicit empty password
final boolean withPassword = password.length() > 0 || rarely();
if (withPassword) {
terminal.addSecretInput(password);
terminal.addSecretInput(password);
execute(randomFrom("-p", "--password"));
} else {
execute();
}
Path configDir = env.configFile();
assertNotNull(KeyStoreWrapper.load(configDir));
}

public void testOverwrite() throws Exception {
String password = randomFrom("", "keystorepassword");
String password = getPossibleKeystorePassword();
Path keystoreFile = KeyStoreWrapper.keystorePath(env.configFile());
byte[] content = "not a keystore".getBytes(StandardCharsets.UTF_8);
Files.write(keystoreFile, content);

terminal.addTextInput(""); // default is no
terminal.addTextInput(""); // default is no (don't overwrite)
execute();
assertArrayEquals(content, Files.readAllBytes(keystoreFile));

terminal.addTextInput("n"); // explicit no
terminal.addTextInput("n"); // explicit no (don't overwrite)
execute();
assertArrayEquals(content, Files.readAllBytes(keystoreFile));

terminal.addTextInput("y");
terminal.addSecretInput(password);
terminal.addSecretInput(password);
execute();
terminal.addTextInput("y"); // overwrite
// Sometimes (rarely) test with explicit empty password
final boolean withPassword = password.length() > 0 || rarely();
if (withPassword) {
terminal.addSecretInput(password);
terminal.addSecretInput(password);
execute(randomFrom("-p", "--password"));
} else {
execute();
}
assertNotNull(KeyStoreWrapper.load(env.configFile()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ public void testFailsWithNoKeystore() throws Exception {
}

public void testFailsWhenKeystoreLacksPassword() throws Exception {
assumeFalse("Cannot create unprotected keystores in FIPS mode", inFipsJvm());
createKeystore("");
UserException e = expectThrows(UserException.class, this::execute);
assertEquals("Unexpected exit code", HasPasswordKeyStoreCommand.NO_PASSWORD_EXIT_CODE, e.exitCode);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,25 +19,25 @@

package org.elasticsearch.common.settings;

import java.io.IOException;
import java.io.InputStream;
import java.nio.file.FileSystem;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;

import com.google.common.jimfs.Configuration;
import com.google.common.jimfs.Jimfs;
import org.elasticsearch.core.internal.io.IOUtils;
import org.apache.lucene.util.LuceneTestCase;
import org.elasticsearch.cli.CommandTestCase;
import org.elasticsearch.common.io.PathUtilsForTesting;
import org.elasticsearch.core.internal.io.IOUtils;
import org.elasticsearch.env.Environment;
import org.elasticsearch.env.TestEnvironment;
import org.junit.After;
import org.junit.Before;

import java.io.IOException;
import java.io.InputStream;
import java.nio.file.FileSystem;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;

/**
* Base test case for manipulating the ES keystore.
*/
Expand Down Expand Up @@ -124,4 +124,12 @@ void assertSecureFile(KeyStoreWrapper keystore, String setting, Path file) throw
}

}

String getPossibleKeystorePassword() {
if (inFipsJvm()) {
// FIPS Mode JVMs require a password for the ES keystore
return "keystorepassword";
}
return randomFrom("", "keystorepassword");
}
}

0 comments on commit d70e5ca

Please sign in to comment.